Project

General

Profile

« Previous | Next » 

Revision 05848084

Added by Timo Goebel about 8 years ago

fixes #14638 - Refactor Subnet into STI to allow different subnet types

View differences:

app/models/subnet.rb
require 'ipaddr'
class Subnet < ActiveRecord::Base
IP_FIELDS = [:network, :mask, :gateway, :dns_primary, :dns_secondary, :from, :to]
REQUIRED_IP_FIELDS = [:network, :mask]
SUBNET_TYPES = {:'Subnet::Ipv4' => N_('IPv4')}
BOOT_MODES = {:static => N_('Static'), :dhcp => N_('DHCP')}
IPAM_MODES = {:dhcp => N_('DHCP'), :db => N_('Internal DB'), :none => N_('None')}
include Authorizable
include Foreman::STI
extend FriendlyId
friendly_id :name
include Taxonomix
include Parameterizable::ByIdName
include EncOutput
attr_accessible :name, :network, :mask, :gateway, :dns_primary, :dns_secondary, :ipam, :from,
attr_accessible :name, :type, :network, :mask, :gateway, :dns_primary, :dns_secondary, :ipam, :from,
:to, :vlanid, :boot_mode, :dhcp_id, :dhcp, :tftp_id, :tftp, :dns_id, :dns, :domain_ids, :domain_names,
:subnet_parameters_attributes
:subnet_parameters_attributes, :cidr
# This casts Subnet to Subnet::Ipv4 if no type is set
def self.new(*attributes, &block)
return Subnet::Ipv4.new_without_cast(*attributes, &block) if self == Subnet
super
end
# 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
audited :allow_mass_assignment => true
......
belongs_to :dns, :class_name => "SmartProxy"
has_many :subnet_domains, :dependent => :destroy, :inverse_of => :subnet
has_many :domains, :through => :subnet_domains
has_many :interfaces, :class_name => 'Nic::Base'
has_many :primary_interfaces, -> { where(:primary => true) }, :class_name => 'Nic::Base'
has_many :hosts, :through => :interfaces
has_many :primary_hosts, :through => :primary_interfaces, :source => :host
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
validates :network, :mask, :name, :presence => true
validates :network, :mask, :name, :cidr, :presence => true
validates_associated :subnet_domains
validates :network, :format => {:with => Net::Validations::IP_REGEXP}
validates :gateway, :dns_primary, :dns_secondary,
:allow_blank => true,
:allow_nil => true,
:format => {:with => Net::Validations::IP_REGEXP},
:length => { :maximum => 15, :message => N_("is too long (maximum is 15 characters)") }
validates :mask, :format => {:with => Net::Validations::MASK_REGEXP}
validates :boot_mode, :inclusion => BOOT_MODES.values
validates :ipam, :inclusion => IPAM_MODES.values
validates :ipam, :inclusion => {:in => Proc.new { |subnet| subnet.class.supported_ipam_modes.map {|m| Subnet::IPAM_MODES[m]} }, :message => N_('not supported by this protocol')}
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(', ')) }
validates :name, :length => {:maximum => 255}, :uniqueness => true
validates :dns, :proxy_features => { :feature => "DNS", :message => N_('does not have the DNS feature') }
validates :tftp, :proxy_features => { :feature => "TFTP", :message => N_('does not have the TFTP feature') }
validates :dhcp, :proxy_features => { :feature => "DHCP", :message => N_('does not have the DHCP feature') }
validates :network, :uniqueness => true
validate :ensure_ip_addr_new
before_validation :cleanup_addresses
before_validation :normalize_addresses
validate :ensure_ip_addrs_valid
validate :validate_ranges
validate :check_if_type_changed, :on => :update
default_scope lambda {
with_taxonomy_scope do
......
}
scoped_search :on => [:name, :network, :mask, :gateway, :dns_primary, :dns_secondary,
:vlanid, :ipam, :boot_mode], :complete_value => true
:vlanid, :ipam, :boot_mode, :type], :complete_value => true
scoped_search :in => :domains, :on => :name, :rename => :domain, :complete_value => true
scoped_search :in => :subnet_parameters, :on => :value, :on_key=> :name, :complete_value => true, :only_explicit => true, :rename => :params
......
end
# Given an IP returns the subnet that contains that IP
# [+ip+] : "doted quad" string
# [+ip+] : IPv4 address
# Returns : Subnet object or nil if not found
def self.subnet_for(ip)
Subnet.all.each {|s| return s if s.contains? IPAddr.new(ip)}
nil
ip = IPAddr.new(ip)
Subnet.all.detect {|s| s.family == ip.family && s.contains?(ip)}
end
# Indicates whether the IP is within this subnet
# [+ip+] String: Contains 4 dotted decimal values
# [+ip+] String: IPv4 address
# Returns Boolean: True if if ip is in this subnet
def contains?(ip)
IPAddr.new("#{network}/#{mask}", Socket::AF_INET).include? IPAddr.new(ip, Socket::AF_INET)
ipaddr.include? IPAddr.new(ip, family)
end
def ipaddr
IPAddr.new("#{network}/#{mask}", family)
end
def cidr
return if mask.nil?
IPAddr.new(mask).to_i.to_s(2).count("1")
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
def supports_ipam_mode?(mode)
self.class.supported_ipam_modes.include?(mode)
end
def dhcp?
......
return
end
if self.ipam == IPAM_MODES[:dhcp] && dhcp?
if self.ipam == IPAM_MODES[:dhcp] && dhcp? && supports_ipam_mode?(:dhcp)
# we have DHCP proxy so asking it for free IP
logger.debug "Asking #{dhcp.url} for free IP"
ip = dhcp_proxy.unused_ip(self, mac)["ip"]
logger.debug("Found #{ip}")
return(ip)
elsif self.ipam == IPAM_MODES[:db]
elsif self.ipam == IPAM_MODES[:db] && supports_ipam_mode?(:db)
# we have no DHCP proxy configured so Foreman becomes `DHCP` and manages reservations internally
logger.debug "Trying to find free IP for subnet in internal DB"
subnet_range = IPAddr.new("#{network}/#{mask}", Socket::AF_INET).to_range.to_a
from = self.from.present? ? IPAddr.new(self.from) : subnet_range[1]
to = self.to.present? ? IPAddr.new(self.to) : subnet_range[-2]
subnet_range = IPAddr.new("#{network}/#{mask}", family).to_range
from = self.from.present? ? IPAddr.new(self.from) : subnet_range.first(2).last
to = self.to.present? ? IPAddr.new(self.to) : IPAddr.new(subnet_range.last.to_i - 2, family)
(from..to).each do |address|
ip = address.to_s
if !self.known_ips.include?(ip) && !excluded_ips.include?(ip)
......
ips = self.interfaces.map(&:ip) + self.hosts.includes(:interfaces).map(&:ip)
ips += [self.gateway, self.dns_primary, self.dns_secondary].select(&:present?)
self.clear_association_cache
ips.uniq
ips.compact.uniq
end
# imports subnets from a dhcp smart proxy
......
end
def as_json(options = {})
super({:methods => [:to_label]}.merge(options))
super({:methods => [:to_label, :type]}.merge(options))
end
private
......
end
def validate_ranges
errors.add(:from, _("invalid IP address")) if from.present? and !from =~ Net::Validations::IP_REGEXP
errors.add(:to, _("invalid IP address")) if to.present? and !to =~ Net::Validations::IP_REGEXP
errors.add(:from, _("does not belong to subnet")) if from.present? and not self.contains?(f=IPAddr.new(from))
errors.add(:to, _("does not belong to subnet")) if to.present? and not self.contains?(t=IPAddr.new(to))
errors.add(:from, _("can't be bigger than to range")) if from.present? and t.present? and f > t
if from.present? or to.present?
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?
end
return if errors.keys.include?(:from) || errors.keys.include?(:to)
errors.add(:from, _("does not belong to subnet")) if from.present? and not self.contains?(f=IPAddr.new(from))
errors.add(:to, _("does not belong to subnet")) if to.present? and not self.contains?(t=IPAddr.new(to))
errors.add(:from, _("can't be bigger than to range")) if from.present? and t.present? and f > t
end
def cleanup_addresses
self.network = cleanup_ip(network) if network.present?
self.mask = cleanup_ip(mask) if mask.present?
self.gateway = cleanup_ip(gateway) if gateway.present?
self.dns_primary = cleanup_ip(dns_primary) if dns_primary.present?
self.dns_secondary = cleanup_ip(dns_secondary) if dns_secondary.present?
self
def check_if_type_changed
if self.type_changed?
errors.add(:type, _("can't be updated after subnet is saved"))
end
end
def cleanup_ip(address)
address.gsub!(/\.\.+/, ".")
address.gsub!(/2555+/, "255")
address
def normalize_addresses
IP_FIELDS.each do |f|
val = send(f)
send("#{f}=", normalize_ip(val)) if val.present?
end
self
end
def ensure_ip_addr_new
errors.add(:network, _("is invalid")) if network.present? && (IPAddr.new(network) rescue nil).nil? && !errors.keys.include?(:network)
errors.add(:mask, _("is invalid")) if mask.present? && (IPAddr.new(mask) rescue nil).nil? && !errors.keys.include?(:mask)
errors.add(:gateway, _("is invalid")) if gateway.present? && (IPAddr.new(gateway) rescue nil).nil? && !errors.keys.include?(:gateway)
errors.add(:dns_primary, _("is invalid")) if dns_primary.present? && (IPAddr.new(dns_primary) rescue nil).nil? && !errors.keys.include?(:dns_primary)
errors.add(:dns_secondary, _("is invalid")) if dns_secondary.present? && (IPAddr.new(dns_secondary) rescue nil).nil? && !errors.keys.include?(:dns_secondary)
def ensure_ip_addrs_valid
IP_FIELDS.each do |f|
errors.add(f, _("is invalid")) if (send(f).present? || REQUIRED_IP_FIELDS.include?(f)) && !validate_ip(send(f)) && !errors.keys.include?(f)
end
end
private
def invalid_address_error
# IPAddr::InvalidAddressError is undefined for ruby 1.9
return IPAddr::InvalidAddressError if IPAddr.const_defined?('InvalidAddressError')
ArgumentError
end
def enc_attributes
@enc_attributes ||= %w(name network mask gateway dns_primary dns_secondary from to boot_mode ipam vlanid)
@enc_attributes ||= %w(name type network mask cidr gateway dns_primary dns_secondary from to boot_mode ipam vlanid)
end
end
app/models/subnet/ipv4.rb
require 'socket'
class Subnet::Ipv4 < Subnet
has_many :interfaces, :class_name => 'Nic::Base', :foreign_key => :subnet_id
has_many :primary_interfaces, -> { where(:primary => true) }, :class_name => 'Nic::Base', :foreign_key => :subnet_id
# The has_many :through associations below have to be defined after the
# corresponding has_many associations and thus can not be defined in the parent class
has_many :hosts, :through => :interfaces
has_many :primary_hosts, :through => :primary_interfaces, :source => :host
validates :mask, :format => {:with => Net::Validations::MASK_REGEXP}
before_validation :cleanup_addresses
def family
Socket::AF_INET
end
def in_mask
IPAddr::IN4MASK
end
def validate_ip(ip)
Net::Validations.validate_ip(ip)
end
def self.supported_ipam_modes
[:dhcp, :db, :none]
end
def cleanup_addresses
IP_FIELDS.each do |f|
send("#{f}=", cleanup_ip(send(f))) if send(f).present?
end
self
end
private
def cleanup_ip(address)
address.gsub!(/\.\.+/, ".")
address.gsub!(/2555+/, "255")
address
end
def normalize_ip(address)
Net::Validations.normalize_ip(address)
end
end
app/validators/mac_address_validator.rb
class MacAddressValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
make_invalid(record, attribute) unless Net::Validations.valid_mac?(value)
make_invalid(record, attribute) unless Net::Validations.validate_mac(value)
rescue Net::Validations::Error
make_invalid(record, attribute)
end
db/migrate/20160414063050_add_sti_to_subnets.rb
class AddStiToSubnets < ActiveRecord::Migration
def self.up
add_column :subnets, :type, :string, :default => 'Subnet::Ipv4', :null => false
add_index :subnets, :type
end
def self.down
remove_column :subnets, :type
end
end
lib/net.rb
module Net
class Record
include Net::Validations
attr_accessor :hostname, :proxy, :logger
def initialize(opts = {})
lib/net/dhcp/record.rb
def initialize(opts = { })
super(opts)
self.mac = validate_mac self.mac
self.network = validate_network self.network
self.ip = validate_ip self.ip
self.mac = Net::Validations.validate_mac! self.mac
self.network = Net::Validations.validate_network! self.network
self.ip = Net::Validations.validate_ip! self.ip
end
def to_s
lib/net/dns.rb
def initialize(opts = { })
super(opts)
self.ip = validate_ip self.ip
self.ip = Validations.validate_ip! self.ip
self.resolver ||= Resolv::DNS.new
end
lib/net/validations.rb
require 'ipaddr'
require 'socket'
module Net
module Validations
IP_REGEXP ||= /\A((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\z/
......
class Error < RuntimeError
end
def valid_mac?(mac)
# validates an IPv4 address
def self.validate_ip(ip)
return false unless ip.present?
IPAddr.new(ip, Socket::AF_INET) rescue return false
true
end
# validates an IPv4 address and raises an error
def self.validate_ip!(ip)
raise Error, "Invalid IP Address #{ip}" unless validate_ip(ip)
ip
end
# validates a network mask
def self.validate_mask(mask)
mask =~ MASK_REGEXP
end
# validates a network mask and raises an error
def self.validate_mask!(mask)
raise Error, "Invalid Subnet Mask #{mask}" unless validate_mask(mask)
mask
end
# validates the mac
def self.validate_mac(mac)
return false if mac.nil?
case mac.size
......
false
end
module_function :valid_mac?
# validates the ip address
def validate_ip(ip)
raise Error, "Invalid IP Address #{ip}" unless (ip =~ IP_REGEXP)
ip
# validates the mac and raises an error
def self.validate_mac!(mac)
raise Error, "Invalid MAC #{mac}" unless validate_mac(mac)
mac
end
def validate_mask(mask)
raise Error, "Invalid Subnet Mask #{mask}" unless (mask =~ MASK_REGEXP)
mask
# validates the hostname
def self.validate_hostname(hostname)
hostname =~ HOST_REGEXP
end
# validates the mac
def validate_mac(mac)
raise Error, "Invalid MAC #{mac}" unless valid_mac? mac
mac
# validates the hostname and raises an error
def self.validate_hostname!(hostname)
raise Error, "Invalid hostname #{hostname}" unless validate_hostname(hostname)
hostname
end
# validates the hostname
def validate_hostname(hostname)
raise Error, "Invalid hostname #{hostname}" unless (hostname =~ HOST_REGEXP)
hostname
def self.validate_network(network)
validate_ip(network)
end
def validate_network(network)
begin
validate_ip(network)
rescue Error
raise Error, "Invalid Network #{network}"
end
def self.validate_network!(network)
raise(Error, "Invalid Network #{network}") unless validate_network(network)
network
end
# ensures that the ip address does not contain any leading spaces or invalid strings
def self.normalize_ip(ip)
return unless ip.present?
return ip unless ip =~ IP_REGEXP
ip.split(".").map(&:to_i).join(".")
end
test/factories/host_related.rb
overrides = {}
overrides[:locations] = [host.location] unless host.location.nil?
overrides[:organizations] = [host.organization] unless host.organization.nil?
host.subnet = FactoryGirl.build(:subnet, overrides)
host.subnet = FactoryGirl.build(:subnet_ipv4, overrides)
end
end
......
overrides[:locations] = [location] unless location.nil?
overrides[:organizations] = [organization] unless organization.nil?
FactoryGirl.create(
:subnet,
:subnet_ipv4,
overrides
)
end
......
overrides[:locations] = [location] unless location.nil?
overrides[:organizations] = [organization] unless organization.nil?
FactoryGirl.create(:subnet, overrides)
FactoryGirl.create(:subnet_ipv4, overrides)
end
domain do
FactoryGirl.create(:domain,
......
end
trait :with_tftp_subnet do
subnet { FactoryGirl.build(:subnet, :tftp, locations: [location], organizations: [organization]) }
subnet { FactoryGirl.build(:subnet_ipv4, :tftp, locations: [location], organizations: [organization]) }
end
trait :with_tftp_orchestration do
......
end
trait :with_subnet do
subnet
association :subnet, :factory => :subnet_ipv4
end
trait :with_rootpass do
test/factories/subnet.rb
FactoryGirl.define do
factory :subnet do
sequence(:name) {|n| "subnet#{n}" }
sequence(:network) {|n| "10.0.#{n}.0" }
mask "255.255.255.0"
trait :tftp do
association :tftp, :factory => :template_smart_proxy
......
association :dns, :factory => :dns_smart_proxy
end
trait :ipam_db do
ipam "Internal DB"
trait :with_domains do
transient do
domains_count 2
end
after(:create) do |subnet, evaluator|
FactoryGirl.create_list(:domain, evaluator.domains_count, :subnets => [subnet])
end
end
factory :subnet_ipv4, :class => Subnet::Ipv4 do
network { 3.times.map { rand(256) }.join('.') + '.0' }
mask { '255.255.255.0' }
factory :subnet_ipv4_with_domains, :traits => [:with_domains]
trait :ipam_db do
ipam "Internal DB"
end
end
end
end
test/fixtures/subnets.yml
one:
name: one
type: Subnet::Ipv4
network: 2.3.4.0
mask: 255.255.255.0
dhcp: one
......
two:
name: two
type: Subnet::Ipv4
network: 3.3.4.0
mask: 255.255.255.0
dhcp: one
......
three:
name: three
type: Subnet::Ipv4
network: 3.3.4.3
mask: 255.255.255.0
dhcp: one
......
four:
name: four
type: Subnet::Ipv4
network: 3.3.5.0
mask: 255.255.255.0
tftp: two
......
five:
name: five
type: Subnet::Ipv4
network: 10.0.0.0
mask: 255.255.255.0
dhcp: one
test/functional/hostgroups_controller_test.rb
test "domain_selected should return subnets" do
domain = FactoryGirl.create(:domain)
subnet = FactoryGirl.create(:subnet)
subnet = FactoryGirl.create(:subnet_ipv4)
domain.subnets << subnet
domain.save
xhr :post, :domain_selected, {:id => Hostgroup.first, :hostgroup => {}, :domain_id => domain.id, :format => :json}, set_session_user
test/functional/subnets_controller_test.rb
assert_template 'new'
end
def test_create_valid
Subnet.any_instance.stubs(:valid?).returns(true)
post :create, {:subnet => {:network => "192.168.0.1", :mask => "255.255.255.0"}}, set_session_user
def test_create_valid_without_type
post :create, {:subnet => {:network => "192.168.0.1", :mask => "255.255.255.0", :name => 'testsubnet'}}, set_session_user
assert_redirected_to subnets_url
end
def test_create_valid_with_type
post :create, {:subnet => {:network => "192.168.0.1", :mask => "255.255.255.0", :name => 'testsubnet', :type => 'Subnet::Ipv4'}}, set_session_user
assert_redirected_to subnets_url
end
test/lib/net/net_test.rb
assert_equal logger, record.logger
end
end
test/lib/net/validations_test.rb
require 'net'
class ValidationsTest < ActiveSupport::TestCase
include Net::Validations
describe "valid_mac?" do
describe "validate_mac" do
test "nil is not valid" do
Net::Validations.valid_mac?(nil).must_be_same_as false
Net::Validations.validate_mac(nil).must_be_same_as false
end
test "48-bit MAC address is valid" do
Net::Validations.valid_mac?("aa:bb:cc:dd:ee:ff").must_be_same_as true
Net::Validations.validate_mac("aa:bb:cc:dd:ee:ff").must_be_same_as true
end
test "64-bit MAC address is valid" do
Net::Validations.valid_mac?("aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd").must_be_same_as true
Net::Validations.validate_mac("aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd").must_be_same_as true
end
test "MAC address is not valid" do
Net::Validations.valid_mac?("aa:bb:cc:dd:ee").must_be_same_as false
Net::Validations.valid_mac?("aa:bb:cc:dd:ee:ff:gg:11").must_be_same_as false
Net::Validations.valid_mac?("aa:bb:cc:dd:ee:zz").must_be_same_as false
Net::Validations.validate_mac("aa:bb:cc:dd:ee").must_be_same_as false
Net::Validations.validate_mac("aa:bb:cc:dd:ee:ff:gg:11").must_be_same_as false
Net::Validations.validate_mac("aa:bb:cc:dd:ee:zz").must_be_same_as false
end
end
test "48-bit mac address should be valid" do
assert_nothing_raised Net::Validations::Error do
validate_mac "aa:bb:cc:dd:ee:ff"
Net::Validations.validate_mac! "aa:bb:cc:dd:ee:ff"
end
end
test "64-bit mac address should be valid" do
assert_nothing_raised Net::Validations::Error do
validate_mac "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd"
Net::Validations.validate_mac! "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd"
end
end
test "mac should be invalid" do
assert_raise Net::Validations::Error do
validate_mac "abc123asdas"
Net::Validations.validate_mac! "abc123asdas"
end
end
test "hostname should be valid" do
assert_nothing_raised Net::Validations::Error do
validate_hostname "this.is.an.example.com"
Net::Validations.validate_hostname! "this.is.an.example.com"
end
assert_nothing_raised Net::Validations::Error do
validate_hostname "this-is.an.example.com"
Net::Validations.validate_hostname! "this-is.an.example.com"
end
assert_nothing_raised Net::Validations::Error do
validate_hostname "localhost"
Net::Validations.validate_hostname! "localhost"
end
end
test "hostname should not be valid" do
assert_raise Net::Validations::Error do
validate_hostname "-this.is.a.bad.example.com"
Net::Validations.validate_hostname! "-this.is.a.bad.example.com"
end
assert_raise Net::Validations::Error do
validate_hostname "this_is_a_bad.example.com"
Net::Validations.validate_hostname! "this_is_a_bad.example.com"
end
end
describe "network validation" do
test "network should be valid" do
assert_nothing_raised Net::Validations::Error do
Net::Validations.validate_network! "123.1.123.1"
end
end
test "network should not be valid" do
assert_raise Net::Validations::Error do
Net::Validations.validate_network! "invalid"
end
assert_raise Net::Validations::Error do
Net::Validations.validate_network! "9999.99.12.1"
end
end
end
describe "mask validation" do
test "mask should be valid" do
assert_nothing_raised Net::Validations::Error do
Net::Validations.validate_mask! "255.255.255.0"
end
end
test "mask should not be valid" do
assert_raise Net::Validations::Error do
Net::Validations.validate_mask! "22.222.22.2"
end
assert_raise Net::Validations::Error do
Net::Validations.validate_mask! "invalid"
end
end
end
......
end
end
end
test "IPv4 address should be valid" do
assert Net::Validations.validate_ip("127.0.0.1")
end
test "IPv4 address should be invalid" do
refute Net::Validations.validate_ip("127.0.0.300")
end
test "empty IPv4 address should be invalid" do
refute Net::Validations.validate_ip('')
end
test "nil should be invalid ip" do
refute Net::Validations.validate_ip(nil)
end
test "return IP when IPv4 address is valid" do
assert_nothing_raised Net::Validations::Error do
assert "127.0.0.1", Net::Validations.validate_ip!("127.0.0.1")
end
end
test "raise error when IPv4 address is invalid" do
assert_raise Net::Validations::Error do
Net::Validations.validate_ip! "127.0.0.1.2"
end
end
test "should normalize IPv4 address" do
assert_equal "127.0.0.1", Net::Validations.normalize_ip("127.000.0.1")
end
test "should ignore invalid data when normalizing IPv4 address" do
assert_equal "xyz.1.2.3", Net::Validations.normalize_ip("xyz.1.2.3")
end
end
test/unit/helpers/application_helper_test.rb
def test_generate_link_for
proxy = FactoryGirl.create(:dhcp_smart_proxy)
subnet = FactoryGirl.create(:subnet, :name => 'My subnet')
subnet = FactoryGirl.create(:subnet_ipv4, :name => 'My subnet')
proxy.subnets = [subnet]
links = generate_links_for(proxy.subnets)
assert_equal(link_to(subnet.to_label, subnets_path(:search => "name = \"#{subnet.name}\"")), links)
test/unit/host_test.rb
test "should have only one provision interface" do
organization = FactoryGirl.create(:organization)
location = FactoryGirl.create(:location)
subnet = FactoryGirl.create(:subnet, :organizations => [organization], :locations => [location])
subnet = FactoryGirl.create(:subnet_ipv4, :organizations => [organization], :locations => [location])
host = FactoryGirl.create(:host, :managed, :organization => organization,
:location => location, :subnet => subnet,
:ip => subnet.network.succ)
......
test "hosts with a DNS-enabled Subnet do require an IP" do
Setting[:token_duration] = 30 #enable tokens so that we only test the subnet
h=FactoryGirl.build(:host, :managed, :subnet => FactoryGirl.build(:subnet, :dns))
h=FactoryGirl.build(:host, :managed, :subnet => FactoryGirl.build(:subnet_ipv4, :dns))
assert h.require_ip_validation?
end
test "hosts with a DHCP-enabled Subnet do require an IP" do
Setting[:token_duration] = 30 #enable tokens so that we only test the subnet
h=FactoryGirl.build(:host, :managed, :subnet => FactoryGirl.build(:subnet, :dhcp))
h=FactoryGirl.build(:host, :managed, :subnet => FactoryGirl.build(:subnet_ipv4, :dhcp))
assert h.require_ip_validation?
end
test "hosts without a DNS/DHCP-enabled Subnet don't require an IP" do
Setting[:token_duration] = 30 #enable tokens so that we only test the subnet
h=FactoryGirl.build(:host, :managed, :subnet => FactoryGirl.build(:subnet, :dhcp => nil, :dns => nil))
h=FactoryGirl.build(:host, :managed, :subnet => FactoryGirl.build(:subnet_ipv4, :dhcp => nil, :dns => nil))
refute h.require_ip_validation?
end
test/unit/location_test.rb
test 'it should return array of used ids by hosts' do
location = taxonomies(:location1)
subnet = FactoryGirl.create(:subnet, :locations => [location])
subnet = FactoryGirl.create(:subnet_ipv4, :locations => [location])
domain = FactoryGirl.create(:domain)
FactoryGirl.create(:host,
:compute_resource => compute_resources(:one),
......
test "used_and_selected_or_inherited_ids for inherited location" do
parent = taxonomies(:location1)
subnet = FactoryGirl.create(:subnet, :organizations => [taxonomies(:organization1)])
subnet = FactoryGirl.create(:subnet_ipv4, :organizations => [taxonomies(:organization1)])
domain1 = FactoryGirl.create(:domain)
domain2 = FactoryGirl.create(:domain)
parent.update_attribute(:domains,[domain1,domain2])
test/unit/nic_test.rb
disable_taxonomies do
orgs = FactoryGirl.build_pair(:organization)
locs = FactoryGirl.build_pair(:location)
subn = FactoryGirl.build(:subnet, :locations => [locs.first], :organizations => [orgs.first])
subn = FactoryGirl.build(:subnet_ipv4, :locations => [locs.first], :organizations => [orgs.first])
host = FactoryGirl.build(:host, :location => locs.last, :organization => orgs.last)
nic = Nic::Base.new :mac => "cabbccddeeff", :host => host
nic.subnet = subn
......
test "Alias subnet can only use static boot mode if it's managed" do
nic = FactoryGirl.build(:nic_managed, :virtual => true, :attached_to => 'eth0', :managed => true, :identifier => 'eth0:0')
nic.host = FactoryGirl.build(:host)
nic.subnet = FactoryGirl.build(:subnet, :boot_mode => Subnet::BOOT_MODES[:dhcp])
nic.subnet = FactoryGirl.build(:subnet_ipv4, :boot_mode => Subnet::BOOT_MODES[:dhcp])
refute nic.valid?
assert_includes nic.errors.keys, :subnet_id
......
context 'BMC' do
setup do
disable_orchestration
@subnet = FactoryGirl.create(:subnet, :dhcp, :ipam => Subnet::IPAM_MODES[:db])
@subnet = FactoryGirl.create(:subnet_ipv4, :dhcp, :ipam => Subnet::IPAM_MODES[:db])
@domain = FactoryGirl.create(:domain)
@interface = FactoryGirl.create(:nic_bmc, :ip => @subnet.unused_ip,
:host => FactoryGirl.create(:host),
test/unit/orchestration/dhcp_test.rb
i = FactoryGirl.build(:nic_managed, :ip => '10.0.0.10', :name => 'eth0:0')
i.host = h
i.domain = domains(:mydomain)
i.subnet = FactoryGirl.build(:subnet, :dhcp, :boot_mode => 'Static', :ipam => 'Internal DB')
i.subnet = FactoryGirl.build(:subnet_ipv4, :dhcp, :boot_mode => 'Static', :ipam => 'Internal DB')
refute i.dhcp?
end
end
......
test "provision interface DHCP records should contain filename/next-server attributes" do
ProxyAPI::TFTP.any_instance.expects(:bootServer).returns('192.168.1.1')
subnet = FactoryGirl.build(:subnet, :dhcp, :tftp)
subnet = FactoryGirl.build(:subnet_ipv4, :dhcp, :tftp)
h = FactoryGirl.create(:host, :with_dhcp_orchestration, :with_tftp_orchestration, :subnet => subnet)
assert_equal 'pxelinux.0', h.provision_interface.dhcp_record.filename
assert_equal '192.168.1.1', h.provision_interface.dhcp_record.nextServer
test/unit/organization_test.rb
test 'it should return array of used ids by hosts' do
organization = taxonomies(:organization1)
subnet = FactoryGirl.create(:subnet, :organizations => [organization])
subnet = FactoryGirl.create(:subnet_ipv4, :organizations => [organization])
domain = FactoryGirl.create(:domain)
FactoryGirl.create(:host,
:compute_resource => compute_resources(:one),
test/unit/subnet.rb
require 'test_helper'
class SubnetTest < ActiveSupport::TestCase
test 'should be cast to Subnet::Ipv4 if no type is set' do
subnet = Subnet.new
assert_equal Subnet::Ipv4, subnet.class
end
test 'should be cast to Subnet::Ipv4 if type is set' do
subnet = Subnet.new(:type => 'Subnet::Ipv4')
assert_equal Subnet::Ipv4, subnet.class
end
test 'child class should not be cast to default sti class even if no type is set' do
class Subnet::Test < Subnet; end
subnet = Subnet::Test.new
assert_equal Subnet::Test, subnet.class
end
end
test/unit/subnet/ipv4_test.rb
require 'test_helper'
class SubnetIpv4Test < ActiveSupport::TestCase
def setup
User.current = users :admin
@subnet = Subnet::Ipv4.new
@attrs = { :network= => "123.123.123.0",
:mask= => "255.255.255.0",
:domains= => [domains(:mydomain)],
:name= => "valid" }
end
test 'can be created with domains' do
subnet = FactoryGirl.build(:subnet_ipv4)
subnet.domain_ids = [ domains(:mydomain).id ]
assert subnet.save
end
test "should have a network" do
create_a_domain_with_the_subnet
@subnet.network = nil
assert !@subnet.save
set_attr(:network=)
assert @subnet.save
end
test "should have a mask" do
create_a_domain_with_the_subnet
@subnet.mask = nil
assert !@subnet.save
@subnet.mask = "255.255.255.0"
assert @subnet.save
end
test "network should have ip format" do
@subnet.network = "asf.fwe6.we6s.q1"
set_attr(:mask=)
assert !@subnet.save
end
test "mask should have ip format" do
@subnet.mask = "asf.fwe6.we6s.q1"
set_attr(:network=, :domains=, :name=)
assert !@subnet.save
end
test "mask should have valid address" do
@subnet.mask = "255.0.0.255"
set_attr(:network=, :domains=, :name=)
refute @subnet.save
end
test "cidr setter should set the mask" do
@subnet = FactoryGirl.build(:subnet_ipv4)
@subnet.cidr = 24
assert_equal '255.255.255.0', @subnet.mask
end
test "cidr setter should not raise exception for invalid value" do
@subnet = FactoryGirl.build(:subnet_ipv4)
@subnet.cidr = 'green'
end
test "network should be unique" do
set_attr(:network=, :mask=, :domains=, :name=)
@subnet.save
other_subnet = Subnet::Ipv4.create(:network => "123.123.123.0", :mask => "255.255.255.0")
assert !other_subnet.save
end
test "the name should be unique in the domain scope" do
create_a_domain_with_the_subnet
other_subnet = Subnet::Ipv4.new( :mask => "111.111.111.1",
:network => "255.255.252.0",
:name => "valid",
:domain_ids => [domains(:mydomain).id] )
assert !other_subnet.valid?
assert !other_subnet.save
end
test "when to_label is applied should show the domain, the mask and network" do
create_a_domain_with_the_subnet
assert_equal "valid (123.123.123.0/24)", @subnet.to_label
end
test "should find the subnet by ip" do
@subnet = Subnet::Ipv4.new(:network => "123.123.123.0", :mask => "255.255.255.0", :name => "valid")
assert @subnet.save
assert @subnet.domain_ids = [domains(:mydomain).id]
assert_equal @subnet, Subnet::Ipv4.subnet_for("123.123.123.1")
end
def set_attr(*attr)
attr.each do |param|
@subnet.send param, @attrs[param]
end
end
def create_a_domain_with_the_subnet
@domain = Domain.where(:name => "domain").first_or_create
@subnet = Subnet::Ipv4.new(:network => "123.123.123.0", :mask => "255.255.255.0", :name => "valid")
assert @subnet.save
assert @subnet.domain_ids = [domains(:mydomain).id]
@subnet.save!
end
test "from cant be bigger than to range" do
s = subnets(:one)
s.to = "2.3.4.15"
s.from = "2.3.4.17"
assert !s.save
end
test "should be able to save ranges" do
s=subnets(:one)
s.from = "2.3.4.15"
s.to = "2.3.4.17"
assert s.save
end
test "should not be able to save ranges if they dont belong to the subnet" do
s=subnets(:one)
s.from = "2.3.3.15"
s.to = "2.3.4.17"
assert !s.save
end
test "should not be able to save ranges if one of them is missing" do
s=subnets(:one)
s.from = "2.3.4.15"
assert !s.save
s.to = "2.3.4.17"
assert s.save
end
test "should not be able to save ranges if one of them is invalid" do
s=subnets(:one)
s.from = "2.3.4.abc"
s.to = "2.3.4.17"
refute s.valid?
end
test "should strip whitespace before save" do
s = subnets(:one)
s.network = " 10.0.0.22 "
s.mask = " 255.255.255.0 "
s.gateway = " 10.0.0.138 "
s.dns_primary = " 10.0.0.50 "
s.dns_secondary = " 10.0.0.60 "
assert s.save
assert_equal "10.0.0.22", s.network
assert_equal "255.255.255.0", s.mask
assert_equal "10.0.0.138", s.gateway
assert_equal "10.0.0.50", s.dns_primary
assert_equal "10.0.0.60", s.dns_secondary
end
test "should fix typo with extra dots to single dot" do
s = subnets(:one)
s.network = "10..0.0..22"
assert s.save
assert_equal "10.0.0.22", s.network
end
test "should fix typo with extra 5 after 255" do
s = subnets(:one)
s.mask = "2555.255.25555.0"
assert s.save
assert_equal "255.255.255.0", s.mask
end
test "should not allow an address great than 15 characters" do
s = subnets(:one)
s.mask = "255.255.255.1111"
refute s.save
assert_match /Mask is invalid/, s.errors.full_messages.join("\n")
end
test "should invalidate addresses are indeed invalid" do
s = subnets(:one)
# more than 3 characters
s.network = "1234.101.102.103"
# missing dot
s.network = "100101.102.103."
refute s.valid?
# greater than 255
s.network = "300.300.300.0"
refute s.valid?
# missing number
s.network = "100.101.102"
refute s.valid?
assert_equal "is invalid", s.errors[:network].first
end
test "should clean invalid addresses" do
s = subnets(:one)
# trailing dot
s.network = "100.101.102.103."
assert s.valid?
end
# test module StripWhitespace which strips leading and trailing whitespace on :name field
test "should strip whitespace on name" do
s = Subnet::Ipv4.new(:name => ' ABC Network ', :network => "10.10.20.1", :mask => "255.255.255.0")
assert s.save!
assert_equal "ABC Network", s.name
end
test "should not destroy if hostgroup uses it" do
hostgroup = FactoryGirl.create(:hostgroup, :with_subnet)
subnet = hostgroup.subnet
refute subnet.destroy
assert_match /is used by/, subnet.errors.full_messages.join("\n")
end
test "should not destroy if host uses it" do
host = FactoryGirl.create(:host, :with_subnet)
subnet = host.subnet
refute subnet.destroy
assert_match /is used by/, subnet.errors.full_messages.join("\n")
end
test "should find unused IP on proxy if proxy is set" do
subnet = FactoryGirl.create(:subnet_ipv4, :name => 'my_subnet', :network => '192.168.1.0')
subnet.stubs(:dhcp? => true)
subnet.stubs(:dhcp => mock('attribute', :url => 'proxy.example.com'))
fake_proxy = mock("dhcp_proxy")
fake_proxy.stubs(:unused_ip => {'ip' => '192.168.1.25'})
subnet.stubs(:dhcp_proxy => fake_proxy)
assert_equal '192.168.1.25', subnet.unused_ip
end
test "should find unused IP in internal DB if proxy is not set" do
host = FactoryGirl.create(:host)
subnet = FactoryGirl.create(:subnet_ipv4, :name => 'my_subnet', :network => '192.168.2.0',
:ipam => Subnet::IPAM_MODES[:db])
subnet.stubs(:dhcp? => false)
assert_equal '192.168.2.1', subnet.unused_ip
subnet.reload
FactoryGirl.create(:nic_managed, :ip => '192.168.2.1', :subnet_id => subnet.id, :host => host, :mac => '00:00:00:00:00:01')
FactoryGirl.create(:nic_managed, :ip => '192.168.2.2', :subnet_id => subnet.id, :host => host, :mac => '00:00:00:00:00:02')
assert_equal '192.168.2.3', subnet.unused_ip
end
test "should find unused IP excluding named values in internal DB if proxy is not set" do
host = FactoryGirl.create(:host)
subnet = FactoryGirl.create(:subnet_ipv4, :name => 'my_subnet', :network => '192.168.2.0',
:ipam => Subnet::IPAM_MODES[:db])
subnet.stubs(:dhcp? => false)
assert_equal '192.168.2.3', subnet.unused_ip(nil, ['192.168.2.1', '192.168.2.2'])
subnet.reload
FactoryGirl.create(:nic_managed, :ip => '192.168.2.1', :subnet_id => subnet.id, :host => host, :mac => '00:00:00:00:00:01')
FactoryGirl.create(:nic_managed, :ip => '192.168.2.2', :subnet_id => subnet.id, :host => host, :mac => '00:00:00:00:00:02')
assert_equal '192.168.2.4', subnet.unused_ip(nil, ['192.168.2.3'])
end
test "#unused should respect subnet from and to if it's set" do
host = FactoryGirl.create(:host)
subnet = FactoryGirl.create(:subnet_ipv4, :name => 'my_subnet', :network => '192.168.2.0', :from => '192.168.2.10', :to => '192.168.2.12',
:ipam => Subnet::IPAM_MODES[:db])
subnet.stubs(:dhcp? => false)
assert_equal '192.168.2.10', subnet.unused_ip
subnet.reload
FactoryGirl.create(:nic_managed, :ip => '192.168.2.10', :subnet_id => subnet.id, :host => host, :mac => '00:00:00:00:00:01')
FactoryGirl.create(:nic_managed, :ip => '192.168.2.11', :subnet_id => subnet.id, :host => host, :mac => '00:00:00:00:00:02')
assert_equal '192.168.2.12', subnet.unused_ip
subnet.reload
FactoryGirl.create(:nic_managed, :ip => '192.168.2.12', :subnet_id => subnet.id, :host => host, :mac => '00:00:00:00:00:03')
assert_nil subnet.unused_ip
end
test "#unused does not suggest IP if mode is set to none" do
subnet = FactoryGirl.create(:subnet_ipv4, :name => 'my_subnet', :network => '192.168.2.0', :from => '192.168.2.10', :to => '192.168.2.12')
subnet.stubs(:dhcp? => false, :ipam => Subnet::IPAM_MODES[:none])
assert_nil subnet.unused_ip
end
test "#known_ips includes all host and interfaces IPs assigned to this subnet" do
subnet = FactoryGirl.create(:subnet_ipv4, :name => 'my_subnet', :network => '192.168.2.0', :from => '192.168.2.10', :to => '192.168.2.12',
:dns_primary => '192.168.2.2', :gateway => '192.168.2.3', :ipam => Subnet::IPAM_MODES[:db])
host = FactoryGirl.create(:host, :subnet => subnet, :ip => '192.168.2.1')
Nic::Managed.create :mac => "00:00:01:10:00:00", :host => host, :subnet => subnet, :name => "", :ip => '192.168.2.4'
assert_includes subnet.known_ips, '192.168.2.1'
assert_includes subnet.known_ips, '192.168.2.2'
assert_includes subnet.known_ips, '192.168.2.3'
assert_includes subnet.known_ips, '192.168.2.4'
assert_equal 4, subnet.known_ips.size
end
context 'import subnets' do
setup do
@mock_proxy = mock('dhcp_proxy')
@mock_proxy.stubs(:has_feature? => true)
@mock_proxy.stubs(:url => 'http://fake')
end
test 'options are imported from the dhcp proxy' do
dhcp_options = { 'routers' => ['192.168.11.1'],
'domain_name_servers' => ['192.168.11.1', '8.8.8.8'],
'range' => ['192.168.11.0', '192.168.11.200'] }
ProxyAPI::DHCP.any_instance.
stubs(:subnets => [ { 'network' => '192.168.11.0',
'netmask' => '255.255.255.0',
'options' => dhcp_options } ])
Subnet.expects(:new).with(:network => "192.168.11.0",
:mask => "255.255.255.0",
:gateway => "192.168.11.1",
:dns_primary => "192.168.11.1",
:dns_secondary => "8.8.8.8",
:from => "192.168.11.0",
:to => "192.168.11.200",
:dhcp => @mock_proxy)
Subnet.import(@mock_proxy)
end
test 'imports subnets without options' do
ProxyAPI::DHCP.any_instance.
stubs(:subnets => [ { 'network' => '192.168.11.0',
'netmask' => '255.255.255.0' } ])
Subnet.expects(:new).with(:network => "192.168.11.0",
:mask => "255.255.255.0",
:dhcp => @mock_proxy)
Subnet.import(@mock_proxy)
end
end
test "should not assign proxies without adequate features" do
proxy = smart_proxies(:puppetmaster)
subnet = Subnet::Ipv4.new(:name => "test subnet",
:network => "192.168.100.0",
:mask => "255.255.255.0",
:dhcp_id => proxy.id,
:dns_id => proxy.id,
:tftp_id => proxy.id)
refute subnet.save
assert_equal "does not have the DNS feature", subnet.errors["dns_id"].first
assert_equal "does not have the DHCP feature", subnet.errors["dhcp_id"].first
assert_equal "does not have the TFTP feature", subnet.errors["tftp_id"].first
end
test "#type cannot be updated for existing subnet" do
subnet = subnets(:one)
subnet.type = 'Subnet::Ipv6'
refute subnet.save
assert subnet.errors[:type].include?("can't be updated after subnet is saved")
end
test "should inherit model name from parent class" do
assert_equal Subnet.model_name, Subnet::Ipv4.model_name
end
end
test/unit/subnet_test.rb
require 'test_helper'
class SubnetTest < ActiveSupport::TestCase
def setup
User.current = users :admin
@subnet = Subnet.new
@attrs = { :network= => "123.123.123.1",
:mask= => "255.255.255.0",
:domains= => [domains(:mydomain)],
:name= => "valid" }
end
test 'can be created with domains' do
subnet = FactoryGirl.build(:subnet)
subnet.domain_ids = [ domains(:mydomain).id ]
assert subnet.save
end
test "should have a network" do
create_a_domain_with_the_subnet
@subnet.network = nil
assert !@subnet.save
set_attr(:network=)
assert @subnet.save
end
test "should have a mask" do
create_a_domain_with_the_subnet
@subnet.mask = nil
assert !@subnet.save
@subnet.mask = "255.255.255.0"
assert @subnet.save
end
test "network should have ip format" do
@subnet.network = "asf.fwe6.we6s.q1"
set_attr(:mask=)
assert !@subnet.save
end
test "mask should have ip format" do
@subnet.mask = "asf.fwe6.we6s.q1"
set_attr(:network=, :domains=, :name=)
assert !@subnet.save
end
test "mask should have valid address" do
@subnet.mask = "255.0.0.255"
set_attr(:network=, :domains=, :name=)
refute @subnet.save
end
test "network should be unique" do
set_attr(:network=, :mask=, :domains=, :name=)
@subnet.save
other_subnet = Subnet.create(:network => "123.123.123.1", :mask => "255.255.255.0")
assert !other_subnet.save
end
test "the name should be unique in the domain scope" do
create_a_domain_with_the_subnet
other_subnet = Subnet.new( :mask => "111.111.111.1",
:network => "255.255.252.0",
:name => "valid",
:domain_ids => [domains(:mydomain).id] )
assert !other_subnet.valid?
assert !other_subnet.save
end
test "when to_label is applied should show the domain, the mask and network" do
create_a_domain_with_the_subnet
assert_equal "valid (123.123.123.1/24)", @subnet.to_label
end
test "should find the subnet by ip" do
@subnet = Subnet.new(:network => "123.123.123.1",:mask => "255.255.255.0",:name => "valid")
assert @subnet.save
assert @subnet.domain_ids = [domains(:mydomain).id]
assert_equal @subnet, Subnet.subnet_for("123.123.123.1")
end
def set_attr(*attr)
attr.each do |param|
@subnet.send param, @attrs[param]
end
end
def create_a_domain_with_the_subnet
@domain = Domain.where(:name => "domain").first_or_create
@subnet = Subnet.new(:network => "123.123.123.1",:mask => "255.255.255.0",:name => "valid")
assert @subnet.save
assert @subnet.domain_ids = [domains(:mydomain).id]
@subnet.save!
end
test "from cant be bigger than to range" do
s = subnets(:one)
s.to = "2.3.4.15"
s.from = "2.3.4.17"
assert !s.save
end
test "should be able to save ranges" do
s=subnets(:one)
s.from = "2.3.4.15"
s.to = "2.3.4.17"
assert s.save
end
test "should not be able to save ranges if they dont belong to the subnet" do
s=subnets(:one)
s.from = "2.3.3.15"
s.to = "2.3.4.17"
assert !s.save
end
test "should not be able to save ranges if one of them is missing" do
s=subnets(:one)
s.from = "2.3.4.15"
assert !s.save
s.to = "2.3.4.17"
assert s.save
end
test "should strip whitespace before save" do
s = subnets(:one)
s.network = " 10.0.0.22 "
s.mask = " 255.255.255.0 "
s.gateway = " 10.0.0.138 "
s.dns_primary = " 10.0.0.50 "
s.dns_secondary = " 10.0.0.60 "
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff