class Bosh::Director::DiskManager

Public Class Methods

new(cloud, logger) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 4
def initialize(cloud, logger)
  @cloud = cloud
  @logger = logger
  @transactor = Transactor.new
end

Public Instance Methods

attach_disk(instance_model) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 139
def attach_disk(instance_model)
  disk_cid = instance_model.persistent_disk_cid
  return @logger.info('Skipping disk attaching') if disk_cid.nil?

  begin
    @cloud.attach_disk(instance_model.vm_cid, disk_cid)
    agent_client(instance_model).mount_disk(disk_cid)
  rescue => e
    @logger.warn("Failed to attach disk to new VM: #{e.inspect}")
    raise e
  end
end
attach_disks_if_needed(instance_plan) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 39
def attach_disks_if_needed(instance_plan)
  unless instance_plan.needs_disk?
    @logger.warn('Skipping disk attachment, instance no longer needs disk')
    return
  end
  attach_disk(instance_plan.instance.model)
end
delete_orphan_disk(orphan_disk) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 125
def delete_orphan_disk(orphan_disk)
  begin
    orphan_disk.orphan_snapshots.each do |orphan_snapshot|
      delete_orphan_snapshot(orphan_snapshot)
    end
    @logger.info("Deleting orphan orphan disk: #{orphan_disk.disk_cid}")
    @cloud.delete_disk(orphan_disk.disk_cid)
    orphan_disk.destroy
  rescue Bosh::Clouds::DiskNotFound
    @logger.debug("Disk not found in IaaS: #{orphan_disk.disk_cid}")
    orphan_disk.destroy
  end
end
delete_orphan_disk_by_disk_cid(disk_cid) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 109
def delete_orphan_disk_by_disk_cid(disk_cid)
  @logger.info("Deleting orphan disk: #{disk_cid}")
  orphan_disk = Models::OrphanDisk.where(disk_cid: disk_cid).first
  if orphan_disk
    delete_orphan_disk(orphan_disk)
  else
    @logger.debug("Disk not found: #{disk_cid}")
  end
end
delete_persistent_disks(instance_model) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 47
def delete_persistent_disks(instance_model)
  instance_model.persistent_disks.each do |disk|
    orphan_disk(disk)
  end
end
detach_disk(instance_model, disk) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 152
def detach_disk(instance_model, disk)
  begin
    @logger.info("Detaching disk #{disk.disk_cid}")
    @cloud.detach_disk(instance_model.vm_cid, disk.disk_cid)
  rescue Bosh::Clouds::DiskNotAttached
    if disk.active
      raise CloudDiskNotAttached,
            "'#{instance_model}' VM should have persistent disk attached " +
                "but it doesn't (according to CPI)"
    end
  end
end
list_orphan_disks() click to toggle source
# File lib/bosh/director/disk_manager.rb, line 95
def list_orphan_disks
  Models::OrphanDisk.all.map do |disk|
    {
      'disk_cid' => disk.disk_cid,
      'size' => disk.size,
      'az' => disk.availability_zone,
      'deployment_name' => disk.deployment_name,
      'instance_name' => disk.instance_name,
      'cloud_properties' => disk.cloud_properties,
      'orphaned_at' => disk.created_at.to_s
    }
  end
end
orphan_disk(disk) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 53
def orphan_disk(disk)
  @transactor.retryable_transaction(Bosh::Director::Config.db) do
    begin
      parent_id = add_event('delete', disk.instance.deployment.name, "#{disk.instance.job}/#{disk.instance.uuid}", disk.disk_cid)
      orphan_disk = Models::OrphanDisk.create(
          disk_cid:          disk.disk_cid,
          size:              disk.size,
          availability_zone: disk.instance.availability_zone,
          deployment_name:   disk.instance.deployment.name,
          instance_name:     "#{disk.instance.job}/#{disk.instance.uuid}",
          cloud_properties:  disk.cloud_properties
      )

      orphan_snapshots(disk.snapshots, orphan_disk)
      @logger.info("Orphaning disk: '#{disk.disk_cid}', #{disk.active ? "active" : "inactive"}")
      disk.destroy
    rescue Exception => e
      raise e
    ensure
      add_event('delete', orphan_disk.deployment_name, orphan_disk.instance_name, orphan_disk.disk_cid, parent_id, e)
    end
  end
