class Bosh::Deployer::InstanceManager

Constants

CONNECTION_EXCEPTIONS
DEPLOYMENTS_FILE

Attributes

config[R]
deployments_state[R]
infrastructure[R]
renderer[RW]

Public Class Methods

create(config) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 28
def self.create(config)
  err 'No cloud properties defined' if config['cloud'].nil?
  err 'No cloud plugin defined' if config['cloud']['plugin'].nil?

  plugin_name = config['cloud']['plugin']

  begin
    require "bosh/deployer/instance_manager/#{plugin_name}"
  rescue LoadError
    err "Could not find Provider Plugin: #{plugin_name}"
  end

  config_sha1 = Bosh::Deployer::HashFingerprinter.new.sha1(config)
  ui_messager = Bosh::Deployer::UiMessager.for_deployer

  new(Config.configure(config), config_sha1, ui_messager, plugin_name)
end
new(config, config_sha1, ui_messager, plugin_name) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 46
def initialize(config, config_sha1, ui_messager, plugin_name)
  @config = config

  plugin_class = InstanceManager.const_get(plugin_name.capitalize)
  @infrastructure = plugin_class.new(self, config, logger)

  @deployments_state = DeploymentsState.load_from_dir(config.base_dir, logger)
  deployments_state.load_deployment(config.name)

  config.uuid = state.uuid

  @config_sha1 = config_sha1
  @ui_messager = ui_messager
  @renderer = LoggerRenderer.new(logger)
end

Public Instance Methods

agent() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 402
def agent
  uri = URI.parse(config.agent_url)
  user, password = uri.userinfo.split(':', 2)
  uri.userinfo = nil
  uri.host = client_services_ip
  Bosh::Agent::HTTPClient.new(uri.to_s, {
    'user' => user,
    'password' => password,
    'reply_to' => config.uuid,
  })
end
apply(spec = nil) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 377
def apply(spec = nil)
  agent_stop

  spec ||= @apply_spec

  step 'Applying micro BOSH spec' do
    # first update spec with infrastructure specific stuff
    infrastructure.update_spec(spec)

    # then update spec with generic changes
    spec = spec.update(agent_services_ip, internal_services_ip)

    microbosh_instance = MicroboshJobInstance.new(client_services_ip, config.agent_url, logger)
    spec = microbosh_instance.render_templates(spec)

    agent.run_task(:apply, spec)
  end

  agent_start
end
attach_disk(disk_cid, is_create = false) click to toggle source

it is up to the caller to save/update disk state info

# File lib/bosh/deployer/instance_manager.rb, line 313
def attach_disk(disk_cid, is_create = false)
  return unless disk_cid

  cloud.attach_disk(state.vm_cid, disk_cid)
  mount_disk(disk_cid)
end
attach_missing_disk() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 331
def attach_missing_disk
  if state.disk_cid
    attach_disk(state.disk_cid, true)
  end
end
check_persistent_disk() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 337
def check_persistent_disk
  return if state.disk_cid.nil?
  agent_disk_cid = disk_info.first
  if agent_disk_cid != state.disk_cid
    err "instance #{state.vm_cid} has invalid disk: " +
          "Agent reports #{agent_disk_cid} while " +
          "deployer's record shows #{state.disk_cid}"
  end
end
create(stemcell_tgz, stemcell_archive) click to toggle source

rubocop:disable MethodLength

# File lib/bosh/deployer/instance_manager.rb, line 110
def create(stemcell_tgz, stemcell_archive)
  err "VM #{state.vm_cid} already exists" if state.vm_cid
  if state.stemcell_cid && state.stemcell_cid != state.stemcell_name
    err "stemcell #{state.stemcell_cid} already exists"
  end

  renderer.enter_stage('Deploy Micro BOSH', 11)

  state.stemcell_cid = create_stemcell(stemcell_tgz)
  state.stemcell_name = File.basename(stemcell_tgz, '.tgz')
  save_state

  step "Creating VM from #{state.stemcell_cid}" do
    state.vm_cid = create_vm(state.stemcell_cid)
    update_vm_metadata(state.vm_cid, { 'Name' => state.name })
  end
  save_state

  step 'Waiting for the agent' do
    begin
      wait_until_agent_ready
    rescue *CONNECTION_EXCEPTIONS
      err 'Unable to connect to Bosh agent. Check logs for more details.'
    end
  end

  step 'Updating persistent disk' do
    update_persistent_disk
  end

  unless @apply_spec
    step 'Fetching apply spec' do
      @apply_spec = Specification.new(agent.release_apply_spec, config)
    end
  end

  apply

  step 'Waiting for the director' do
    begin
      wait_until_director_ready
    rescue *CONNECTION_EXCEPTIONS
      err 'Unable to connect to Bosh Director. Retry manually or check logs for more details.'
    end
  end

  # Capture stemcell and config sha1 here (end of the deployment)
  # to avoid locking deployer out if this deployment does not succeed
  save_fingerprints(stemcell_tgz, stemcell_archive)
