Class: Subnet

Inherits:
ApplicationRecord show all
Extended by:
FriendlyId
Includes:
Authorizable, BelongsToProxies, Exportable, ParameterSearch, Parameterizable::ByIdName, ScopedSearchExtensions, Taxonomix
Defined in:
app/models/subnet.rb

Direct Known Subclasses

Ipv4, Ipv6

Defined Under Namespace

Classes: Ipv4, Ipv6, Jail

Constant Summary collapse

IP_FIELDS =
[:network, :mask, :gateway, :dns_primary, :dns_secondary, :from, :to]
REQUIRED_IP_FIELDS =
[:network, :mask]
SUBNET_TYPES =
{:'Subnet::Ipv4' => N_('IPv4'), :'Subnet::Ipv6' => N_('IPv6')}
BOOT_MODES =
{:static => N_('Static'), :dhcp => N_('DHCP')}

Constants included from Taxonomix

Taxonomix::TAXONOMY_JOIN_TABLE

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Exportable

#export_attr, #export_iterable, #to_export

Methods included from Taxonomix

#add_current_location?, #add_current_organization?, #add_current_taxonomy?, #children_of_selected_location_ids, #children_of_selected_organization_ids, #ensure_taxonomies_not_escalated, #set_current_taxonomy, #used_location_ids, #used_or_selected_location_ids, #used_or_selected_organization_ids, #used_organization_ids

Methods included from DirtyAssociations

#reset_dirty_cache_state

Methods included from Authorizable

#authorized?, #check_permissions_after_save

Methods included from PermissionName

#permission_name

Methods inherited from ApplicationRecord

graphql_type, #logger, logger

Methods included from AuditAssociations::AssociationsDefinitions

#audit_associations, #audited, #configure_dirty_associations, #normalize_associations

Class Method Details

.boot_modes_with_translationsObject


397
398
399
# File 'app/models/subnet.rb', line 397

def boot_modes_with_translations
  BOOT_MODES.map { |_, mode_name| [_(mode_name), mode_name] }
end

.inherited(child) ⇒ Object

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, …


28
29
30
31
32
33
34
35
36
37
# File 'app/models/subnet.rb', line 28

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

.network_reorder(order_string = 'network') ⇒ Object


161
162
163
164
165
166
167
168
# File 'app/models/subnet.rb', line 161

def self.network_reorder(order_string = 'network')
  adapter = connection.adapter_name.downcase
  if adapter.starts_with?('postgresql')
    reorder(Arel.sql(order_string.sub('network', 'inet(network)')))
  else
    self
  end
end

.new(*attributes, &block) ⇒ Object

This casts Subnet to Subnet::Ipv4 if no type is set


419
420
421
422
423
# File 'app/models/subnet.rb', line 419

def new(*attributes, &block)
  type = attributes.first.with_indifferent_access.delete(:type) if attributes.first.is_a?(Hash)
  return Subnet::Ipv4.new(*attributes, &block) if self == Subnet && type.nil?
  super
end

.new_network_type(args) ⇒ Object

allows to create a specific subnet class based on the network_type. network_type is more user friendly than the class names


427
428
429
430
431
432
433
# File 'app/models/subnet.rb', line 427

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

.subnet_for(ip) ⇒ Object

Given an IP returns the subnet that contains that IP preferring highest CIDR prefix

ip

: IPv4 or IPv6 address

Returns : Subnet object or nil if not found


412
413
414
415
416
# File 'app/models/subnet.rb', line 412

def subnet_for(ip)
  return unless ip.present?
  ip = IPAddr.new(ip)
  Subnet.unscoped.all.select { |s| s.family == ip.family && s.contains?(ip) }.max_by(&:cidr)
end

.supported_ipam_modes_with_translationsObject


405
406
407
# File 'app/models/subnet.rb', line 405

def supported_ipam_modes_with_translations
  supported_ipam_modes.map { |mode| [_(IPAM::MODES[mode]), IPAM::MODES[mode]] }
end

.supports_ipam_mode?(mode) ⇒ Boolean

Returns:

  • (Boolean)

401
402
403
# File 'app/models/subnet.rb', line 401

def supports_ipam_mode?(mode)
  supported_ipam_modes.include?(mode)
end

Instance Method Details

#as_json(options = {}) ⇒ Object


355
356
357
# File 'app/models/subnet.rb', line 355

def as_json(options = {})
  super({:methods => [:to_label, :type]}.merge(options))
end

#bmc?Boolean

Returns:

  • (Boolean)

257
258
259
# File 'app/models/subnet.rb', line 257

def bmc?
  !!(bmc && bmc.url && bmc.url.present?)
end

#cidrObject


202
203
204
205
206
207
# File 'app/models/subnet.rb', line 202