end
unmount_disk(instance_model, disk) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 165
def unmount_disk(instance_model, disk)
  disk_cid = disk.disk_cid
  if disk_cid.nil?
    @logger.info('Skipping disk unmounting, instance does not have a disk')
    return
  end

  if agent_mounted_disks(instance_model).include?(disk_cid)
    @logger.info("Stopping instance '#{instance_model}' before unmount")
    agent_client(instance_model).stop
    @logger.info("Unmounting disk '#{disk_cid}'")
    agent_client(instance_model).unmount_disk(disk_cid)
  end
end
unmount_disk_for(instance_plan) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 119
def unmount_disk_for(instance_plan)
  disk = instance_plan.instance.model.persistent_disk
  return if disk.nil?
  unmount_disk(instance_plan.instance.model, disk)
end
unorphan_disk(disk, instance_id) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 77
def unorphan_disk(disk, instance_id)
  @transactor.retryable_transaction(Bosh::Director::Config.db) do
    new_disk = Models::PersistentDisk.create(
        disk_cid: disk.disk_cid,
        instance_id: instance_id,
        active: true,
        size: disk.size,
        cloud_properties: disk.cloud_properties)

    disk.orphan_snapshots.each do |snapshot|
      Models::Snapshot.create(persistent_disk: new_disk, snapshot_cid: snapshot.snapshot_cid, clean: snapshot.clean)
      snapshot.destroy
    end

    disk.destroy
  end
end
update_persistent_disk(instance_plan) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 10
def update_persistent_disk(instance_plan)
  @logger.info('Updating persistent disk')
  check_persistent_disk(instance_plan)

  return unless instance_plan.persistent_disk_changed?

  instance = instance_plan.instance
  old_disk = instance.model.persistent_disk

  disk = nil
  if instance_plan.needs_disk?
    disk = create_and_attach_disk(instance_plan)
    mount_and_migrate_disk(instance, disk, old_disk)
  end

  @transactor.retryable_transaction(Bosh::Director::Config.db) do
    old_disk.update(:active => false) if old_disk
    disk.update(:active => true) if disk
  end

  orphan_mounted_persistent_disk(instance.model, old_disk) if old_disk

  inactive_disks = Models::PersistentDisk.where(active: false, instance: instance.model)
  inactive_disks.each do |disk|
    detach_disk(instance.model, disk)
    orphan_disk(disk)
  end
end

Private Instance Methods

add_event(action, deployment_name, instance_name, object_name = nil, parent_id = nil, error = nil) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 182
def add_event(action, deployment_name, instance_name, object_name = 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: 'disk',
          object_name: object_name,
          deployment:  deployment_name,
          instance:    instance_name,
          task:        Config.current_job.task_id,
          error:       error
      })
  event.id
end
agent_client(instance_model) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 264
def agent_client(instance_model)
  AgentClient.with_vm_credentials_and_agent_id(instance_model.credentials, instance_model.agent_id)
end
agent_mounted_disks(instance_model) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 260
def agent_mounted_disks(instance_model)
  agent_client(instance_model).list_disk
end
check_persistent_disk(instance_plan) click to toggle source

Synchronizes persistent_disks with the agent. (Currently assumes that we only have 1 persistent disk.) @return [void]

