Project

General

Profile

Download (10 KB) Statistics
| Branch: | Tag: | Revision:
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