class Bosh::Clouds::Dummy

Constants

ATTACH_DISK_SCHEMA
CREATE_DISK_SCHEMA
CREATE_STEMCELL_SCHEMA
CREATE_VM_SCHEMA
DELETE_SNAPSHOT_SCHEMA
DELETE_STEMCELL_SCHEMA
DELETE_VM_SCHEMA
DELTE_DISK_SCHEMA
DETACH_DISK_SCHEMA
HAS_DISK_SCHEMA
HAS_VM_SCHEMA
REBOOT_VM_SCHEMA
SET_VM_METADATA_SCHEMA
SNAPSHOT_DISK_SCHEMA

Attributes

commands[R]

Public Class Methods

new(options) click to toggle source
# File lib/cloud/dummy.rb, line 13
def initialize(options)
  @options = options

  @base_dir = options['dir']
  if @base_dir.nil?
    raise ArgumentError, 'Must specify dir'
  end

  @running_vms_dir = File.join(@base_dir, 'running_vms')
  @vm_repo = VMRepo.new(@running_vms_dir)
  @tmp_dir = File.join(@base_dir, 'tmp')
  FileUtils.mkdir_p(@tmp_dir)

  @logger = Logging::Logger.new('DummyCPI')
  @logger.add_appenders(Logging.appenders.io(
      'DummyCPIIO',
      options['log_buffer'] || STDOUT
    ))

  @commands = CommandTransport.new(@base_dir, @logger)
  @inputs_recorder = InputsRecorder.new(@base_dir, @logger)

  prepare
rescue Errno::EACCES
  raise ArgumentError, "cannot create dummy cloud base directory #{@base_dir}"
end

Public Instance Methods

