class Bosh::Director::DeploymentPlan::PlacementPlanner::StaticIpsAvailabilityZonePicker

Public Class Methods

new(instance_plan_factory, network_planner, job_networks, job_name, desired_azs, logger) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 8
def initialize(instance_plan_factory, network_planner, job_networks, job_name, desired_azs, logger)
  @instance_plan_factory = instance_plan_factory
  @network_planner = network_planner
  @job_networks = job_networks
  @job_name = job_name
  @networks_to_static_ips = NetworksToStaticIps.create(@job_networks, desired_azs, job_name)
  @desired_azs = desired_azs
  @logger = logger
end

Public Instance Methods

place_and_match_in(desired_instances, existing_instance_models) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 18
def place_and_match_in(desired_instances, existing_instance_models)
  @networks_to_static_ips.validate_azs_are_declared_in_job_and_subnets(@desired_azs)
  @networks_to_static_ips.validate_ips_are_in_desired_azs(@desired_azs)
  validate_ignored_instances_networks(existing_instance_models)

  desired_instances = desired_instances.dup

  instance_plans = place_existing_instance_plans(desired_instances, existing_instance_models)
  instance_plans = place_new_instance_plans(desired_instances, instance_plans)

  if ignored_instances_are_obsolete?(instance_plans)
    raise DeploymentIgnoredInstancesModification, "In instance group '#{@job_name}', an attempt was made to remove a static ip"+
        ' that is used by an ignored instance. This operation is not allowed.'
  end

  instance_plans
end

Private Instance Methods

already_has_instance_plan?(existing_instance_model, instance_plans) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 182
def already_has_instance_plan?(existing_instance_model, instance_plans)
  instance_plans.map(&:existing_instance).include?(existing_instance_model)
end
assign_az_based_on_ip(desired_instance, existing_instance_model, network, ip_address) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 194
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
  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.")
  else
    raise Bosh::Director::NetworkReservationError,
      "Existing instance '#{instance_name(existing_instance_model)}' is using IP '#{format_ip(ip_address)}' in availability zone '#{existing_instance_model.availability_zone}'"
  end
  desired_instance.az = to_az(az_name)
end
create_existing_instance_plan(desired_instance, existing_instance_model) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 237
def create_existing_instance_plan(desired_instance, existing_instance_model)
  if desired_instance.nil?
    # potentially a code path that never happens. It had been sending 2 for 1 which should have EXPLODED!!!
    @instance_plan_factory.obsolete_instance_plan(existing_instance_model)
  else
    @instance_plan_factory.desired_existing_instance_plan(existing_instance_model, desired_instance)
  end
end
create_existing_instance_plan_with_az(desired_instance, existing_instance_model, network, ip_address) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 186
def create_existing_instance_plan_with_az(desired_instance, existing_instance_model, network, ip_address)
  instance_plan = create_existing_instance_plan(desired_instance, existing_instance_model)
  unless instance_plan.obsolete?
    assign_az_based_on_ip(desired_instance, existing_instance_model, network, ip_address)
  end
  instance_plan
end
create_existing_instance_plan_with_az_validation(desired_instances, instance_plans, existing_instance_model) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 206
def create_existing_instance_plan_with_az_validation(desired_instances, instance_plans, existing_instance_model)
  if desired_instances.empty?
    @logger.debug("Marking instance '#{instance_name(existing_instance_model)}' as obsolete")
    @instance_plan_factory.obsolete_instance_plan(existing_instance_model)
  else
    # we can only reuse an instance if its AZ contains enough IPs for its networks

    instance_az_name = existing_instance_model.availability_zone
    @job_networks.each do |network|
      next unless network.static?
      static_ip_to_azs = @networks_to_static_ips.find_by_network_and_az(network, instance_az_name)
      if static_ip_to_azs.nil?
        @logger.debug("Marking instance '#{instance_name(existing_instance_model)}' as obsolete, not enough IPs in instance az")
        return @instance_plan_factory.obsolete_instance_plan(existing_instance_model)
      end
    end

    # we have enough IPs to fit an instance in its AZ
    @logger.debug("Reusing instance '#{instance_name(existing_instance_model)}' with new IPs")
    desired_instance = desired_instances.shift
    desired_instance.az = to_az(instance_az_name)
    instance_plan = @instance_plan_factory.desired_existing_instance_plan(existing_instance_model, desired_instance)
    @job_networks.each do |network|
      next unless network.static?
      instance_plan.network_plans << create_network_plan_with_az(instance_plan, network, instance_plans)
    end

    instance_plan
  end
end
create_instance_plan_based_on_existing_ips(desired_instances, existing_instance_model) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 142
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
      # 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?
        desired_instance = desired_instances.shift
        instance_plan = create_existing_instance_plan_with_az(desired_instance, existing_instance_model, network, ip_address)
        instance_plan
      end

      if network_plan.nil? && instance_plan.desired_instance
        create_network_plan_with_ip(instance_plan, network, ip_address)
      end

      if instance_plan.desired_instance.nil?
        # delete so that other instances not reusing ips of existing instance
        # obsolete instances should not affect distribution
        @networks_to_static_ips.delete(ip_address)
      else
        # put ip in az where existing instance is so that
        # during distribution it will be taken into account
        @networks_to_static_ips.claim_in_az(ip_address, instance_plan.desired_instance.availability_zone)
      end
    end
  end

  instance_plan
