Skip to content

Commit

Permalink
feat: Improve IP calculation logic
Browse files Browse the repository at this point in the history
Handle the case of unnumbered net + per-actor numbering + clustered config
  • Loading branch information
mromulus committed Mar 22, 2024
1 parent f444129 commit c04e965
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 20 deletions.
2 changes: 1 addition & 1 deletion app/calculation/address_values.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def result
.all_numbers
.values_at(0, 1, -1)
.uniq
.map { |number| subject.ip_object(nil, number).to_s }
.map { |number| subject.ip_object(actor_number: number).to_s }

case values.size
when 2
Expand Down
6 changes: 5 additions & 1 deletion app/calculation/network_map_graph_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ def to_node
addresses: vm.connection_nic.addresses.for_search.map do |address|
LiquidReplacer.new(
UnsubstitutedAddress.result_for(
address.ip_object(sequential_number, team_number),
address.ip_object(
sequence_number: sequential_number,
sequence_total: vm.custom_instance_count,
actor_number: team_number
),
address_pool: address.address_pool
)
).iterate do |variable_node|
Expand Down
16 changes: 7 additions & 9 deletions app/models/address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,14 @@ def ip_family_network_template
end
end

def ip_object(sequential = nil, team = nil)
def ip_object(sequence_number: nil, sequence_total: nil, actor_number: nil)
return unless fixed? && ip_family_network_template.present?
static_offset = -1
static_offset = offset.to_i
static_offset -= 1 if ipv6?
static_offset += (sequence_number || 1) - 1
static_offset += ((actor_number || 1) - 1) * (sequence_total || 1) if !address_pool&.numbered?

if address_pool&.numbered?
ip_family_network(team).allocate(offset.to_i + (sequential || 1) + static_offset)
else
ip_family_network.allocate(offset.to_i + (sequential || 1) + (team || 1) + static_offset - 1)
end
ip_family_network(actor_number).allocate(static_offset)
end

def ipv4?
Expand Down Expand Up @@ -146,11 +144,11 @@ def all_ip_objects
return unless offset
if virtual_machine.custom_instance_count
1.upto(virtual_machine.custom_instance_count).map do |seq|
ip_object(seq)
ip_object(sequence_number: seq, sequence_total: virtual_machine.custom_instance_count)
end
elsif !(address_pool || network).numbered? && virtual_machine.deploy_count > 1
1.upto(virtual_machine.deploy_count).map do |team_nr|
ip_object(nil, team_nr)
ip_object(actor_number: team_nr, sequence_total: virtual_machine.custom_instance_count)
end
else
[ip_object]
Expand Down
2 changes: 1 addition & 1 deletion app/models/virtual_machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def numbered_actor

def single_network_instances(presenter)
if numbered_actor && connection_nic.network.numbered?
1.upto(custom_instance_count || 1).map do |seq|
host_spec.sequential_numbers.map do |seq|
presenter.new(host_spec, seq, nil)
end
else
Expand Down
11 changes: 6 additions & 5 deletions app/presenters/address_preview_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ def interfaces
addresses: nic.addresses.for_api.map do |address|
{
mode: address.mode,
}.tap do |hash|
if address.fixed?
hash[:address] = address.ip_object(sequential_number, team_number).to_s
end
end
address: address.ip_object(
sequence_number: sequential_number,
sequence_total: vm.custom_instance_count,
actor_number: team_number
).to_s
}.compact
end
}
end
Expand Down
12 changes: 10 additions & 2 deletions app/presenters/api/v3/instance_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ def as_json
hostname:,
domain: substitute(connection_namespec.domain.to_s),
fqdn: substitute(connection_namespec.fqdn.to_s),
connection_address: connection_address&.ip_object(sequential_number, team_number)&.to_s,
connection_address: connection_address&.ip_object(
sequence_number: sequential_number,
sequence_total: vm.custom_instance_count,
actor_number: team_number
)&.to_s,
interfaces:,
checks:,
tags:,
Expand Down Expand Up @@ -159,7 +163,11 @@ def interfaces
gateway: nil
}.tap do |hash|
if address.fixed?
hash[:address] = address.ip_object(sequential_number, team_number).to_string
hash[:address] = address.ip_object(
sequence_number: sequential_number,
sequence_total: vm.custom_instance_count,
actor_number: team_number
).to_string
hash[:dns_enabled] = address.dns_enabled
end
if nic.egress? && (address.mode_ipv4_static? || address.mode_ipv6_static?)
Expand Down
2 changes: 1 addition & 1 deletion app/presenters/api/v3/network_instance_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def as_json
id: pool.slug,
ip_family: pool.ip_family,
network_address: substitute(pool.network_address),
gateway: pool.gateway_address_object&.ip_object(nil, team_number)&.to_s
gateway: pool.gateway_address_object&.ip_object(actor_number: team_number)&.to_s
}
end,
config_map: network.config_map&.deep_transform_values do |value|
Expand Down
147 changes: 147 additions & 0 deletions spec/models/address_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Address do
context 'ip_object' do
subject { build(:address, network:, address_pool:) }
let(:network) { build(:network) }
let(:address_pool) { build(:address_pool, network:, network_address:) }

context 'ipv4' do
context 'non-numbered net' do
let(:network_address) { '1.0.0.0/24' }

it 'should return first address by default' do
expect(subject.ip_object.to_s).to eq '1.0.0.1'
end