# File lib/bosh/director/disk_manager.rb, line 239
def check_persistent_disk(instance_plan)
  instance = instance_plan.instance
  return if instance.model.persistent_disks.empty?
  agent_disk_cid = agent_mounted_disks(instance.model).first

  if agent_disk_cid.nil? && !instance_plan.needs_disk?
    @logger.debug('Disk is already detached')
  elsif agent_disk_cid != instance.model.persistent_disk_cid
    raise AgentDiskOutOfSync,
      "'#{instance}' has invalid disks: agent reports " +
        "'#{agent_disk_cid}' while director record shows " +
        "'#{instance.model.persistent_disk_cid}'"
  end

  instance.model.persistent_disks.each do |disk|
    unless disk.active
      @logger.warn("'#{instance}' has inactive disk #{disk.disk_cid}")
    end
  end
end
create_and_attach_disk(instance_plan) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 268
def create_and_attach_disk(instance_plan)
  instance = instance_plan.instance
  disk = create_disk(instance_plan)
  @cloud.attach_disk(instance.model.vm_cid, disk.disk_cid)
  disk
end
create_disk(instance_plan) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 287
def create_disk(instance_plan)
  job = instance_plan.desired_instance.job
  instance_model = instance_plan.instance.model
  parent_id = add_event('create', instance_model.deployment.name, "#{instance_model.job}/#{instance_model.uuid}")
  disk_size = job.persistent_disk_type.disk_size
  cloud_properties = job.persistent_disk_type.cloud_properties
  disk_cid = @cloud.create_disk(disk_size, cloud_properties, instance_model.vm_cid)

  Models::PersistentDisk.create(
    disk_cid: disk_cid,
    active: false,
    instance_id: instance_model.id,
    size: disk_size,
    cloud_properties: cloud_properties,
  )
rescue Exception => e
  raise e
ensure
  add_event('create', instance_model.deployment.name, "#{instance_model.job}/#{instance_model.uuid}", disk_cid, parent_id, e)
end
delete_orphan_snapshot(orphan_snapshot) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 211
def delete_orphan_snapshot(orphan_snapshot)
  begin
    snapshot_cid = orphan_snapshot.snapshot_cid
    @logger.info("Deleting orphan snapshot: #{snapshot_cid}")
    @cloud.delete_snapshot(snapshot_cid)
    orphan_snapshot.destroy
  rescue Bosh::Clouds::DiskNotFound
    @logger.debug("Disk not found in IaaS: #{snapshot_cid}")
    orphan_snapshot.destroy
  end
end
mount_and_migrate_disk(instance, new_disk, old_disk) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 275
def mount_and_migrate_disk(instance, new_disk, old_disk)
  agent_client = agent_client(instance.model)
  agent_client.mount_disk(new_disk.disk_cid)
  # Mirgate to and from cids are actually ignored by the agent.
  # The first mount invocation is the source, and the last mount invocation is the target.
  agent_client.migrate_disk(old_disk.disk_cid, new_disk.disk_cid) if old_disk
rescue => e
  @logger.debug("Failed to migrate disk, deleting new disk. #{e.inspect}")
  orphan_mounted_persistent_disk(instance.model, new_disk)
  raise e
end
orphan_mounted_persistent_disk(instance_model, disk) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 198
def orphan_mounted_persistent_disk(instance_model, disk)
  unmount_disk(instance_model, disk)

  disk_cid = disk.disk_cid
  if disk_cid.nil?
    @logger.info('Skipping disk detaching, instance does not have a disk')
    return
  end

  detach_disk(instance_model, disk)
  orphan_disk(disk)
end
orphan_snapshots(snapshots, orphan_disk) click to toggle source
# File lib/bosh/director/disk_manager.rb, line 223
def orphan_snapshots(snapshots, orphan_disk)
  snapshots.each do |snapshot|
    @logger.info("Orphaning snapshot: '#{snapshot.snapshot_cid}'")
    Models::OrphanSnapshot.create(
      orphan_disk: orphan_disk,
      snapshot_cid: snapshot.snapshot_cid,
      clean: snapshot.clean,
      snapshot_created_at: snapshot.created_at
    )
    snapshot.destroy
  end
end