end
create_deployment(stemcell_tgz, stemcell_archive) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 97
def create_deployment(stemcell_tgz, stemcell_archive)
  with_lifecycle { create(stemcell_tgz, stemcell_archive) }
end
create_disk() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 279
def create_disk
  step 'Create disk' do
    size = config.resources['persistent_disk']
    state.disk_cid = cloud.create_disk(size, persistent_disk_cloud_properties, state.vm_cid)
    save_state
  end
end
create_stemcell(stemcell_tgz) click to toggle source

rubocop:disable MethodLength

# File lib/bosh/deployer/instance_manager.rb, line 205
def create_stemcell(stemcell_tgz)
  unless File.extname(stemcell_tgz) == '.tgz'
    step 'Using existing stemcell' do
    end

    return stemcell_tgz
  end

  Dir.mktmpdir('sc-') do |stemcell|
    step 'Unpacking stemcell' do
      run_command("tar -zxf #{stemcell_tgz} -C #{stemcell}")
    end

    @apply_spec = Specification.load_from_stemcell(stemcell, config)

    # load properties from stemcell manifest
    properties = load_stemcell_manifest(stemcell)

    # override with values from the deployment manifest
    override = config.cloud_options['properties']['stemcell']
    properties['cloud_properties'].merge!(override) if override

    step 'Uploading stemcell' do
      cloud.create_stemcell("#{stemcell}/image", properties['cloud_properties'])
    end
  end
rescue => e
  logger.err("create stemcell failed: #{e.message}:\n#{e.backtrace.join("\n")}")
  # make sure we clean up the stemcell if something goes wrong
  delete_stemcell if File.extname(stemcell_tgz) == '.tgz' && state.stemcell_cid
  raise e
end
create_vm(stemcell_cid) click to toggle source

rubocop:enable MethodLength

# File lib/bosh/deployer/instance_manager.rb, line 239
def create_vm(stemcell_cid)
  resources = config.resources['cloud_properties']
  networks = config.networks
  env = config.env
  cloud.create_vm(state.uuid, stemcell_cid, resources, networks, [], env)
end
delete_deployment() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 105
def delete_deployment
  with_lifecycle { destroy }
end
delete_disk(disk_cid, vm_cid) click to toggle source

it is up to the caller to save/update disk state info

# File lib/bosh/deployer/instance_manager.rb, line 292
def delete_disk(disk_cid, vm_cid)
  unmount_disk(disk_cid)

  begin
    step 'Detach disk' do
      cloud.detach_disk(vm_cid, disk_cid) if vm_cid
    end
  rescue Bosh::Clouds::DiskNotAttached => e
    logger.info(e.inspect)
  end

  begin
    step 'Delete disk' do
      cloud.delete_disk(disk_cid)
    end
  rescue Bosh::Clouds::DiskNotFound => e
    logger.info(e.inspect)
  end
end
destroy() click to toggle source

rubocop:enable MethodLength

# File lib/bosh/deployer/instance_manager.rb, line 162
def destroy
  renderer.enter_stage('Delete micro BOSH', 7)
  agent_stop
  if state.disk_cid
    step "Deleting persistent disk '#{state.disk_cid}'" do
      delete_disk(state.disk_cid, state.vm_cid)
      state.disk_cid = nil
      save_state
    end
  end
  delete_vm
  delete_stemcell