end
create_network_plan_with_az(instance_plan, network, instance_plans) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 115
def create_network_plan_with_az(instance_plan, network, instance_plans)
  desired_instance = instance_plan.desired_instance
  instance = instance_plan.instance
  if desired_instance.az.nil?
    static_ip_to_azs = @networks_to_static_ips.next_ip_for_network(network)
    if static_ip_to_azs.az_names.size == 1
      az_name = static_ip_to_azs.az_names.first
      @logger.debug("Assigning az '#{az_name}' to instance '#{instance}'")
    else
      az_name = find_az_name_with_least_number_of_instances(static_ip_to_azs.az_names, instance_plans)
      @logger.debug("Assigning az '#{az_name}' to instance '#{instance}' based on least number of instances")
    end
    desired_instance.az = to_az(az_name)
  else
    static_ip_to_azs = @networks_to_static_ips.find_by_network_and_az(network, desired_instance.availability_zone)
  end
  if static_ip_to_azs.nil?
    raise Bosh::Director::NetworkReservationError,
          'Failed to distribute static IPs to satisfy existing instance reservations'
  end

  @logger.debug("Claiming IP '#{format_ip(static_ip_to_azs.ip)}' on network #{network.name} and az '#{desired_instance.availability_zone}' for instance '#{instance}'")
  @networks_to_static_ips.claim_in_az(static_ip_to_azs.ip, desired_instance.availability_zone)

  @network_planner.network_plan_with_static_reservation(instance_plan, network, static_ip_to_azs.ip)
end
create_network_plan_with_ip(instance_plan, network, ip_address) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 246
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)
  end
end
find_az_name_with_least_number_of_instances(az_names, instance_plans) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 255
def find_az_name_with_least_number_of_instances(az_names, instance_plans)
  az_names.sort_by do |az_name|
    instance_plans.select { |instance_plan| instance_plan.desired_instance.availability_zone == az_name }.size
  end.first
end
find_instance_ips_on_network(existing_instance_model, network) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 178
def find_instance_ips_on_network(existing_instance_model, network)
  existing_instance_model.ip_addresses.select { |ip_address| network.static_ips.include?(ip_address.address) }
end
ignored_instances_are_obsolete?(instance_plans) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 269
def ignored_instances_are_obsolete?(instance_plans)
  instance_plans.select{ |i| i.obsolete? && i.should_be_ignored? }.any?
end
instance_name(existing_instance_model) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 265
def instance_name(existing_instance_model)
  "#{existing_instance_model.job}/#{existing_instance_model.index}"
end
place_existing_instance_plans(desired_instances, existing_instance_models) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 63
def place_existing_instance_plans(desired_instances, existing_instance_models)
  instance_plans = []
  # create existing instance plans with network plans that use specified static IPs
  existing_instance_models.each do |existing_instance_model|
    instance_plan = create_instance_plan_based_on_existing_ips(desired_instances, existing_instance_model)
    instance_plans << instance_plan if instance_plan
  end

  # create the rest existing instance plans
  existing_instance_models.each do |existing_instance_model|
    unless already_has_instance_plan?(existing_instance_model, instance_plans)
      instance_plans << create_existing_instance_plan_with_az_validation(desired_instances, instance_plans, existing_instance_model)
    end
  end

  # fulfill missing network plans
  instance_plans.reject(&:obsolete?).each do |instance_plan|
    @job_networks.each do |network|
      unless network.static?
        instance_plan.network_plans << @network_planner.network_plan_with_dynamic_reservation(instance_plan, network)
        next
      end

      unless instance_plan.network_plan_for_network(network.deployment_network)
        instance_plan.network_plans << create_network_plan_with_az(instance_plan, network, instance_plans)
      end
    end
  end

  instance_plans
end
place_new_instance_plans(desired_instances, instance_plans) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 95
def place_new_instance_plans(desired_instances, instance_plans)
  @networks_to_static_ips.distribute_evenly_per_zone

  desired_instances.each do |desired_instance|
    instance_plan = @instance_plan_factory.desired_new_instance_plan(desired_instance)
    @job_networks.each do |network|
      unless network.static?
        instance_plan.network_plans << @network_planner.network_plan_with_dynamic_reservation(instance_plan, network)
        next
      end

      instance_plan.network_plans << create_network_plan_with_az(instance_plan, network, instance_plans)
    end

    instance_plans << instance_plan
  end

  instance_plans
end
to_az(az_name) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 261
def to_az(az_name)
  @desired_azs.to_a.find { |az| az.name == az_name }
end
validate_ignored_instances_networks(existing_instance_models) click to toggle source
# File lib/bosh/director/deployment_plan/placement_planner/static_ips_availability_zone_picker.rb, line 38
def validate_ignored_instances_networks(existing_instance_models)
  existing_instance_models.each do |existing_instance_model|
    next if !existing_instance_model.ignore

    # Validate that no networks were added or deleted
    desired_networks_names = @job_networks.map(&:name).uniq.sort
    existing_networks_names = existing_instance_model.ip_addresses.map(&:network_name).uniq.sort

    if desired_networks_names != existing_networks_names
      raise DeploymentIgnoredInstancesModification, "In instance group '#{@job_name}', which contains ignored vms,"+
          ' an attempt was made to modify the networks. This operation is not allowed.'
    end

    # Validate that no ip addresses, that were assigned to an ignored VM, have been removed
    existing_instance_model.ip_addresses.each do |ip_address|
      ignored_vm_network = @job_networks.select { |n| n.name == ip_address.network_name }.first

      if !ignored_vm_network.static_ips.include?(ip_address.address)
        raise DeploymentIgnoredInstancesModification, "In instance group '#{@job_name}', an attempt was made to remove a static ip"+
            ' that is used by an ignored instance. This operation is not allowed.'
      end
    end
  end
end