class Chef::Provisioning::OpenNebulaDriver::Driver
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 bootedand 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.
Attributes
Public Class Methods
URL must have an endpoint to prevent machine moving, which is not possible today between different endpoints.
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 184 def self.canonicalize_url(driver_url, config) [driver_url, config] end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 178 def self.from_url(driver_url, config) Driver.new(driver_url, config) end
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
# 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
Public Instance Methods
Allocate an image. Returns quickly with an ID that tracks the image.
@param [Chef::Provisioning::ActionHandler] action_handler
The action_handler object that is calling this method
@param [Chef::Provisioning::ImageSpec] image_spec
A machine specification representing this machine.
@param [Hash] image_options
A set of options representing the desired state of the machine
# 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 a load balancer @param [ChefMetal::ActionHandler] action_handler The action handler @param [ChefMetal::LoadBalancerSpec] lb_spec Frozen LB specification @param [Hash] lb_options A hash of options to pass the LB @param [Array] machine_specs
An array of machine specs the load balancer should have
# 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 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 :)
@param [Chef::Provisioning::ActionHandler] action_handler
The action_handler object that is calling this method
@param [Chef::Provisioning::MachineSpec] machine_spec
A machine specification representing this machine.
@param [Hash] machine_options
A set of options representing the desired options when constructing the machine
@return [Chef::Provisioning::MachineSpec]
Modifies the passed-in machine_spec. Anything in here will be saved back after allocate_machine completes.
# 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 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 @example 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
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.
@example Passing a block
allocate_machines( action_handler, specs_and_options, parallelizer) do |machine_spec| ... end
@param [Chef::Provisioning::ActionHandler] action_handler
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).
@param [Hash] specs_and_options
A hash of machine_spec -> machine_options representing the machines to allocate.
@param [Parallelizer] parallelizer an object with a
parallelize() method that works like this:
@return [Array<Machine>] An array of machine objects created
# 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 a machine without allocating or readying it. This method will NOT make any changes to anything, or attempt to wait.
@param [Chef::Provisioning::MachineSpec] machine_spec
MachineSpec representing this machine.
@param [Hash] machine_options @return [Machine] A machine object pointing at the machine, allowing
useful actions like setup, converge, execute, file and directory.
# 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 an image using this service.
@param [Chef::Provisioning::ActionHandler] action_handler
The action_handler object that is calling this method
@param [Chef::Provisioning::ImageSpec] image_spec
A machine specification representing this machine.
@param [Hash] image_options
A set of options representing the desired state of the machine
# 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 the load balancer @param [ChefMetal::ActionHandler] action_handler The action handler @param [ChefMetal::LoadBalancerSpec] lb_spec Frozen LB specification @param [Hash] lb_options A hash of options to pass the LB
# 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
Delete the given machine – destroy the machine, returning things to the state before allocate_machine
was called.
@param [Chef::Provisioning::ActionHandler] action_handler
The action_handler object that is calling this method
@param [Chef::Provisioning::MachineSpec] machine_spec
A machine specification representing this machine.
@param [Hash] machine_options
A set of options representing the desired state of the machine
# 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
Delete machines in batch, in parallel if possible.
# 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 an image, waiting till the point where it is ready to be used.
@param [Chef::Provisioning::ActionHandler] action_handler
The action_handler object that is calling this method
@param [Chef::Provisioning::ImageSpec] image_spec
A machine specification representing this machine.
@param [Hash] image_options
A set of options representing the desired state of the machine
# 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
Make the load balancer ready @param [ChefMetal::ActionHandler] action_handler The action handler @param [ChefMetal::LoadBalancerSpec] lb_spec Frozen LB specification @param [Hash] lb_options A hash of options to pass the LB
# 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 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.
@param [Chef::Provisioning::ActionHandler] action_handler
The action_handler object that is calling this method
@param [Chef::Provisioning::MachineSpec] machine_spec
A machine specification representing this machine.
@param [Hash] machine_options
A set of options representing the desired state of the machine
@return [Machine] A machine object pointing at the machine, allowing
useful actions like setup, converge, execute, file and directory.
# 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 in batch, in parallel if possible.
# 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 the given machine.
@param [Chef::Provisioning::ActionHandler] action_handler
The action_handler object that is calling this method
@param [Chef::Provisioning::MachineSpec] machine_spec
A machine specification representing this machine.
@param [Hash] machine_options
A set of options representing the desired state of the machine
# 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 in batch, in parallel if possible.
# 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
Protected Instance Methods
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 678 def add_prefix(machine_spec, action_handler) AddPrefixActionHandler.new(action_handler, "[#{machine_spec.name}] ") end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 546 def check_unique_names(machine_options, machine_spec) return unless machine_options[:bootstrap_options][:unique_names] hostname = if machine_options[:vm_name] == :short machine_spec.name.split('.').first elsif machine_options[:vm_name].is_a?(String) machine_options[:vm_name] else machine_spec.name end return if @one.get_resource(:virtualmachine, :name => hostname).nil? fail "VM with name '#{hostname}' already exists" end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 665 def convergence_strategy_for(machine_spec, machine_options) # TODO: Support Windoze VMs (see chef-provisioning-vagrant) convergence_options = Cheffish::MergedConfig.new(machine_options ? machine_options[:convergence_options] || {} : {}) if !machine_spec.reference Chef::Provisioning::ConvergenceStrategy::NoConverge.new(convergence_options, config) elsif machine_options[:cached_installer] == true Chef::Provisioning::ConvergenceStrategy::InstallCached.new(convergence_options, config) else Chef::Provisioning::ConvergenceStrategy::InstallSh.new(convergence_options, config) end end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 682 def get_private_key(name) Cheffish.get_private_key(name, config) end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 630 def get_ssh_user(machine_spec, machine_options) # handle ssh_user and ssh_username for backward compatibility Chef::Log.warn("':ssh_user' will be deprecated in next version in favour of ':ssh_username'") if machine_options.key?(:ssh_user) machine_spec.reference['ssh_username'] || machine_options[:ssh_username] || machine_options[:ssh_user] || 'local' end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 592 def instance_for(machine_spec) instance = nil if machine_spec.reference current_endpoint, = Chef::Provisioning::OpenNebulaDriver.match_driver_url(machine_spec.driver_url, true) fail "Cannot move '#{machine_spec.name}' from #{current_endpoint} to #{driver_url}: machine moving is not supported. Destroy and recreate." if current_endpoint != driver_url instance = @one.get_resource(:virtualmachine, :id => machine_spec.reference['instance_id'].to_i) machine_spec.driver_url = @driver_url_with_profile elsif machine_spec.location current_endpoint, = Chef::Provisioning::OpenNebulaDriver.match_driver_url(machine_spec.driver_url, true) fail "Cannot move '#{machine_spec.name}' from #{current_endpoint} to #{driver_url}: machine moving is not supported. Destroy and recreate." if current_endpoint != driver_url instance = @one.get_resource(:virtualmachine, :id => machine_spec.location['server_id'].to_i) machine_spec.driver_url = @driver_url_with_profile unless instance.nil? # Convert from previous driver machine_spec.reference = { 'driver_version' => machine_spec.location['driver_version'], 'allocated_at' => machine_spec.location['allocated_at'], 'image_id' => machine_spec.location['image_id'], 'instance_id' => machine_spec.location['server_id'], 'name' => machine_spec.location['name'], 'state' => machine_spec.location['state'] } end end instance end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 619 def machine_for(machine_spec, machine_options) instance = instance_for(machine_spec) fail "#{machine_spec.name} (#{machine_spec.reference['instance_id']}) does not exist!" if instance.nil? # TODO: Support Windoze VMs (see chef-provisioning-vagrant) Chef::Provisioning::Machine::UnixMachine.new( machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options) ) end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 534 def one_credentials @one_credentials ||= begin credentials = Credentials.new(driver_options[:one_options] || {}) if driver_options[:credentials] credentials.load_plain(driver_options[:credentials], driver_options[:one_options] || {}) elsif driver_options[:secret_file] credentials.load_file(driver_options[:secret_file], driver_options[:one_options] || {}) end credentials end end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 580 def populate_img_object(image_spec, new_image) image_spec.driver_url = @driver_url_with_profile image_spec.reference = { 'driver_version' => Chef::Provisioning::OpenNebulaDriver::VERSION, 'image_id' => new_image, 'allocated_at' => Time.now.to_i, 'state' => 'none' } image_spec.machine_options ||= {} image_spec.machine_options.merge!(:bootstrap_options => { :image_id => new_image }) end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 560 def populate_node_object(machine_spec, machine_options, vm) machine_spec.driver_url = @driver_url_with_profile machine_spec.reference = { 'driver_version' => Chef::Provisioning::OpenNebulaDriver::VERSION, 'allocated_at' => Time.now.utc.to_s, 'image_id' => machine_options[:bootstrap_options][:image_id] || nil, 'is_shutdown' => machine_options[:bootstrap_options][:is_shutdown] || false, 'shutdown_hard' => machine_options[:bootstrap_options][:shutdown_hard] || false, 'instance_id' => vm.id, 'name' => vm.name, 'state' => vm.state_str } # handle ssh_user and ssh_username for backward compatibility Chef::Log.warn("':ssh_user' will be deprecated in next version in favour of ':ssh_username'") if machine_options.key?(:ssh_user) machine_spec.reference['ssh_username'] = get_ssh_user(machine_spec, machine_options) %w(is_windows sudo transport_address_location ssh_gateway).each do |key| machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym] end end
# File lib/chef/provisioning/opennebula_driver/driver.rb, line 636 def transport_for(machine_spec, machine_options, _instance) ssh_options = { :keys_only => false, :forward_agent => true, :use_agent => true, :user_known_hosts_file => '/dev/null', :timeout => 10 }.merge(machine_options[:ssh_options] || {}) ssh_options[:proxy] = Net::SSH::Proxy::Command.new(ssh_options[:proxy]) if ssh_options.key?(:proxy) connection_timeout = machine_options[:connection_timeout] || 300 # default is 5 min username = get_ssh_user(machine_spec, machine_options) options = {} if machine_spec.reference[:sudo] || (!machine_spec.reference.key?(:sudo) && username != 'root') options[:prefix] = 'sudo ' end options[:ssh_pty_enable] = machine_options[:ssh_pty_enable] || true # User provided ssh_gateway takes precedence over machine_spec value options[:ssh_gateway] = machine_options[:ssh_gateway] || machine_spec.reference['ssh_gateway'] transport = Chef::Provisioning::Transport::SSH.new(machine_spec.reference['ip'], username, ssh_options, options, config) retries = connection_timeout.to_i / 3 rc = @one.retry_one("Waiting for SSH connection", retries, 3) { transport.available? ? true : nil } fail "Failed to establish SSH connection to '#{machine_spec.name}'" if rc.nil? transport end