end
detach_disk(disk_cid) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 320
def detach_disk(disk_cid)
  unless disk_cid
    err 'Error: nil value given for persistent disk id'
  end

  unmount_disk(disk_cid)
  step 'Detach disk' do
    cloud.detach_disk(state.vm_cid, disk_cid)
  end
end
disk_info() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 274
def disk_info
  return @disk_list if @disk_list
  @disk_list = agent.list_disk
end
migrate_disk(src_disk_cid, dst_disk_cid) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 268
def migrate_disk(src_disk_cid, dst_disk_cid)
  step 'Migrate disk' do
    agent.run_task(:migrate_disk, src_disk_cid.to_s, dst_disk_cid.to_s)
  end
end
mount_disk(disk_cid) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 252
def mount_disk(disk_cid)
  step 'Mount disk' do
    agent.run_task(:mount_disk, disk_cid.to_s)
  end
end
persistent_disk_cloud_properties() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 287
def persistent_disk_cloud_properties
  config.resources.fetch('persistent_disk_cloud_properties', {})
end
save_state() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 398
def save_state
  deployments_state.save(infrastructure)
end
step(task) { || ... } click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 83
def step(task)
  renderer.update(:started, task)
  result = yield
  renderer.update(:finished, task)
  result
end
unmount_disk(disk_cid) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 258
def unmount_disk(disk_cid)
  step 'Unmount disk' do
    if disk_info.include?(disk_cid)
      agent.run_task(:unmount_disk, disk_cid.to_s)
    else
      logger.error("not unmounting #{disk_cid} as it doesn't belong to me: #{disk_info}")
    end
  end
end
update(stemcell_tgz, stemcell_archive) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 176
def update(stemcell_tgz, stemcell_archive)
  result, message = has_pending_changes?(state, stemcell_tgz, stemcell_archive)
  @ui_messager.info(message)
  return unless result

  renderer.enter_stage('Prepare for update', 5)

  # Reset stemcell and config sha1 before deploying
  # to make sure that if any step in current deploy fails
  # subsequent redeploys will not be skipped because sha1s matched
  reset_saved_fingerprints

  if state.vm_cid
    agent_stop
    detach_disk(state.disk_cid)
    delete_vm
  end

  # Do we always want to delete the stemcell?
  # What if we are redeploying to the same stemcell version just so
  # we can upgrade to a bigger persistent disk.
  if state.stemcell_cid
    delete_stemcell
  end

  create(stemcell_tgz, stemcell_archive)
end
update_deployment(stemcell_tgz, stemcell_archive) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 101
def update_deployment(stemcell_tgz, stemcell_archive)
  with_lifecycle { update(stemcell_tgz, stemcell_archive) }
end
update_persistent_disk() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 347
def update_persistent_disk
  attach_missing_disk
  check_persistent_disk

  if state.disk_cid.nil?
    create_disk
    attach_disk(state.disk_cid, true)
  elsif infrastructure.persistent_disk_changed?
    size = config.resources['persistent_disk']

    # save a reference to the old disk
    old_disk_cid = state.disk_cid

    # create a new disk and attach it
    new_disk_cid = cloud.create_disk(size, persistent_disk_cloud_properties, state.vm_cid)
    attach_disk(new_disk_cid, true)

    # migrate data (which mounts the disks)
    migrate_disk(old_disk_cid, new_disk_cid)

    # replace the old with the new in the state file
    state.disk_cid = new_disk_cid

    # delete the old disk
    delete_disk(old_disk_cid, state.vm_cid)
  end
ensure
  save_state
end
update_vm_metadata(vm, metadata) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 246
def update_vm_metadata(vm, metadata)
  cloud.set_vm_metadata(vm, metadata) if cloud.respond_to?(:set_vm_metadata)
rescue Bosh::Clouds::NotImplemented => e
  logger.error(e)
end
with_lifecycle() { || ... } click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 90
def with_lifecycle
  infrastructure.start
  yield
ensure
  infrastructure.stop
end

Private Instance Methods

agent_port() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 447
def agent_port
  URI.parse(config.cloud_options['properties']['agent']['mbus']).port
end
agent_start() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 428
def agent_start
  step 'Starting agent services' do
    agent.run_task(:start)
  end
