diff --git a/src/bosh-director/db/migrations/20250618102610_migrate_ip_address_representation_from_integer_to_cidr_notation.rb b/src/bosh-director/db/migrations/20250618102610_migrate_ip_address_representation_from_integer_to_cidr_notation.rb new file mode 100644 index 00000000000..304a4ba3d6e --- /dev/null +++ b/src/bosh-director/db/migrations/20250618102610_migrate_ip_address_representation_from_integer_to_cidr_notation.rb @@ -0,0 +1,28 @@ +Sequel.migration do + up do + # Iterate over each record to convert integer IP to CIDR notation + from(:ip_addresses).each do |row| + integer_representation = row[:address_str].to_i + + # Convert the integer to IPAddr object + cidr_notation = Bosh::Director::IpAddrOrCidr.new(integer_representation).to_cidr_s + + # Update the row with the new CIDR notation + from(:ip_addresses).where(id: row[:id]).update(address_str: cidr_notation) + end + end + down do + # Revert CIDR notation back to integer representation + from(:ip_addresses).each do |row| + cidr_notation = row[:address_str] + + ip_addr = Bosh::Director::IpAddrOrCidr.new(cidr_notation) + + # Convert IPAddr object back to integer + integer_representation = ip_addr.to_i + + # Update the column to store integer representation + from(:ip_addresses).where(id: row[:id]).update(address_str: integer_representation) + end + end +end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/compilation_instance_pool.rb b/src/bosh-director/lib/bosh/director/deployment_plan/compilation_instance_pool.rb index 333218c53f4..882039a607e 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/compilation_instance_pool.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/compilation_instance_pool.rb @@ -189,6 +189,7 @@ def create_instance_plan(stemcell) compilation_network = @deployment_plan.network(@deployment_plan.compilation.network_name) reservation = DesiredNetworkReservation.new_dynamic(instance.model, compilation_network) + @logger.debug("Creating new dynamic reservation #{reservation.inspect} for instance '#{instance}' and compile instance group '#{compile_instance_group}'") desired_instance = DeploymentPlan::DesiredInstance.new(compile_instance_group) instance_plan = DeploymentPlan::InstancePlan.new( existing_instance: instance.model, diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/dynamic_network.rb b/src/bosh-director/lib/bosh/director/deployment_plan/dynamic_network.rb index e80045480b1..b8501e655c9 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/dynamic_network.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/dynamic_network.rb @@ -13,6 +13,7 @@ def self.parse(network_spec, availability_zones, logger) validate_network_has_no_key('az', name, network_spec) validate_network_has_no_key('azs', name, network_spec) validate_network_has_no_key('managed', name, network_spec) + validate_network_has_no_key('prefix', name, network_spec) if network_spec.has_key?('subnets') validate_network_has_no_key_while_subnets_present('dns', name, network_spec) @@ -21,16 +22,24 @@ def self.parse(network_spec, availability_zones, logger) subnets = network_spec['subnets'].map do |subnet_properties| name_servers = name_server_parser.parse(subnet_properties['name'], subnet_properties) cloud_properties = safe_property(subnet_properties, 'cloud_properties', class: Hash, default: {}) + prefix = safe_property(subnet_properties, 'prefix', class: Integer, default: nil) + if prefix.nil? + prefix = 32 # we need to set the ipv4 default value (dynamic networks only support ipv4) + else + raise NetworkInvalidProperty, "Prefix property is not supported for dynamic networks." + end subnet_availability_zones = parse_availability_zones(subnet_properties, availability_zones, name) - DynamicNetworkSubnet.new(name_servers, cloud_properties, subnet_availability_zones) + DynamicNetworkSubnet.new(name_servers, cloud_properties, subnet_availability_zones, prefix) end else cloud_properties = safe_property(network_spec, 'cloud_properties', class: Hash, default: {}) + prefix = 32 # we need to set the ipv4 default value (dynamic networks only support ipv4) + name_servers = name_server_parser.parse(network_spec['name'], network_spec) - subnets = [DynamicNetworkSubnet.new(name_servers, cloud_properties, nil)] + subnets = [DynamicNetworkSubnet.new(name_servers, cloud_properties, nil, prefix)] end - new(name, subnets, logger) + new(name, subnets, logger, prefix) end def self.validate_network_has_no_key_while_subnets_present(key, name, network_spec) @@ -77,9 +86,10 @@ def self.check_validity_of_availability_zone(availability_zone, availability_zon end end - def initialize(name, subnets, logger) + def initialize(name, subnets, prefix, logger) super(name, logger) @subnets = subnets + @prefix = prefix end attr_reader :subnets @@ -94,7 +104,7 @@ def initialize(name, subnets, logger) def network_settings(reservation, default_properties = Network::REQUIRED_DEFAULTS, availability_zone = nil) unless reservation.dynamic? raise NetworkReservationWrongType, - "IP '#{format_ip(reservation.ip)}' on network '#{reservation.network.name}' does not belong to dynamic pool" + "IP '#{reservation.ip}' on network '#{reservation.network.name}' does not belong to dynamic pool" end if availability_zone.nil? diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/dynamic_network_subnet.rb b/src/bosh-director/lib/bosh/director/deployment_plan/dynamic_network_subnet.rb index bcd6bb04f34..1770bdf2b22 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/dynamic_network_subnet.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/dynamic_network_subnet.rb @@ -1,13 +1,14 @@ module Bosh::Director module DeploymentPlan class DynamicNetworkSubnet - def initialize(dns, cloud_properties, availability_zone_names) + def initialize(dns, cloud_properties, availability_zone_names, prefix) @dns = dns @cloud_properties = cloud_properties @availability_zone_names = availability_zone_names.nil? ? nil : availability_zone_names + @prefix = prefix.to_s end - attr_reader :dns, :cloud_properties, :availability_zone_names + attr_reader :dns, :cloud_properties, :availability_zone_names, :prefix end end end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/instance_network_reservations.rb b/src/bosh-director/lib/bosh/director/deployment_plan/instance_network_reservations.rb index de51af3e03b..e6febf64ea7 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/instance_network_reservations.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/instance_network_reservations.rb @@ -24,7 +24,7 @@ def self.create_from_db(instance_model, deployment, logger) # Dynamic network reservations are not saved in DB, recreating from instance spec instance_model.spec.fetch('networks', []).each do |network_name, network_config| next unless network_config['type'] == 'dynamic' - reservations.add_existing(instance_model, deployment, network_name, network_config['ip'], network_config['type']) + reservations.add_existing(instance_model, deployment, network_name, Bosh::Director::IpAddrOrCidr.new("#{network_config['ip']}"), network_config['type']) end end @@ -75,17 +75,17 @@ def find_network(deployment, cidr_ip, network_name, instance_model) ip_in_subnet = network.subnets.find { |snet| snet.is_reservable?(cidr_ip) } next unless ip_in_subnet - @logger.debug("Registering existing reservation with IP '#{format_ip(cidr_ip)}' for instance '#{instance_model}'"\ + @logger.debug("Registering existing reservation with IP '#{cidr_ip.to_cidr_s}' for instance '#{instance_model}'"\ "on network '#{network.name}'") return network end elsif network_match_on_name # dynamic and static vip - @logger.debug("Registering existing reservation with IP '#{format_ip(cidr_ip)}' for instance '#{instance_model}'"\ + @logger.debug("Registering existing reservation with IP '#{cidr_ip.to_cidr_s}' for instance '#{instance_model}'"\ "on network '#{network_name}'") return network_match_on_name end - @logger.debug("Failed to find network #{network_name} or a network with valid subnets for #{format_ip(cidr_ip)},"\ + @logger.debug("Failed to find network #{network_name} or a network with valid subnets for #{cidr_ip.to_cidr_s},"\ 'reservation will be marked as obsolete') Network.new(network_name, nil) end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_provider.rb b/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_provider.rb index b4de47f1aad..a6c7255582e 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_provider.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_provider.rb @@ -66,7 +66,7 @@ def reserve_manual(reservation) filter_subnet_by_instance_az(reservation).each do |subnet| if (ip = @ip_repo.allocate_dynamic_ip(reservation, subnet)) - @logger.debug("Reserving dynamic IP '#{ip}' for manual network '#{reservation.network.name}'") + @logger.debug("Reserving dynamic IP '#{ip.to_cidr_s}' for manual network '#{reservation.network.name}'") reservation.resolve_ip(ip) reservation.resolve_type(:dynamic) break @@ -81,7 +81,7 @@ def reserve_manual(reservation) @logger.debug("Reserving #{reservation.desc} for manual network '#{reservation.network.name}'") if (subnet = reservation.network.find_subnet_containing(reservation.ip)) - if subnet.restricted_ips.include?(reservation.ip) + if ip_in_array?(reservation.ip, subnet.restricted_ips) message = "Failed to reserve IP '#{format_ip(reservation.ip)}' for network '#{subnet.network_name}': IP belongs to reserved range" @logger.error(message) raise Bosh::Director::NetworkReservationIpReserved, message @@ -99,7 +99,8 @@ def reserve_manual_with_subnet(reservation, subnet) @ip_repo.add(reservation) subnet_az_names = subnet.availability_zone_names.to_a.join(', ') - if subnet.static_ips.include?(reservation.ip) + + if ip_in_array?(reservation.ip, subnet.static_ips) reservation.resolve_type(:static) @logger.debug("Found subnet with azs '#{subnet_az_names}' for #{format_ip(reservation.ip)}. Reserved as static network reservation.") else diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_repo.rb b/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_repo.rb index 8d37093a18a..b92a3e6df35 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_repo.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/ip_provider/ip_repo.rb @@ -3,6 +3,7 @@ class IpRepo include Bosh::Director::IpUtil class IpFoundInDatabaseAndCanBeRetried < StandardError; end class NoMoreIPsAvailableAndStopRetrying < StandardError; end + class PrefixOutOfRange < StandardError; end def initialize(logger) @logger = Bosh::Director::TaggedLogger.new(logger, 'network-configuration') @@ -11,18 +12,18 @@ def initialize(logger) def delete(ip) ip_or_cidr = Bosh::Director::IpAddrOrCidr.new(ip) - ip_address = Bosh::Director::Models::IpAddress.first(address_str: ip_or_cidr.to_i.to_s) + ip_address = Bosh::Director::Models::IpAddress.first(address_str: ip_or_cidr.to_cidr_s) if ip_address - @logger.debug("Releasing ip '#{ip_or_cidr}'") + @logger.debug("Releasing ip '#{ip_or_cidr.to_cidr_s}'") ip_address.destroy else - @logger.debug("Skipping releasing ip '#{ip_or_cidr}': not reserved") + @logger.debug("Skipping releasing ip '#{ip_or_cidr.to_cidr_s}': not reserved") end end def add(reservation) - ip_or_cidr = Bosh::Director::IpAddrOrCidr.new(reservation.ip) + ip_or_cidr = reservation.ip reservation_type = reservation.network.ip_type(ip_or_cidr) @@ -34,7 +35,8 @@ def add(reservation) ) reservation.resolve_type(reservation_type) - @logger.debug("Reserved ip '#{ip_or_cidr}' for #{reservation.network.name} as #{reservation_type}") + + @logger.debug("Reserved ip '#{ip_or_cidr.to_cidr_s}' for #{reservation.network.name} as #{reservation_type}") end def allocate_dynamic_ip(reservation, subnet) @@ -50,8 +52,8 @@ def allocate_dynamic_ip(reservation, subnet) retry end - @logger.debug("Allocated dynamic IP '#{ip_address}' for #{reservation.network.name}") - ip_address.to_i + @logger.debug("Allocated dynamic IP '#{ip_address.to_cidr_s}' for #{reservation.network.name}") + ip_address end def allocate_vip_ip(reservation, subnet) @@ -69,64 +71,96 @@ def allocate_vip_ip(reservation, subnet) end @logger.debug("Allocated vip IP '#{ip_address}' for #{reservation.network.name}") - ip_address.to_i + ip_address end private + def all_ip_addresses + Bosh::Director::Models::IpAddress.select(:address_str).all.map { |a| a.address } + end + def try_to_allocate_dynamic_ip(reservation, subnet) addresses_in_use = Set.new(all_ip_addresses) - first_range_address = subnet.range.to_range.first.to_i - 1 + first_range_address = Bosh::Director::IpAddrOrCidr.new(subnet.range.to_range.first.to_i - 1) + addresses_we_cant_allocate = addresses_in_use - addresses_we_cant_allocate << first_range_address - addresses_we_cant_allocate.merge(subnet.restricted_ips.to_a) unless subnet.restricted_ips.empty? - addresses_we_cant_allocate.merge(subnet.static_ips.to_a) unless subnet.static_ips.empty? - addr = find_first_available_address(addresses_we_cant_allocate, first_range_address) - ip_address = Bosh::Director::IpAddrOrCidr.new(addr) + addresses_we_cant_allocate.merge(subnet.restricted_ips) unless subnet.restricted_ips.empty? + addresses_we_cant_allocate.merge(subnet.static_ips) unless subnet.static_ips.empty? - unless subnet.range == ip_address || subnet.range.include?(ip_address) - raise NoMoreIPsAvailableAndStopRetrying + if subnet.range.ipv6? + addresses_we_cant_allocate.delete_if { |ipaddr| ipaddr.ipv4? } + else + addresses_we_cant_allocate.delete_if { |ipaddr| ipaddr.ipv6? } end - save_ip(ip_address, reservation, false) + prefix = subnet.prefix - ip_address + ip_address_cidr = find_next_available_ip(addresses_we_cant_allocate, first_range_address, prefix) + + if !(subnet.range == ip_address_cidr || subnet.range.include?(ip_address_cidr)) || + subnet.range.to_range.last.to_i < (ip_address_cidr.to_i + ip_address_cidr.count) + raise NoMoreIPsAvailableAndStopRetrying + end + + save_ip(ip_address_cidr.to_cidr_s, reservation, false) + + ip_address_cidr end - def find_first_available_address(addresses_we_cant_allocate, first_address) - last_address_we_cant_use = addresses_we_cant_allocate - .to_a - .reject { |a| a < first_address } - .sort - .find { |a| !addresses_we_cant_allocate.include?(a + 1) } - last_address_we_cant_use + 1 + def find_next_available_ip(addresses_we_cant_allocate, first_range_address, prefix) + filtered_ips = addresses_we_cant_allocate.sort_by { |ip| ip.to_i }.reject { |ip| ip.to_i < first_range_address.to_i } #remove ips that are below subnet range + + current_ip = Bosh::Director::IpAddrOrCidr.new(first_range_address.to_i + 1) + found = false + + while found == false + current_prefix = Bosh::Director::IpAddrOrCidr.new("#{current_ip}/#{prefix}") + + if filtered_ips.any? { |ip| current_prefix.include?(ip) } + filtered_ips.reject! { |ip| ip.to_i < current_prefix.to_i } + actual_ip_prefix = filtered_ips.first.count + if actual_ip_prefix > current_prefix.count + current_ip = Bosh::Director::IpAddrOrCidr.new(current_ip.to_i + actual_ip_prefix) + else + current_ip = Bosh::Director::IpAddrOrCidr.new(current_ip.to_i + current_prefix.count) + end + else + found_cidr = current_prefix + found = true + end + end + + found_cidr end def try_to_allocate_vip_ip(reservation, subnet) - addresses_in_use = Set.new(all_ip_addresses) + addresses_in_use = Set.new(all_ip_addresses.map { |ip| ip.to_i }) + + if Bosh::Director::IpAddrOrCidr.new(subnet.static_ips.first.to_i).ipv6? + prefix = 128 + else + prefix = 32 + end - available_ips = subnet.static_ips - addresses_in_use + available_ips = subnet.static_ips.map(&:to_i).to_set - addresses_in_use raise NoMoreIPsAvailableAndStopRetrying if available_ips.empty? - ip_address = Bosh::Director::IpAddrOrCidr.new(available_ips.first) + ip_address = Bosh::Director::IpAddrOrCidr.new("#{Bosh::Director::IpAddrOrCidr.new(available_ips.first)}/#{prefix}") - save_ip(ip_address, reservation, false) + save_ip(ip_address.to_cidr_s, reservation, false) ip_address end - def all_ip_addresses - Bosh::Director::Models::IpAddress.select(:address_str).all.map { |a| a.address_str.to_i } - end - def reserve_with_instance_validation(instance_model, ip, reservation, is_static) # try to save IP first before validating its instance to prevent race conditions - save_ip(ip, reservation, is_static) + save_ip(ip.to_cidr_s, reservation, is_static) rescue IpFoundInDatabaseAndCanBeRetried - ip_address = Bosh::Director::Models::IpAddress.first(address_str: ip.to_i.to_s) + ip_address = Bosh::Director::Models::IpAddress.first(address_str: ip.to_cidr_s) retry unless ip_address @@ -156,17 +190,18 @@ def validate_instance_and_update_reservation_type(instance_model, ip, ip_address end def save_ip(ip, reservation, is_static) + @logger.debug("Adding IP Address: #{ip} from reservation: #{reservation}") ip_address = Bosh::Director::Models::IpAddress.new( - address_str: ip.to_i.to_s, + address_str: ip, network_name: reservation.network.name, task_id: Bosh::Director::Config.current_job.task_id, static: is_static, - ) + ) reservation.instance_model.add_ip_address(ip_address) rescue Sequel::ValidationFailed, Sequel::DatabaseError => e error_message = e.message.downcase if error_message.include?('unique') || error_message.include?('duplicate') - raise IpFoundInDatabaseAndCanBeRetried + raise IpFoundInDatabaseAndCanBeRetried, e.inspect else raise e end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/manual_network.rb b/src/bosh-director/lib/bosh/director/deployment_plan/manual_network.rb index 26c76cd5964..0ccba4345d4 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/manual_network.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/manual_network.rb @@ -13,24 +13,32 @@ def self.parse(network_spec, availability_zones, logger) managed = Config.network_lifecycle_enabled? && safe_property(network_spec, 'managed', default: false) subnet_specs = safe_property(network_spec, 'subnets', class: Array) subnets = [] + prefix = nil subnet_specs.each do |subnet_spec| new_subnet = ManualNetworkSubnet.parse(name, subnet_spec, availability_zones, managed) subnets.each do |subnet| raise NetworkOverlappingSubnets, "Network '#{name}' has overlapping subnets" if subnet.overlaps?(new_subnet) + if prefix != subnet.prefix + raise NetworkPrefixSizesDiffer, "Network '#{name}' has subnets that define different prefixes" + end + end + if prefix.nil? + prefix = new_subnet.prefix end subnets << new_subnet end validate_all_subnets_use_azs(subnets, name) - new(name, subnets, logger, managed) + new(name, subnets, prefix, logger, managed) end def managed? @managed end - def initialize(name, subnets, logger, managed = false) + def initialize(name, subnets, prefix, logger, managed = false) super(name, TaggedLogger.new(logger, 'network-configuration')) @subnets = subnets + @prefix = prefix @managed = managed end @@ -46,15 +54,21 @@ def network_settings(reservation, default_properties = REQUIRED_DEFAULTS, availa "Can't generate network settings without an IP" end - ip_or_cidr = Bosh::Director::IpAddrOrCidr.new(reservation.ip) + ip_or_cidr = reservation.ip subnet = find_subnet_containing(reservation.ip) + unless subnet raise NetworkReservationInvalidIp, "Provided IP '#{ip_or_cidr}' does not belong to any subnet" end + + unless subnet.prefix.to_i == ip_or_cidr.prefix.to_i + raise NetworkReservationInvalidPrefix, "Subnet Prefix #{subnet.prefix} and ip reservation prefix #{ip_or_cidr.prefix} do not match" + end config = { "type" => "manual", "ip" => ip_or_cidr.to_s, + "prefix" => ip_or_cidr.prefix.to_s, "netmask" => subnet.netmask, "cloud_properties" => subnet.cloud_properties } diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/manual_network_subnet.rb b/src/bosh-director/lib/bosh/director/deployment_plan/manual_network_subnet.rb index 7d282155b07..ca88858f6e8 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/manual_network_subnet.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/manual_network_subnet.rb @@ -5,7 +5,7 @@ class ManualNetworkSubnet < Subnet extend IpUtil attr_reader :network_name, :name, :dns, - :availability_zone_names, :netmask_bits + :availability_zone_names, :netmask_bits, :prefix attr_accessor :cloud_properties, :range, :gateway, :restricted_ips, :static_ips, :netmask @@ -17,6 +17,7 @@ def self.parse(network_name, subnet_spec, availability_zones, managed = false) ignore_missing_gateway = Bosh::Director::Config.ignore_missing_gateway gateway_property = safe_property(subnet_spec, 'gateway', class: String, optional: ignore_missing_gateway || managed) reserved_property = safe_property(subnet_spec, 'reserved', optional: true) + prefix = safe_property(subnet_spec, 'prefix', optional: true) restricted_ips = Set.new static_ips = Set.new @@ -33,7 +34,7 @@ def self.parse(network_name, subnet_spec, availability_zones, managed = false) end netmask = range.netmask - broadcast = range.to_range.last + broadcast = range.last if gateway_property gateway = Bosh::Director::IpAddrOrCidr.new(gateway_property) @@ -45,13 +46,13 @@ def self.parse(network_name, subnet_spec, availability_zones, managed = false) static_property = safe_property(subnet_spec, 'static', optional: true) - restricted_ips.add(gateway.to_i) if gateway - restricted_ips.add(range.to_i) - restricted_ips.add(broadcast.to_i) + restricted_ips.add(gateway) if gateway + restricted_ips.add(range.first) + restricted_ips.add(broadcast) - each_ip(reserved_property) do |ip| + each_ip(reserved_property, false) do |ip| unless range.include?(ip) - raise NetworkReservedIpOutOfRange, "Reserved IP '#{format_ip(ip)}' is out of " \ + raise NetworkReservedIpOutOfRange, "Reserved IP '#{to_ipaddr(ip)}' is out of " \ "network '#{network_name}' range" end @@ -64,8 +65,15 @@ def self.parse(network_name, subnet_spec, availability_zones, managed = false) end end + restricted_ips.reject! do |ip| + restricted_ips.any? do |other_ip| + includes = other_ip.include?(ip) rescue false + includes && other_ip.prefix < ip.prefix + end + end + each_ip(static_property) do |ip| - if restricted_ips.include?(ip) + if ip_in_array?(ip, restricted_ips) raise NetworkStaticIpOutOfRange, "Static IP '#{to_ipaddr(ip)}' is in network '#{network_name}' reserved range" end @@ -75,6 +83,28 @@ def self.parse(network_name, subnet_spec, availability_zones, managed = false) static_ips.add(ip) end + + if prefix.nil? + if range.ipv6? + prefix = "128" + else + prefix = "32" + end + else + if range.prefix > prefix.to_i + raise NetworkPrefixSizeTooBig, "Prefix size '#{prefix}' is larger than range prefix '#{range.prefix}'" + end + # if a prefix is provided the static ips can only be the base_addresses of the prefix otherwise we through an error + static_ips.each do |static_ip| + range.each_base_address(prefix) do |base_address_int| + if static_ip.to_i == base_address_int + break + elsif static_ip.to_i < base_address_int + raise NetworkPrefixStaticIpNotBaseAddress, "Static IP '#{to_ipaddr(static_ip)}' is not a base address of the prefix '#{prefix}'" + end + end + end + end end name_server_parser = NetworkParser::NameServersParser.new @@ -95,10 +125,11 @@ def self.parse(network_name, subnet_spec, availability_zones, managed = false) static_ips, sn_name, netmask_bits, + prefix ) end - def initialize(network_name, range, gateway, name_servers, cloud_properties, netmask, availability_zone_names, restricted_ips, static_ips, subnet_name = nil, netmask_bits = nil) + def initialize(network_name, range, gateway, name_servers, cloud_properties, netmask, availability_zone_names, restricted_ips, static_ips, subnet_name = nil, netmask_bits = nil, prefix = nil) @network_name = network_name @name = subnet_name @netmask_bits = netmask_bits @@ -110,6 +141,7 @@ def initialize(network_name, range, gateway, name_servers, cloud_properties, net @availability_zone_names = availability_zone_names @restricted_ips = restricted_ips @static_ips = static_ips + @prefix = prefix.to_s end def overlaps?(subnet) @@ -123,7 +155,13 @@ def overlaps?(subnet) end def is_reservable?(ip) - range.include?(ip) && !restricted_ips.include?(ip.to_i) + restricted_ips.each do | restricted_ip | + return false if restricted_ip.include?(ip) + rescue IPAddr::InvalidAddressError # when ip versions are not the same + return false + end + + range.include?(ip.to_range.first) && range.include?(ip.to_range.last) end def self.parse_properties_from_database(network_name, subnet_name) diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/network.rb b/src/bosh-director/lib/bosh/director/deployment_plan/network.rb index f970fc75d2e..64fd643433a 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/network.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/network.rb @@ -87,6 +87,10 @@ def has_azs?(az_names) def availability_zones @subnets.map(&:availability_zone_names).flatten.uniq end + + def prefix # for now the prefix should be considered the same for all subnets + @subnets.first.prefix + end end end end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/network_planner/reservation_reconciler.rb b/src/bosh-director/lib/bosh/director/deployment_plan/network_planner/reservation_reconciler.rb index 22996950388..d31525d819b 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/network_planner/reservation_reconciler.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/network_planner/reservation_reconciler.rb @@ -22,6 +22,7 @@ def reconcile(existing_reservations) end if desired_reservation + @logger.debug( "For desired reservation #{desired_reservation} found existing reservation " \ "on the same network #{existing_reservation}", @@ -96,6 +97,8 @@ def reservation_contains_assigned_address?(existing_reservation, desired_reserva return false if desired_reservation.network.is_a?(DynamicNetwork) || existing_reservation.network.is_a?(DynamicNetwork) + return false if desired_reservation.network.prefix != existing_reservation.network.prefix + desired_reservation.network.subnets.any? do |subnet| if existing_reservation.instance_model.availability_zone != '' && !subnet.availability_zone_names.nil? next unless subnet.availability_zone_names.include?(existing_reservation.instance_model.availability_zone) diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/placement_planner/networks_to_static_ips.rb b/src/bosh-director/lib/bosh/director/deployment_plan/placement_planner/networks_to_static_ips.rb index 994ce32a727..136fb9619f8 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/placement_planner/networks_to_static_ips.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/placement_planner/networks_to_static_ips.rb @@ -16,7 +16,7 @@ def self.create(job_networks, desired_azs, job_name) subnets = job_network.deployment_network.subnets job_network.static_ips.each do |static_ip| - subnet_for_ip = subnets.find { |subnet| subnet.static_ips.include?(static_ip) } + subnet_for_ip = subnets.find { |subnet| ip_in_array?(static_ip, subnet.static_ips) } if subnet_for_ip.nil? raise InstanceGroupNetworkInstanceIpMismatch, "Instance group '#{job_name}' with network '#{job_network.name}' declares static ip '#{format_ip(static_ip)}', " + diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb b/src/bosh-director/lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb index 6a0cd6173c8..f1b015dd870 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb @@ -141,13 +141,15 @@ def create_network_plan_with_az(instance_plan, network, instance_plans) def create_instance_plan_based_on_existing_ips(desired_instances, existing_instance_model) instance_plan = nil + @job_networks.each do |network| next unless network.static? instance_ips_on_network = find_instance_ips_on_network(existing_instance_model, network) network_plan = nil instance_ips_on_network.each do |instance_ip| - ip_address = instance_ip.address + ip_address = instance_ip.address.to_i + # Instance is using IP in static IPs list, we have to use this instance @logger.debug("Existing instance '#{instance_name(existing_instance_model)}' is using static IP '#{format_ip(ip_address)}' on network '#{network.name}'") if instance_plan.nil? @@ -192,7 +194,8 @@ def create_existing_instance_plan_with_az(desired_instance, existing_instance_mo end def assign_az_based_on_ip(desired_instance, existing_instance_model, network, ip_address) - ip_az_names = @networks_to_static_ips.find_by_network_and_ip(network, ip_address).az_names + ip_az_names = @networks_to_static_ips.find_by_network_and_ip(network, ip_address.to_i).az_names + if ip_az_names.include?(existing_instance_model.availability_zone) az_name = existing_instance_model.availability_zone @logger.debug("Instance '#{instance_name(existing_instance_model)}' belongs to az '#{az_name}' that is in subnet az list, reusing instance az.") @@ -246,6 +249,7 @@ def create_existing_instance_plan(desired_instance, existing_instance_model) def create_network_plan_with_ip(instance_plan, network, ip_address) instance_az = instance_plan.desired_instance.az instance_az_name = instance_az.nil? ? nil : instance_az.name + ip_az_names = @networks_to_static_ips.find_by_network_and_ip(network, ip_address).az_names if ip_az_names.include?(instance_az_name) instance_plan.network_plans << @network_planner.network_plan_with_static_reservation(instance_plan, network, ip_address) diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/stages/create_network.rb b/src/bosh-director/lib/bosh/director/deployment_plan/stages/create_network.rb index 64c6df56864..bbeb7baf9f0 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/stages/create_network.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/stages/create_network.rb @@ -157,9 +157,9 @@ def populate_subnet_properties(subnet, db_subnet) subnet.gateway = Bosh::Director::IpAddrOrCidr.new(db_subnet.gateway) subnet.netmask = subnet.range.netmask - subnet.restricted_ips.add(subnet.gateway.to_i) if subnet.gateway - subnet.restricted_ips.add(subnet.range.to_i) - subnet.restricted_ips.add(subnet.range.to_range.last.to_i) + subnet.restricted_ips.add(subnet.gateway) if subnet.gateway + subnet.restricted_ips.add(subnet.range) + subnet.restricted_ips.add(subnet.range.to_range.last) each_ip(JSON.parse(db_subnet.reserved)) do |ip| unless subnet.range.include?(ip) raise NetworkReservedIpOutOfRange, diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/steps/commit_instance_network_settings_step.rb b/src/bosh-director/lib/bosh/director/deployment_plan/steps/commit_instance_network_settings_step.rb index eb2dccc8742..2dad78d0d20 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/steps/commit_instance_network_settings_step.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/steps/commit_instance_network_settings_step.rb @@ -11,7 +11,7 @@ def perform(report) next if ip.nil? - ip_model = Models::IpAddress.find(address_str: ip.to_s) + ip_model = Models::IpAddress.find(address_str: ip.to_cidr_s) ip_model&.update(vm: report.vm) end end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/vip_network.rb b/src/bosh-director/lib/bosh/director/deployment_plan/vip_network.rb index 4e1125523c1..7770b1f4e9b 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/vip_network.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/vip_network.rb @@ -6,7 +6,7 @@ class VipNetwork < NetworkWithSubnets # @return [Hash] Network cloud properties attr_reader :cloud_properties - attr_reader :subnets + attr_reader :subnets, :prefix def self.parse(network_spec, availability_zones, logger) name = safe_property(network_spec, 'name', class: String) @@ -15,8 +15,14 @@ def self.parse(network_spec, availability_zones, logger) DeploymentPlan::VipNetworkSubnet.parse(subnet_spec, name, availability_zones) end + unless subnets.empty? + prefix = subnets.first.prefix + else + prefix = "32" + end + cloud_properties = safe_property(network_spec, 'cloud_properties', class: Hash, default: {}) - new(name, cloud_properties, subnets, logger) + new(name, cloud_properties, subnets, prefix, logger) end ## @@ -25,10 +31,11 @@ def self.parse(network_spec, availability_zones, logger) # @param [Hash] network_spec parsed from the cloud config # @param [VipNetworkSubnet] vip network subnets parsed from the cloud config # @param [Logger] logger - def initialize(name, cloud_properties, subnets, logger) + def initialize(name, cloud_properties, subnets, prefix, logger) super(name, logger) @cloud_properties = cloud_properties @subnets = subnets + @prefix = prefix @logger = TaggedLogger.new(logger, 'network-configuration') end @@ -69,7 +76,7 @@ def has_azs?(*) end def find_az_names_for_ip(ip) - subnet = @subnets.find { |sn| sn.static_ips.include?(ip) } + subnet = @subnets.find { |sn| ip_in_array?(ip, sn.static_ips) } subnet.availability_zone_names if subnet end diff --git a/src/bosh-director/lib/bosh/director/deployment_plan/vip_network_subnet.rb b/src/bosh-director/lib/bosh/director/deployment_plan/vip_network_subnet.rb index 50e4f608127..a0dd9465d19 100644 --- a/src/bosh-director/lib/bosh/director/deployment_plan/vip_network_subnet.rb +++ b/src/bosh-director/lib/bosh/director/deployment_plan/vip_network_subnet.rb @@ -4,11 +4,16 @@ class VipNetworkSubnet < Subnet extend ValidationHelper extend IpUtil - attr_reader :static_ips, :availability_zone_names + attr_reader :static_ips, :availability_zone_names, :prefix def self.parse(subnet_spec, network_name, azs) static_ips = Set.new + prefix = safe_property(subnet_spec, 'prefix', optional: true) + if prefix.nil? + prefix = "32" + end + static_property = safe_property(subnet_spec, 'static', class: Array, default: []) each_ip(static_property) do |ip| static_ips.add(ip) @@ -16,16 +21,17 @@ def self.parse(subnet_spec, network_name, azs) availability_zone_names = parse_availability_zones(subnet_spec, network_name, azs) - new(static_ips, availability_zone_names) + new(static_ips, availability_zone_names, prefix) end - def initialize(static_ips, availability_zone_names) + def initialize(static_ips, availability_zone_names, prefix) @static_ips = static_ips @availability_zone_names = availability_zone_names + @prefix = prefix.to_s end def is_reservable?(ip) - @static_ips.include?(ip) + VipNetworkSubnet.ip_in_array?(ip, @static_ips) end end end diff --git a/src/bosh-director/lib/bosh/director/errors.rb b/src/bosh-director/lib/bosh/director/errors.rb index 00e2699d8ba..4756d9904ce 100644 --- a/src/bosh-director/lib/bosh/director/errors.rb +++ b/src/bosh-director/lib/bosh/director/errors.rb @@ -165,6 +165,7 @@ def self.err(error_code, response_code = BAD_REQUEST) NetworkReservationIpOutsideSubnet = err(130012) NetworkReservationIpReserved = err(130013) NetworkReservationVipMisconfigured = err(130014) + NetworkReservationInvalidPrefix = err(130015) # Manifest parsing: instance group section InstanceGroupMissingRelease = err(140001) @@ -224,6 +225,9 @@ def self.err(error_code, response_code = BAD_REQUEST) NetworkInvalidIpRangeFormat = err(160010) NetworkDeletingUnorphanedError = err(160011) NetworkNotFoundError = err(16012) + NetworkPrefixSizeTooBig = err(16013) + NetworkPrefixSizesDiffer = err(16014) + NetworkPrefixStaticIpNotBaseAddress = err(16015) # ResourcePool ResourcePoolUnknownNetwork = err(170001) diff --git a/src/bosh-director/lib/bosh/director/ip_addr_or_cidr.rb b/src/bosh-director/lib/bosh/director/ip_addr_or_cidr.rb index 4a9f4154821..f384bfe9985 100644 --- a/src/bosh-director/lib/bosh/director/ip_addr_or_cidr.rb +++ b/src/bosh-director/lib/bosh/director/ip_addr_or_cidr.rb @@ -3,26 +3,68 @@ module Bosh module Director class IpAddrOrCidr - delegate :==, :include?, :ipv4?, :ipv6?, :netmask, :to_i, :to_range, :to_string, to: :@ipaddr + include Comparable + + delegate :==, :include?, :ipv4?, :ipv6?, :netmask, :mask, :to_i, :to_range, :to_string, :prefix, :succ, :<=>, to: :@ipaddr alias :to_s :to_string def initialize(ip_or_cidr) @ipaddr = if ip_or_cidr.kind_of?(IpAddrOrCidr) - IPAddr.new(ip_or_cidr.to_s) + IPAddr.new(ip_or_cidr.to_cidr_s) elsif ip_or_cidr.kind_of?(Integer) IPAddr.new(ip_or_cidr, inet_type_for(ip_or_cidr)) else - IPAddr.new(ip_or_cidr) + begin + IPAddr.new(ip_or_cidr) + rescue IPAddr::InvalidAddressError => e + raise e, "Invalid IP or CIDR format: #{ip_or_cidr}" + end end end + def each_base_address(prefix_length) + if @ipaddr.ipv4? + bits = 32 + elsif @ipaddr.ipv6? + bits = 128 + end + step_size = 2**(bits - prefix_length.to_i) + base_address_int = @ipaddr.to_i + + while base_address_int <= @ipaddr.to_range.last.to_i + yield base_address_int + base_address_int += step_size + end + end + def count (@ipaddr.to_range.last.to_i - @ipaddr.to_range.first.to_i) + 1 end def to_cidr_s - "#{@ipaddr}/#{@ipaddr.prefix}" + "#{@ipaddr.to_string}/#{@ipaddr.prefix}" + end + + def to_range + @ipaddr.to_range + end + + def succ + next_ip = @ipaddr.succ + Bosh::Director::IpAddrOrCidr.new(next_ip.to_i) + end + + def <=>(other) + @ipaddr.to_i <=> other.to_i + end + + def last + Bosh::Director::IpAddrOrCidr.new(@ipaddr.to_range.last.to_i) + end + + def first + Bosh::Director::IpAddrOrCidr.new(@ipaddr.to_range.first.to_i) end private diff --git a/src/bosh-director/lib/bosh/director/ip_util.rb b/src/bosh-director/lib/bosh/director/ip_util.rb index 62c67f667c4..6d0575ab847 100644 --- a/src/bosh-director/lib/bosh/director/ip_util.rb +++ b/src/bosh-director/lib/bosh/director/ip_util.rb @@ -1,9 +1,9 @@ module Bosh::Director module IpUtil - def each_ip(range_string_or_strings) + def each_ip(range_string_or_strings, expanded = true) [range_string_or_strings].flatten.compact.each do |range_string| - string_to_range(range_string).each do |ip| - yield ip + string_to_range(range_string, expanded).each do |ip_or_range| + yield ip_or_range end end rescue ArgumentError => e @@ -21,7 +21,11 @@ def to_ipaddr(ip) # @param [Integer] ip Integer IP representation # @return [String] Human-readable IP representation def format_ip(ip) - to_ipaddr(ip).to_s + to_ipaddr(ip) + end + + def format_cidr_ip(ip) + ip.to_cidr_s end def ip_address?(ip) @@ -32,30 +36,83 @@ def ip_address?(ip) return false end + def ip_in_array?(ip_to_check, ip_objects_array) + ip_to_check = Bosh::Director::IpAddrOrCidr.new(ip_to_check) + + ip_objects_array.any? do |ip_object| + ip_object.include?(ip_to_check) + rescue IPAddr::InvalidAddressError + false + end + end + private - def string_to_range(range_string) + def string_to_range(range_string, expanded) parts = range_string.split('-').map { |part| part.strip } - unless [1,2].include?(parts.length) - raise NetworkInvalidIpRangeFormat, "Invalid IP range format: #{range_string}" - end - if parts.size == 1 - cidr_range = Bosh::Director::IpAddrOrCidr.new(parts[0]).to_range - first_ip = cidr_range.first - last_ip = cidr_range.last - + if expanded + cidr_range = Bosh::Director::IpAddrOrCidr.new(parts[0]).to_range + first_ip = Bosh::Director::IpAddrOrCidr.new(cidr_range.first.to_i) + last_ip = Bosh::Director::IpAddrOrCidr.new(cidr_range.last.to_i) + (first_ip .. last_ip) + else + [Bosh::Director::IpAddrOrCidr.new(parts[0])] + end elsif parts.size == 2 first_ip = Bosh::Director::IpAddrOrCidr.new(parts[0]) last_ip = Bosh::Director::IpAddrOrCidr.new(parts[1]) - unless first_ip.count == 1 && last_ip.count == 1 raise NetworkInvalidIpRangeFormat, "Invalid IP range format: #{range_string}" end + if expanded + (first_ip .. last_ip) + else + ip_range_to_cidr_list(first_ip, last_ip) + end + else + raise NetworkInvalidIpRangeFormat, "Invalid IP range format: #{range_string}" + end + end + + def ip_range_to_cidr_list(first_ip, last_ip) + cidr_blocks = [] + + current_ip = first_ip + + while current_ip.to_i <= last_ip.to_i + mask = current_ip.ipv4? ? 32 : 128 + + while mask >= 0 + potential_subnet = Bosh::Director::IpAddrOrCidr.new("#{current_ip}/#{mask}") + + first_ip_in_range = potential_subnet.first + last_ip_in_range = potential_subnet.last + + if first_ip_in_range == current_ip && last_ip_in_range == last_ip + cidr_blocks << potential_subnet + current_ip = potential_subnet.last.succ + break + end + + if first_ip_in_range < current_ip || ( first_ip_in_range == current_ip && last_ip_in_range >= last_ip ) + previous_mask = mask + 1 + found_subnet = Bosh::Director::IpAddrOrCidr.new("#{current_ip}/#{previous_mask}") + cidr_blocks << found_subnet + current_ip = found_subnet.last.succ + break + end + + mask -= 1 + end + + if current_ip.to_i > last_ip.to_i + break + end end + cidr_blocks - (first_ip.to_i .. last_ip.to_i) end end end diff --git a/src/bosh-director/lib/bosh/director/metrics_collector.rb b/src/bosh-director/lib/bosh/director/metrics_collector.rb index 91b600b17ab..0c94c941639 100644 --- a/src/bosh-director/lib/bosh/director/metrics_collector.rb +++ b/src/bosh-director/lib/bosh/director/metrics_collector.rb @@ -173,7 +173,9 @@ def calculate_network_metrics(network) network.subnets.each do |subnet| total_static += subnet.static_ips.size - total_restricted += subnet.restricted_ips.size + subnet.restricted_ips.each do |ip_addr_or_cidr| + total_restricted += ip_addr_or_cidr.count + end total_available += subnet.range.count end diff --git a/src/bosh-director/lib/bosh/director/models/ip_address.rb b/src/bosh-director/lib/bosh/director/models/ip_address.rb index ecc441816a8..938d40940fc 100644 --- a/src/bosh-director/lib/bosh/director/models/ip_address.rb +++ b/src/bosh-director/lib/bosh/director/models/ip_address.rb @@ -23,12 +23,16 @@ def info [ "#{instance.deployment.name}.#{instance.job}/#{instance.index}", network_name, - "#{Bosh::Director::IpAddrOrCidr.new(address_str.to_i)} (#{type})" + "#{Bosh::Director::IpAddrOrCidr.new(address_str).to_cidr_s} (#{type})" ].join(' - ') end def formatted_ip - Bosh::Director::IpAddrOrCidr.new(address).to_s + address.to_cidr_s + end + + def base_address + address.to_string end def type @@ -36,7 +40,7 @@ def type end def address - unless address_str.match?(/\A\d+\z/) + unless address_str.include?('/') || address_str.match?(/\A\d+\z/) info_display = '' begin info_display = info @@ -45,7 +49,8 @@ def address end raise "Unexpected address '#{address_str}' (#{info_display})" end - address_str.to_i + + return Bosh::Director::IpAddrOrCidr.new(address_str) end def to_s diff --git a/src/bosh-director/lib/bosh/director/models/vm.rb b/src/bosh-director/lib/bosh/director/models/vm.rb index a7465642fc9..b5fb53fe70d 100644 --- a/src/bosh-director/lib/bosh/director/models/vm.rb +++ b/src/bosh-director/lib/bosh/director/models/vm.rb @@ -19,6 +19,16 @@ def network_spec=(spec) end def ips + ips_cidr.map do | cidr_ip | + if ( cidr_ip.include?(':') && cidr_ip.include?('/128') ) || ( cidr_ip.include?('.') && cidr_ip.include?('/32') ) + cidr_ip.split('/')[0] + else + cidr_ip + end + end + end + + def ips_cidr manual_or_vip_ips.concat(dynamic_ips).uniq end @@ -29,7 +39,15 @@ def manual_or_vip_ips end def dynamic_ips - network_spec.map { |_, network| network['ip'] } + network_spec.map do |_, network| + prefix = network['prefix'].to_s + if network['ip'].include?(':') && prefix.empty? + prefix = '128' + elsif network['ip'].include?('.') && prefix.empty? + prefix = '32' + end + "#{network['ip']}/#{prefix}" + end end end end diff --git a/src/bosh-director/lib/bosh/director/network_reservation.rb b/src/bosh-director/lib/bosh/director/network_reservation.rb index f1f99702dda..29dd6e6178d 100644 --- a/src/bosh-director/lib/bosh/director/network_reservation.rb +++ b/src/bosh-director/lib/bosh/director/network_reservation.rb @@ -19,7 +19,7 @@ def dynamic? private def formatted_ip - IpAddrOrCidr.new(@ip).to_s if @ip + IpAddrOrCidr.new(@ip).to_cidr_s if @ip end end @@ -28,7 +28,7 @@ class ExistingNetworkReservation < NetworkReservation def initialize(instance_model, network, ip, network_type) super(instance_model, network) - @ip = IpAddrOrCidr.new(ip).to_i if ip + @ip = IpAddrOrCidr.new(ip) if ip @network_type = network_type @obsolete = network.instance_of? Bosh::Director::DeploymentPlan::Network end @@ -52,17 +52,18 @@ def self.new_dynamic(instance_model, network) end def self.new_static(instance_model, network, ip) - new(instance_model, network, ip, :static) + cidr_ip = "#{IpAddrOrCidr.new(ip)}/#{network.prefix}" + new(instance_model, network, cidr_ip, :static) end def initialize(instance_model, network, ip, type) super(instance_model, network) - @ip = IpAddrOrCidr.new(ip).to_i if ip + @ip = resolve_ip(ip) if ip @type = type end def resolve_ip(ip) - @ip = IpAddrOrCidr.new(ip).to_i + @ip = IpAddrOrCidr.new(ip) end def resolve_type(type) diff --git a/src/bosh-director/spec/factories.rb b/src/bosh-director/spec/factories.rb index c0ebfa335e3..232c503b684 100644 --- a/src/bosh-director/spec/factories.rb +++ b/src/bosh-director/spec/factories.rb @@ -170,7 +170,7 @@ factory :models_ip_address, class: Bosh::Director::Models::IpAddress do sequence(:network_name) { |i| "ip-address-network-name-#{i}" } sequence(:task_id) { |i| "ip-address-task-id-#{i}" } - address_str { IPAddr.new(Random.rand(IPAddr::IN4MASK.to_i), Socket::AF_INET).to_i.to_s } + address_str { Bosh::Director::IpAddrOrCidr.new(Random.rand(IPAddr::IN4MASK)).to_cidr_s } static { false } created_at { Time.now } association :instance, factory: :models_instance, strategy: :create diff --git a/src/bosh-director/spec/unit/bosh/director/api/controllers/deployments_controller_spec.rb b/src/bosh-director/spec/unit/bosh/director/api/controllers/deployments_controller_spec.rb index ea7469d0b56..1e817efaad8 100644 --- a/src/bosh-director/spec/unit/bosh/director/api/controllers/deployments_controller_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/api/controllers/deployments_controller_spec.rb @@ -1356,7 +1356,7 @@ def manifest_with_errand(deployment_name='errand') 'state' => 'started', 'uuid' => "instance-#{i}", 'variable_set_id' => (Models::VariableSet.create(deployment: deployment).id), - 'spec' => {'networks' => {'network1' => {'ip' => "#{i}.#{i}.#{i}.#{i}"}}}, + 'spec' => {'networks' => {'network1' => {'ip' => "#{i}.#{i}.#{i}.#{i}", 'prefix' => '32'}}}, } instance_params['availability_zone'] = 'az0' if i == 0 @@ -1368,7 +1368,7 @@ def manifest_with_errand(deployment_name='errand') 'cid' => "cid-#{i}-#{j}", 'instance_id' => instance.id, 'created_at' => time, - 'network_spec' => {'network1' => {'ip' => "#{i}.#{i}.#{j}.#{j}"}}, + 'network_spec' => {'network1' => {'ip' => "#{i}.#{i}.#{j}.#{j}", 'prefix' => '32'}}, } vm = Models::Vm.create(vm_params) @@ -1453,7 +1453,7 @@ def manifest_with_errand(deployment_name='errand') ip_addresses_params = { 'instance_id' => instance.id, 'task_id' => i.to_s, - 'address_str' => ip_to_i("1.2.#{i}.#{j}").to_s, + 'address_str' => ("1.2.#{i}.#{j}/32").to_s, 'vm_id' => vm.id, } Models::IpAddress.create(ip_addresses_params) @@ -1485,6 +1485,69 @@ def manifest_with_errand(deployment_name='errand') end end + it 'returns ip addresses with prefix if prefix differs from 32 or 128 for a vm' do + 9.times do |i| + instance_params = { + 'deployment_id' => deployment.id, + 'job' => "job-#{i}", + 'index' => i, + 'state' => 'started', + 'uuid' => "instance-#{i}", + 'variable_set_id' => (Models::VariableSet.create(deployment: deployment).id) + } + + instance_params['availability_zone'] = 'az0' if i == 0 + instance_params['availability_zone'] = 'az1' if i == 1 + instance = Models::Instance.create(instance_params) + 2.times do |j| + vm_params = { + 'agent_id' => "agent-#{i}-#{j}", + 'cid' => "cid-#{i}-#{j}", + 'instance_id' => instance.id, + 'created_at' => time, + } + + vm = Models::Vm.create(vm_params) + + if j == 0 + instance.active_vm = vm + end + + ip_addresses_params = { + 'instance_id' => instance.id, + 'task_id' => i.to_s, + 'address_str' => ("1.2.#{i}.#{j*16}/28").to_s, + 'vm_id' => vm.id, + } + Models::IpAddress.create(ip_addresses_params) + end + end + + get '/test_deployment/vms' + + expect(last_response.status).to eq(200) + body = JSON.parse(last_response.body) + expect(body.size).to eq(18) + + body.sort_by { |instance| instance['agent_id'] }.each_with_index do |instance_with_vm, i| + instance_idx = i / 2 + vm_by_instance = i % 2 + vm_is_active = vm_by_instance == 0 + expect(instance_with_vm).to eq( + 'agent_id' => "agent-#{instance_idx}-#{vm_by_instance}", + 'job' => "job-#{instance_idx}", + 'index' => instance_idx, + 'cid' => "cid-#{instance_idx}-#{vm_by_instance}", + 'id' => "instance-#{instance_idx}", + 'active' => vm_is_active, + 'az' => { 0 => 'az0', 1 => 'az1', nil => nil }[instance_idx], + 'ips' => ["1.2.#{instance_idx}.#{vm_by_instance*16}/28"], + 'vm_created_at' => time.utc.iso8601, + 'permanent_nats_credentials' => false, + ) + end + end + it 'returns network spec ip addresses' do 15.times do |i| instance_params = { @@ -1504,7 +1567,7 @@ def manifest_with_errand(deployment_name='errand') 'cid' => "cid-#{i}", 'instance_id' => instance.id, 'created_at' => time, - 'network_spec' => {'network1' => {'ip' => "1.2.3.#{i}"}}, + 'network_spec' => {'network1' => {'ip' => "1.2.3.#{i}", 'prefix' => '32'}}, } vm = Models::Vm.create(vm_params) @@ -1558,8 +1621,8 @@ def manifest_with_errand(deployment_name='errand') 'created_at' => time, 'instance_id' => instance.id, 'network_spec' => { - 'network1' => { 'ip' => network_spec_ip }, - 'network2' => { 'ip' => vip }, + 'network1' => { 'ip' => network_spec_ip, 'prefix' => '32' }, + 'network2' => { 'ip' => vip, 'prefix' => '32'}, }, } @@ -1567,7 +1630,7 @@ def manifest_with_errand(deployment_name='errand') instance.active_vm = vm ip_addresses_params = { - 'address_str' => ip_to_i(vip).to_s, + 'address_str' => "#{vip}/32", 'instance_id' => instance.id, 'task_id' => '1', 'vm_id' => vm.id, @@ -1587,7 +1650,7 @@ def manifest_with_errand(deployment_name='errand') 'cid' => 'cid', 'id' => 'instance-id', 'index' => 0, - 'ips' => [vip, network_spec_ip], + 'ips' => ["#{vip}", "#{network_spec_ip}"], 'job' => 'job', 'vm_created_at' => time.utc.iso8601, 'permanent_nats_credentials' => false, diff --git a/src/bosh-director/spec/unit/bosh/director/cloudcheck_helper_spec.rb b/src/bosh-director/spec/unit/bosh/director/cloudcheck_helper_spec.rb index 94d852c80f1..a47aef3acdc 100644 --- a/src/bosh-director/spec/unit/bosh/director/cloudcheck_helper_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/cloudcheck_helper_spec.rb @@ -32,7 +32,7 @@ def initialize(instance_uuid, data) instance end - let!(:ip_address) { FactoryBot.create(:models_ip_address, instance: instance, address_str: ip_to_i('192.1.3.4').to_s) } + let!(:ip_address) { FactoryBot.create(:models_ip_address, instance: instance, address_str: Bosh::Director::IpAddrOrCidr.new('192.1.3.4').to_cidr_s) } let!(:vm) { FactoryBot.create(:models_vm, instance: instance, active: true) } let(:spec) do diff --git a/src/bosh-director/spec/unit/bosh/director/db/migrations/verify_migration_digest_spec.rb b/src/bosh-director/spec/unit/bosh/director/db/migrations/verify_migration_digest_spec.rb index 192d0b16657..5fcfdc9a25e 100644 --- a/src/bosh-director/spec/unit/bosh/director/db/migrations/verify_migration_digest_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/db/migrations/verify_migration_digest_spec.rb @@ -20,6 +20,10 @@ module Bosh::Director filename: '20240319204601_remove_dns_records_from_instances.rb', sha1: 'bb637d410772d09caabdb68a1126fbc9f9b4deec', }, + { + filename: '20250618102610_migrate_ip_address_representation_from_integer_to_cidr_notation.rb', + sha1: '4133b36632a30d88c2e264cc112ede34640d0327', + }, ] end diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/dynamic_network_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/dynamic_network_spec.rb index 4dae3f9b50a..f7776201f6a 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/dynamic_network_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/dynamic_network_spec.rb @@ -104,6 +104,19 @@ ) }.to raise_error(Bosh::Director::NetworkInvalidProperty, "Network 'foo' must not specify 'azs'.") end + + it "raises error when 'prefix' is present on the network spec" do + expect { + Bosh::Director::DeploymentPlan::DynamicNetwork.parse( + { + 'name' => 'foo', + 'prefix' => '28' + }, + [Bosh::Director::DeploymentPlan::AvailabilityZone.new('foo-zone', {})], + logger + ) + }.to raise_error(Bosh::Director::NetworkInvalidProperty, "Network 'foo' must not specify 'prefix'.") + end end context 'with a manifest specifying subnets' do @@ -242,6 +255,30 @@ }.to raise_error(Bosh::Director::ValidationInvalidType) end + it 'raises error when prefix is defined' do + expect { + Bosh::Director::DeploymentPlan::DynamicNetwork.parse( + { + 'name' => 'foo', + 'subnets' => [ + { + 'dns' => %w[1.2.3.4 5.6.7.8], + 'cloud_properties' => { + 'foz' => 'baz' + }, + 'az' => 'foz-zone', + 'prefix' => 28 + }, + ] + }, + [ + Bosh::Director::DeploymentPlan::AvailabilityZone.new('foz-zone', {}), + ], + logger + ) + }.to raise_error(Bosh::Director::NetworkInvalidProperty, "Prefix property is not supported for dynamic networks.") + end + it 'raises error when dns is present at the top level' do expect { Bosh::Director::DeploymentPlan::DynamicNetwork.parse( @@ -341,7 +378,7 @@ end it 'should fail when for static reservation' do - reservation = Bosh::Director::DesiredNetworkReservation.new_static(instance_model, @network, 1) + reservation = Bosh::Director::DesiredNetworkReservation.new_static(instance_model, @network, "1.1.1.1") expect { @network.network_settings(reservation) }.to raise_error Bosh::Director::NetworkReservationWrongType @@ -537,7 +574,7 @@ describe :validate_reference_from_job do it 'returns true if job has a valid network spec' do - dynamic_network = Bosh::Director::DeploymentPlan::DynamicNetwork.new('dynamic', [], logger) + dynamic_network = Bosh::Director::DeploymentPlan::DynamicNetwork.new('dynamic', [], '32', logger) job_network_spec = {'name' => 'dynamic'} expect { @@ -547,7 +584,7 @@ context 'when network is dynamic but job network spec uses static ips' do it 'raises StaticIPNotSupportedOnDynamicNetwork' do - dynamic_network = Bosh::Director::DeploymentPlan::DynamicNetwork.new('dynamic', [], logger) + dynamic_network = Bosh::Director::DeploymentPlan::DynamicNetwork.new('dynamic', [], '32', logger) job_network_spec = { 'name' => 'dynamic', 'static_ips' => ['192.168.1.10'] diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_group_networks_parser_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_group_networks_parser_spec.rb index cf0331b2d70..17dfbe5ca8c 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_group_networks_parser_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_group_networks_parser_spec.rb @@ -11,10 +11,10 @@ module Bosh::Director::DeploymentPlan instance_group_network['static_ips'] = ['192.168.1.1', '192.168.1.2'] instance_group end - let(:manifest_networks) { [ManualNetwork.new('a', [], per_spec_logger)] } + let(:manifest_networks) { [ManualNetwork.new('a', [], '32', per_spec_logger)] } context 'when instance group references a network not mentioned in the networks spec' do - let(:manifest_networks) { [ManualNetwork.new('my-network', [], per_spec_logger)] } + let(:manifest_networks) { [ManualNetwork.new('my-network', [], '32', per_spec_logger)] } it 'raises JobUnknownNetwork' do expect do @@ -38,7 +38,7 @@ module Bosh::Director::DeploymentPlan end context 'when instance group network spec references dynamic network with static IPs' do - let(:dynamic_network) { Bosh::Director::DeploymentPlan::DynamicNetwork.new('a', [], per_spec_logger) } + let(:dynamic_network) { Bosh::Director::DeploymentPlan::DynamicNetwork.new('a', [], '32', per_spec_logger) } let(:instance_group_spec) do instance_group = SharedSupport::DeploymentManifestHelper.simple_manifest_with_instance_groups['instance_groups'].first instance_group['networks'] = [{ @@ -71,6 +71,24 @@ module Bosh::Director::DeploymentPlan end context 'when called with a valid instance group spec' do + let(:subnet_spec) do + { + 'range' => '192.168.1.0/24', + 'gateway' => '192.168.1.1' + } + end + let(:manifest_networks) { [ManualNetwork.new('a', [ManualNetworkSubnet.parse('a', subnet_spec, "")], '32', per_spec_logger)] } + + let(:instance_group_spec) do + instance_group = SharedSupport::DeploymentManifestHelper.simple_manifest_with_instance_groups['instance_groups'].first + instance_group['networks'] = [{ + 'name' => 'a', + 'static_ips' => ['192.168.1.1', '192.168.1.2'], + }] + instance_group + end + + it 'adds static ips to instance group networks in order as they are in manifest' do networks = instance_group_networks_parser.parse(instance_group_spec, 'instance-group-name', manifest_networks) @@ -80,17 +98,16 @@ module Bosh::Director::DeploymentPlan name: 'a', static_ips: ['192.168.1.1', '192.168.1.2'], default_for: %w[dns gateway], - deployment_network: manifest_networks.first, - ), + deployment_network: manifest_networks.first ), ) - expect(networks.first.static_ips).to eq([ip_to_i('192.168.1.1'), ip_to_i('192.168.1.2')]) + expect(networks.first.static_ips).to eq([to_ipaddr('192.168.1.1'), to_ipaddr('192.168.1.2')]) end end RSpec::Matchers.define :be_an_instance_group_network do |expected| match do |actual| actual.name == expected.name && - actual.static_ips == expected.static_ips.map { |ip_to_i| IPAddr.new(ip_to_i) } && + actual.static_ips == expected.static_ips.map { |ip| IPAddr.new(ip) } && actual.deployment_network == expected.deployment_network end end diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_group_spec_parser_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_group_spec_parser_spec.rb index fa8d9d62ba1..0c13aa1dc5c 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_group_spec_parser_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_group_spec_parser_spec.rb @@ -32,7 +32,7 @@ module DeploymentPlan ) end let(:deployment_model) { FactoryBot.create(:models_deployment) } - let(:network) { ManualNetwork.new('fake-network-name', [], per_spec_logger) } + let(:network) { ManualNetwork.new('fake-network-name', [], '32', per_spec_logger) } let(:task) { FactoryBot.create(:models_task, id: 42) } let(:task_writer) { Bosh::Director::TaskDBWriter.new(:event_output, task.id) } let(:event_log) { Bosh::Director::EventLog::Log.new(task_writer) } @@ -1002,7 +1002,7 @@ module DeploymentPlan instance_group_spec['instances'] = 3 instance_group_spec['networks'].first['default'] = %w[gateway dns] instance_group_spec['networks'] << instance_group_spec['networks'].first.merge('name' => 'duped-network') # dupe it - duped_network = ManualNetwork.new('duped-network', [], per_spec_logger) + duped_network = ManualNetwork.new('duped-network', [], '32', per_spec_logger) allow(deployment_plan).to receive(:networks).and_return([duped_network, network]) expect do @@ -1029,7 +1029,7 @@ module DeploymentPlan end context 'when there are two networks, each being a separate default' do - let(:network2) { ManualNetwork.new('fake-network-name-2', [], per_spec_logger) } + let(:network2) { ManualNetwork.new('fake-network-name-2', [], '32', per_spec_logger) } it 'picks the only network as default' do instance_group_spec['networks'].first['default'] = ['dns'] diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_network_reservations_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_network_reservations_spec.rb index 978dbb72031..4133f84d452 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_network_reservations_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_network_reservations_spec.rb @@ -54,15 +54,15 @@ module Bosh::Director describe 'create_from_db' do context 'when there are IP addresses in db' do - let(:ip1) { IPAddr.new('192.168.0.1').to_i } - let(:ip2) { IPAddr.new('192.168.0.2').to_i } + let(:ip1) { Bosh::Director::IpAddrOrCidr.new('192.168.0.1/32') } + let(:ip2) { Bosh::Director::IpAddrOrCidr.new('192.168.0.2/32') } let(:ip_model1) do - FactoryBot.create(:models_ip_address, address_str: ip1.to_s, instance: instance_model, network_name: 'fake-network') + FactoryBot.create(:models_ip_address, address_str: ip1.to_cidr_s, instance: instance_model, network_name: 'fake-network') end let(:ip_model2) do - FactoryBot.create(:models_ip_address, address_str: ip2.to_s, instance: instance_model, network_name: 'fake-network') + FactoryBot.create(:models_ip_address, address_str: ip2.to_cidr_s, instance: instance_model, network_name: 'fake-network') end context 'when there is a last VM with IP addresses' do @@ -165,7 +165,7 @@ module Bosh::Director context 'when the network name saved in the database is of type Vip Static (ips in instance groups)' do let(:network_with_subnets) { [] } - let(:static_vip_network) { DeploymentPlan::VipNetwork.new('dummy', nil, [], nil) } + let(:static_vip_network) { DeploymentPlan::VipNetwork.new('dummy', nil, [], nil, nil) } before do instance_model.add_ip_address(ip_model1) @@ -209,7 +209,7 @@ module Bosh::Director end let(:dynamic_network) do - DeploymentPlan::DynamicNetwork.new('dynamic-network', [], per_spec_logger) + DeploymentPlan::DynamicNetwork.new('dynamic-network', [], '32', per_spec_logger) end before do allow(deployment).to receive(:network).with('dynamic-network').and_return(dynamic_network) diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_plan_factory_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_plan_factory_spec.rb index f5c4f9a67b9..3bdf2da1489 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_plan_factory_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_plan_factory_spec.rb @@ -45,7 +45,7 @@ module DeploymentPlan let(:range) { IPAddr.new('192.168.1.1/24') } let(:manual_network_subnet) { ManualNetworkSubnet.new('name-7', range, nil, nil, nil, nil, nil, [], []) } - let(:network) { Bosh::Director::DeploymentPlan::ManualNetwork.new('name-7', [manual_network_subnet], per_spec_logger) } + let(:network) { Bosh::Director::DeploymentPlan::ManualNetwork.new('name-7', [manual_network_subnet], '32', per_spec_logger) } let(:ip_repo) { Bosh::Director::DeploymentPlan::IpRepo.new(per_spec_logger) } let(:deployment_plan) do instance_double( diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_plan_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_plan_spec.rb index 656ac4ac98e..32d83688c4d 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_plan_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_plan_spec.rb @@ -120,10 +120,10 @@ module Bosh::Director::DeploymentPlan let(:network) { ManualNetwork.parse(network_spec, [availability_zone], per_spec_logger) } let(:reservation) do reservation = Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network) - reservation.resolve_ip('192.168.1.3') + reservation.resolve_ip('192.168.1.3/32') reservation end - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } + let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az'], '32') } let(:network_plans) do [ NetworkPlanner::Plan.new(reservation: reservation, existing: true), @@ -205,8 +205,7 @@ module Bosh::Director::DeploymentPlan describe 'networks_changed?' do context 'when the instance plan has desired network plans' do - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], per_spec_logger) } + let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) do [ @@ -295,7 +294,7 @@ module Bosh::Director::DeploymentPlan allow(per_spec_logger).to receive(:debug) expect(per_spec_logger).to receive(:debug).with( 'networks_changed? obsolete reservations: ' \ - "[{type=dynamic, ip=10.0.0.5, network=existing-network, instance=#{instance_model}}]", + "[{type=dynamic, ip=10.0.0.5/32, network=existing-network, instance=#{instance_model}}]", ) instance_plan.networks_changed? end @@ -317,7 +316,7 @@ module Bosh::Director::DeploymentPlan allow(per_spec_logger).to receive(:debug) expect(per_spec_logger).to receive(:debug).with( 'networks_changed? desired reservations: ' \ - "[{type=dynamic, ip=10.0.0.5, network=existing-network, instance=#{instance_model}}]", + "[{type=dynamic, ip=10.0.0.5/32, network=existing-network, instance=#{instance_model}}]", ) instance_plan.networks_changed? end @@ -335,8 +334,7 @@ module Bosh::Director::DeploymentPlan describe 'network_settings_changed?' do context 'when the instance plan has desired network plans' do - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], per_spec_logger) } + let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) do [ @@ -373,6 +371,7 @@ module Bosh::Director::DeploymentPlan 'a' => { 'type' => 'manual', 'ip' => '192.168.1.3', + 'prefix' => '32', 'netmask' => '255.255.255.0', 'cloud_properties' => {}, 'default' => %w[dns gateway], @@ -400,7 +399,7 @@ module Bosh::Director::DeploymentPlan }, } end - let(:subnet) { DynamicNetworkSubnet.new('8.8.8.8', subnet_cloud_properties, ['foo-az']) } + let(:subnet) { DynamicNetworkSubnet.new('8.8.8.8', subnet_cloud_properties, ['foo-az'], '32') } let(:network_plans) { [NetworkPlanner::Plan.new(reservation: existing_reservation, existing: true)] } let(:subnet_cloud_properties) do {} @@ -517,8 +516,8 @@ module Bosh::Director::DeploymentPlan end context 'when the vm type name has changed' do - let(:subnet) { DynamicNetworkSubnet.new(['10.0.0.1'], {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('a', [subnet], per_spec_logger) } + let(:subnet) { DynamicNetworkSubnet.new(['10.0.0.1'], {}, ['foo-az'], '32') } + let(:existing_network) { DynamicNetwork.new('a', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) { [NetworkPlanner::Plan.new(reservation: existing_reservation, existing: true)] } @@ -560,8 +559,7 @@ module Bosh::Director::DeploymentPlan context 'when the network has changed' do # everything else should be the same let(:availability_zone) { AvailabilityZone.new('foo-az', 'old' => 'value') } - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], per_spec_logger) } + let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) do @@ -891,8 +889,7 @@ module Bosh::Director::DeploymentPlan end context 'when the vm type name has changed' do - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('a', [subnet], per_spec_logger) } + let(:existing_network) { DynamicNetwork.new('a', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) { [NetworkPlanner::Plan.new(reservation: existing_reservation, existing: true)] } @@ -934,8 +931,7 @@ module Bosh::Director::DeploymentPlan context 'when the network has changed' do # everything else should be the same let(:availability_zone) { AvailabilityZone.new('foo-az', 'old' => 'value') } - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], per_spec_logger) } + let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) do @@ -1039,8 +1035,7 @@ module Bosh::Director::DeploymentPlan end context 'when the vm type name has changed' do - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('a', [subnet], per_spec_logger) } + let(:existing_network) { DynamicNetwork.new('a', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) { [NetworkPlanner::Plan.new(reservation: existing_reservation, existing: true)] } @@ -1123,8 +1118,7 @@ module Bosh::Director::DeploymentPlan context 'when the network has changed' do # everything else should be the same let(:availability_zone) { AvailabilityZone.new('foo-az', 'old' => 'value') } - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], per_spec_logger) } + let(:existing_network) { DynamicNetwork.new('existing-network', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) do @@ -1277,8 +1271,8 @@ module Bosh::Director::DeploymentPlan end describe '#persist_current_spec' do - let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az']) } - let(:existing_network) { DynamicNetwork.new('a', [subnet], per_spec_logger) } + let(:subnet) { DynamicNetworkSubnet.new('10.0.0.1', {}, ['foo-az'], '32') } + let(:existing_network) { DynamicNetwork.new('a', [subnet], '32', per_spec_logger) } let(:existing_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(existing_instance, existing_network) } let(:network_plans) do @@ -1545,6 +1539,7 @@ module Bosh::Director::DeploymentPlan 'a' => { 'type' => 'manual', 'ip' => '192.168.1.3', + 'prefix' => '32', 'netmask' => '255.255.255.0', 'cloud_properties' => {}, 'dns' => ['192.168.1.1', '192.168.1.2'], @@ -2033,7 +2028,7 @@ module Bosh::Director::DeploymentPlan describe '#remove_network_plans_for_ips' do let(:plan1) do reservation = Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network) - reservation.resolve_ip('192.168.1.25') + reservation.resolve_ip('192.168.1.25/32') NetworkPlanner::Plan.new(reservation: reservation, existing: false, obsolete: true) end @@ -2061,8 +2056,8 @@ module Bosh::Director::DeploymentPlan let(:network_plans) { [plan1, plan2, plan3, plan4] } - let(:ip1) { IPAddr.new('192.168.1.25').to_i } - let(:ip2) { IPAddr.new('192.168.1.26').to_i } + let(:ip1) { IPAddr.new('192.168.1.25/32') } + let(:ip2) { IPAddr.new('192.168.1.26/32') } let(:ip_address1) { FactoryBot.create(:models_ip_address, address_str: ip1.to_s) } let(:ip_address2) { FactoryBot.create(:models_ip_address, address_str: ip2.to_s) } diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_planner_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_planner_spec.rb index 89d856b19da..75e6fc7d013 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_planner_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_planner_spec.rb @@ -747,7 +747,7 @@ def make_instance_with_existing_model(existing_instance_model) before do FactoryBot.create(:models_ip_address, - address_str: ip_to_i('192.168.1.5').to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('192.168.1.5').to_cidr_s, network_name: 'fake-network', instance: existing_instance_model, ) @@ -764,7 +764,7 @@ def make_instance_with_existing_model(existing_instance_model) fake_job end - let(:manual_network) { Bosh::Director::DeploymentPlan::ManualNetwork.new('fake-network', [subnet], logger) } + let(:manual_network) { Bosh::Director::DeploymentPlan::ManualNetwork.new('fake-network', [subnet], nil, logger) } let(:subnet) do Bosh::Director::DeploymentPlan::ManualNetworkSubnet.new( 'fake-network', diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_repository_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_repository_spec.rb index 5d83da4aed9..e0b456e471b 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_repository_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_repository_spec.rb @@ -7,7 +7,7 @@ subject(:instance_repository) { Bosh::Director::DeploymentPlan::InstanceRepository.new(logger, variables_interpolator) } let(:variables_interpolator) { instance_double(Bosh::Director::ConfigServer::VariablesInterpolator) } - let(:network) { Bosh::Director::DeploymentPlan::DynamicNetwork.new('name-7', [], logger) } + let(:network) { Bosh::Director::DeploymentPlan::DynamicNetwork.new('name-7', [], '32', logger) } let(:deployment_plan) do ip_repo = Bosh::Director::DeploymentPlan::IpRepo.new(logger) @@ -68,13 +68,15 @@ describe 'binding existing reservations' do context 'when instance has reservations in db' do + let(:ip_address) { Bosh::Director::IpAddrOrCidr.new('1.1.1.1') } + before do - existing_instance.add_ip_address(FactoryBot.create(:models_ip_address, address_str: '123')) + existing_instance.add_ip_address(FactoryBot.create(:models_ip_address, address_str: ip_address.to_cidr_s)) end it 'is using reservation from database' do instance = instance_repository.fetch_existing(existing_instance, {}, desired_instance) - expect(instance.existing_network_reservations.map(&:ip)).to eq([123]) + expect(instance.existing_network_reservations.map(&:ip)).to eq([ip_address.to_cidr_s]) end end end @@ -200,13 +202,15 @@ end context 'binding existing reservations' do context 'when instance has reservations in db' do + let(:ip_address) { Bosh::Director::IpAddrOrCidr.new('1.1.1.1') } + before do - existing_instance.add_ip_address(FactoryBot.create(:models_ip_address, address_str: '123')) + existing_instance.add_ip_address(FactoryBot.create(:models_ip_address, address_str: ip_address.to_cidr_s)) end it 'is using reservation from database' do instance = instance_repository.fetch_obsolete_existing(existing_instance, {}, deployment_plan) - expect(instance.existing_network_reservations.map(&:ip)).to eq([123]) + expect(instance.existing_network_reservations.map(&:ip)).to eq([ip_address.to_cidr_s]) end end end diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_spec_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_spec_spec.rb index 1516804cc62..b5896dd48ad 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_spec_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/instance_spec_spec.rb @@ -338,7 +338,7 @@ module Bosh::Director::DeploymentPlan } end let(:subnet) { ManualNetworkSubnet.parse(network_spec['name'], subnet_spec, [availability_zone]) } - let(:network) { ManualNetwork.new(network_spec['name'], [subnet], per_spec_logger) } + let(:network) { ManualNetwork.new(network_spec['name'], [subnet], nil, per_spec_logger) } it 'returns a valid instance template_spec' do network_name = network_spec['name'] diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/database_ip_repo_ipv6_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/database_ip_repo_ipv6_spec.rb index 83bf5b1fd8f..7233a94bbdf 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/database_ip_repo_ipv6_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/database_ip_repo_ipv6_spec.rb @@ -56,7 +56,7 @@ module Bosh::Director::DeploymentPlan before { fake_job } def cidr_ip(ip) - IPAddr.new(ip).to_i + Bosh::Director::IpAddrOrCidr.new(ip).to_cidr_s end context :add do diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/ip_provider_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/ip_provider_spec.rb index 23b00411e7f..6b1a3084861 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/ip_provider_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/ip_provider_spec.rb @@ -102,7 +102,7 @@ module Bosh::Director::DeploymentPlan context 'when reservation is on dynamic network with no IP address' do it 'does not fail to release it' do - dynamic_network = DynamicNetwork.new('my-manual-network', [], per_spec_logger) + dynamic_network = DynamicNetwork.new('my-manual-network', [], nil, per_spec_logger) reservation = Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, dynamic_network) expect do @@ -127,7 +127,7 @@ module Bosh::Director::DeploymentPlan describe :reserve_existing_ips do context 'when dynamic network' do - let(:dynamic_network) { Bosh::Director::DeploymentPlan::DynamicNetwork.new('fake-dynamic-network', [], per_spec_logger) } + let(:dynamic_network) { Bosh::Director::DeploymentPlan::DynamicNetwork.new('fake-dynamic-network', [], nil, per_spec_logger) } let(:existing_network_reservation) do Bosh::Director::ExistingNetworkReservation.new( instance_model, @@ -161,7 +161,7 @@ module Bosh::Director::DeploymentPlan Bosh::Director::ExistingNetworkReservation.new( instance_model, manual_network, - '192.168.1.2', + '192.168.1.2/32', 'manual', ) end @@ -289,7 +289,7 @@ module Bosh::Director::DeploymentPlan it 'raises an error' do reservation = Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, manual_network) - reservation.resolve_ip(IPAddr.new('192.168.1.11').to_i) + reservation.resolve_ip(Bosh::Director::IpAddrOrCidr.new('192.168.1.11')) expect do ip_provider.reserve(reservation) end.to raise_error Bosh::Director::NetworkReservationIpReserved, @@ -307,7 +307,7 @@ module Bosh::Director::DeploymentPlan expect { ip_provider.reserve(reservation) }.to raise_error Bosh::Director::NetworkReservationWrongType, - "IP '192.168.1.2' on network 'my-manual-network' does not belong to dynamic pool" + "IP '192.168.1.2/32' on network 'my-manual-network' does not belong to dynamic pool" end end end @@ -348,7 +348,7 @@ module Bosh::Director::DeploymentPlan expect { ip_provider.reserve(reservation) }.to raise_error Bosh::Director::NetworkReservationWrongType, - "IP '192.168.1.2' on network 'my-manual-network' does not belong to dynamic pool" + "IP '192.168.1.2/32' on network 'my-manual-network' does not belong to dynamic pool" end end end @@ -468,7 +468,7 @@ module Bosh::Director::DeploymentPlan it 'adds the ip address to the ip repository' do ip_provider.reserve(reservation) - expect(reservation.ip).to eq(IPAddr.new('1.1.1.1').to_i) + expect(reservation.ip).to eq('1.1.1.1') end end @@ -477,7 +477,7 @@ module Bosh::Director::DeploymentPlan it 'allocates an ip address for the reservation' do ip_provider.reserve(reservation) - expect(reservation.ip).to eq(IPAddr.new('1.1.1.1').to_i) + expect(reservation.ip).to eq('1.1.1.1') end context 'and there are no available vips' do diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/ip_repo_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/ip_repo_spec.rb index 946047a9ce9..47cee418b16 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/ip_repo_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/ip_provider/ip_repo_spec.rb @@ -36,6 +36,22 @@ module Bosh::Director::DeploymentPlan ) end + let(:subnet_with_prefix) do + ManualNetworkSubnet.parse( + network.name, + network_spec['subnets'].first.merge('prefix' => '31'), + availability_zones, + ) + end + + let(:subnet_with_too_big_prefix) do + ManualNetworkSubnet.parse( + network.name, + network_spec['subnets'].first.merge('prefix' => '30'), + availability_zones, + ) + end + let(:other_network_spec) { network_spec.merge('name' => 'my-other-manual-network') } let(:other_network) do ManualNetwork.parse( @@ -56,7 +72,7 @@ module Bosh::Director::DeploymentPlan before { fake_job } def cidr_ip(ip) - IPAddr.new(ip).to_i + Bosh::Director::IpAddrOrCidr.new(ip) end context :add do @@ -97,6 +113,7 @@ def dynamic_reservation_with_ip(ip) context 'from Dynamic to Static' do it 'update type of reservation' do dynamic_reservation = dynamic_reservation_with_ip('192.168.1.5') + ip_repo.add(dynamic_reservation) expect(Bosh::Director::Models::IpAddress.count).to eq(1) @@ -169,7 +186,7 @@ def dynamic_reservation_with_ip(ip) ip_repo.add(reservation) saved_address = Bosh::Director::Models::IpAddress.order(:address_str).last - expect(saved_address.address_str).to eq(cidr_ip('192.168.1.5').to_s) + expect(saved_address.address_str).to eq(cidr_ip('192.168.1.5').to_cidr_s) expect(saved_address.network_name).to eq('my-manual-network') expect(saved_address.task_id).to eq('fake-task-id') expect(saved_address.created_at).to_not be_nil @@ -310,22 +327,82 @@ def dynamic_reservation_with_ip(ip) end end + context 'when a prefix is assigned to the subnet' do + let(:reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network) } + it 'reserves the prefix address' do + ip_address = ip_repo.allocate_dynamic_ip(reservation, subnet_with_prefix) + + expect(ip_address).to eq(cidr_ip('192.168.1.2/31')) + end + + it 'reserves the next available prefix address' do + ip_address = ip_repo.allocate_dynamic_ip(other_reservation, other_subnet) + + expected_ip_address = cidr_ip('192.168.1.2') + expect(ip_address).to eq(expected_ip_address) + + ip_address = ip_repo.allocate_dynamic_ip(reservation, subnet_with_prefix) + + expected_ip_address = cidr_ip('192.168.1.4/31') + expect(ip_address).to eq(expected_ip_address) + + ip_address = ip_repo.allocate_dynamic_ip(other_reservation, other_subnet) + + expected_ip_address = cidr_ip('192.168.1.3') + expect(ip_address).to eq(expected_ip_address) + + ip_address = ip_repo.allocate_dynamic_ip(other_reservation, other_subnet) + + expected_ip_address = cidr_ip('192.168.1.6') + expect(ip_address).to eq(expected_ip_address) + end + + it 'should stop retrying and return nil if no sufficient range is available' do + ip_address = ip_repo.allocate_dynamic_ip(other_reservation, other_subnet) + + expected_ip_address = cidr_ip('192.168.1.2') + expect(ip_address).to eq(expected_ip_address) + + ip_address = ip_repo.allocate_dynamic_ip(reservation, subnet_with_prefix) + + expected_ip_address = cidr_ip('192.168.1.4/31') + expect(ip_address).to eq(expected_ip_address) + + ip_address = ip_repo.allocate_dynamic_ip(other_reservation, other_subnet) + + expected_ip_address = cidr_ip('192.168.1.3') + expect(ip_address).to eq(expected_ip_address) + + expect do + ip_address = ip_repo.allocate_dynamic_ip(other_reservation, subnet_with_prefix) + expect(ip_address).to be_nil + end.not_to(change { Bosh::Director::Models::IpAddress.count }) + end + + it 'should stop retrying and return nil if no sufficient range is available' do + expect do + ip = ip_repo.allocate_dynamic_ip(reservation, subnet_with_too_big_prefix) + expect(ip).to be_nil + end.not_to(change { Bosh::Director::Models::IpAddress.count }) + end + end + context 'when reserving IP fails' do def fail_saving_ips(ips, fail_error) original_saves = {} ips.each do |ip| ip_address = Bosh::Director::Models::IpAddress.new( - address_str: ip.to_s, + address_str: ip.to_cidr_s, network_name: 'my-manual-network', instance: instance_model, task_id: Bosh::Director::Config.current_job.task_id ) original_save = ip_address.method(:save) - original_saves[ip.to_s] = original_save + original_saves[ip.to_cidr_s] = original_save end allow_any_instance_of(Bosh::Director::Models::IpAddress).to receive(:save) do |model| - if ips.map(&:to_s).include?(model.address_str) + if ips.map(&:to_cidr_s).include?(model.address_str) original_save = original_saves[model.address_str] original_save.call raise fail_error @@ -425,7 +502,7 @@ def fail_saving_ips(ips, fail_error) end.to change { Bosh::Director::Models::IpAddress.count }.by(1) ip_address = instance_model.ip_addresses.first - expect(ip_address.address_str.to_i).to eq(cidr_ip('69.69.69.69')) + expect(ip_address.address_str).to eq(cidr_ip('69.69.69.69').to_cidr_s) end context 'when there are no vips defined in the network' do @@ -506,7 +583,7 @@ def fail_saving_ips(ips, fail_error) it 'deletes IP address' do expect { - ip_repo.delete('192.168.1.5') + ip_repo.delete('192.168.1.5/32') }.to change { Bosh::Director::Models::IpAddress.all.size }.by(-1) diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/manual_network_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/manual_network_spec.rb index d09ba135424..ea52314d062 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/manual_network_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/manual_network_spec.rb @@ -133,6 +133,7 @@ expect(manual_network.network_settings(reservation, [])).to eq( 'type' => 'manual', 'ip' => '192.168.1.2', + 'prefix' => '32', 'netmask' => '255.255.255.0', 'cloud_properties' => {}, 'gateway' => '192.168.1.1', @@ -151,6 +152,7 @@ expect(manual_network.network_settings(reservation)).to eq( 'type' => 'manual', 'ip' => '192.168.1.2', + 'prefix' => '32', 'netmask' => '255.255.255.0', 'cloud_properties' => {}, 'gateway' => '192.168.1.1', @@ -159,6 +161,32 @@ ) end + context 'when a prefix is maintained for a subnet' do + let(:network_spec) do + cloud_config_hash['networks'].first['subnets'].first['prefix'] = '30' + cloud_config_hash['networks'].first + end + + it 'should set the correct prefix' do + reservation = Bosh::Director::DesiredNetworkReservation.new_static( + instance_model, + manual_network, + '192.168.1.2', + ) + + expect(manual_network.network_settings(reservation)).to eq( + 'type' => 'manual', + 'ip' => '192.168.1.0', + 'prefix' => '30', + 'netmask' => '255.255.255.0', + 'cloud_properties' => {}, + 'gateway' => '192.168.1.1', + 'dns' => ['192.168.1.1', '192.168.1.2'], + 'default' => %w[dns gateway], + ) + end + end + it 'should fail when there is no IP' do reservation = Bosh::Director::DesiredNetworkReservation.new_dynamic( instance_model, diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/manual_network_subnet_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/manual_network_subnet_spec.rb index 185363dccb1..7b80772d33b 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/manual_network_subnet_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/manual_network_subnet_spec.rb @@ -328,6 +328,54 @@ def make_managed_subnet(properties, availability_zones) expect(subnet.restricted_ips).to include(ip1.to_i) expect(subnet.restricted_ips).to include(ip2.to_i) end + + it 'should create a subnet spec with prefix' do + subnet = make_subnet( + { + 'range' => '192.168.0.0/24', + 'gateway' => '192.168.0.254', + 'cloud_properties' => {'foo' => 'bar'}, + 'prefix' => '25' + }, + [], + ) + + expect(subnet.range.to_cidr_s).to eq('192.168.0.0/24') + expect(subnet.netmask).to eq('255.255.255.0') + expect(subnet.gateway).to eq('192.168.0.254') + expect(subnet.prefix).to eq('25') + expect(subnet.dns).to eq(nil) + end + + + it 'should fail if the prefix size is larger than the range' do + expect { + make_subnet( + { + 'range' => '192.168.0.0/24', + 'gateway' => '192.168.0.254', + 'cloud_properties' => {'foo' => 'bar'}, + 'prefix' => '23' + }, + [], + )}.to raise_error(Bosh::Director::NetworkPrefixSizeTooBig, + "Prefix size '23' is larger than range prefix '24'") + end + + it 'should fail if a static ip provided is not a base address of the prefix' do + expect { + make_subnet( + { + 'range' => '192.168.0.0/24', + 'gateway' => '192.168.0.254', + 'static' => ['192.168.0.64','192.168.0.128','192.168.0.191'], + 'cloud_properties' => {'foo' => 'bar'}, + 'prefix' => '26' + }, + [], + )}.to raise_error(Bosh::Director::NetworkPrefixStaticIpNotBaseAddress, + "Static IP '192.168.0.191' is not a base address of the prefix '26'") + end end describe :overlaps? do @@ -397,26 +445,34 @@ def make_managed_subnet(properties, availability_zones) let(:reserved) { ['192.168.0.50-192.168.0.60'] } it 'returns false' do - expect(subnet.is_reservable?(IPAddr.new('192.168.0.55'))).to be_falsey + expect(subnet.is_reservable?(Bosh::Director::IpAddrOrCidr.new('192.168.0.55'))).to be_falsey end end context 'when subnet reserved does not include IP' do it 'returns true' do - expect(subnet.is_reservable?(IPAddr.new('192.168.0.55'))).to be_truthy + expect(subnet.is_reservable?(Bosh::Director::IpAddrOrCidr.new('192.168.0.55'))).to be_truthy + end + end + + context 'when subnet reserved does not include any address of a cidr' do + let(:reserved) { ['192.168.0.50-192.168.0.60'] } + + it 'returns true' do + expect(subnet.is_reservable?(Bosh::Director::IpAddrOrCidr.new('192.168.0.62/31'))).to be_truthy end end end context 'when subnet range does not include IP' do it 'returns false' do - expect(subnet.is_reservable?(IPAddr.new('192.168.10.55'))).to be_falsey + expect(subnet.is_reservable?(Bosh::Director::IpAddrOrCidr.new('192.168.10.55'))).to be_falsey end end context 'when subnet range is not the same IP version' do it 'returns false' do - expect(subnet.is_reservable?(IPAddr.new('f1ee:0000:0000:0000:0000:0000:0000:0001'))).to be_falsey + expect(subnet.is_reservable?(Bosh::Director::IpAddrOrCidr.new('f1ee:0000:0000:0000:0000:0000:0000:0001'))).to be_falsey end end end diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_parser/name_servers_parser_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_parser/name_servers_parser_spec.rb index e2562315aaa..40642d8c2e3 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_parser/name_servers_parser_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_parser/name_servers_parser_spec.rb @@ -16,7 +16,7 @@ module DeploymentPlan::NetworkParser it "should raise an error if a DNS server isn't specified with as an IP" do expect { name_servers_parser.parse('network', {'dns' => %w[1.2.3.4 foo.bar]}) - }.to raise_error(/invalid address/) + }.to raise_error(/Invalid IP or CIDR format/) end it 'returns an array containing the nameserver address' do diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/planner_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/planner_spec.rb index b9c02d890d2..ce47fa4f697 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/planner_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/planner_spec.rb @@ -19,11 +19,11 @@ module Bosh::Director::DeploymentPlan 'network_A', IPAddr.new('192.168.1.0/24'), nil, nil, nil, nil, ['zone_1'], [], - ['192.168.1.10', '192.168.1.11', '192.168.1.12', '192.168.1.13', '192.168.1.14'] + ['192.168.1.10', '192.168.1.11', '192.168.1.12', '192.168.1.13', '192.168.1.14'], nil, nil, 32 ), ] end - let(:deployment_network) { ManualNetwork.new('network_A', deployment_subnets, nil) } + let(:deployment_network) { ManualNetwork.new('network_A', deployment_subnets, nil, nil) } let(:instance_group_network) { FactoryBot.build(:deployment_plan_job_network, name: 'network_A', deployment_network: deployment_network) } describe 'network_plan_with_dynamic_reservation' do diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/reservation_reconciler_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/reservation_reconciler_spec.rb index 1b090973a70..4e41552e31d 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/reservation_reconciler_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/reservation_reconciler_spec.rb @@ -9,20 +9,20 @@ module Bosh::Director::DeploymentPlan let(:instance_model) { FactoryBot.create(:models_instance, availability_zone: initial_az) } let(:instance) { instance_double(Instance, model: instance_model) } let(:variables_interpolator) { instance_double(Bosh::Director::ConfigServer::VariablesInterpolator) } - let(:network) { ManualNetwork.new('my-network', subnets, per_spec_logger) } + let(:network) { ManualNetwork.new('my-network', subnets, '32', per_spec_logger) } let(:subnets) do [ ManualNetworkSubnet.new( 'my-network', IPAddr.new('192.168.1.0/24'), nil, nil, nil, nil, ['zone_1'], [], - ['192.168.1.10'] + ['192.168.1.10'], nil, nil, '32' ), ManualNetworkSubnet.new( 'my-network', IPAddr.new('192.168.2.0/24'), nil, nil, nil, nil, ['zone_2'], [], - ['192.168.2.10'] + ['192.168.2.10'], nil, nil, '32' ), ] end @@ -40,13 +40,13 @@ module Bosh::Director::DeploymentPlan let(:desired_az) { AvailabilityZone.new('zone_1', {}) } let(:existing_reservations) do [ - Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2', 'manual'), - Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.3', 'manual'), + Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2/32', 'manual'), + Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.3/32', 'manual'), ] end let(:dynamic_network_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network) } - let(:static_network_reservation) { Bosh::Director::DesiredNetworkReservation.new_static(instance_model, network, '192.168.1.2') } + let(:static_network_reservation) { Bosh::Director::DesiredNetworkReservation.new_static(instance_model, network, '192.168.1.2/32') } let(:should_create_swap_delete?) { false } @@ -55,9 +55,9 @@ module Bosh::Director::DeploymentPlan end context 'when the instance group is on a dynamic network' do - let(:network) { DynamicNetwork.new('my-network', subnets, per_spec_logger) } + let(:network) { DynamicNetwork.new('my-network', subnets, '32', per_spec_logger) } let(:desired_reservations) { [dynamic_network_reservation] } - let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2', 'dynamic')] } + let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2/32', 'dynamic')] } it 'uses the existing reservation' do existing_reservations.map { |reservation| reservation.resolve_type(:dynamic) } @@ -82,7 +82,7 @@ module Bosh::Director::DeploymentPlan let(:existing_reservations) do [ - Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2', 'manual'), + Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2/32', 'manual'), ] end @@ -178,8 +178,8 @@ module Bosh::Director::DeploymentPlan let(:existing_reservations) do [ - Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2', 'manual'), - Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.3', 'manual'), + Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2/32', 'manual'), + Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.3/32', 'manual'), ] end @@ -202,7 +202,7 @@ module Bosh::Director::DeploymentPlan end describe 'changes to specifications about the instances network' do - let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2', 'manual')] } + let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2/32', 'manual')] } let(:desired_reservations) { [Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network2)] } before do @@ -210,7 +210,7 @@ module Bosh::Director::DeploymentPlan end context 'when the existing ip is part of a vip static network but the desired network is a global vip network' do - let(:network) { VipNetwork.new('static-vip-network', nil, [], per_spec_logger) } + let(:network) { VipNetwork.new('static-vip-network', nil, [], '32', per_spec_logger) } let(:network_spec) do { 'name' => 'global-vip-network', @@ -221,7 +221,7 @@ module Bosh::Director::DeploymentPlan end let(:network2) { VipNetwork.parse(network_spec, [], per_spec_logger) } - let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2', 'vip')] } + let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2/32', 'vip')] } let(:initial_az) { 'zone_1' } it 'should keep the existing reservation' do network_plans = network_planner.reconcile(existing_reservations) @@ -241,7 +241,7 @@ module Bosh::Director::DeploymentPlan context 'when the network name changes' do let(:initial_az) { 'zone_1' } - let(:network2) { ManualNetwork.new('my-network-2', subnets, per_spec_logger) } + let(:network2) { ManualNetwork.new('my-network-2', subnets, '32', per_spec_logger) } it 'should keep existing reservations' do network_plans = network_planner.reconcile(existing_reservations) @@ -282,7 +282,7 @@ module Bosh::Director::DeploymentPlan 'my-network', IPAddr.new('192.168.1.0/24'), nil, nil, nil, nil, [], [], - ['192.168.1.10'] + ['192.168.1.10'], '32' ), ] end @@ -306,14 +306,14 @@ module Bosh::Director::DeploymentPlan context 'when the instance model has no az' do let(:initial_az) { '' } let(:desired_az) { nil } - let(:network2) { ManualNetwork.new('my-network-2', subnets, per_spec_logger) } + let(:network2) { ManualNetwork.new('my-network-2', subnets, '32', per_spec_logger) } let(:subnets) do [ ManualNetworkSubnet.new( 'my-network', IPAddr.new('192.168.1.0/24'), nil, nil, nil, nil, [], [], - ['192.168.1.10'] + ['192.168.1.10'], nil, nil, '32' ), ] end @@ -334,7 +334,7 @@ module Bosh::Director::DeploymentPlan end context 'when the network type changes to dynamic' do - let(:network2) { DynamicNetwork.new('my-network-2', subnets, per_spec_logger) } + let(:network2) { DynamicNetwork.new('my-network-2', subnets, '32', per_spec_logger) } it 'should have a new reservation' do network_plans = network_planner.reconcile(existing_reservations) @@ -352,8 +352,8 @@ module Bosh::Director::DeploymentPlan end context 'when the network type changes to manual' do - let(:network) { DynamicNetwork.new('my-network', subnets, per_spec_logger) } - let(:network2) { ManualNetwork.new('my-network-2', subnets, per_spec_logger) } + let(:network) { DynamicNetwork.new('my-network', subnets, '32', per_spec_logger) } + let(:network2) { ManualNetwork.new('my-network-2', subnets, '32', per_spec_logger) } it 'should have a new reservation' do network_plans = network_planner.reconcile(existing_reservations) @@ -428,7 +428,7 @@ module Bosh::Director::DeploymentPlan context 'when existing reservation availability zones do not match job availability zones' do let(:desired_az) { AvailabilityZone.new('zone_2', {}) } - let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2', 'manual')] } + let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2/32', 'manual')] } let(:desired_reservations) { [Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network)] } before { existing_reservations[0].resolve_type(:dynamic) } @@ -462,7 +462,7 @@ module Bosh::Director::DeploymentPlan context 'when existing reservation and job do not belong to any availability zone' do let(:desired_az) { nil } - let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2', 'manual')] } + let(:existing_reservations) { [Bosh::Director::ExistingNetworkReservation.new(instance_model, network, '192.168.1.2/32', 'manual')] } let(:desired_reservations) { [Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network)] } let(:subnets) do [ @@ -470,7 +470,9 @@ module Bosh::Director::DeploymentPlan 'my-network', IPAddr.new('192.168.1.0/24'), nil, nil, nil, nil, nil, [], - ['192.168.1.10'] + ['192.168.1.10'], + nil, nil, + '32' ), ] end @@ -485,7 +487,7 @@ module Bosh::Director::DeploymentPlan expect(obsolete_plans.count).to eq(0) expect(existing_plans.count).to eq(1) - expect(to_ipaddr(existing_plans.first.reservation.ip)).to eq('192.168.1.2') + expect(to_ipaddr(existing_plans.first.reservation.ip)).to eq('192.168.1.2/32') expect(desired_plans.count).to eq(0) end end @@ -494,8 +496,8 @@ module Bosh::Director::DeploymentPlan let(:dynamic_network_reservation) { Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network) } let(:desired_reservations) do [ - Bosh::Director::DesiredNetworkReservation.new_static(instance_model, network, '192.168.1.2'), - Bosh::Director::DesiredNetworkReservation.new_static(instance_model, network, '192.168.1.4'), + Bosh::Director::DesiredNetworkReservation.new_static(instance_model, network, '192.168.1.2/32'), + Bosh::Director::DesiredNetworkReservation.new_static(instance_model, network, '192.168.1.4/32'), dynamic_network_reservation, ] end diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/vip_planner_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/vip_planner_spec.rb index 828d8832930..084a605dd08 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/vip_planner_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_planner/vip_planner_spec.rb @@ -87,13 +87,13 @@ def make_instance_plan context 'when existing instance IPs can be reused' do before do FactoryBot.create(:models_ip_address, - address_str: ip_to_i('69.69.69.69').to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('69.69.69.69').to_cidr_s, network_name: 'fake-network-1', instance: instance_plan.existing_instance, ) FactoryBot.create(:models_ip_address, - address_str: ip_to_i('79.79.79.79').to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('79.79.79.79').to_cidr_s, network_name: 'fake-network-2', instance: instance_plan.existing_instance, ) @@ -112,13 +112,13 @@ def make_instance_plan context 'when existing instance static IP is no longer in the list of IPs' do before do FactoryBot.create(:models_ip_address, - address_str: ip_to_i('65.65.65.65').to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('65.65.65.65').to_cidr_s, network_name: 'fake-network-1', instance: instance_plan.existing_instance, ) FactoryBot.create(:models_ip_address, - address_str: ip_to_i('79.79.79.79').to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('79.79.79.79').to_cidr_s, network_name: 'fake-network-2', instance: instance_plan.existing_instance, ) @@ -142,13 +142,13 @@ def make_instance_plan before do FactoryBot.create(:models_ip_address, - address_str: ip_to_i('68.68.68.68').to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('68.68.68.68').to_cidr_s, network_name: 'fake-network-1', instance: instance_plans[1].existing_instance, ) FactoryBot.create(:models_ip_address, - address_str: ip_to_i('77.77.77.77').to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('77.77.77.77').to_cidr_s, network_name: 'fake-network-2', instance: instance_plans[1].existing_instance, ) diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_settings_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_settings_spec.rb index 8b5ca81aa51..b1048f0090e 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_settings_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/network_settings_spec.rb @@ -49,6 +49,10 @@ module Bosh::Director::DeploymentPlan let(:plan) { instance_double(Planner, name: 'fake-deployment') } let(:use_short_dns_addresses) { false } let(:use_link_dns_addresses) { false } + let(:dynamic_network) do + subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], {'foo' => 'bar'}, 'az-1', '32')] + DynamicNetwork.new('net_a', subnets, '32', per_spec_logger) + end before do allow_any_instance_of(Bosh::Director::DnsEncoder).to receive(:num_for_uuid) @@ -61,11 +65,6 @@ module Bosh::Director::DeploymentPlan describe '#to_hash' do context 'dynamic network' do - let(:dynamic_network) do - subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], {'foo' => 'bar'}, 'az-1')] - DynamicNetwork.new('net_a', subnets, per_spec_logger) - end - let(:reservations) { [Bosh::Director::DesiredNetworkReservation.new_dynamic(nil, dynamic_network)] } it 'returns the network settings plus current IP, Netmask & Gateway from agent state' do @@ -130,10 +129,6 @@ module Bosh::Director::DeploymentPlan end context 'when it is a dynamic network' do - let(:dynamic_network) do - subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], {'foo' => 'bar'}, 'az-1')] - DynamicNetwork.new('net_a', subnets, per_spec_logger) - end let(:reservations) {[Bosh::Director::DesiredNetworkReservation.new_dynamic(nil, dynamic_network)]} context 'when local dns is disabled' do @@ -251,10 +246,6 @@ module Bosh::Director::DeploymentPlan end context 'when it is a dynamic network' do - let(:dynamic_network) do - subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], {'foo' => 'bar'}, 'az-1')] - DynamicNetwork.new('net_a', subnets, per_spec_logger) - end let(:reservations) {[Bosh::Director::DesiredNetworkReservation.new_dynamic(nil, dynamic_network)]} context 'when local dns is disabled' do @@ -291,11 +282,6 @@ module Bosh::Director::DeploymentPlan describe '#network_addresses' do context 'dynamic network' do - let(:dynamic_network) do - subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], {'foo' => 'bar'}, 'az-1')] - DynamicNetwork.new('net_a', subnets, per_spec_logger) - end - let(:reservations) {[Bosh::Director::DesiredNetworkReservation.new_dynamic(nil, dynamic_network)]} context 'when DNS entries are requested' do it 'includes the network name and domain record' do @@ -379,8 +365,8 @@ module Bosh::Director::DeploymentPlan context 'when it is a dynamic network' do let(:dynamic_network) do - subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], { 'foo' => 'bar' }, 'az-1')] - DynamicNetwork.new('net_a', subnets, per_spec_logger) + subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], { 'foo' => 'bar' }, 'az-1', '32')] + DynamicNetwork.new('net_a', subnets, '32', per_spec_logger) end let(:reservations) { [Bosh::Director::DesiredNetworkReservation.new_dynamic(nil, dynamic_network)] } @@ -493,8 +479,8 @@ module Bosh::Director::DeploymentPlan context 'when it is a dynamic network' do let(:dynamic_network) do - subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], { 'foo' => 'bar' }, 'az-1')] - DynamicNetwork.new('net_a', subnets, per_spec_logger) + subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], { 'foo' => 'bar' }, 'az-1', '32')] + DynamicNetwork.new('net_a', subnets, '32', per_spec_logger) end let(:reservations) { [Bosh::Director::DesiredNetworkReservation.new_dynamic(nil, dynamic_network)] } @@ -531,13 +517,13 @@ module Bosh::Director::DeploymentPlan context 'when there are multiple network reservations' do let(:dynamic_network) do - subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], { 'foo' => 'bar' }, 'az-1')] - DynamicNetwork.new('net_a', subnets, per_spec_logger) + subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], { 'foo' => 'bar' }, 'az-1', '32')] + DynamicNetwork.new('net_a', subnets, '32', per_spec_logger) end let(:dynamic_network2) do - subnets = [DynamicNetworkSubnet.new(['9.8.7.6'], { 'bob' => 'joe' }, 'az-1')] - DynamicNetwork.new('net_b', subnets, per_spec_logger) + subnets = [DynamicNetworkSubnet.new(['9.8.7.6'], { 'bob' => 'joe' }, 'az-1', '32')] + DynamicNetwork.new('net_b', subnets, '32', per_spec_logger) end let(:reservations) do @@ -561,8 +547,8 @@ module Bosh::Director::DeploymentPlan context 'dynamic network' do let(:dynamic_network) do - subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], { 'foo' => 'bar' }, 'az-1')] - DynamicNetwork.new('net_a', subnets, per_spec_logger) + subnets = [DynamicNetworkSubnet.new(['1.2.3.4'], { 'foo' => 'bar' }, 'az-1', '32')] + DynamicNetwork.new('net_a', subnets, '32', per_spec_logger) end let(:reservations) { [Bosh::Director::DesiredNetworkReservation.new_dynamic(nil, dynamic_network)] } diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/networks_to_static_ips_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/networks_to_static_ips_spec.rb index 52550914189..1c0fe1f3945 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/networks_to_static_ips_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/networks_to_static_ips_spec.rb @@ -104,10 +104,17 @@ module Bosh::Director::DeploymentPlan 'network_A', IPAddr.new('192.168.1.0/24'), nil, nil, nil, nil, subnet_azs, [], - ['192.168.1.10', '192.168.1.11', '192.168.1.12', '192.168.1.13', '192.168.1.14']) + [ + Bosh::Director::IpAddrOrCidr.new('192.168.1.10'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.11'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.12'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.13'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.14') + ] + ) ] end - let(:deployment_network) { ManualNetwork.new('network_A', deployment_subnets, nil) } + let(:deployment_network) { ManualNetwork.new('network_A', deployment_subnets, '32', nil) } let(:job_networks) do [FactoryBot.build(:deployment_plan_job_network, name: 'network_A', static_ips: job_static_ips, deployment_network: deployment_network)] end diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/plan_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/plan_spec.rb index 3c7f5d6c11c..d6d82a070ef 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/plan_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/plan_spec.rb @@ -46,44 +46,50 @@ module Bosh::Director::DeploymentPlan let(:deployment) { instance_double(Planner, model: deployment_model, skip_drain: SkipDrain.new(true)) } let(:deployment_model) { FactoryBot.create(:models_deployment) } - let(:deployment_network) { ManualNetwork.new('network_A', deployment_subnets, nil) } + let(:deployment_network) { ManualNetwork.new('network_A', deployment_subnets, nil, nil) } let(:deployment_subnets) do [ ManualNetworkSubnet.new( 'network_A', IPAddr.new('192.168.1.0/24'), nil, nil, nil, nil, ['zone_1'], [], - %w[ - 192.168.1.10 - 192.168.1.11 - 192.168.1.12 - 192.168.1.13 - 192.168.1.14 - ] + [ + Bosh::Director::IpAddrOrCidr.new('192.168.1.10'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.11'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.12'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.13'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.14'), + ], + nil, nil, + '32' ), ManualNetworkSubnet.new( 'network_A', IPAddr.new('10.10.1.0/24'), nil, nil, nil, nil, ['zone_2'], [], - %w[ - 10.10.1.10 - 10.10.1.11 - 10.10.1.12 - 10.10.1.13 - 10.10.1.14 - ] + [ + Bosh::Director::IpAddrOrCidr.new('10.10.1.10'), + Bosh::Director::IpAddrOrCidr.new('10.10.1.11'), + Bosh::Director::IpAddrOrCidr.new('10.10.1.12'), + Bosh::Director::IpAddrOrCidr.new('10.10.1.13'), + Bosh::Director::IpAddrOrCidr.new('10.10.1.14'), + ], + nil, nil, + '32' ), ManualNetworkSubnet.new( 'network_A', IPAddr.new('10.0.1.0/24'), nil, nil, nil, nil, ['zone_3'], [], - %w[ - 10.0.1.10 - 10.0.1.11 - 10.0.1.12 - 10.0.1.13 - 10.0.1.14 - ] + [ + Bosh::Director::IpAddrOrCidr.new('10.0.1.10'), + Bosh::Director::IpAddrOrCidr.new('10.0.1.11'), + Bosh::Director::IpAddrOrCidr.new('10.0.1.12'), + Bosh::Director::IpAddrOrCidr.new('10.0.1.13'), + Bosh::Director::IpAddrOrCidr.new('10.0.1.14'), + ], + nil, nil, + '32' ), ] end @@ -96,7 +102,13 @@ module Bosh::Director::DeploymentPlan end context 'when job networks include static IPs' do - let(:job_static_ips) { ['192.168.1.10', '192.168.1.11', '10.10.1.10'] } + let(:job_static_ips) do + [ + Bosh::Director::IpAddrOrCidr.new('192.168.1.10'), + Bosh::Director::IpAddrOrCidr.new('192.168.1.11'), + Bosh::Director::IpAddrOrCidr.new('10.10.1.10') + ] + end it 'places the instances in azs there static IPs are in order of their indexes' do expect(instance_plans.select(&:new?).map(&:desired_instance).map(&:az)).to eq([zone_1]) diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker_spec.rb index 693138623b8..88b9326176f 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker_spec.rb @@ -327,10 +327,10 @@ def make_subnet_spec(range, static_ips, zone_names) network_plans[network_plan.reservation.network.name] << format_ip(network_plan.reservation.ip) end - expect(network_plans['a']).to match_array(['192.168.1.10', '192.168.1.11', '192.168.1.12', '192.168.2.10']) - expect(network_plans['b']).to match_array(['10.10.1.10', '10.10.1.11', '10.10.2.10', '10.10.2.11']) - expect(network_plans['c']).to match_array(['172.16.1.10', '172.16.2.12', '172.16.2.10', '172.16.2.11']) - expect(network_plans['d']).to match_array(['64.8.1.10', '64.8.2.10', '64.8.3.10', '64.8.3.11']) + expect(network_plans['a']).to match_array([Bosh::Director::IpAddrOrCidr.new('192.168.1.10'), Bosh::Director::IpAddrOrCidr.new('192.168.1.11'), Bosh::Director::IpAddrOrCidr.new('192.168.1.12'), Bosh::Director::IpAddrOrCidr.new('192.168.2.10')]) + expect(network_plans['b']).to match_array([Bosh::Director::IpAddrOrCidr.new('10.10.1.10'), Bosh::Director::IpAddrOrCidr.new('10.10.1.11'), Bosh::Director::IpAddrOrCidr.new('10.10.2.10'), Bosh::Director::IpAddrOrCidr.new('10.10.2.11')]) + expect(network_plans['c']).to match_array([Bosh::Director::IpAddrOrCidr.new('172.16.1.10'), Bosh::Director::IpAddrOrCidr.new('172.16.2.12'), Bosh::Director::IpAddrOrCidr.new('172.16.2.10'), Bosh::Director::IpAddrOrCidr.new('172.16.2.11')]) + expect(network_plans['d']).to match_array([Bosh::Director::IpAddrOrCidr.new('64.8.1.10'), Bosh::Director::IpAddrOrCidr.new('64.8.2.10'), Bosh::Director::IpAddrOrCidr.new('64.8.3.10'), Bosh::Director::IpAddrOrCidr.new('64.8.3.11')]) expect(new_instance_plans.map(&:desired_instance).map(&:az).map(&:name)).to match_array(%w[z1 z2 z3 z4]) end @@ -361,8 +361,29 @@ def make_subnet_spec(range, static_ips, zone_names) let(:static_ips) { ['192.168.1.10', '192.168.2.10'] } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32']), + ] + end + + it 'reuses existing instances' do + expect(new_instance_plans).to eq([]) + expect(obsolete_instance_plans).to eq([]) + expect(existing_instance_plans.size).to eq(2) + expect(existing_instance_plans[0].desired_instance.az.name).to eq('zone1') + expect(existing_instance_plans[0].network_plans.map(&:reservation).map(&:ip)).to eq([ip_to_i('192.168.1.10')]) + expect(existing_instance_plans[1].desired_instance.az.name).to eq('zone2') + expect(existing_instance_plans[1].network_plans.map(&:reservation).map(&:ip)).to eq([ip_to_i('192.168.2.10')]) + end + end + + context 'when all existing instances match static IPs and AZs but the ips are still stored as integer format' do + let(:desired_instance_count) { 2 } + let(:static_ips) { ['192.168.1.10', '192.168.2.10'] } + let(:existing_instances) do + [ + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32']), ] end @@ -382,8 +403,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:static_ips) { ['192.168.1.10', '192.168.2.10'] } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32']), ] end let(:instance_group_availability_zones) { ['zone1'] } @@ -408,8 +429,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:static_ips) { ['192.168.1.14', '192.168.2.14'] } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32']), ] end let(:instance_group_availability_zones) { %w[zone1 zone2] } @@ -487,7 +508,7 @@ def make_subnet_spec(range, static_ips, zone_names) end let(:new_subnet_azs) { %w[zone2 zone1] } let(:static_ips) { ['192.168.1.10'] } - let(:existing_instances) { [existing_instance_with_az_and_ips('zone1', ['192.168.1.10'])] } + let(:existing_instances) { [existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32'])] } it 'reuses AZ that existing instance with static IP belongs to' do expect(new_instance_plans).to eq([]) @@ -518,8 +539,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:static_ips) { ['192.168.1.10', '192.168.1.11', '192.168.1.12', '192.168.1.13'] } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10']), - existing_instance_with_az_and_ips('zone1', ['192.168.1.12']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.12/32']), ] end it 'should distribute the instances across the azs taking into account the existing instances' do @@ -541,8 +562,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:static_ips) { ['192.168.1.10', '192.168.2.11'] } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32']), ] end @@ -577,10 +598,10 @@ def make_subnet_spec(range, static_ips, zone_names) context 'when all existing instances match specified static ips' do let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '10.10.1.10']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10', '10.10.2.10']), - existing_instance_with_az_and_ips('zone1', ['192.168.1.11', '10.10.1.11']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.11', '10.10.2.11']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '10.10.1.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32', '10.10.2.10/32']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.11/32', '10.10.1.11/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.11/32', '10.10.2.11/32']), ] end @@ -614,10 +635,10 @@ def make_subnet_spec(range, static_ips, zone_names) context 'when some existing instances have IPs that are different from the instance group static IPs' do let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '10.10.1.10']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.14', '10.10.2.14']), - existing_instance_with_az_and_ips('zone1', ['192.168.1.14', '10.10.1.14']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.11', '10.10.2.11']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '10.10.1.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.14/32', '10.10.2.14/32']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.14/32', '10.10.1.14/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.11/32', '10.10.2.11/32']), ] end @@ -655,7 +676,7 @@ def make_subnet_spec(range, static_ips, zone_names) let(:desired_instance_count) { 1 } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '10.10.2.10']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '10.10.2.10/32']), ] end let(:a_static_ips) { ['192.168.1.10'] } @@ -676,8 +697,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:desired_instance_count) { 3 } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '10.10.1.10']), - existing_instance_with_az_and_ips('zone1', ['192.168.1.11', '10.10.1.11']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '10.10.1.10/32']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.11/32', '10.10.1.11/32']), ] end let(:a_static_ips) { ['192.168.1.10 - 192.168.1.11', '192.168.2.10'] } @@ -698,9 +719,9 @@ def make_subnet_spec(range, static_ips, zone_names) let(:desired_instance_count) { 2 } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '10.10.1.10']), - existing_instance_with_az_and_ips('zone1', ['192.168.1.11', '10.10.1.11']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10', '10.10.2.10']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '10.10.1.10/32']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.11/32', '10.10.1.11/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32', '10.10.2.10/32']), ] end let(:a_static_ips) { ['192.168.1.10', '192.168.2.10'] } @@ -725,8 +746,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '10.10.2.10']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10', '10.10.2.11']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '10.10.2.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32', '10.10.2.11/32']), ] end let(:a_static_ips) { ['192.168.1.10', '192.168.2.10'] } @@ -765,8 +786,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:desired_instance_count) { 2 } let(:existing_instances) do [ - existing_instance_with_az_and_ips(nil, ['192.168.1.10', '10.10.1.10']), - existing_instance_with_az_and_ips(nil, ['192.168.2.10', '10.10.2.11']), + existing_instance_with_az_and_ips(nil, ['192.168.1.10/32', '10.10.1.10/32']), + existing_instance_with_az_and_ips(nil, ['192.168.2.10/32', '10.10.2.11/32']), ] end let(:a_static_ips) { ['192.168.1.10', '192.168.2.10'] } @@ -780,8 +801,8 @@ def make_subnet_spec(range, static_ips, zone_names) context 'when existing instances have AZs' do let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '10.10.1.10']), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10', '10.10.2.11']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '10.10.1.10/32']), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32', '10.10.2.11/32']), ] end @@ -816,8 +837,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:desired_instance_count) { 2 } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.5.10', '10.10.5.10']), - existing_instance_with_az_and_ips('zone1', ['192.168.6.10', '10.10.6.11']), + existing_instance_with_az_and_ips('zone1', ['192.168.5.10/32', '10.10.5.10/32']), + existing_instance_with_az_and_ips('zone1', ['192.168.6.10/32', '10.10.6.11/32']), ] end let(:a_static_ips) { ['192.168.1.10', '192.168.2.10'] } @@ -848,7 +869,7 @@ def make_subnet_spec(range, static_ips, zone_names) end let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '192.168.2.10']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '192.168.2.10/32']), ] end let(:a_static_ips) { ['192.168.1.10 - 192.168.1.11'] } @@ -882,7 +903,7 @@ def make_subnet_spec(range, static_ips, zone_names) let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10', '192.168.2.10']), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32', '192.168.2.10/32']), ] end @@ -909,8 +930,8 @@ def make_subnet_spec(range, static_ips, zone_names) let(:static_ips) { ['192.168.1.10', '192.168.2.10'] } let(:existing_instances) do [ - existing_instance_with_az_and_ips('zone1', ['192.168.1.10'], 'old-network-name'), - existing_instance_with_az_and_ips('zone2', ['192.168.2.10'], 'old-network-name'), + existing_instance_with_az_and_ips('zone1', ['192.168.1.10/32'], 'old-network-name'), + existing_instance_with_az_and_ips('zone2', ['192.168.2.10/32'], 'old-network-name'), ] end @@ -952,7 +973,7 @@ def existing_instance_with_az_and_ips(az, ips, network_name = 'a') ips.each do |ip| instance.add_ip_address( FactoryBot.create(:models_ip_address, - address_str: IPAddr.new(ip).to_i.to_s, + address_str: ip, network_name: network_name, ), ) diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/steps/commit_instance_network_settings_step_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/steps/commit_instance_network_settings_step_spec.rb index a80027ed712..4d4760368e9 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/steps/commit_instance_network_settings_step_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/steps/commit_instance_network_settings_step_spec.rb @@ -14,13 +14,13 @@ module Steps ] end - let(:existing_ip_address_string) { 'existing_ip_address_string' } - let(:obsolete_ip_address_string) { 'obsolete_ip_address_string' } - let(:desired_ip_address_string) { 'desired_ip_address_string' } + let(:existing_ip_address_string) { '1.1.1.1/32' } + let(:obsolete_ip_address_string) { '2.2.2.2/32' } + let(:desired_ip_address_string) { '3.3.3.3/32' } - let(:existing_reservation) { instance_double(NetworkReservation, ip: existing_ip_address_string) } - let(:obsolete_reservation) { instance_double(NetworkReservation, ip: obsolete_ip_address_string) } - let(:desired_reservation) { instance_double(NetworkReservation, ip: desired_ip_address_string) } + let(:existing_reservation) { instance_double(NetworkReservation, ip: Bosh::Director::IpAddrOrCidr.new(existing_ip_address_string)) } + let(:obsolete_reservation) { instance_double(NetworkReservation, ip: Bosh::Director::IpAddrOrCidr.new(obsolete_ip_address_string)) } + let(:desired_reservation) { instance_double(NetworkReservation, ip: Bosh::Director::IpAddrOrCidr.new(desired_ip_address_string)) } let!(:existing_ip_address) { FactoryBot.create(:models_ip_address, address_str: existing_ip_address_string) } let!(:obsolete_ip_address) { FactoryBot.create(:models_ip_address, address_str: obsolete_ip_address_string) } diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/steps/create_vm_step_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/steps/create_vm_step_spec.rb index a42255fe256..921d10dc7c0 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/steps/create_vm_step_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/steps/create_vm_step_spec.rb @@ -146,8 +146,8 @@ module Steps end let(:reservation) do - subnet = Bosh::Director::DeploymentPlan::DynamicNetworkSubnet.new('dns', network_cloud_properties, ['az-1']) - network = Bosh::Director::DeploymentPlan::DynamicNetwork.new('name', [subnet], per_spec_logger) + subnet = Bosh::Director::DeploymentPlan::DynamicNetworkSubnet.new('dns', network_cloud_properties, ['az-1'], '32') + network = Bosh::Director::DeploymentPlan::DynamicNetwork.new('name', [subnet], '32', per_spec_logger) reservation = Bosh::Director::DesiredNetworkReservation.new_dynamic(instance_model, network) reservation end diff --git a/src/bosh-director/spec/unit/bosh/director/deployment_plan/vip_network_spec.rb b/src/bosh-director/spec/unit/bosh/director/deployment_plan/vip_network_spec.rb index 2e05982e416..393297ecb16 100644 --- a/src/bosh-director/spec/unit/bosh/director/deployment_plan/vip_network_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/deployment_plan/vip_network_spec.rb @@ -103,7 +103,7 @@ it 'returns the availability zones associated with the given ip' do network = Bosh::Director::DeploymentPlan::VipNetwork.parse(network_spec, azs, per_spec_logger) - az = network.find_az_names_for_ip(IPAddr.new('69.69.69.69').to_i) + az = network.find_az_names_for_ip(Bosh::Director::IpAddrOrCidr.new('69.69.69.69')) expect(az).to include('z1') end end diff --git a/src/bosh-director/spec/unit/bosh/director/ip_addr_or_cidr_spec.rb b/src/bosh-director/spec/unit/bosh/director/ip_addr_or_cidr_spec.rb index 9f097e31b99..ed272de954c 100644 --- a/src/bosh-director/spec/unit/bosh/director/ip_addr_or_cidr_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/ip_addr_or_cidr_spec.rb @@ -70,7 +70,7 @@ module Bosh::Director end context 'IPv6' do - let(:input) { 'fd00::/8' } + let(:input) { 'fd00:0000:0000:0000:0000:0000:0000:0000/8' } it 'returns a string representing the IP' do expect(ip_addr_or_cidr.to_cidr_s).to eq(input) diff --git a/src/bosh-director/spec/unit/bosh/director/ip_util_spec.rb b/src/bosh-director/spec/unit/bosh/director/ip_util_spec.rb index 6a917afca27..19ebf74cc3c 100644 --- a/src/bosh-director/spec/unit/bosh/director/ip_util_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/ip_util_spec.rb @@ -10,41 +10,151 @@ end describe 'each_ip' do - it 'should handle single ip' do - counter = 0 - ip_util_includer.each_ip('1.2.3.4') do |ip| - expect(ip).to eql(IPAddr.new('1.2.3.4').to_i) - counter += 1 + context 'when expanding is turned on' do + it 'should handle single ip' do + counter = 0 + ip_util_includer.each_ip('1.2.3.4') do |ip| + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.2.3.4').to_i)) + counter += 1 + end + expect(counter).to eq(1) end - expect(counter).to eq(1) - end - it 'should handle a range' do - counter = 0 - ip_util_includer.each_ip('1.0.0.0/24') do |ip| - expect(ip).to eql(IPAddr.new('1.0.0.0').to_i + counter) - counter += 1 + it 'should handle a range' do + counter = 0 + ip_util_includer.each_ip('1.0.0.0/24') do |ip| + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.0').to_i + counter)) + counter += 1 + end + expect(counter).to eq(256) + end + + it 'should handle a differently formatted range' do + counter = 0 + ip_util_includer.each_ip('1.0.0.0 - 1.0.1.0') do |ip| + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.0').to_i + counter)) + counter += 1 + end + expect(counter).to eq(257) end - expect(counter).to eq(256) end - it 'should handle a differently formatted range' do - counter = 0 - ip_util_includer.each_ip('1.0.0.0 - 1.0.1.0') do |ip| - expect(ip).to eql(IPAddr.new('1.0.0.0').to_i + counter) - counter += 1 + context 'when expanding is turned off' do + it 'should handle a range' do + counter = 0 + ip_util_includer.each_ip('1.0.0.0/24', false) do |ip| + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.0').to_i + counter)) + expect(ip.prefix).to eq(24) + counter += 1 + end + expect(counter).to eq(1) + end + + it 'formats the ips to cidr blocks' do + counter = 0 + ip_util_includer.each_ip('1.0.0.0 - 1.0.1.0', false) do |ip| + if counter == 0 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new("1.0.0.0")) + expect(ip.prefix).to eq(24) + elsif counter == 1 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new("1.0.1.0")) + expect(ip.prefix).to eq(32) + else + raise "Unexpected counter value: #{counter}" + end + + counter += 1 + end + expect(counter).to eq(2) + end + + it 'formats the ips to cidr blocks' do + counter = 0 + ip_util_includer.each_ip('1.0.0.5 - 1.0.0.98', false) do |ip| + if counter == 0 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.5').to_i)) + expect(ip.prefix).to eq(32) + elsif counter == 1 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.6').to_i)) + expect(ip.prefix).to eq(31) + elsif counter == 2 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.8').to_i)) + expect(ip.prefix).to eq(29) + elsif counter == 3 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.16').to_i)) + expect(ip.prefix).to eq(28) + elsif counter == 4 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.32').to_i)) + expect(ip.prefix).to eq(27) + elsif counter == 5 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.64').to_i)) + expect(ip.prefix).to eq(27) + elsif counter == 6 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.96').to_i)) + expect(ip.prefix).to eq(31) + elsif counter == 7 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.0.0.98').to_i)) + expect(ip.prefix).to eq(32) + else + raise "Unexpected counter value: #{counter}" + end + counter += 1 + + end + expect(counter).to eq(8) + end + + it 'formats the ips to cidr blocks for ipv6' do + counter = 0 + ip_util_includer.each_ip('2001:db8::5 - 2001:db8::e', false) do |ip| + if counter == 0 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('2001:db8::5').to_i + counter)) + expect(ip.prefix).to eq(128) + elsif counter == 1 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('2001:db8::6').to_i)) + expect(ip.prefix).to eq(127) + elsif counter == 2 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('2001:db8::8').to_i)) + expect(ip.prefix).to eq(126) + elsif counter == 3 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('2001:db8::c').to_i)) + expect(ip.prefix).to eq(127) + elsif counter == 4 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('2001:db8::e').to_i)) + expect(ip.prefix).to eq(128) + else + raise "Unexpected counter value: #{counter}" + end + counter += 1 + + end + expect(counter).to eq(5) + end + + it 'formats the ips to cidr blocks for ipv6' do + counter = 0 + ip_util_includer.each_ip('2001:db8:: - 2001:db8::ff', false) do |ip| + if counter == 0 + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('2001:db8::').to_i)) + expect(ip.prefix).to eq(120) + else + raise "Unexpected counter value: #{counter}" + end + counter += 1 + + end + expect(counter).to eq(1) end - expect(counter).to eq(257) end it 'should not accept invalid input' do - expect { ip_util_includer.each_ip('1.2.4') }.to raise_error(Bosh::Director::NetworkInvalidIpRangeFormat, /invalid address/) + expect { ip_util_includer.each_ip('1.2.4') }.to raise_error(Bosh::Director::NetworkInvalidIpRangeFormat, /Invalid IP or CIDR format/) end it 'should ignore nil values' do counter = 0 ip_util_includer.each_ip(nil) do |ip| - expect(ip).to eql(IPAddr.new('1.2.3.4').to_i) + expect(ip).to eq(Bosh::Director::IpAddrOrCidr.new(IPAddr.new('1.2.3.4').to_i)) counter += 1 end expect(counter).to eq(0) @@ -67,7 +177,7 @@ describe 'format_ip' do it 'converts integer to CIDR IP' do - expect(ip_util_includer.format_ip(168427582)).to eq('10.10.0.62') + expect(ip_util_includer.format_ip(168427582)).to eq('10.10.0.62/32') end end diff --git a/src/bosh-director/spec/unit/bosh/director/jobs/vm_state_spec.rb b/src/bosh-director/spec/unit/bosh/director/jobs/vm_state_spec.rb index 36f54d641b6..22604f14b64 100644 --- a/src/bosh-director/spec/unit/bosh/director/jobs/vm_state_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/jobs/vm_state_spec.rb @@ -55,7 +55,7 @@ def stub_agent_get_state_to_return_state_with_vitals FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: vm.id, - address_str: IPAddr.new('1.1.1.1').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.1').to_cidr_s, task_id: '12345', ) expect(agent).to receive(:get_state).with('full').and_return( @@ -82,13 +82,13 @@ def stub_agent_get_state_to_return_state_with_vitals FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: vm.id, - address_str: IPAddr.new('1.1.1.1').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.1').to_cidr_s, task_id: '12345', ) FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: vm.id, - address_str: IPAddr.new('2.2.2.2').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('2.2.2.2').to_cidr_s, task_id: '12345', ) end @@ -104,7 +104,7 @@ def stub_agent_get_state_to_return_state_with_vitals context "when 'ip_addresses' is empty for instance" do before do - vm.network_spec = { 'a' => { 'ip' => '3.3.3.3' }, 'b' => { 'ip' => '4.4.4.4' } } + vm.network_spec = { 'a' => { 'ip' => '3.3.3.3/32' }, 'b' => { 'ip' => '4.4.4.4/32' } } vm.save instance.spec = { 'networks' => { 'a' => { 'ip' => '1.1.1.1' }, 'b' => { 'ip' => '2.2.2.2' } } } instance.save @@ -125,19 +125,19 @@ def stub_agent_get_state_to_return_state_with_vitals FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: vm.id, - address_str: IPAddr.new('1.1.1.1').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.1').to_cidr_s, task_id: '12345', ) FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: vm.id, - address_str: IPAddr.new('2.2.2.2').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('2.2.2.2').to_cidr_s, task_id: '12345', ) vm.network_spec = { - 'a' => { 'ip' => '3.3.3.3' }, - 'b' => { 'ip' => '4.4.4.4' }, + 'a' => { 'ip' => '3.3.3.3/32' }, + 'b' => { 'ip' => '4.4.4.4/32' }, } vm.save @@ -168,7 +168,7 @@ def stub_agent_get_state_to_return_state_with_vitals FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: vm.id, - address_str: IPAddr.new('1.1.1.1').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.1').to_cidr_s, task_id: '12345', ) stub_agent_get_state_to_return_state_with_vitals @@ -359,7 +359,7 @@ def stub_agent_get_state_to_return_state_with_vitals FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: vm.id, - address_str: IPAddr.new('1.1.1.1').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.1').to_cidr_s, task_id: '12345', ) instance.update(spec: { 'vm_type' => { 'name' => 'fake-vm-type', 'cloud_properties' => {} } }) @@ -440,13 +440,13 @@ def stub_agent_get_state_to_return_state_with_vitals FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: vm.id, - address_str: IPAddr.new('1.1.1.1').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.1').to_cidr_s, task_id: '12345', ) FactoryBot.create(:models_ip_address, instance_id: instance.id, vm_id: inactive_vm.id, - address_str: IPAddr.new('1.1.1.2').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.2').to_cidr_s, task_id: '12345', ) allow(AgentClient).to receive(:with_agent_id).with('other_agent_id', anything, timeout: 5).and_return(lazy_agent) diff --git a/src/bosh-director/spec/unit/bosh/director/models/ip_address_spec.rb b/src/bosh-director/spec/unit/bosh/director/models/ip_address_spec.rb index 6531d13e5e5..968d0cb9375 100644 --- a/src/bosh-director/spec/unit/bosh/director/models/ip_address_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/models/ip_address_spec.rb @@ -5,7 +5,7 @@ module Bosh::Director::Models subject(:ip_address) do IpAddress.new(instance: instance, network_name: 'foonetwork', - address_str: IPAddr.new('10.10.0.1').to_i.to_s, + address_str: Bosh::Director::IpAddrOrCidr.new('10.10.0.1/32').to_cidr_s, task_id: 'fake-task-id', static: true, vm: vm) @@ -16,7 +16,7 @@ module Bosh::Director::Models context '#info' do it 'should display debugging information (job, index, network name and ip address)' do - expect(ip_address.info).to eq('foodeployment.foojob/1 - foonetwork - 10.10.0.1 (static)') + expect(ip_address.info).to eq('foodeployment.foojob/1 - foonetwork - 10.10.0.1/32 (static)') end end diff --git a/src/bosh-director/spec/unit/bosh/director/models/vm_spec.rb b/src/bosh-director/spec/unit/bosh/director/models/vm_spec.rb index b7c5a11f6ba..a3b59acf849 100644 --- a/src/bosh-director/spec/unit/bosh/director/models/vm_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/models/vm_spec.rb @@ -39,11 +39,11 @@ module Bosh::Director::Models end describe '#ips' do - let!(:ip_address) { FactoryBot.create(:models_ip_address, vm: vm, address_str: IPAddr.new('1.1.1.1').to_i.to_s) } - let!(:ip_address2) { FactoryBot.create(:models_ip_address, vm: vm, address_str: IPAddr.new('1.1.1.2').to_i.to_s) } + let!(:ip_address) { FactoryBot.create(:models_ip_address, vm: vm, address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.1/32').to_cidr_s) } + let!(:ip_address2) { FactoryBot.create(:models_ip_address, vm: vm, address_str: Bosh::Director::IpAddrOrCidr.new('1.1.1.2/32').to_cidr_s) } before do - vm.network_spec = { 'some' => { 'ip' => '1.1.1.3' } } + vm.network_spec = { 'some' => { 'ip' => '1.1.1.3/32' } } end it 'returns all ips for the vm' do diff --git a/src/bosh-director/spec/unit/bosh/director/orphaned_vm_deleter_spec.rb b/src/bosh-director/spec/unit/bosh/director/orphaned_vm_deleter_spec.rb index e3d74802c8a..8fda2239eac 100644 --- a/src/bosh-director/spec/unit/bosh/director/orphaned_vm_deleter_spec.rb +++ b/src/bosh-director/spec/unit/bosh/director/orphaned_vm_deleter_spec.rb @@ -20,7 +20,7 @@ module Director Bosh::Director::Models::IpAddress.create( orphaned_vm: orphaned_vm1, network_name: 'my-manual-network', - address_str: IPAddr.new('127.0.0.2').to_i, + address_str: Bosh::Director::IpAddrOrCidr.new('127.0.0.2/32').to_cidr_s, task_id: 1, ) end @@ -38,7 +38,7 @@ module Director Bosh::Director::Models::IpAddress.create( orphaned_vm: orphaned_vm2, network_name: 'my-manual-network', - address_str: IPAddr.new('127.0.0.1').to_i, + address_str: Bosh::Director::IpAddrOrCidr.new('127.0.0.1/32').to_cidr_s, task_id: 1, ) end