def cidr
  return if mask.nil?
  IPAddr.new(mask).to_i.to_s(2).count("1")
rescue invalid_address_error
  nil
end

#cidr=(cidr) ⇒ Object


209
210
211
212
213
214
# File 'app/models/subnet.rb', line 209

def cidr=(cidr)
  return if cidr.nil?
  self[:mask] = IPAddr.new(in_mask, family).mask(cidr).to_s
rescue invalid_address_error
  nil
end

#contains?(ip) ⇒ Boolean

Indicates whether the IP is within this subnet

ip

String: IPv4 or IPv6 address

Returns Boolean: True if if ip is in this subnet

Returns:

  • (Boolean)

194
195
196
# File 'app/models/subnet.rb', line 194

def contains?(ip)
  ipaddr.include? IPAddr.new(ip, family)
end

#dhcp?Boolean

Returns:

  • (Boolean)

216
217
218
# File 'app/models/subnet.rb', line 216

def dhcp?
  supports_ipam_mode?(:dhcp) && dhcp && dhcp.url.present?
end

#dhcp_boot_mode?Boolean

Returns:

  • (Boolean)

281
282
283
# File 'app/models/subnet.rb', line 281

def dhcp_boot_mode?
  boot_mode == Subnet::BOOT_MODES[:dhcp]
end

#dhcp_proxy(attrs = {}) ⇒ Object


220
221
222
# File 'app/models/subnet.rb', line 220

def dhcp_proxy(attrs = {})
  @dhcp_proxy ||= ProxyAPI::DHCP.new({:url => dhcp.url}.merge(attrs)) if dhcp?
end

#dns?Boolean

do we support DNS PTR records for this subnet

Returns:

  • (Boolean)

241
242
243
# File 'app/models/subnet.rb', line 241

def dns?
  !!(dns && dns.url && dns.url.present?)
end

#dns_proxy(attrs = {}) ⇒ Object


245
246
247
# File 'app/models/subnet.rb', line 245

def dns_proxy(attrs = {})
  @dns_proxy ||= ProxyAPI::DNS.new({:url => dns.url}.merge(attrs)) if dns?
end

#dns_serversObject


359
360
361
# File 'app/models/subnet.rb', line 359

def dns_servers
  [dns_primary, dns_secondary].select(&:present?)
end

#external_ipam?Boolean

Returns:

  • (Boolean)

261
262
263
# File 'app/models/subnet.rb', line 261

def external_ipam?
  supports_ipam_mode?(:external_ipam) && externalipam && externalipam.url.present?
end

#external_ipam_proxy(attrs = {}) ⇒ Object


265
266
267
# File 'app/models/subnet.rb', line 265

def external_ipam_proxy(attrs = {})
  @external_ipam_proxy ||= ProxyAPI::ExternalIpam.new({:url => externalipam.url}.merge(attrs)) if external_ipam?
end

#group_exists_in_external_ipamObject


318
319
320
321
322
323
# File 'app/models/subnet.rb', line 318

def group_exists_in_external_ipam
  return true if externalipam_group.empty?
  return false unless external_ipam_proxy
  group = external_ipam_proxy.get_group(externalipam_group)
  group.empty? ? false : true
end

#has_vlanid?Boolean

Returns:

  • (Boolean)

345
346
347
# File 'app/models/subnet.rb', line 345

def has_vlanid?
  vlanid.present?
end

#httpboot?Boolean

Returns:

  • (Boolean)

232
233
234
# File 'app/models/subnet.rb', line 232

def httpboot?
  !!(httpboot && httpboot.url && httpboot.url.present?)
end

#httpboot_proxy(attrs = {}) ⇒ Object


236
237
238
# File 'app/models/subnet.rb', line 236

def httpboot_proxy(attrs = {})
  @httpboot_proxy ||= ProxyAPI::TFTP.new({:url => httpboot.url}.merge(attrs)) if httpboot?
end

#ipaddrObject


198
199
200
# File 'app/models/subnet.rb', line 198

def ipaddr
  IPAddr.new("#{network}/#{mask}", family)
end

#ipam?Boolean

Returns:

  • (Boolean)

269
270
271
# File 'app/models/subnet.rb', line 269

def ipam?
  ipam != IPAM::MODES[:none]
end

#ipam_needs_group?Boolean

Returns:

  • (Boolean)

277
278
279
# File 'app/models/subnet.rb', line 277

def ipam_needs_group?
  ipam? && ipam == IPAM::MODES[:external_ipam]
end

#ipam_needs_range?Boolean

Returns:

  • (Boolean)

273
274
275
# File 'app/models/subnet.rb', line 273

def ipam_needs_range?
  ipam? && ipam != IPAM::MODES[:eui64] && ipam != IPAM::MODES[:external_ipam]
end

#known_ipsObject


