foreman/app/models/nic/base.rb @ ca34010f
8838eb42 | Ohad Levy | # Represents a Host's network interface
|
|
# This class is the both parent
|
|||
module Nic
|
|||
4deab2f3 | Lukas Zapletal | class Base < ApplicationRecord
|
|
a03e5341 | Marek Hulan | audited associated_with: :host
|
|
38964973 | Dominic Cleal | prepend Foreman::STI
|
|
977f12ed | amirfefer | include Encryptable
|
|
encrypts :password
|
|||
8838eb42 | Ohad Levy | ||
feacea35 | Amos Benari | self.table_name = 'nics'
|
|
8838eb42 | Ohad Levy | ||
3034e8e2 | Ori Rabin | validates_lengths_from_database
|
|
8838eb42 | Ohad Levy | ||
before_validation :normalize_mac
|
|||
43c4bd72 | Marek Hulan | after_validation :set_validated
|
|
before_destroy :not_required_interface
|
|||
8838eb42 | Ohad Levy | ||
35241dd6 | Marek Hulan | validate :mac_uniqueness,
|
|
82b4a36d | Michael Moll | :if => proc { |nic| nic.managed? && nic.host && nic.host.managed? && !nic.host.compute? && !nic.virtual? && nic.mac.present? }
|
|
6a2fce1f | Marek Hulan | validates :mac, :presence => true,
|
|
9e5352f8 | Dominik Matoulek | :if => proc { |nic| nic.managed? && nic.host_managed? && !nic.host.compute? && !nic.host.compute_provides?(:mac) && !nic.virtual? && (nic.provision? || nic.subnet.present? || nic.subnet6.present?) }
|
|
c1f41f89 | Timo Goebel | validate :validate_mac_is_unicast,
|
|
82b4a36d | Michael Moll | :if => proc { |nic| nic.managed? && !nic.virtual? }
|
|
356b2e69 | Marek Hulan | validates :mac, :mac_address => true, :allow_blank => true
|
|
8838eb42 | Ohad Levy | ||
82b4a36d | Michael Moll | validates :host, :presence => true, :if => proc { |nic| nic.require_host? }
|
|
8838eb42 | Ohad Levy | ||
fedf3791 | Brandon Weeks | validates :identifier, :uniqueness => { :scope => :host_id },
|
|
4dec807a | Tomer Brisker | :if => ->(nic) { nic.identifier.present? && nic.host && nic.identifier_was.blank? }
|
|
fedf3791 | Brandon Weeks | ||
43c4bd72 | Marek Hulan | validate :exclusive_primary_interface
|
|
validate :exclusive_provision_interface
|
|||
82b4a36d | Michael Moll | validates :domain, :presence => true, :if => proc { |nic| nic.host_managed? && nic.primary? }
|
|
validate :valid_domain, :if => proc { |nic| nic.host_managed? && nic.primary? }
|
|||
validates :ip, :presence => true, :if => proc { |nic| nic.host_managed? && nic.require_ip4_validation? }
|
|||
validates :ip6, :presence => true, :if => proc { |nic| nic.host_managed? && nic.require_ip6_validation? }
|
|||
43c4bd72 | Marek Hulan | ||
9d56081f | Timo Goebel | validate :validate_subnet_types
|
|
6b65706a | Baptiste Agasse | validates_with SubnetsConsistencyValidator
|
|
9a41f58f | Tomas Strachota | validate :validate_updating_types
|
|
9d56081f | Timo Goebel | ||
# Validate that subnet's taxonomies are defined for nic's host
|
|||
a53eafb0 | Ondřej Ezr | validates :subnet, :belongs_to_host_taxonomy => { :taxonomy => :location }
|
|
validates :subnet6, :belongs_to_host_taxonomy => { :taxonomy => :location }
|
|||
validates :subnet, :belongs_to_host_taxonomy => { :taxonomy => :organization }
|
|||
validates :subnet6, :belongs_to_host_taxonomy => { :taxonomy => :organization }
|
|||
8f695d94 | Shimon Shtein | ||
9e5352f8 | Dominik Matoulek | validate :check_blank_mac_for_virtual_resources, on: :create
|
|
bb3572ff | Daniel Lobato | scope :bmc, -> { where(:type => "Nic::BMC") }
|
|
scope :bonds, -> { where(:type => "Nic::Bond") }
|
|||
cee12a22 | Julien Pivotto | scope :bridges, -> { where(:type => "Nic::Bridge") }
|
|
bb3572ff | Daniel Lobato | scope :interfaces, -> { where(:type => "Nic::Interface") }
|
|
scope :managed, -> { where(:type => "Nic::Managed") }
|
|||
scope :virtual, -> { where(:virtual => true) }
|
|||
scope :physical, -> { where(:virtual => false) }
|
|||
scope :is_managed, -> { where(:managed => true) }
|
|||
02e4c535 | Daniel Lobato | scope :primary, -> { where(:primary => true) }
|
|
scope :provision, -> { where(:provision => true) }
|
|||
43c4bd72 | Marek Hulan | ||
9d56081f | Timo Goebel | belongs_to :subnet, -> { where :type => 'Subnet::Ipv4' }
|
|
belongs_to :subnet6, -> { where :type => 'Subnet::Ipv6' }, :class_name => "Subnet"
|
|||
a152a1b2 | Tomer Brisker | belongs_to :domain
|
|
43c4bd72 | Marek Hulan | ||
d455f32c | Marek Hulan | belongs_to_host :inverse_of => :interfaces, :class_name => "Host::Base"
|
|
abd8f1d1 | Daniel Lobato | ||
cf260685 | Oleh Fedorenko | scoped_search :on => :id, :complete_enabled => false, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER
|
|
29cc0741 | Tomer Brisker | scoped_search :on => :mac, :complete_value => true, :only_explicit => true
|
|
scoped_search :on => :ip, :complete_value => true, :only_explicit => true
|
|||
scoped_search :on => :name, :complete_value => true, :only_explicit => true
|
|||
scoped_search :on => :managed, :complete_value => {:true => true, :false => false}, :only_explicit => true
|
|||
scoped_search :on => :primary, :complete_value => {:true => true, :false => false}, :only_explicit => true
|
|||
scoped_search :on => :domain_id, :complete_value => true, :only_explicit => true
|
|||
459b2cf3 | Amit Karsale | ||
8838eb42 | Ohad Levy | # keep extra attributes needed for sub classes.
|
|
serialize :attrs, Hash
|
|||
43c4bd72 | Marek Hulan | # provider specific attributes
|
|
serialize :compute_attributes, Hash
|
|||
7b333c00 | Oleh Fedorenko | apipie :class, 'A class representing Network Interface Controller object' do
|
|
name 'NIC'
|
|||
sections only: %w[all additional]
|
|||
refs 'Nic::Base', 'Nic::Managed'
|
|||
prop_group :basic_model_props, ApplicationRecord, meta: { friendly_name: 'interface', name_desc: 'FQDN represented by this interface' }
|
|||
property :subnet, 'Subnet::Ipv4', desc: 'Returns associated IPv4 subnet'
|
|||
property :subnet6, 'Subnet::Ipv6', desc: 'Returns associated IPv6 subnet'
|
|||
property :virtual?, one_of: [true, false], desc: 'Returns true if the controller is virtual, false otherwise'
|
|||
property :physical?, one_of: [true, false], desc: 'Returns true if the controller is physical, false otherwise'
|
|||
property :mac, String, desc: 'Returns MAC address of this controller'
|
|||
property :ip, String, desc: 'Returns IPv4 of this controller'
|
|||
property :ip6, String, desc: 'Returns IPv6 of this controller'
|
|||
property :identifier, String, desc: 'Returns identifier of this controller, e.g. eth0'
|
|||
property :attached_to, String, desc: 'Returns identifier of the controller this controller is attached to'
|
|||
property :tag, String, desc: 'Returns VLAN tag, this attribute has precedence over the subnet VLAN ID. Only for virtual interfaces.'
|
|||
property :domain, 'Domain', desc: 'Returns domain associated with this interface'
|
|||
property :mtu, Integer, desc: 'Returns MTU for this controller'
|
|||
property :bond_options, String, desc: 'Returns space separated options, e.g. miimon=100. Only for bond interfaces'
|
|||
property :attached_devices, String, desc: 'Returns comma separated identifiers of attached devices'
|
|||
property :attached_devices_identifiers, Array, desc: 'Returns identifiers of attached devices'
|
|||
property :mode, String, desc: 'Returns bond mode of the interface, e.g. balance-rr'
|
|||
property :primary, one_of: [true, false], desc: 'Returns true if this controller is primary device, false otherwise'
|
|||
property :provision, one_of: [true, false], desc: 'Returns true if this controller is used for provisioning, false otherwise'
|
|||
property :alias?, one_of: [true, false], desc: 'Returns true if this controller is used as an alias, false otherwise'
|
|||
property :inheriting_mac, String, desc: 'Returns inherited MAC address of the controller this controller is alias for'
|
|||
property :children_mac_addresses, Array, desc: 'Returns MAC addresses of attached devices'
|
|||
property :nic_delay, Integer, desc: 'Returns the delay in seconds for network activity during install'
|
|||
property :fqdn, String, desc: 'Returns FQDN for this device'
|
|||
property :shortname, String, desc: 'Returns the controller\'s name without its domain'
|
|||
property :type, String, desc: 'Returns type of this controller, e.g. Nic::Managed'
|
|||
property :vlanid, String, desc: 'Returns VLAN ID of the subnet this device is associated with'
|
|||
property :managed?, one_of: [true, false], desc: 'Returns true if external services such as DNS, DHCP and TFTP are configured for this interface, false otherwise'
|
|||
property :bond?, one_of: [true, false], desc: 'Returns true if the type of the interface is Bond, false otherwise'
|
|||
property :bridge?, one_of: [true, false], desc: 'Returns true if the type of the interface is Bridge, false otherwise'
|
|||
property :bmc?, one_of: [true, false], desc: 'Returns true if the type of the interface is BMC, false otherwise'
|
|||
property :link, one_of: [true, false], desc: 'Returns true if the interface is up, false otherwise'
|
|||
end
|
|||
d455f32c | Marek Hulan | class Jail < ::Safemode::Jail
|
|
7b333c00 | Oleh Fedorenko | allow :id, :subnet, :subnet6, :virtual?, :physical?, :mac, :ip, :ip6, :identifier, :attached_to,
|
|
ca34010f | Shim Shtein | :link, :tag, :domain, :bond_options, :attached_devices, :mode,
|
|
:attached_devices_identifiers, :primary, :provision, :inheriting_mac,
|
|||
:children_mac_addresses, :nic_delay, :fqdn, :shortname, :type, :managed?, :bond?, :bmc?, :provision?
|
|||
d455f32c | Marek Hulan | end
|
|
2a663a34 | Timo Goebel | # include STI inheritance column in audits
|
|
def self.default_ignored_attributes
|
|||
super - [inheritance_column]
|
|||
end
|
|||
6d05514a | Tomas Strachota | def physical?
|
|
!virtual?
|
|||
end
|
|||
e8a6b6d8 | Lukáš Zapletal | def bond?
|
|
type == 'Nic::Bond'
|
|||
end
|
|||
def bridge?
|
|||
type == 'Nic::Bridge'
|
|||
end
|
|||
def bmc?
|
|||
type == 'Nic::BMC'
|
|||
end
|
|||
5da15d1a | Tomas Strachota | def type_name
|
|
type.split("::").last
|
|||
end
|
|||
cad1b13c | Tomas Strachota | def self.humanized_name
|
|
# provide class name as a default value
|
|||
name.split("::").last
|
|||
end
|
|||
def self.type_by_name(name)
|
|||
a1b2ee53 | Marek Hulan | allowed_types.find { |nic_class| nic_class.humanized_name.downcase == name.to_s.downcase }
|
|
cad1b13c | Tomas Strachota | end
|
|
350c9f8f | Timo Goebel | # NIC types have to be registered to expose them to users
|
|
cad1b13c | Tomas Strachota | def self.register_type(type)
|
|
allowed_types << type
|
|||
end
|
|||
def self.allowed_types
|
|||
@allowed_types ||= []
|
|||
end
|
|||
43c4bd72 | Marek Hulan | # after every name change, we synchronize it to host object
|
|
def name=(*args)
|
|||
result = super
|
|||
sync_name
|
|||
result
|
|||
end
|
|||
e49da576 | Romuald Conty | def mac=(value)
|
|
super value&.downcase
|
|||
end
|
|||
5d828bc6 | Kamil Szubrycht | def hostname
|
|
if domain.present? && name.present?
|
|||
"#{shortname}.#{domain.name}"
|
|||
else
|
|||
name
|
|||
end
|
|||
end
|
|||
43c4bd72 | Marek Hulan | def shortname
|
|
a525db23 | Stephen Benjamin | if domain
|
|
name.to_s.chomp("." + domain.name)
|
|||
elsif domain_id && (unscoped_domain = Domain.unscoped.find_by(id: domain_id))
|
|||
# If domain is nil, but domain_id is set, domain could be
|
|||
# in another taxonomy. Don't fail to create a correct shortname.
|
|||
name.to_s.chomp("." + unscoped_domain.name)
|
|||
else
|
|||
name
|
|||
end
|
|||
43c4bd72 | Marek Hulan | end
|
|
def validated?
|
|||
!!@validated
|
|||
end
|
|||
# we should guarantee the fqdn is always fully qualified
|
|||
def fqdn
|
|||
return name if name.blank? || domain.blank?
|
|||
name.include?('.') ? name : "#{name}.#{domain}"
|
|||
end
|
|||
def clone
|
|||
# do not copy system specific attributes
|
|||
c86ed9c6 | Michael Moll | deep_clone(:except => [:name, :mac, :ip, :ip6, :host_id])
|
|
43c4bd72 | Marek Hulan | end
|
|
5ad3c4f3 | Marek Hulan | # if this interface does not have MAC and is attached to other interface,
|
|
# we can fetch mac from this other interface
|
|||
def inheriting_mac
|
|||
c86ed9c6 | Michael Moll | mac.presence || host.interfaces.detect { |i| i.identifier == attached_to }.try(:mac)
|
|
5ad3c4f3 | Marek Hulan | end
|
|
edadebee | Timo Goebel | # if this interface has attached devices (e.g. in a bond),
|
|
# we can get the mac addresses from the children
|
|||
def children_mac_addresses
|
|||
[]
|
|||
end
|
|||
ba64f022 | Marek Hulan | def host_managed?
|
|
c67703aa | Tomer Brisker | host&.managed?
|
|
ba64f022 | Marek Hulan | end
|
|
350c9f8f | Timo Goebel | def require_ip4_validation?(from_compute = true)
|
|
NicIpRequired::Ipv4.new(:nic => self, :from_compute => from_compute).required?
|
|||
end
|
|||
9d56081f | Timo Goebel | ||
350c9f8f | Timo Goebel | def require_ip6_validation?(from_compute = true)
|
|
NicIpRequired::Ipv6.new(:nic => self, :from_compute => from_compute).required?
|
|||
end
|
|||
9d56081f | Timo Goebel | ||
350c9f8f | Timo Goebel | def required_ip_addresses_set?(from_compute = true)
|
|
errors.add(:ip, :blank) if ip.blank? && require_ip4_validation?(from_compute)
|
|||
errors.add(:ip6, :blank) if ip6.blank? && require_ip6_validation?(from_compute)
|
|||
!errors.include?(:ip) && !errors.include?(:ip6)
|
|||
9d56081f | Timo Goebel | end
|
|
350c9f8f | Timo Goebel | def compute_provides_ip?(field)
|
|
return false unless managed? && host_managed? && primary?
|
|||
da9865b8 | Michael Moll | subnet_field = (field == :ip6) ? :subnet6 : :subnet
|
|
350c9f8f | Timo Goebel | host.compute_provides?(field) || host.compute_provides?(:mac) && mac_based_ipam?(subnet_field)
|
|
9d56081f | Timo Goebel | end
|
|
350c9f8f | Timo Goebel | def mac_based_ipam?(subnet_field)
|
|
send(subnet_field).present? && send(subnet_field).ipam == IPAM::MODES[:eui64]
|
|||
3b75c0a7 | Daniel Lobato | end
|
|
b215c092 | Timo Goebel | # Overwrite setter for ip to force normalization
|
|
# even when address is set during a callback
|
|||
def ip=(addr)
|
|||
super(Net::Validations.normalize_ip(addr))
|
|||
end
|
|||
# Overwrite setter for ip6 to force normalization
|
|||
# even when address is set during a callback
|
|||
def ip6=(addr)
|
|||
super(Net::Validations.normalize_ip6(addr))
|
|||
end
|
|||
911c6e9e | Timo Goebel | def matches_subnet?(ip_field, subnet_field)
|
|
return unless send(subnet_field).present?
|
|||
ip_value = send(ip_field)
|
|||
ip_value.present? && public_send(subnet_field).contains?(ip_value)
|
|||
end
|
|||
2a663a34 | Timo Goebel | def to_audit_label
|
|
return "#{name} (#{identifier})" if name.present? && identifier.present?
|
|||
return "#{mac} (#{identifier})" if mac.present? && identifier.present?
|
|||
[mac, name, identifier, _('Unnamed')].detect(&:present?)
|
|||
end
|
|||
8838eb42 | Ohad Levy | protected
|
|
def normalize_mac
|
|||
self.mac = Net::Validations.normalize_mac(mac)
|
|||
b215c092 | Timo Goebel | true
|
|
990dee69 | Timo Goebel | rescue Net::Validations::Error => e
|
|
c86ed9c6 | Michael Moll | errors.add(:mac, e.message)
|
|
8838eb42 | Ohad Levy | end
|
|
d455f32c | Marek Hulan | ||
cbe1391f | Shlomi Zadok | def valid_domain
|
|
unless Domain.find_by_id(domain_id)
|
|||
c86ed9c6 | Michael Moll | errors.add(:domain_id, _("can't find domain with this id"))
|
|
cbe1391f | Shlomi Zadok | end
|
|
end
|
|||
43c4bd72 | Marek Hulan | def set_validated
|
|
@validated = true
|
|||
end
|
|||
d455f32c | Marek Hulan | # do we require a host object associate to the interface? defaults to true
|
|
def require_host?
|
|||
true
|
|||
end
|
|||
356b2e69 | Marek Hulan | ||
43c4bd72 | Marek Hulan | def not_required_interface
|
|
b03dcd1b | Michael Moll | if host&.managed? && !host.being_destroyed?
|
|
c86ed9c6 | Michael Moll | if primary?
|
|
errors.add :primary, _("can't delete primary interface of managed host")
|
|||
43c4bd72 | Marek Hulan | end
|
|
c86ed9c6 | Michael Moll | if provision?
|
|
errors.add :provision, _("can't delete provision interface of managed host")
|
|||
43c4bd72 | Marek Hulan | end
|
|
end
|
|||
c86ed9c6 | Michael Moll | throw :abort if errors[:primary].present? || errors[:provision].present?
|
|
43c4bd72 | Marek Hulan | end
|
|
def exclusive_primary_interface
|
|||
c86ed9c6 | Michael Moll | if host && primary?
|
|
416a52e8 | yifatmakias | duplicate = host.interfaces.any? { |i| i.primary? && i != self }
|
|
errors.add :primary, _("interface is already set on the host") if duplicate
|
|||
43c4bd72 | Marek Hulan | end
|
|
end
|
|||
def exclusive_provision_interface
|
|||
c86ed9c6 | Michael Moll | if host && provision?
|
|
416a52e8 | yifatmakias | duplicate = host.interfaces.any? { |i| i.provision? && i != self }
|
|
errors.add :provision, _("interface is already set on the host") if duplicate
|
|||
43c4bd72 | Marek Hulan | end
|
|
end
|
|||
def sync_name
|
|||
synchronizer = NameSynchronizer.new(self)
|
|||
111b0459 | Daniel Lobato | synchronizer.sync_name if synchronizer.sync_required?
|
|
43c4bd72 | Marek Hulan | end
|
|
8f695d94 | Shimon Shtein | ||
9d56081f | Timo Goebel | def validate_subnet_types
|
|
c86ed9c6 | Michael Moll | errors.add(:subnet, _("must be of type Subnet::Ipv4.")) if subnet.present? && subnet.type != 'Subnet::Ipv4'
|
|
errors.add(:subnet6, _("must be of type Subnet::Ipv6.")) if subnet6.present? && subnet6.type != 'Subnet::Ipv6'
|
|||
8f695d94 | Shimon Shtein | end
|
|
35241dd6 | Marek Hulan | def mac_uniqueness
|
|
a3212817 | Marek Hulan | interface_attribute_uniqueness(:mac, Nic::Base.physical.is_managed)
|
|
35241dd6 | Marek Hulan | end
|
|
c1f41f89 | Timo Goebel | def validate_mac_is_unicast
|
|
errors.add(:mac, _('must be a unicast MAC address')) if Net::Validations.multicast_mac?(mac) || Net::Validations.broadcast_mac?(mac)
|
|||
end
|
|||
9a41f58f | Tomas Strachota | def validate_updating_types
|
|
c86ed9c6 | Michael Moll | sti_type = type || 'Nic::Base'
|
|
errors.add(:type, _("can't be changed once the interface is saved")) if persisted? && (self.class.name != sti_type)
|
|||
9a41f58f | Tomas Strachota | end
|
|
f6c313fb | Timo Goebel | def mac_addresses_for_provisioning
|
|
[mac, children_mac_addresses].flatten.compact.uniq
|
|||
end
|
|||
8f695d94 | Shimon Shtein | private
|
|
cd4b1820 | Tomer Brisker | def interface_attribute_uniqueness(attr, base = Nic::Base)
|
|
c86ed9c6 | Michael Moll | in_memory_candidates = host.present? ? host.interfaces.select { |i| i.persisted? && !i.marked_for_destruction? } : [self]
|
|
db_candidates = base.where(attr => public_send(attr))
|
|||
db_candidates = db_candidates.select { |c| c.id != id && in_memory_candidates.map(&:id).include?(c.id) }
|
|||
35241dd6 | Marek Hulan | errors.add(attr, :taken) if db_candidates.present?
|
|
end
|
|||
9e5352f8 | Dominik Matoulek | ||
def check_blank_mac_for_virtual_resources
|
|||
if virtual? && host.try(:compute_provides?, :mac) && host.uuid.empty? && mac.present?
|
|||
errors.add(:mac, _("can't be set for this interface because it's provided by the compute resource"))
|
|||
end
|
|||
end
|
|||
cad1b13c | Tomas Strachota | end
|
|
5da15d1a | Tomas Strachota | end
|
|
cad1b13c | Tomas Strachota | ||
require_dependency 'nic/interface'
|