agent_dir_for_vm_cid(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 307
def agent_dir_for_vm_cid(vm_cid)
  agent_id = agent_id_for_vm_id(vm_cid)
  agent_base_dir(agent_id)
end
agent_log_path(agent_id) click to toggle source
# File lib/cloud/dummy.rb, line 259
def agent_log_path(agent_id)
  "#{@base_dir}/agent.#{agent_id}.log"
end
all_ips() click to toggle source
# File lib/cloud/dummy.rb, line 301
def all_ips
  Dir.glob(File.join(@base_dir, 'dummy_cpi_networks', '*'))
    .reject { |path| File.directory?(path) }
    .map { |path| File.basename(path) }
end
all_snapshots() click to toggle source
# File lib/cloud/dummy.rb, line 293
def all_snapshots
  if File.exists?(snapshot_file(''))
    Dir.glob(snapshot_file('*'))
  else
    []
  end
end
all_stemcells() click to toggle source
# File lib/cloud/dummy.rb, line 275
def all_stemcells
  files = Dir.entries(@base_dir).select { |file| file.match /stemcell_./ }

  Dir.chdir(@base_dir) do
    [].tap do |results|
      files.each do |file|
        # data --> [{ 'name' => 'ubuntu-stemcell', 'version': '1', 'image' => <image path> }]
        data = YAML.load(File.read(file))
        results << { 'id' => file.sub(/^stemcell_/, '') }.merge(data)
      end
    end.sort { |a, b| a[:version].to_i <=> b[:version].to_i }
  end
end
attach_disk(vm_cid, disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 159
def attach_disk(vm_cid, disk_id)
  validate_and_record_inputs(ATTACH_DISK_SCHEMA, __method__, vm_cid, disk_id)
  if disk_attached?(disk_id)
    raise "#{disk_id} is already attached to an instance"
  end
  file = attachment_file(vm_cid, disk_id)
  FileUtils.mkdir_p(File.dirname(file))
  FileUtils.touch(file)

  @logger.info("Attached disk: '#{disk_id}' to vm: '#{vm_cid}' at attachment file: #{file}")

  agent_id = agent_id_for_vm_id(vm_cid)
  settings = read_agent_settings(agent_id)
  settings['disks']['persistent'][disk_id] = 'attached'
  write_agent_settings(agent_id, settings)
end
create_disk(size, cloud_properties, vm_locality) click to toggle source
# File lib/cloud/dummy.rb, line 191
def create_disk(size, cloud_properties, vm_locality)
  validate_and_record_inputs(CREATE_DISK_SCHEMA, __method__, size, cloud_properties, vm_locality)
  disk_id = SecureRandom.hex
  file = disk_file(disk_id)
  FileUtils.mkdir_p(File.dirname(file))
  File.write(file, size.to_s)
  disk_id
end
create_stemcell(image_path, cloud_properties) click to toggle source
# File lib/cloud/dummy.rb, line 41
def create_stemcell(image_path, cloud_properties)
  validate_and_record_inputs(CREATE_STEMCELL_SCHEMA, __method__, image_path, cloud_properties)

  content = File.read(image_path)
  data = YAML.load(content)
  data.merge!('image' => image_path)
  stemcell_id = Digest::SHA1.hexdigest(content)

  File.write(stemcell_file(stemcell_id), YAML.dump(data))
  stemcell_id
end
create_vm(agent_id, stemcell_id, cloud_properties, networks, disk_cids, env) click to toggle source

rubocop:disable ParameterLists

# File lib/cloud/dummy.rb, line 71
def create_vm(agent_id, stemcell_id, cloud_properties, networks, disk_cids, env)
  # rubocop:enable ParameterLists
  @logger.info('Dummy: create_vm')
  validate_and_record_inputs(CREATE_VM_SCHEMA, __method__, agent_id, stemcell_id, cloud_properties, networks, disk_cids, env)

  ips = []
  cmd = commands.next_create_vm_cmd

  if cmd.failed?
    raise Bosh::Clouds::CloudError.new("Creating vm failed")
  end

  networks.each do |network_name, network|
    if network['type'] != 'dynamic'
      ips << { 'network' => network_name, 'ip' => network.fetch('ip') }
    else
      if cmd.ip_address
        ip_address = cmd.ip_address
      elsif cloud_properties['az_name']
        ip_address = cmd.ip_address_for_az(cloud_properties['az_name'])
      else
        ip_address =  NetAddr::CIDRv4.new(rand(0..4294967295)).ip #collisions?
      end

      if ip_address
        ips << { 'network' => network_name, 'ip' => ip_address }
        write_agent_default_network(agent_id, ip_address)
      end
    end
  end

  allocate_ips(ips)

  write_agent_settings(agent_id, {
      agent_id: agent_id,
      blobstore: @options['agent']['blobstore'],
      ntp: [],
      disks: { persistent: {} },
      networks: networks,
      vm: { name: "vm-#{agent_id}" },
      cert: '',
      env: env,
      mbus: @options['nats'],
    })

  agent_pid = spawn_agent_process(agent_id)
  vm = VM.new(agent_pid.to_s, agent_id, cloud_properties, ips)

  @vm_repo.save(vm)

  vm.id
end
current_apply_spec_for_vm(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 316
def current_apply_spec_for_vm(vm_cid)
  agent_base_dir = agent_dir_for_vm_cid(vm_cid)
  spec_file = File.join(agent_base_dir, 'bosh', 'spec.json')
  JSON.parse(File.read(spec_file))
end
delete_disk(disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 201
def delete_disk(disk_id)
  validate_and_record_inputs(DELTE_DISK_SCHEMA, __method__, disk_id)
  FileUtils.rm(disk_file(disk_id))
end
delete_snapshot(snapshot_id) click to toggle source
# File lib/cloud/dummy.rb, line 217
def delete_snapshot(snapshot_id)
  validate_and_record_inputs(DELETE_SNAPSHOT_SCHEMA, __method__, snapshot_id)
  FileUtils.rm(snapshot_file(snapshot_id))
end
delete_stemcell(stemcell_id) click to toggle source
# File lib/cloud/dummy.rb, line 54
def delete_stemcell(stemcell_id)
  validate_and_record_inputs(DELETE_STEMCELL_SCHEMA, __method__, stemcell_id)
  FileUtils.rm(stemcell_file(stemcell_id))
end
delete_vm(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 125
def delete_vm(vm_cid)
  validate_and_record_inputs(DELETE_VM_SCHEMA, __method__, vm_cid)
  commands.wait_for_unpause_delete_vms
  detach_disks_attached_to_vm(vm_cid)
  agent_pid = vm_cid.to_i
  Process.kill('KILL', agent_pid)

    # rubocop:disable HandleExceptions
rescue Errno::ESRCH
  # rubocop:enable HandleExceptions
ensure
  free_ips(vm_cid)
  FileUtils.rm_rf(File.join(@base_dir, 'running_vms', vm_cid))
end
detach_disk(vm_cid, disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 177
def detach_disk(vm_cid, disk_id)
  validate_and_record_inputs(DETACH_DISK_SCHEMA, __method__, vm_cid, disk_id)
  unless disk_attached_to_vm?(vm_cid, disk_id)
    raise Bosh::Clouds::DiskNotAttached, "#{disk_id} is not attached to instance #{vm_cid}"
  end
  FileUtils.rm(attachment_file(vm_cid, disk_id))

  agent_id = agent_id_for_vm_id(vm_cid)
  settings = read_agent_settings(agent_id)
  settings['disks']['persistent'].delete(disk_id)
  write_agent_settings(agent_id, settings)
end
disk_attached_to_vm?(vm_cid, disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 312
def disk_attached_to_vm?(vm_cid, disk_id)
  File.exist?(attachment_file(vm_cid, disk_id))
end
disk_cids() click to toggle source
# File lib/cloud/dummy.rb, line 243
def disk_cids
  # Shuffle so that no one relies on the order of disks
  Dir.glob(disk_file('*')).map { |disk| File.basename(disk) }.shuffle
end
has_disk?(disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 153
def has_disk?(disk_id)
  validate_and_record_inputs(HAS_DISK_SCHEMA, __method__, disk_id)
  File.exists?(disk_file(disk_id))
end
has_vm?(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 147
def has_vm?(vm_cid)
  validate_and_record_inputs(HAS_VM_SCHEMA, __method__, vm_cid)
  @vm_repo.exists?(vm_cid)
end
invocations() click to toggle source
# File lib/cloud/dummy.rb, line 267
def invocations
  @inputs_recorder.read_all
end
invocations_for_method(method) click to toggle source
# File lib/cloud/dummy.rb, line 271
def invocations_for_method(method)
  @inputs_recorder.read(method)
end
kill_agents() click to toggle source
# File lib/cloud/dummy.rb, line 248
def kill_agents
  vm_cids.each do |agent_pid|
    begin
      Process.kill('KILL', agent_pid.to_i)
        # rubocop:disable HandleExceptions
    rescue Errno::ESRCH
      # rubocop:enable HandleExceptions
    end
  end
end
latest_stemcell() click to toggle source
# File lib/cloud/dummy.rb, line 289
def latest_stemcell
  all_stemcells.last
end
prepare() click to toggle source

Additional Dummy test helpers

# File lib/cloud/dummy.rb, line 229
def prepare
  FileUtils.mkdir_p(@base_dir)
end
read_cloud_properties(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 263
def read_cloud_properties(vm_cid)
  @vm_repo.load(vm_cid).cloud_properties
end
reboot_vm(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 141
def reboot_vm(vm_cid)
  validate_and_record_inputs(__method__, vm_cid)
  raise NotImplemented, 'Dummy CPI does not implement reboot_vm'
end
reset() click to toggle source
# File lib/cloud/dummy.rb, line 233
def reset
  FileUtils.rm_rf(@base_dir)
  prepare
end
set_vm_metadata(vm_cid, metadata) click to toggle source
# File lib/cloud/dummy.rb, line 223
def set_vm_metadata(vm_cid, metadata)
  validate_and_record_inputs(SET_VM_METADATA_SCHEMA, __method__, vm_cid, metadata)
end
snapshot_disk(disk_id, metadata) click to toggle source
# File lib/cloud/dummy.rb, line 207
def snapshot_disk(disk_id, metadata)
  validate_and_record_inputs(SNAPSHOT_DISK_SCHEMA, __method__, disk_id, metadata)
  snapshot_id = SecureRandom.hex
  file = snapshot_file(snapshot_id)
  FileUtils.mkdir_p(File.dirname(file))
  File.write(file, metadata.to_json)
  snapshot_id
end
vm_cids() click to toggle source
# File lib/cloud/dummy.rb, line 238
def vm_cids
  # Shuffle so that no one relies on the order of VMs
  Dir.glob(File.join(@running_vms_dir, '*')).map { |vm| File.basename(vm) }.shuffle
end

Private Instance Methods

agent_base_dir(agent_id) click to toggle source
# File lib/cloud/dummy.rb, line 379
def agent_base_dir(agent_id)
  "#{@base_dir}/agent-base-dir-#{agent_id}"
end
agent_cmd(agent_id) click to toggle source
# File lib/cloud/dummy.rb, line 395
def agent_cmd(agent_id)
  agent_config_file = File.join(agent_base_dir(agent_id), 'agent.json')

  agent_config = {
    'Infrastructure' => {
      'Settings' => {
        'Sources' => [{
            'Type' => 'File',
            'SettingsPath' => agent_settings_file(agent_id)
          }],
        'UseRegistry' => true
      }
    }
  }

  File.write(agent_config_file, JSON.generate(agent_config))

  go_agent_exe = File.expand_path('../../../../go/src/github.com/cloudfoundry/bosh-agent/out/bosh-agent', __FILE__)

  %W[#{go_agent_exe} -b #{agent_base_dir(agent_id)} -P dummy -M dummy-nats -C #{agent_config_file}]
end
agent_id_for_vm_id(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 368
def agent_id_for_vm_id(vm_cid)
  @vm_repo.load(vm_cid).agent_id
end
agent_settings_file(agent_id) click to toggle source
# File lib/cloud/dummy.rb, line 372
def agent_settings_file(agent_id)
  # Even though dummy CPI has complete access to agent execution file system
  # it should never write directly to settings.json because
  # the agent is responsible for retrieving the settings from the CPI.
  File.join(agent_base_dir(agent_id), 'bosh', 'dummy-cpi-agent-env.json')
end
allocate_ips(ips) click to toggle source
# File lib/cloud/dummy.rb, line 346
def allocate_ips(ips)
  ips.each do |ip|
    begin
      network_dir = File.join(@base_dir, 'dummy_cpi_networks')
      FileUtils.makedirs(network_dir)
      open(File.join(network_dir, ip['ip']), File::WRONLY|File::CREAT|File::EXCL).close
    rescue Errno::EEXIST
      # at this point we should actually free all the IPs we successfully allocated before the collision,
      # but in practice the tests only feed in one IP per VM so that cleanup code would never be exercised
      raise "IP Address #{ip['ip']} in network '#{ip['network']}' is already in use"
    end
  end
end
attachment_file(vm_cid, disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 441
def attachment_file(vm_cid, disk_id)
  File.join(attachment_path(disk_id), vm_cid)
end
attachment_path(disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 445
def attachment_path(disk_id)
  File.join(@base_dir, 'attachments', disk_id)
end
detach_disks_attached_to_vm(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 433
def detach_disks_attached_to_vm(vm_cid)
  @logger.info("Detaching disks for vm #{vm_cid}")
  Dir.glob(attachment_file(vm_cid, '*')) do |file_path|
    @logger.info("Detaching found attachment #{file_path}")
    FileUtils.rm_rf(File.dirname(file_path))
  end
end
disk_attached?(disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 429
def disk_attached?(disk_id)
  File.exist?(attachment_path(disk_id))
end
disk_file(disk_id) click to toggle source
# File lib/cloud/dummy.rb, line 425
def disk_file(disk_id)
  File.join(@base_dir, 'disks', disk_id)
end
free_ips(vm_cid) click to toggle source
# File lib/cloud/dummy.rb, line 360
def free_ips(vm_cid)
  return unless @vm_repo.exists?(vm_cid)
  vm = @vm_repo.load(vm_cid)
  vm.ips.each do |ip|
    FileUtils.rm_rf(File.join(@base_dir, 'dummy_cpi_networks', ip['ip']))
  end
end
parameter_names_to_values(the_method, *the_method_args) click to toggle source
# File lib/cloud/dummy.rb, line 467
def parameter_names_to_values(the_method, *the_method_args)
  hash = {}
  method(the_method).parameters.each_with_index do |param, index|
    hash[param[1]] = the_method_args[index]
  end
  hash
end
read_agent_settings(agent_id) click to toggle source
# File lib/cloud/dummy.rb, line 417
def read_agent_settings(agent_id)
  JSON.parse(File.read(agent_settings_file(agent_id)))
end
record_inputs(method, args) click to toggle source
# File lib/cloud/dummy.rb, line 463
def record_inputs(method, args)
  @inputs_recorder.record(method, args)
end
snapshot_file(snapshot_id) click to toggle source
# File lib/cloud/dummy.rb, line 449
def snapshot_file(snapshot_id)
  File.join(@base_dir, 'snapshots', snapshot_id)
end
spawn_agent_process(agent_id) click to toggle source
# File lib/cloud/dummy.rb, line 324
def spawn_agent_process(agent_id)
  root_dir = File.join(agent_base_dir(agent_id), 'root_dir')
  FileUtils.mkdir_p(File.join(root_dir, 'etc', 'logrotate.d'))

  agent_cmd = agent_cmd(agent_id)
  agent_log = agent_log_path(agent_id)

  agent_pid = Process.spawn(
    { 'TMPDIR' => @tmp_dir },
    *agent_cmd,
    {
      chdir: agent_base_dir(agent_id),
      out: agent_log,
      err: agent_log,
    }
  )

  Process.detach(agent_pid)

  agent_pid
end
stemcell_file(stemcell_id) click to toggle source
# File lib/cloud/dummy.rb, line 421
def stemcell_file(stemcell_id)
  File.join(@base_dir, "stemcell_#{stemcell_id}")
end
validate_and_record_inputs(schema, the_method, *args) click to toggle source
# File lib/cloud/dummy.rb, line 453
def validate_and_record_inputs(schema, the_method, *args)
  parameter_names_to_values = parameter_names_to_values(the_method, *args)
  begin
    schema.validate(parameter_names_to_values)
  rescue Membrane::SchemaValidationError => err
    raise ArgumentError, "Invalid arguments sent to #{the_method}: #{err.message}"
  end
  record_inputs(the_method, parameter_names_to_values)
end
write_agent_default_network(agent_id, ip_address) click to toggle source
# File lib/cloud/dummy.rb, line 388
def write_agent_default_network(agent_id, ip_address)
  # Agent looks for following file to resolve default network on dummy infrastructure
  path = File.join(agent_base_dir(agent_id), 'bosh', 'dummy-default-network-settings.json')
  FileUtils.mkdir_p(File.dirname(path))
  File.write(path, JSON.generate('ip' => ip_address))
end
write_agent_settings(agent_id, settings) click to toggle source
# File lib/cloud/dummy.rb, line 383
def write_agent_settings(agent_id, settings)
  FileUtils.mkdir_p(File.dirname(agent_settings_file(agent_id)))
  File.write(agent_settings_file(agent_id), JSON.generate(settings))
end