theforeman/foreman

View on GitHub
app/models/concerns/orchestration/external_ipam.rb

Summary

Maintainability
C
7 hrs
Test Coverage
module Orchestration::ExternalIPAM
  extend ActiveSupport::Concern
  include Orchestration::Common
  include SubnetsHelper

  included do
    after_validation :queue_external_ipam
    before_destroy :queue_external_ipam_destroy
  end

  def generate_external_ipam_task_id(action, network_type, interface = self)
    id = [interface.mac, interface.ip, interface.identifier, interface.id].find { |x| x&.present? }
    "external_ipam_#{action}_#{id}_#{network_type}"
  end

  protected

  def set_add_external_ip(params)
    ip, subnet = params[:ip], params[:subnet]

    if ip_is_available?(ip, subnet)
      subnet.external_ipam_proxy.add_ip_to_subnet(ip, subnet.network_address, subnet.externalipam_group)
    else
      errors.add :ip, _('This IP address has already been reserved in External IPAM') if subnet.network_type == "IPv4"
      errors.add :ip6, _('This IP address has already been reserved in External IPAM') if subnet.network_type == "IPv6"
      errors.add :interfaces, _('Some interfaces are invalid')
    end
  end

  def set_remove_external_ip(params)
    ip, subnet = params[:ip], params[:subnet]
    subnet.external_ipam_proxy.delete_ip_from_subnet(ip, subnet.network_address, subnet.externalipam_group)
  end

  def del_add_external_ip(params)
    new_record? ? rollback_on_add : rollback_on_update
  end

  def del_remove_external_ip(params)
    logger.warn "IP address deletion failed in External IPAM, and it cannot be compensated."
  end

  def rollback_on_add
    if ipv4_errors_not_ipv6? && !ip_is_available?(ip6, subnet6)
      subnet.external_ipam_proxy.delete_ip_from_subnet(ip6, subnet6.network_address, subnet6.externalipam_group)
    elsif ipv6_errors_not_ipv4? && !ip_is_available?(ip, subnet)
      subnet.external_ipam_proxy.delete_ip_from_subnet(ip, subnet.network_address, subnet.externalipam_group)
    end
  end

  def rollback_on_update
    if ipv4_errors_not_ipv6? && ip_is_available?(old.ip, old.subnet)
      subnet.external_ipam_proxy.add_ip_to_subnet(old.ip, old.subnet.network_address, old.subnet.externalipam_group)
    elsif ipv6_errors_not_ipv4? && ip_is_available?(old.ip6, old.subnet6)
      subnet.external_ipam_proxy.add_ip_to_subnet(old.ip6, old.subnet6.network_address, old.subnet6.externalipam_group)
    end
  end

  def requires_update?
    return false if new_record?
    old.ip != ip
  end

  def requires_update6?
    return false if new_record?
    old.ip6 != ip6
  end

  def requires_delete?
    old_nic = Nic::Base.find(id)
    !old_nic.ip.nil? && !old_nic.subnet_id.nil?
  end

  def requires_delete6?
    old_nic = Nic::Base.find(id)
    !old_nic.ip6.nil? && !old_nic.subnet6_id.nil?
  end

  def ip_is_available?(ip, subnet)
    !subnet.external_ipam_proxy.ip_exists(ip, subnet.network_address, subnet.externalipam_group)
  end

  def ipv4_errors_not_ipv6?
    !errors[:ip].empty? && errors[:ip6].empty? && ip6.present?
  end

  def ipv6_errors_not_ipv4?
    !errors[:ip6].empty? && errors[:ip].empty? && ip.present?
  end

  def ipv4_removed?
    old.ip.present? && ip.blank?
  end

  def ipv6_removed?
    old.ip6.present? && ip6.blank?
  end

  def ipv4_added?
    old.ip.blank? && ip.present?
  end

  def ipv6_added?
    old.ip6.blank? && ip6.present?
  end

  def ipv4_changed?
    old.ip.present? && ip.present? && old.ip != ip
  end

  def ipv6_changed?
    old.ip6.present? && ip6.present? && old.ip6 != ip6
  end

  private

  def queue_external_ipam
    new_record? ? queue_external_ipam_create : queue_external_ipam_update
  end

  def queue_external_ipam_create
    return unless (external_ipam?(subnet) || external_ipam?(subnet6)) && errors.empty?
    logger.debug "Scheduling new IP reservation(s) in external IPAM for #{self}"
    queue.create(id: generate_external_ipam_task_id("create", subnet.network_type), name: _("Creating IPv4 in External IPAM for %s") % self, priority: 10, action: [self, :set_add_external_ip, {:ip => ip, :subnet => subnet}]) if ip.present? && subnet.present? && external_ipam?(subnet)
    queue.create(id: generate_external_ipam_task_id("create", subnet6.network_type), name: _("Creating IPv6 in External IPAM for %s") % self, priority: 10, action: [self, :set_add_external_ip, {:ip => ip6, :subnet => subnet6}]) if ip6.present? && subnet6.present? && external_ipam?(subnet6)
    true
  end

  def queue_external_ipam_destroy
    return unless (external_ipam?(subnet) || external_ipam?(subnet6)) && errors.empty?
    logger.debug "Removing IP reservation(s) in external IPAM for #{self}"
    queue.create(id: generate_external_ipam_task_id("remove", "IPv4"), name: _("Removing IPv4 in External IPAM for %s") % self, priority: 5, action: [self, :set_remove_external_ip, {:ip => ip, :subnet => subnet}]) if requires_delete? && external_ipam?(subnet)
    queue.create(id: generate_external_ipam_task_id("remove", "IPv6"), name: _("Removing IPv6 in External IPAM for %s") % self, priority: 5, action: [self, :set_remove_external_ip, {:ip => ip6, :subnet => subnet6}]) if requires_delete6? && external_ipam?(subnet6)
    true
  end

  def queue_external_ipam_update
    return false if old.nil?
    return unless (external_ipam?(subnet) || external_ipam?(subnet6) || external_ipam?(old.subnet) || external_ipam?(old.subnet6)) && errors.empty?
    logger.debug "Updating IP reservation in external IPAM for #{self}"
    queue.create(id: generate_external_ipam_task_id("create", subnet.network_type), name: _("Creating IPv4 in External IPAM for %s") % self, priority: 10, action: [self, :set_add_external_ip, {:ip => ip, :subnet => subnet}]) if (ipv4_added? || ipv4_changed?) && external_ipam?(subnet)
    queue.create(id: generate_external_ipam_task_id("remove", old.subnet.network_type), name: _("Removing IPv4 in External IPAM for %s") % self, priority: 5, action: [old, :set_remove_external_ip, {:ip => old.ip, :subnet => old.subnet}]) if (ipv4_removed? || ipv4_changed?) && external_ipam?(old.subnet)
    queue.create(id: generate_external_ipam_task_id("create", subnet6.network_type), name: _("Creating IPv6 in External IPAM for %s") % self, priority: 10, action: [self, :set_add_external_ip, {:ip => ip6, :subnet => subnet6}]) if (ipv6_added? || ipv6_changed?) && external_ipam?(subnet6)
    queue.create(id: generate_external_ipam_task_id("remove", old.subnet6.network_type), name: _("Removing IPv6 in External IPAM for %s") % self, priority: 5, action: [old, :set_remove_external_ip, {:ip => old.ip6, :subnet => old.subnet6}]) if (ipv6_removed? || ipv6_changed?) && external_ipam?(old.subnet6)
    true
  end
end