Class: Chef::Provisioning::OpenNebulaDriver::Driver

Inherits:
Driver
  • Object
show all
Defined in:
lib/chef/provisioning/opennebula_driver/driver.rb

Overview

A Driver instance represents a place where machines can be created and found, and contains methods to create, delete, start, stop, and find them.

For AWS, a Driver instance corresponds to a single account. For Vagrant, it is a directory where VM files are found.

How to Make a Driver

To implement a Driver, you must implement the following methods:

  • initialize(driver_url) - create a new driver with the given URL

  • driver_url - a URL representing everything unique about your

    driver. (NOT credentials)
    
  • allocate_machine - ask the driver to allocate a machine to you.

  • ready_machine - get the machine “ready” - wait for it to be booted

    and accessible (for example, accessible via SSH
    transport).
    
  • stop_machine - stop the machine.

  • destroy_machine - delete the machine.

  • connect_to_machine - connect to the given machine.

Optionally, you can also implement:

  • allocate_machines - allocate an entire group of machines.

  • ready_machines - get a group of machines warm and booted.

  • stop_machines - stop a group of machines.

  • destroy_machines - delete a group of machines.

Additionally, you must create a file named ‘chef/provisioning/driver_init/<scheme>.rb`, where <scheme> is the name of the scheme you chose for your driver_url. This file, when required, must call Chef::Provisioning.add_registered_driver(<scheme>, <class>). The given <class>.from_url(url, config) will be called with a driver_url and configuration.

All of these methods must be idempotent - if the work is already done, they just don’t do anything.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(driver_url, config) ⇒ Driver

OpenNebula by default reads the following information:

username:password from ENV['ONE_AUTH'] or ENV['HOME']/.one/one_auth
endpoint from ENV['ONE_XMLRPC']

driver_url format:

opennebula:<endpoint>:<profile>

where <profile> points to a one_auth file in ENV/.one/<profile>

driver_options:

credentials: bbsl-auto:text_pass  credentials has precedence over secret_file
endpoint: opennebula endpoint
options: additional options for OpenNebula::Client


171
172
173
174
175
176
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 171

def initialize(driver_url, config)
  super
  @one = Chef::Provisioning::OpenNebulaDriver.get_onelib(:driver_url => driver_url)
  @driver_url_with_profile = driver_url
  @driver_url = @one.client.one_endpoint
end

Instance Attribute Details

#oneObject (readonly)

Returns the value of attribute one.



154
155
156
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 154

def one
  @one
end

Class Method Details

.canonicalize_url(driver_url, config) ⇒ Object

URL must have an endpoint to prevent machine moving, which is not possible today between different endpoints.



184
185
186
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 184

def self.canonicalize_url(driver_url, config)
  [driver_url, config]
end

.from_url(driver_url, config) ⇒ Object



178
179
180
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 178

def self.from_url(driver_url, config)
  Driver.new(driver_url, config)
end

Instance Method Details

#allocate_image(action_handler, image_spec, image_options, machine_spec) ⇒ Object

Allocate an image. Returns quickly with an ID that tracks the image.

Parameters:

  • action_handler (Chef::Provisioning::ActionHandler)

    The action_handler object that is calling this method

  • image_spec (Chef::Provisioning::ImageSpec)

    A machine specification representing this machine.

  • image_options (Hash)

    A set of options representing the desired state of the machine



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 363

def allocate_image(action_handler, image_spec, image_options, machine_spec)
  if image_spec.reference
    # check if image already exists
    image = @one.get_resource(:image, :id => image_spec.reference['image_id'].to_i)
    action_handler.report_progress "image #{image_spec.name} (ID: #{image_spec.reference['image_id']}) already exists" unless image.nil?
  else
    action_handler.perform_action "create image #{image_spec.name} from machine ID #{machine_spec.reference['instance_id']} with options #{image_options.inspect}" do
      vm = @one.get_resource(:virtualmachine, :id => machine_spec.reference['instance_id'])
      fail "allocate_image: VM does not exist" if vm.nil?
      # set default disk ID
      disk_id = 1
      if image_options.disk_id
        disk_id = image_options.disk_id.is_a?(Integer) ? image_options.disk_id : @one.get_disk_id(vm, new_resource.disk_id)
      end
      new_img = @one.version_ge_4_14 ? vm.disk_saveas(disk_id, image_spec.name) : vm.disk_snapshot(disk_id, image_spec.name, "", true)

      fail "Failed to create snapshot '#{new_resource.name}': #{new_img.message}" if OpenNebula.is_error?(new_img)
      populate_img_object(image_spec, new_image)
    end
  end
end

#allocate_load_balancer(_action_handler, _lb_spec, _lb_options, _machine_specs) ⇒ Object

Allocate a load balancer

Parameters:

  • action_handler (ChefMetal::ActionHandler)

    The action handler

  • lb_spec (ChefMetal::LoadBalancerSpec)

    Frozen LB specification

  • lb_options (Hash)

    A hash of options to pass the LB

  • machine_specs (Array[ChefMetal::MachineSpec])

    An array of machine specs the load balancer should have



512
513
514
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 512

def allocate_load_balancer(_action_handler, _lb_spec, _lb_options, _machine_specs)
  fail "'allocate_load_balancer' is not implemented"
end

#allocate_machine(action_handler, machine_spec, machine_options) ⇒ Chef::Provisioning::MachineSpec

Allocate a machine from the underlying service. This method does not need to wait for the machine to boot or have an IP, but it must store enough information in machine_spec.location to find the machine later in ready_machine.

If a machine is powered off or otherwise unusable, this method may start it, but does not need to wait until it is started. The idea is to get the gears moving, but the job doesn’t need to be done :)

Parameters:

  • action_handler (Chef::Provisioning::ActionHandler)

    The action_handler object that is calling this method

  • machine_spec (Chef::Provisioning::MachineSpec)

    A machine specification representing this machine.

  • machine_options (Hash)

    A set of options representing the desired options when constructing the machine

Returns:

  • (Chef::Provisioning::MachineSpec)

    Modifies the passed-in machine_spec. Anything in here will be saved back after allocate_machine completes.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 210

def allocate_machine(action_handler, machine_spec, machine_options)
  instance = instance_for(machine_spec)
  return machine_spec unless instance.nil?

  unless machine_options[:bootstrap_options]
    instance = @one.retry_one("Retrying allocate_machine.instance_for (missing bootstrap options)") do
      instance_for(machine_spec)
    end
    return machine_spec unless instance.nil?
    fail "'bootstrap_options' must be specified"
  end
  check_unique_names(machine_options, machine_spec)
  action_handler.perform_action "created vm '#{machine_spec.name}'" do
    Chef::Log.debug(machine_options)
    tpl = @one.get_template(machine_spec.name,
                            machine_options[:bootstrap_options])
    vm = @one.allocate_vm(tpl)
    populate_node_object(machine_spec, machine_options, vm)
    @one.chmod_resource(vm, machine_options[:bootstrap_options][:mode])

    # This option allows to manipulate how the machine shows up
    # in the OpenNebula UI and CLI tools.  We either set the VM
    # name to the short hostname of the machine, rename it
    # to the String passed to us, or leave it alone.
    if machine_options[:vm_name] == :short
      @one.rename_vm(vm, machine_spec.name.split('.').first)
    elsif machine_options[:vm_name].is_a?(String)
      @one.rename_vm(vm, machine_options[:vm_name])
      # else use machine_spec.name for name in OpenNebula
    end
  end
  Chef::Log.debug(machine_spec.reference)

  machine_spec
end

#allocate_machines(action_handler, specs_and_options, parallelizer) ⇒ Array<Machine>

Allocate a set of machines. This should have the same effect as running allocate_machine on all machine_specs.

Drivers do not need to implement this; the default implementation calls acquire_machine in parallel.

Parallelizing

The parallelizer must implement #parallelize This object is shared among other chef-provisioning actions, ensuring that you do not go over parallelization limits set by the user. Use of the parallelizer to parallelizer machines is not required.

Passing a block

If you pass a block to this function, each machine will be yielded to you as it completes, and then the function will return when all machines are yielded.

Examples:

Example parallelizer

parallelizer.parallelize(specs_and_options) do |machine_spec|
  allocate_machine(action_handler, machine_spec)
end.to_a
# The to_a at the end causes you to wait until the
parallelization is done

Passing a block

allocate_machines(
  action_handler,
  specs_and_options,
  parallelizer) do |machine_spec|
  ...
end

Parameters:

  • action_handler (Chef::Provisioning::ActionHandler)

    The action_handler object that is calling this method; this is generally a driver, but could be anything that can support the interface (i.e., in the case of the test kitchen provisioning driver for acquiring and destroying VMs).

  • specs_and_options (Hash)

    A hash of machine_spec -> machine_options representing the machines to allocate.

  • parallelizer (Parallelizer)

    an object with a parallelize() method that works like this:

Returns:

  • (Array<Machine>)

    An array of machine objects created



473
474
475
476
477
478
479
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 473

def allocate_machines(action_handler, specs_and_options, parallelizer)
  parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
    allocate_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
    yield machine_spec if block_given?
    machine_spec
  end.to_a
end

#connect_to_machine(machine_spec, machine_options) ⇒ Machine

Connect to a machine without allocating or readying it. This method will NOT make any changes to anything, or attempt to wait.

Parameters:

  • machine_spec (Chef::Provisioning::MachineSpec)

    MachineSpec representing this machine.

  • machine_options (Hash)

Returns:

  • (Machine)

    A machine object pointing at the machine, allowing useful actions like setup, converge, execute, file and directory.



292
293
294
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 292

def connect_to_machine(machine_spec, machine_options)
  machine_for(machine_spec, machine_options)
end

#destroy_image(action_handler, image_spec, _image_options) ⇒ Object

Destroy an image using this service.

Parameters:

  • action_handler (Chef::Provisioning::ActionHandler)

    The action_handler object that is calling this method

  • image_spec (Chef::Provisioning::ImageSpec)

    A machine specification representing this machine.

  • image_options (Hash)

    A set of options representing the desired state of the machine



411
412
413
414
415
416
417
418
419
420
421
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 411

def destroy_image(action_handler, image_spec, _image_options)
  img = @one.get_resource(:image, :id => image_spec.location['image_id'].to_i)
  if img.nil?
    action_handler.report_progress "image #{image_spec.name} (#{image_spec.location['image_id']}) does not exist - (up to date)"
  else
    action_handler.perform_action "deleted image #{image_spec.name} (#{image_spec.location['image_id']})" do
      rc = img.delete
      fail "Failed to delete image '#{image_spec.name}' : #{rc.message}" if OpenNebula.is_error?(rc)
    end
  end
end

#destroy_load_balancer(_action_handler, _lb_spec, _lb_options) ⇒ Object

Destroy the load balancer

Parameters:

  • action_handler (ChefMetal::ActionHandler)

    The action handler

  • lb_spec (ChefMetal::LoadBalancerSpec)

    Frozen LB specification

  • lb_options (Hash)

    A hash of options to pass the LB



528
529
530
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 528

def destroy_load_balancer(_action_handler, _lb_spec, _lb_options)
  fail "'destroy_load_balancer' is not implemented"
end

#destroy_machine(action_handler, machine_spec, machine_options) ⇒ Object

Delete the given machine – destroy the machine, returning things to the state before allocate_machine was called.

Parameters:

  • action_handler (Chef::Provisioning::ActionHandler)

    The action_handler object that is calling this method

  • machine_spec (Chef::Provisioning::MachineSpec)

    A machine specification representing this machine.

  • machine_options (Hash)

    A set of options representing the desired state of the machine



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 305

def destroy_machine(action_handler, machine_spec, machine_options)
  instance = instance_for(machine_spec)
  if !instance.nil?
    action_handler.perform_action "destroyed machine #{machine_spec.name} (#{machine_spec.reference['instance_id']})" do
      instance.delete
      1.upto(10) do
        instance.info
        break if instance.state_str == 'DONE'
        Chef::Log.debug("Waiting for VM '#{instance.id}' to be in 'DONE' state: '#{instance.state_str}'")
        sleep(2)
      end
      fail "Failed to destroy '#{instance.name}'.  Current state: #{instance.state_str}" if instance.state_str != 'DONE'
    end
  elsif machine_spec.reference
    Chef::Log.info("vm #{machine_spec.name} (#{machine_spec.reference['instance_id']}) does not exist - (up to date)")
  else
    Chef::Log.info("vm #{machine_spec.name} does not exist - (up to date)")
  end
  begin
    strategy = convergence_strategy_for(machine_spec, machine_options)
    strategy.cleanup_convergence(action_handler, machine_spec)
  rescue Net::HTTPServerException => e
    raise unless e.response.code == '404'
  end
end

#destroy_machines(action_handler, specs_and_options, parallelizer) ⇒ Object

Delete machines in batch, in parallel if possible.



499
500
501
502
503
504
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 499

def destroy_machines(action_handler, specs_and_options, parallelizer)
  parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
    destroy_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
    yield machine_spec if block_given?
  end.to_a
end

#ready_image(action_handler, image_spec, _image_options) ⇒ Object

Ready an image, waiting till the point where it is ready to be used.

Parameters:

  • action_handler (Chef::Provisioning::ActionHandler)

    The action_handler object that is calling this method

  • image_spec (Chef::Provisioning::ImageSpec)

    A machine specification representing this machine.

  • image_options (Hash)

    A set of options representing the desired state of the machine



393
394
395
396
397
398
399
400
401
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 393

def ready_image(action_handler, image_spec, _image_options)
  img = @one.get_resource(:image, :id => image_spec.reference['image_id'].to_i)
  fail "Image #{image_spec.name} (#{image_spec.reference['image_id']}) does not exist" if img.nil?
  action_handler.perform_action "image #{image_spec.name} is ready" do
    deployed = @one.wait_for_img(img.name, img.id)
    image_spec.reference['state'] = deployed.state_str
  end
  img
end

#ready_load_balancer(_action_handler, _lb_spec, _lb_options, _machine_specs) ⇒ Object

Make the load balancer ready

Parameters:

  • action_handler (ChefMetal::ActionHandler)

    The action handler

  • lb_spec (ChefMetal::LoadBalancerSpec)

    Frozen LB specification

  • lb_options (Hash)

    A hash of options to pass the LB



520
521
522
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 520

def ready_load_balancer(_action_handler, _lb_spec, _lb_options, _machine_specs)
  fail "'ready_load_balancer' is not implemented"
end

#ready_machine(action_handler, machine_spec, machine_options) ⇒ Machine

Ready a machine, to the point where it is running and accessible via a transport. This will NOT allocate a machine, but may kick it if it is down. This method waits for the machine to be usable, returning a Machine object pointing at the machine, allowing useful actions like setup, converge, execute, file and directory.

Parameters:

  • action_handler (Chef::Provisioning::ActionHandler)

    The action_handler object that is calling this method

  • machine_spec (Chef::Provisioning::MachineSpec)

    A machine specification representing this machine.

  • machine_options (Hash)

    A set of options representing the desired state of the machine

Returns:

  • (Machine)

    A machine object pointing at the machine, allowing useful actions like setup, converge, execute, file and directory.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 263

def ready_machine(action_handler, machine_spec, machine_options)
  instance = instance_for(machine_spec)
  fail "Machine '#{machine_spec.name}' does not have an instance associated with it, or instance does not exist." if instance.nil?

  # TODO: Currently it does not start stopped VMs, it only waits for new VMs to be in RUNNING state
  machine = nil
  action_handler.perform_action "vm '#{machine_spec.name}' is ready" do
    deployed = @one.wait_for_vm(instance.id)
    machine_spec.reference['name'] = deployed.name
    machine_spec.reference['state'] = deployed.state_str
    if deployed.to_hash['VM']['TEMPLATE']['NIC']
      ip = [deployed.to_hash['VM']['TEMPLATE']['NIC']].flatten.first['IP']
    end
    fail "Could not get IP from VM '#{deployed.name}'" if ip.nil? || ip.to_s.empty?
    machine_spec.reference['ip'] = ip
    machine = machine_for(machine_spec, machine_options)
  end
  machine
end

#ready_machines(action_handler, specs_and_options, parallelizer) ⇒ Object

Ready machines in batch, in parallel if possible.



482
483
484
485
486
487
488
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 482

def ready_machines(action_handler, specs_and_options, parallelizer)
  parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
    machine = ready_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
    yield machine if block_given?
    machine
  end.to_a
end

#stop_machine(action_handler, machine_spec, machine_options) ⇒ Object

Stop the given machine.

Parameters:

  • action_handler (Chef::Provisioning::ActionHandler)

    The action_handler object that is calling this method

  • machine_spec (Chef::Provisioning::MachineSpec)

    A machine specification representing this machine.

  • machine_options (Hash)

    A set of options representing the desired state of the machine



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 339

def stop_machine(action_handler, machine_spec, machine_options)
  instance = instance_for(machine_spec)
  if !instance.nil?
    action_handler.perform_action "powered off machine #{machine_spec.name} (#{machine_spec.reference['instance_id']})" do
      if machine_spec.reference[:is_shutdown] || (machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:is_shutdown])
        hard = machine_spec.reference[:shutdown_hard] || machine_options[:bootstrap_options][:shutdown_hard] || false
        instance.shutdown(hard)
      else
        instance.stop
      end
    end
  else
    Chef::Log.info("vm #{machine_spec.name} (#{machine_spec.reference['instance_id']}) does not exist - (up to date)")
  end
end

#stop_machines(action_handler, specs_and_options, parallelizer) ⇒ Object

Stop machines in batch, in parallel if possible.



491
492
493
494
495
496
# File 'lib/chef/provisioning/opennebula_driver/driver.rb', line 491

def stop_machines(action_handler, specs_and_options, parallelizer)
  parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
    stop_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
    yield machine_spec if block_given?
  end.to_a
end