class Bosh::Director::InstanceUpdater

Constants

MAX_RECREATE_ATTEMPTS

Public Class Methods

new(cloud, logger, ip_provider, blobstore, vm_deleter, vm_creator, dns_manager, disk_manager, vm_recreator) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 30
def initialize(cloud, logger, ip_provider, blobstore, vm_deleter, vm_creator, dns_manager, disk_manager, vm_recreator)
  @cloud = cloud
  @logger = logger
  @blobstore = blobstore
  @vm_deleter = vm_deleter
  @vm_creator = vm_creator
  @dns_manager = dns_manager
  @disk_manager = disk_manager
  @ip_provider = ip_provider
  @vm_recreator = vm_recreator
  @current_state = {}
end
new_instance_updater(ip_provider) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 7
def self.new_instance_updater(ip_provider)
  logger = Config.logger
  cloud = Config.cloud
  vm_deleter = VmDeleter.new(cloud, logger, false, Config.enable_virtual_delete_vms)
  disk_manager = DiskManager.new(cloud, logger)
  job_renderer = JobRenderer.create
  arp_flusher = ArpFlusher.new
  vm_creator = VmCreator.new(cloud, logger, vm_deleter, disk_manager, job_renderer, arp_flusher)
  vm_recreator = VmRecreator.new(vm_creator, vm_deleter)
  dns_manager = DnsManagerProvider.create
  new(
    cloud,
    logger,
    ip_provider,
    App.instance.blobstores.blobstore,
    vm_deleter,
    vm_creator,
    dns_manager,
    disk_manager,
    vm_recreator
  )
end

Public Instance Methods

update(instance_plan, options = {}) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 43
def update(instance_plan, options = {})
  instance = instance_plan.instance
  action, context = get_action_and_context(instance_plan)
  parent_id = add_event(instance.deployment_model.name, action, instance.model.name, context) if instance_plan.changed?
  @logger.info("Updating instance #{instance}, changes: #{instance_plan.changes.to_a.join(', ').inspect}")

  update_procedure = lambda do
    # Optimization to only update DNS if nothing else changed.
    if dns_change_only?(instance_plan)
      @logger.debug('Only change is DNS configuration')
      update_dns(instance_plan)
      return
    end

    unless instance_plan.already_detached?
      Preparer.new(instance_plan, agent(instance), @logger).prepare

      stop(instance_plan)
      take_snapshot(instance)

      if instance.state == 'stopped'
        instance.update_state
        return
      end
    end

    if instance.state == 'detached'
      @logger.info("Detaching instance #{instance}")
      unless instance_plan.already_detached?
        @disk_manager.unmount_disk_for(instance_plan)
        instance_model = instance_plan.new? ? instance_plan.instance.model : instance_plan.existing_instance
        @vm_deleter.delete_for_instance(instance_model)
      end
      release_obsolete_ips(instance_plan)
      instance.update_state
      return
    end

    recreated = false
    if needs_recreate?(instance_plan)
      @logger.debug('Failed to update in place. Recreating VM')
      @disk_manager.unmount_disk_for(instance_plan)
      @vm_recreator.recreate_vm(instance_plan, nil)
      recreated = true
    end

    release_obsolete_ips(instance_plan)

    update_dns(instance_plan)
    @disk_manager.update_persistent_disk(instance_plan)

    unless recreated
      if instance.trusted_certs_changed?
        @logger.debug('Updating trusted certs')
        instance.update_trusted_certs
      end
    end

    cleaner = RenderedJobTemplatesCleaner.new(instance.model, @blobstore, @logger)
    state_applier = InstanceUpdater::StateApplier.new(
      instance_plan,
      agent(instance),
      cleaner,
      @logger,
      canary: options[:canary]
    )
    state_applier.apply(instance_plan.desired_instance.job.update)
  end
  InstanceUpdater::InstanceState.with_instance_update_and_event_creation(instance.model, parent_id, instance.deployment_model.name, action, &update_procedure)
end

Private Instance Methods

add_event(deployment_name, action, instance_name = nil, context = nil, parent_id = nil, error = nil) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 116
def add_event(deployment_name, action, instance_name = nil, context = nil, parent_id = nil, error = nil)
  event  = Config.current_job.event_manager.create_event(
      {
          parent_id:   parent_id,
          user:        Config.current_job.username,
          action:      action,
          object_type: 'instance',
          object_name: instance_name,
          task:        Config.current_job.task_id,
          deployment:  deployment_name,
          instance:    instance_name,
          error:       error,
          context:     context ? context: {}
      })
  event.id
end
agent(instance) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 213
def agent(instance)
  AgentClient.with_vm_credentials_and_agent_id(instance.model.credentials, instance.model.agent_id)
end
dns_change_only?(instance_plan) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 188
def dns_change_only?(instance_plan)
  instance_plan.changes.include?(:dns) && instance_plan.changes.size == 1
end
get_action_and_context(instance_plan) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 133
def get_action_and_context(instance_plan)
  changes = instance_plan.changes
  context = {}
  if changes.size == 1 && [:state, :restart].include?(changes.first)
    action = case instance_plan.instance.virtual_state
      when 'started'
        'start'
      when 'stopped'
        'stop'
      when 'detached'
        'stop'
      else
        instance_plan.instance.virtual_state
    end
  else
    context['az'] = instance_plan.desired_az_name if instance_plan.desired_az_name
    if instance_plan.new?
      action = 'create'
    else
      context['changes'] = changes.to_a unless changes.size == 1 && changes.first == :recreate
      action = needs_recreate?(instance_plan) ? 'recreate' : 'update'
    end
  end
  return action, context
end
needs_recreate?(instance_plan) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 192
def needs_recreate?(instance_plan)
  instance = instance_plan.instance

  if instance_plan.needs_shutting_down?
    @logger.debug('VM needs to be shutdown before it can be updated.')
    return true
  end

  if instance.cloud_properties_changed?
    @logger.debug('Cloud Properties have changed. Recreating VM')
    return true
  end

  if instance_plan.networks_changed?
    @logger.debug('Networks have changed. Recreating VM')
    return true
  end

  false
end
release_obsolete_ips(instance_plan) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 159
def release_obsolete_ips(instance_plan)
  instance_plan.network_plans
    .select(&:obsolete?)
    .each do |network_plan|
    reservation = network_plan.reservation
    @ip_provider.release(reservation)
  end
  instance_plan.release_obsolete_network_plans
end
stop(instance_plan) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 169
def stop(instance_plan)
  instance = instance_plan.instance
  stopper = Stopper.new(instance_plan, instance.state, Config, @logger)
  stopper.stop
end
take_snapshot(instance) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 175
def take_snapshot(instance)
  Api::SnapshotManager.take_snapshot(instance.model, clean: true)
end
update_dns(instance_plan) click to toggle source
# File lib/bosh/director/instance_updater.rb, line 179
def update_dns(instance_plan)
  instance = instance_plan.instance

  return unless instance_plan.dns_changed?

  @dns_manager.update_dns_record_for_instance(instance.model, instance_plan.network_settings.dns_record_info)
  @dns_manager.flush_dns_cache
end