end
agent_stop() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 418
def agent_stop
  step 'Stopping agent services' do
    begin
      agent.run_task(:stop)
    rescue => e
      logger.info(e.inspect)
    end
  end
end
delete_stemcell() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 475
def delete_stemcell
  err 'Cannot find existing stemcell' unless state.stemcell_cid

  if state.stemcell_cid == state.stemcell_name
    step('Preserving stemcell') { }
  else
    step 'Delete stemcell' do
      cloud.delete_stemcell(state.stemcell_cid)
    end
  end

  state.stemcell_cid = nil
  state.stemcell_name = nil
  save_state
end
delete_vm() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 491
def delete_vm
  err 'Cannot find existing VM' unless state.vm_cid
  step('Delete VM') { cloud.delete_vm(state.vm_cid) }
  state.vm_cid = nil
  save_state
end
has_pending_changes?(state, stemcell_tgz, stemcell_archive) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 524
def has_pending_changes?(state, stemcell_tgz, stemcell_archive)
  # If stemcell_archive is not provided
  # it means that there is no file on the file system
  # but rather we have a unique stemcell identifier (e.g. ami id)
  if stemcell_archive
    if state.stemcell_sha1 != stemcell_archive.sha1
      return [true, :update_stemcell_changed]
    end
  elsif stemcell_tgz
    if state.stemcell_sha1 != stemcell_tgz
      return [true, :update_stemcell_changed]
    end
  else
    return [true, :update_stemcell_unknown]
  end

  if state.config_sha1 != @config_sha1
    return [true, :update_config_changed]
  end

  [false, :update_no_changes]
end
load_apply_spec(dir) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 498
def load_apply_spec(dir)
  load_spec("#{dir}/apply_spec.yml") do
    err "this isn't a micro bosh stemcell - apply_spec.yml missing"
  end
end
load_spec(file) { || ... } click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 510
def load_spec(file)
  yield unless File.exist?(file)
  logger.info("Loading yaml from #{file}")
  Psych.load_file(file)
end
load_stemcell_manifest(dir) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 504
def load_stemcell_manifest(dir)
  load_spec("#{dir}/stemcell.MF") do
    err "this isn't a stemcell - stemcell.MF missing"
  end
end
reset_saved_fingerprints() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 547
def reset_saved_fingerprints
  state.stemcell_sha1 = nil
  state.config_sha1 = nil
  save_state
end
run_command(command) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 516
def run_command(command)
  output, status = Open3.capture2e(command)
  if status.exitstatus != 0
    $stderr.puts output
    err "'#{command}' failed with exit status=#{status.exitstatus} [#{output}]"
  end
end
save_fingerprints(stemcell_tgz, stemcell_archive) click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 553
def save_fingerprints(stemcell_tgz, stemcell_archive)
  state.stemcell_sha1 = stemcell_archive ? stemcell_archive.sha1 : stemcell_tgz
  state.config_sha1 = @config_sha1
  save_state
end
wait_until_agent_ready() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 451
def wait_until_agent_ready
  infrastructure.remote_tunnel
  wait_until_ready('agent') { agent.ping }
end
wait_until_director_ready() click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 456
def wait_until_director_ready
  port = @apply_spec.director_port
  url = "https://#{client_services_ip}:#{port}/info"

  wait_until_ready('director', 1, 600) do

    http_client = HTTPClient.new

    http_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
    http_client.ssl_config.verify_callback = proc {}

    response = http_client.get(url)
    message = 'Nginx has started but the application it is proxying to has not started yet.'
    raise DirectorGatewayError.new(message) if response.status == 502 || response.status == 503
    info = Yajl::Parser.parse(response.body)
    logger.info("Director is ready: #{info.inspect}")
  end
end
wait_until_ready(component, wait_time = 1, retries = 300) { || ... } click to toggle source
# File lib/bosh/deployer/instance_manager.rb, line 434
def wait_until_ready(component, wait_time = 1, retries = 300)
  retry_options = {
    sleep: wait_time,
    tries: retries,
    on: CONNECTION_EXCEPTIONS,
  }
  Bosh::Common.retryable(retry_options) do |tries, e|
    logger.debug("Waiting for #{component} to be ready: #{e.inspect}") if tries > 0
    yield
    true
  end
end