foreman/app/models/subnet.rb @ 9d43fc71
06823dc7 | Ohad Levy | require 'ipaddr'
|
|
05848084 | Timo Goebel | ||
4deab2f3 | Lukas Zapletal | class Subnet < ApplicationRecord
|
|
a03e5341 | Marek Hulan | audited
|
|
05848084 | Timo Goebel | IP_FIELDS = [:network, :mask, :gateway, :dns_primary, :dns_secondary, :from, :to]
|
|
REQUIRED_IP_FIELDS = [:network, :mask]
|
|||
990dee69 | Timo Goebel | SUBNET_TYPES = {:'Subnet::Ipv4' => N_('IPv4'), :'Subnet::Ipv6' => N_('IPv6')}
|
|
d455f32c | Marek Hulan | BOOT_MODES = {:static => N_('Static'), :dhcp => N_('DHCP')}
|
|
acfbc458 | Marek Hulan | include Authorizable
|
|
38964973 | Dominic Cleal | prepend Foreman::STI
|
|
8b737c9c | Joseph Magen | extend FriendlyId
|
|
4af1e2ba | Shlomi Zadok | friendly_id :name
|
|
611f5bff | Amos Benari | include Taxonomix
|
|
e768c976 | Tomas Strachota | include Parameterizable::ByIdName
|
|
d1e29487 | Stephen Benjamin | include Exportable
|
|
c6760930 | Timo Goebel | include BelongsToProxies
|
|
d1e29487 | Stephen Benjamin | ||
attr_exportable :name, :network, :mask, :gateway, :dns_primary, :dns_secondary, :from, :to, :boot_mode,
|
|||
f175f751 | Baptiste Agasse | :ipam, :vlanid, :mtu, :network_type, :description
|
|
d1e29487 | Stephen Benjamin | ||
05848084 | Timo Goebel | # This sets the rails model name of all child classes to the
|
|
# model name of the parent class, i.e. Subnet.
|
|||
# This is necessary for all STI classes to share the same
|
|||
# route_key, param_key, ...
|
|||
def self.inherited(child)
|
|||
child.instance_eval do
|
|||
# rubocop:disable Rails/Delegate
|
|||
def model_name
|
|||
superclass.model_name
|
|||
end
|
|||
# rubocop:enable Rails/Delegate
|
|||
end
|
|||
super
|
|||
end
|
|||
3326499a | Daniel Lobato | ||
3034e8e2 | Ori Rabin | validates_lengths_from_database :except => [:gateway]
|
|
e0d618ef | Joseph Magen | before_destroy EnsureNotUsedBy.new(:hosts, :hostgroups, :interfaces, :domains)
|
|
c6760930 | Timo Goebel | ||
belongs_to_proxy :dhcp,
|
|||
:feature => 'DHCP',
|
|||
:label => N_('DHCP Proxy'),
|
|||
:description => N_('DHCP Proxy to use within this subnet'),
|
|||
:api_description => N_('DHCP Proxy ID to use within this subnet'),
|
|||
:if => ->(subnet) { subnet.supports_ipam_mode?(:dhcp) }
|
|||
belongs_to_proxy :tftp,
|
|||
:feature => N_('TFTP'),
|
|||
:label => N_('TFTP Proxy'),
|
|||
:api_description => N_('TFTP Proxy ID to use within this subnet'),
|
|||
:description => N_('TFTP Proxy to use within this subnet')
|
|||
belongs_to_proxy :dns,
|
|||
:feature => N_('DNS'),
|
|||
:label => N_('Reverse DNS Proxy'),
|
|||
:api_description => N_('DNS Proxy ID to use within this subnet'),
|
|||
:description => N_('DNS Proxy to use within this subnet for managing PTR records, note that A and AAAA records are managed via Domain DNS proxy')
|
|||
009e7bbd | Lukas Zapletal | belongs_to_proxy :template,
|
|
:feature => N_('Templates'),
|
|||
:label => N_('Template Proxy'),
|
|||
:api_description => N_('Template HTTP(S) Proxy ID to use within this subnet'),
|
|||
:description => N_('Template HTTP(S) Proxy to use within this subnet to allow access templating endpoint from isolated networks')
|
|||
7d993b41 | Joseph Mitchell Magen | has_many :hostgroups
|
|
46eec8ba | Marek Hulan | has_many :subnet_domains, :dependent => :destroy, :inverse_of => :subnet
|
|
fa62ea80 | Ohad Levy | has_many :domains, :through => :subnet_domains
|
|
03261ebb | Sean O'Keeffe | has_many :subnet_parameters, :dependent => :destroy, :foreign_key => :reference_id, :inverse_of => :subnet
|
|
has_many :parameters, :dependent => :destroy, :foreign_key => :reference_id, :class_name => "SubnetParameter"
|
|||
accepts_nested_attributes_for :subnet_parameters, :allow_destroy => true
|
|||
05848084 | Timo Goebel | validates :network, :mask, :name, :cidr, :presence => true
|
|
f4459c11 | David Davis | validates_associated :subnet_domains
|
|
d455f32c | Marek Hulan | validates :boot_mode, :inclusion => BOOT_MODES.values
|
|
990dee69 | Timo Goebel | validates :ipam, :inclusion => {:in => Proc.new { |subnet| subnet.supported_ipam_modes.map {|m| IPAM::MODES[m]} }, :message => N_('not supported by this protocol')}
|
|
05848084 | Timo Goebel | validates :type, :inclusion => {:in => Proc.new { Subnet::SUBNET_TYPES.keys.map(&:to_s) }, :message => N_("must be one of [ %s ]" % Subnet::SUBNET_TYPES.keys.map(&:to_s).join(', ')) }
|
|
f4459c11 | David Davis | validates :name, :length => {:maximum => 255}, :uniqueness => true
|
|
55e2afa1 | Tomer Brisker | validates :vlanid, numericality: { :only_integer => true, :greater_than_or_equal_to => 0, :less_than => 4096}, :allow_blank => true
|
|
f175f751 | Baptiste Agasse | validates :mtu, :presence => true
|
|
f2c78d4a | Joseph Magen | ||
05848084 | Timo Goebel | before_validation :normalize_addresses
|
|
validate :ensure_ip_addrs_valid
|
|||
611f5bff | Amos Benari | ||
7a900b06 | Ohad Levy | validate :validate_ranges
|
|
05848084 | Timo Goebel | validate :check_if_type_changed, :on => :update
|
|
96b38b3c | Ohad Levy | ||
611f5bff | Amos Benari | default_scope lambda {
|
|
with_taxonomy_scope do
|
|||
55e2afa1 | Tomer Brisker | order(:vlanid)
|
|
611f5bff | Amos Benari | end
|
|
}
|
|||
7af45f09 | Christine Fouant | scoped_search :on => [:name, :network, :mask, :gateway, :dns_primary, :dns_secondary,
|
|
f175f751 | Baptiste Agasse | :vlanid, :mtu, :ipam, :boot_mode, :type], :complete_value => true
|
|
d455f32c | Marek Hulan | ||
ed67b8c9 | Dominic Cleal | scoped_search :relation => :domains, :on => :name, :rename => :domain, :complete_value => true
|
|
9d43fc71 | Michael Moll | scoped_search :relation => :subnet_parameters, :on => :value, :on_key => :name, :complete_value => true, :only_explicit => true, :rename => :params
|
|
8104eced | Ohad Levy | ||
79c0664a | Timo Goebel | delegate :supports_ipam_mode?, :supported_ipam_modes, :show_mask?, to: 'self.class'
|
|
990dee69 | Timo Goebel | ||
8bd4e480 | Ohad Levy | class Jail < ::Safemode::Jail
|
|
d455f32c | Marek Hulan | allow :name, :network, :mask, :cidr, :title, :to_label, :gateway, :dns_primary, :dns_secondary,
|
|
f175f751 | Baptiste Agasse | :vlanid, :mtu, :boot_mode, :dhcp?, :nil?, :has_vlanid?, :dhcp_boot_mode?, :description
|
|
d455f32c | Marek Hulan | end
|
|
06823dc7 | Ohad Levy | # Subnets are displayed in the form of their network network/network mask
|
|
1fa008a4 | Joseph Magen | def network_address
|
|
06823dc7 | Ohad Levy | "#{network}/#{cidr}"
|
|
96b38b3c | Ohad Levy | end
|
|
1fa008a4 | Joseph Magen | def to_label
|
|
"#{name} (#{network_address})"
|
|||
be770e85 | Ohad Levy | end
|
|
6c09e04f | Ori Rabin | def to_s
|
|
name
|
|||
end
|
|||
7b7d7d1d | Timo Goebel | def network_type
|
|
SUBNET_TYPES[type.to_sym]
|
|||
end
|
|||
def network_type=(value)
|
|||
self[:type] = SUBNET_TYPES.key(value)
|
|||
end
|
|||
06823dc7 | Ohad Levy | # Indicates whether the IP is within this subnet
|
|
990dee69 | Timo Goebel | # [+ip+] String: IPv4 or IPv6 address
|
|
06823dc7 | Ohad Levy | # Returns Boolean: True if if ip is in this subnet
|
|
5f029ed6 | Daniel Lobato | def contains?(ip)
|
|
05848084 | Timo Goebel | ipaddr.include? IPAddr.new(ip, family)
|
|
end
|
|||
def ipaddr
|
|||
IPAddr.new("#{network}/#{mask}", family)
|
|||
06823dc7 | Ohad Levy | end
|
|
def cidr
|
|||
05848084 | Timo Goebel | return if mask.nil?
|
|
06823dc7 | Ohad Levy | IPAddr.new(mask).to_i.to_s(2).count("1")
|
|
05848084 | Timo Goebel | rescue invalid_address_error
|
|
nil
|
|||
end
|
|||
def cidr=(cidr)
|
|||
return if cidr.nil?
|
|||
self[:mask] = IPAddr.new(in_mask, family).mask(cidr).to_s
|
|||
rescue invalid_address_error
|
|||
nil
|
|||
end
|
|||
b1116c90 | Ohad Levy | def dhcp?
|
|
990dee69 | Timo Goebel | supports_ipam_mode?(:dhcp) && dhcp && dhcp.url.present?
|
|
b1116c90 | Ohad Levy | end
|
|
5f029ed6 | Daniel Lobato | def dhcp_proxy(attrs = {})
|
|
6285a614 | Ohad Levy | @dhcp_proxy ||= ProxyAPI::DHCP.new({:url => dhcp.url}.merge(attrs)) if dhcp?
|
|
end
|
|||
def tftp?
|
|||
85021506 | Michael Moll | !!(tftp && tftp.url && tftp.url.present?)
|
|
6285a614 | Ohad Levy | end
|
|
5f029ed6 | Daniel Lobato | def tftp_proxy(attrs = {})
|
|
6285a614 | Ohad Levy | @tftp_proxy ||= ProxyAPI::TFTP.new({:url => tftp.url}.merge(attrs)) if tftp?
|
|
06823dc7 | Ohad Levy | end
|
|
dd42df0a | Ohad Levy | # do we support DNS PTR records for this subnet
|
|
def dns?
|
|||
85021506 | Michael Moll | !!(dns && dns.url && dns.url.present?)
|
|
dd42df0a | Ohad Levy | end
|
|
5f029ed6 | Daniel Lobato | def dns_proxy(attrs = {})
|
|
dd42df0a | Ohad Levy | @dns_proxy ||= ProxyAPI::DNS.new({:url => dns.url}.merge(attrs)) if dns?
|
|
end
|
|||
009e7bbd | Lukas Zapletal | def template?
|
|
!!(template && template.url)
|
|||
end
|
|||
def template_proxy(attrs = {})
|
|||
@template_proxy ||= ProxyAPI::Template.new({:url => template.url}.merge(attrs)) if template?
|
|||
end
|
|||
d455f32c | Marek Hulan | def ipam?
|
|
990dee69 | Timo Goebel | self.ipam != IPAM::MODES[:none]
|
|
d455f32c | Marek Hulan | end
|
|
79c0664a | Timo Goebel | def ipam_needs_range?
|
|
ipam? && self.ipam != IPAM::MODES[:eui64]
|
|||
end
|
|||
d455f32c | Marek Hulan | def dhcp_boot_mode?
|
|
self.boot_mode == Subnet::BOOT_MODES[:dhcp]
|
|||
end
|
|||
43c4bd72 | Marek Hulan | def unused_ip(mac = nil, excluded_ips = [])
|
|
990dee69 | Timo Goebel | unless supported_ipam_modes.map {|m| IPAM::MODES[m]}.include?(self.ipam)
|
|
e18361ce | Lukas Zapletal | raise ::Foreman::Exception.new(N_("Unsupported IPAM mode for %s"), self.class.name)
|
|
d455f32c | Marek Hulan | end
|
|
990dee69 | Timo Goebel | ||
opts = {:subnet => self, :mac => mac, :excluded_ips => excluded_ips}
|
|||
79c0664a | Timo Goebel | IPAM.new(self.ipam, opts)
|
|
96b38b3c | Ohad Levy | end
|
|
10139fde | José Luis Escalante | ||
d455f32c | Marek Hulan | def known_ips
|
|
6a3b4abc | Dominic Cleal | self.interfaces.reload
|
|
9d56081f | Timo Goebel | ips = self.interfaces.map(&ip_sym) + self.hosts.includes(:interfaces).map(&ip_sym)
|
|
d455f32c | Marek Hulan | ips += [self.gateway, self.dns_primary, self.dns_secondary].select(&:present?)
|
|
05848084 | Timo Goebel | ips.compact.uniq
|
|
d455f32c | Marek Hulan | end
|
|
57a32e98 | Daniel Lobato | def proxies
|
|
[dhcp, tftp, dns].compact
|
|||
end
|
|||
d455f32c | Marek Hulan | def has_vlanid?
|
|
self.vlanid.present?
|
|||
end
|
|||
43c4bd72 | Marek Hulan | # overwrite method in taxonomix, since subnet is not direct association of host anymore
|
|
def used_taxonomy_ids(type)
|
|||
return [] if new_record?
|
|||
ff1e9ffc | Dominic Cleal | Host::Base.joins(:primary_interface).where(:nics => {:subnet_id => id}).distinct.pluck(type).compact
|
|
43c4bd72 | Marek Hulan | end
|
|
def as_json(options = {})
|
|||
05848084 | Timo Goebel | super({:methods => [:to_label, :type]}.merge(options))
|
|
43c4bd72 | Marek Hulan | end
|
|
7a900b06 | Ohad Levy | private
|
|
def validate_ranges
|
|||
287082a5 | David Davis | if from.present? || to.present?
|
|
bfbf7ed8 | Lukas Zapletal | errors.add(:from, _("must be specified if to is defined")) if from.blank?
|
|
errors.add(:to, _("must be specified if from is defined")) if to.blank?
|
|||
7a900b06 | Ohad Levy | end
|
|
e0910b7e | Michael Moll | return if errors.key?(:from) || errors.key?(:to)
|
|
9d43fc71 | Michael Moll | errors.add(:from, _("does not belong to subnet")) if from.present? && !self.contains?(f = IPAddr.new(from))
|
|
errors.add(:to, _("does not belong to subnet")) if to.present? && !self.contains?(t = IPAddr.new(to))
|
|||
287082a5 | David Davis | errors.add(:from, _("can't be bigger than to range")) if from.present? && t.present? && f > t
|
|
7a900b06 | Ohad Levy | end
|
|
fa62ea80 | Ohad Levy | ||
05848084 | Timo Goebel | def check_if_type_changed
|
|
if self.type_changed?
|
|||
errors.add(:type, _("can't be updated after subnet is saved"))
|
|||
end
|
|||
31aa5db5 | Joseph Mitchell Magen | end
|
|
05848084 | Timo Goebel | def normalize_addresses
|
|
IP_FIELDS.each do |f|
|
|||
val = send(f)
|
|||
send("#{f}=", normalize_ip(val)) if val.present?
|
|||
end
|
|||
self
|
|||
31aa5db5 | Joseph Mitchell Magen | end
|
|
05848084 | Timo Goebel | def ensure_ip_addrs_valid
|
|
IP_FIELDS.each do |f|
|
|||
e0910b7e | Michael Moll | errors.add(f, _("is invalid")) if (send(f).present? || REQUIRED_IP_FIELDS.include?(f)) && !validate_ip(send(f)) && !errors.key?(f)
|
|
05848084 | Timo Goebel | end
|
|
31aa5db5 | Joseph Mitchell Magen | end
|
|
990dee69 | Timo Goebel | class << self
|
|
def boot_modes_with_translations
|
|||
79c0664a | Timo Goebel | BOOT_MODES.map { |_, mode_name| [_(mode_name), mode_name] }
|
|
990dee69 | Timo Goebel | end
|
|
def supports_ipam_mode?(mode)
|
|||
supported_ipam_modes.include?(mode)
|
|||
end
|
|||
79c0664a | Timo Goebel | def supported_ipam_modes_with_translations
|
|
supported_ipam_modes.map {|mode| [_(IPAM::MODES[mode]), IPAM::MODES[mode]]}
|
|||
end
|
|||
990dee69 | Timo Goebel | # Given an IP returns the subnet that contains that IP
|
|
# [+ip+] : IPv4 or IPv6 address
|
|||
# Returns : Subnet object or nil if not found
|
|||
def subnet_for(ip)
|
|||
911c6e9e | Timo Goebel | return unless ip.present?
|
|
990dee69 | Timo Goebel | ip = IPAddr.new(ip)
|
|
Subnet.all.detect {|s| s.family == ip.family && s.contains?(ip)}
|
|||
end
|
|||
7b7d7d1d | Timo Goebel | ||
12612809 | Dominic Cleal | # This casts Subnet to Subnet::Ipv4 if no type is set
|
|
38964973 | Dominic Cleal | def new(*attributes, &block)
|
|
12612809 | Dominic Cleal | type = attributes.first.with_indifferent_access.delete(:type) if attributes.first.is_a?(Hash)
|
|
38964973 | Dominic Cleal | return Subnet::Ipv4.new(*attributes, &block) if self == Subnet && type.nil?
|
|
super
|
|||
12612809 | Dominic Cleal | end
|
|
7b7d7d1d | Timo Goebel | # allows to create a specific subnet class based on the network_type.
|
|
# network_type is more user friendly than the class names
|
|||
def new_network_type(args)
|
|||
network_type = args.delete(:network_type) || 'IPv4'
|
|||
SUBNET_TYPES.each do |network_type_class, network_type_name|
|
|||
return network_type_class.to_s.constantize.new(args) if network_type_name.downcase == network_type.downcase
|
|||
end
|
|||
raise ::Foreman::Exception.new N_("unknown network_type")
|
|||
end
|
|||
990dee69 | Timo Goebel | end
|
|
05848084 | Timo Goebel | def invalid_address_error
|
|
# IPAddr::InvalidAddressError is undefined for ruby 1.9
|
|||
return IPAddr::InvalidAddressError if IPAddr.const_defined?('InvalidAddressError')
|
|||
ArgumentError
|
|||
end
|
|||
06823dc7 | Ohad Levy | end
|