it 'should generate correct sequential addresses' do
expect(subject.ip_object(sequence_number: 1, sequence_total: 3).to_s).to eq '1.0.0.1'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3).to_s).to eq '1.0.0.2'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3).to_s).to eq '1.0.0.3'
end

it 'should generate correct team addresses' do
expect(subject.ip_object(actor_number: 1).to_s).to eq '1.0.0.1'
expect(subject.ip_object(actor_number: 2).to_s).to eq '1.0.0.2'
expect(subject.ip_object(actor_number: 3).to_s).to eq '1.0.0.3'
end

it 'should generate correct sequential + team addresses' do
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 1).to_s).to eq '1.0.0.1'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 1).to_s).to eq '1.0.0.2'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 1).to_s).to eq '1.0.0.3'
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 2).to_s).to eq '1.0.0.4'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 2).to_s).to eq '1.0.0.5'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 2).to_s).to eq '1.0.0.6'
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 3).to_s).to eq '1.0.0.7'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 3).to_s).to eq '1.0.0.8'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 3).to_s).to eq '1.0.0.9'
end
end

context 'numbered net' do
let(:network_address) { '1.{{ team_nr }}.0.0/24' }

it 'should return first address by default' do
expect(subject.ip_object.to_s).to eq '1.1.0.1'
end

it 'should generate correct sequential addresses' do
expect(subject.ip_object(sequence_number: 1, sequence_total: 3).to_s).to eq '1.1.0.1'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3).to_s).to eq '1.1.0.2'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3).to_s).to eq '1.1.0.3'
end

it 'should generate correct team addresses' do
expect(subject.ip_object(actor_number: 1).to_s).to eq '1.1.0.1'
expect(subject.ip_object(actor_number: 2).to_s).to eq '1.2.0.1'
expect(subject.ip_object(actor_number: 3).to_s).to eq '1.3.0.1'
end

it 'should generate correct sequential + team addresses' do
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 1).to_s).to eq '1.1.0.1'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 1).to_s).to eq '1.1.0.2'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 1).to_s).to eq '1.1.0.3'
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 2).to_s).to eq '1.2.0.1'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 2).to_s).to eq '1.2.0.2'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 2).to_s).to eq '1.2.0.3'
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 3).to_s).to eq '1.3.0.1'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 3).to_s).to eq '1.3.0.2'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 3).to_s).to eq '1.3.0.3'
end
end
end

context 'ipv6' do
subject { build(:address, network:, address_pool:, mode: :ipv6_static) }
let(:address_pool) { build(:address_pool, network:, network_address:, ip_family: :v6) }

context 'non-numbered net' do
let(:network_address) { '1:a::/64' }

it 'should return first address by default' do
expect(subject.ip_object.to_s).to eq '1:a::'
end

it 'should generate correct sequential addresses' do
expect(subject.ip_object(sequence_number: 1, sequence_total: 15).to_s).to eq '1:a::'
expect(subject.ip_object(sequence_number: 2, sequence_total: 15).to_s).to eq '1:a::1'
expect(subject.ip_object(sequence_number: 3, sequence_total: 15).to_s).to eq '1:a::2'
expect(subject.ip_object(sequence_number: 15, sequence_total: 15).to_s).to eq '1:a::e'
end

it 'should generate correct team addresses' do
expect(subject.ip_object(actor_number: 1).to_s).to eq '1:a::'
expect(subject.ip_object(actor_number: 2).to_s).to eq '1:a::1'
expect(subject.ip_object(actor_number: 3).to_s).to eq '1:a::2'
end

it 'should generate correct sequential + team addresses' do
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 1).to_s).to eq '1:a::'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 1).to_s).to eq '1:a::1'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 1).to_s).to eq '1:a::2'
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 2).to_s).to eq '1:a::3'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 2).to_s).to eq '1:a::4'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 2).to_s).to eq '1:a::5'
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 3).to_s).to eq '1:a::6'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 3).to_s).to eq '1:a::7'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 3).to_s).to eq '1:a::8'
end
end

context 'numbered net' do
let(:network_address) { '1:a:{{ team_nr }}::/64' }

it 'should return first address by default' do
expect(subject.ip_object.to_s).to eq '1:a:1::'
end

it 'should generate correct sequential addresses' do
expect(subject.ip_object(sequence_number: 1, sequence_total: 3).to_s).to eq '1:a:1::'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3).to_s).to eq '1:a:1::1'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3).to_s).to eq '1:a:1::2'
end

it 'should generate correct team addresses' do
expect(subject.ip_object(actor_number: 1).to_s).to eq '1:a:1::'
expect(subject.ip_object(actor_number: 2).to_s).to eq '1:a:2::'
expect(subject.ip_object(actor_number: 3).to_s).to eq '1:a:3::'
end

it 'should generate correct sequential + team addresses' do
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 1).to_s).to eq '1:a:1::'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 1).to_s).to eq '1:a:1::1'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 1).to_s).to eq '1:a:1::2'
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 2).to_s).to eq '1:a:2::'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 2).to_s).to eq '1:a:2::1'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 2).to_s).to eq '1:a:2::2'
expect(subject.ip_object(sequence_number: 1, sequence_total: 3, actor_number: 3).to_s).to eq '1:a:3::'
expect(subject.ip_object(sequence_number: 2, sequence_total: 3, actor_number: 3).to_s).to eq '1:a:3::1'
expect(subject.ip_object(sequence_number: 3, sequence_total: 3, actor_number: 3).to_s).to eq '1:a:3::2'
end
end
end
end
end

0 comments on commit c04e965

Please sign in to comment.