334
335
336
337
338
339
# File 'app/models/subnet.rb', line 334

def known_ips
  interfaces.reload
  ips = interfaces.map(&ip_sym) + hosts.includes(:interfaces).map(&ip_sym)
  ips += [gateway, dns_primary, dns_secondary].select(&:present?)
  ips.compact.uniq
end

#network_addressObject

Subnets are displayed in the form of their network network/network mask


171
172
173
# File 'app/models/subnet.rb', line 171

def network_address
  "#{network}/#{cidr}"
end

#network_typeObject


183
184
185
# File 'app/models/subnet.rb', line 183

def network_type
  SUBNET_TYPES[type.to_sym]
end

#network_type=(value) ⇒ Object


187
188
189
# File 'app/models/subnet.rb', line 187

def network_type=(value)
  self[:type] = SUBNET_TYPES.key(value)
end

#proxiesObject


341
342
343
# File 'app/models/subnet.rb', line 341

def proxies
  [dhcp, tftp, dns, httpboot].compact
end

#subnet_exists_in_external_ipamObject


312
313
314
315
316
# File 'app/models/subnet.rb', line 312

def subnet_exists_in_external_ipam
  return false unless external_ipam_proxy
  subnet = external_ipam_proxy.get_subnet(network_address, externalipam_group)
  subnet.empty? ? false : true
end

#subnet_exists_in_groupObject


305
306
307
308
309
310
# File 'app/models/subnet.rb', line 305

def subnet_exists_in_group
  return true if externalipam_group.empty?
  return false unless external_ipam_proxy
  subnet = external_ipam_proxy.get_subnet_from_group(network_address, externalipam_group)
  subnet.empty? ? false : true
end

#template?Boolean

Returns:

  • (Boolean)

249
250
251
# File 'app/models/subnet.rb', line 249

def template?
  !!(template && template.url)
end

#template_proxy(attrs = {}) ⇒ Object


253
254
255
# File 'app/models/subnet.rb', line 253

def template_proxy(attrs = {})
  @template_proxy ||= ProxyAPI::Template.new({:url => template.url}.merge(attrs)) if template?
end

#tftp?Boolean

Returns:

  • (Boolean)

224
225
226
# File 'app/models/subnet.rb', line 224

def tftp?
  !!(tftp && tftp.url && tftp.url.present?)
end

#tftp_proxy(attrs = {}) ⇒ Object


228
229
230
# File 'app/models/subnet.rb', line 228

def tftp_proxy(attrs = {})
  @tftp_proxy ||= ProxyAPI::TFTP.new({:url => tftp.url}.merge(attrs)) if tftp?
end

#to_labelObject


175
176
177
# File 'app/models/subnet.rb', line 175

def to_label
  "#{name} (#{network_address})"
end

#to_sObject


179
180
181
# File 'app/models/subnet.rb', line 179

def to_s
  name
end

#unused_ip(mac = nil, excluded_ips = []) ⇒ Object


325
326
327
328
329
330
331
332
# File 'app/models/subnet.rb', line 325

def unused_ip(mac = nil, excluded_ips = [])
  unless supported_ipam_modes.map { |m| IPAM::MODES[m] }.include?(ipam)
    raise ::Foreman::Exception.new(N_("Unsupported IPAM mode for %s"), self.class.name)
  end

  opts = {:subnet => self, :mac => mac, :excluded_ips => excluded_ips}
  IPAM.new(ipam, opts)
end

#used_taxonomy_ids(type) ⇒ Object

overwrite method in taxonomix, since subnet is not direct association of host anymore


350
351
352
353
# File 'app/models/subnet.rb', line 350

def used_taxonomy_ids(type)
  return [] if new_record?
  Host::Base.joins(:primary_interface).where(:nics => {:subnet_id => id}).distinct.pluck(type).compact
end

#validate_against_external_ipamObject


285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'app/models/subnet.rb', line 285

def validate_against_external_ipam
  return unless errors.full_messages.empty?

  if ipam == IPAM::MODES[:external_ipam]
    external_ipam_proxy = SmartProxy.with_features('External IPAM').first

    if external_ipam_proxy.nil?
      errors.add :ipam, _('There must be at least one Smart Proxy present with an External IPAM plugin installed and configured')
    elsif self.external_ipam_proxy.nil?
      errors.add :ipam, _('A Smart Proxy with an External IPAM feature enabled must be selected in the Proxies tab.')
    elsif !group_exists_in_external_ipam
      errors.add :externalipam_group, _('Group not found in the configured External IPAM instance')
    elsif !subnet_exists_in_external_ipam
      errors.add :network, _('Subnet not found in the configured External IPAM instance')
    elsif !subnet_exists_in_group
      errors.add :network, _('Subnet not found in the specified External IPAM group')
    end
  end
end