From c04e965e877eed73ae5d33076bcd40c2135e8737 Mon Sep 17 00:00:00 2001 From: Mikk Romulus Date: Tue, 27 Feb 2024 16:47:08 +0200 Subject: [PATCH] feat: Improve IP calculation logic Handle the case of unnumbered net + per-actor numbering + clustered config --- app/calculation/address_values.rb | 2 +- .../network_map_graph_serializer.rb | 6 +- app/models/address.rb | 16 +- app/models/virtual_machine.rb | 2 +- app/presenters/address_preview_presenter.rb | 11 +- app/presenters/api/v3/instance_presenter.rb | 12 +- .../api/v3/network_instance_presenter.rb | 2 +- spec/models/address_spec.rb | 147 ++++++++++++++++++ 8 files changed, 178 insertions(+), 20 deletions(-) create mode 100644 spec/models/address_spec.rb diff --git a/app/calculation/address_values.rb b/app/calculation/address_values.rb index 4e354349..be616d6c 100644 --- a/app/calculation/address_values.rb +++ b/app/calculation/address_values.rb @@ -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 diff --git a/app/calculation/network_map_graph_serializer.rb b/app/calculation/network_map_graph_serializer.rb index 7ecc6113..629a219e 100644 --- a/app/calculation/network_map_graph_serializer.rb +++ b/app/calculation/network_map_graph_serializer.rb @@ -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| diff --git a/app/models/address.rb b/app/models/address.rb index 5e164a18..567c23d1 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -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? @@ -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] diff --git a/app/models/virtual_machine.rb b/app/models/virtual_machine.rb index f40495e7..9895b76f 100644 --- a/app/models/virtual_machine.rb +++ b/app/models/virtual_machine.rb @@ -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 diff --git a/app/presenters/address_preview_presenter.rb b/app/presenters/address_preview_presenter.rb index 946e654a..cac045dd 100644 --- a/app/presenters/address_preview_presenter.rb +++ b/app/presenters/address_preview_presenter.rb @@ -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 diff --git a/app/presenters/api/v3/instance_presenter.rb b/app/presenters/api/v3/instance_presenter.rb index c37d1fb0..ad8adb12 100644 --- a/app/presenters/api/v3/instance_presenter.rb +++ b/app/presenters/api/v3/instance_presenter.rb @@ -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:, @@ -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?) diff --git a/app/presenters/api/v3/network_instance_presenter.rb b/app/presenters/api/v3/network_instance_presenter.rb index 4367c2cf..cf791963 100644 --- a/app/presenters/api/v3/network_instance_presenter.rb +++ b/app/presenters/api/v3/network_instance_presenter.rb @@ -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| diff --git a/spec/models/address_spec.rb b/spec/models/address_spec.rb new file mode 100644 index 00000000..571c4734 --- /dev/null +++ b/spec/models/address_spec.rb @@ -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