mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 01:58:41 -05:00
(POOLER-70) Update the provider base class
Previously it was expected that the timeout setting should be passed when
determining whether a VM was ready. However this should be in the pool
configuration and is not required to be a method parameter.
Previously many of the methods did not have a pool name passed as a parameter.
While this may be ok for the vSphere provider, it may not for other providers.
This commit changes the base provider to consistently use (pool,vm,other..) as
method parameters. This commit also updates the base spec tests as well.
Additionally:
- Updated documentation around expected error states
- Updated documentation to be more consistent in format
- Added snapshot and disk manager functions and unit tests
- Update the initialization method to take in a more formal defintion with
required global objects for metrics, logging and configuration
- Added helper functions
- logger : Allows providers to log information as per Pool Manager
- metrics : Allows providers to submit metrics as per Pool Manager
- provider_options : Allows providers to access initialization options for a
provider
- pool_config : Get the configuration for a specific pool
- provider_config : Get the configuration of this specific provider
- global_config: Get the VMPooler global configuration
This commit is contained in:
parent
901ddde7c3
commit
821dcf45c2
2 changed files with 266 additions and 62 deletions
|
|
@ -4,101 +4,191 @@ module Vmpooler
|
|||
class Base
|
||||
# These defs must be overidden in child classes
|
||||
|
||||
def initialize(options)
|
||||
# Helper Methods
|
||||
# Global Logger object
|
||||
attr_reader :logger
|
||||
# Global Metrics object
|
||||
attr_reader :metrics
|
||||
# Provider options passed in during initialization
|
||||
attr_reader :provider_options
|
||||
|
||||
def initialize(config, logger, metrics, name, options)
|
||||
@config = config
|
||||
@logger = logger
|
||||
@metrics = metrics
|
||||
@provider_name = name
|
||||
|
||||
@provider_options = options
|
||||
end
|
||||
|
||||
# returns
|
||||
# [String] Name of the provider service
|
||||
def name
|
||||
'base'
|
||||
end
|
||||
# Helper Methods
|
||||
|
||||
# inputs
|
||||
# pool : hashtable from config file
|
||||
# [String] pool_name : Name of the pool to get the configuration
|
||||
# returns
|
||||
# hashtable
|
||||
# name : name of the device <---- TODO is this all?
|
||||
def vms_in_pool(_pool)
|
||||
# [Hashtable] : The pools configuration from the config file. Returns nil if the pool does not exist
|
||||
def pool_config(pool_name)
|
||||
# Get the configuration of a specific pool
|
||||
@config[:pools].each do |pool|
|
||||
return pool if pool['name'] == pool_name
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# returns
|
||||
# [Hashtable] : This provider's configuration from the config file. Returns nil if the provider does not exist
|
||||
def provider_config
|
||||
@config[:providers].each do |provider|
|
||||
# Convert the symbol from the config into a string for comparison
|
||||
return provider[1] if provider[0].to_s == @provider_name
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# returns
|
||||
# [Hashtable] : The entire VMPooler configuration
|
||||
def global_config
|
||||
# This entire VM Pooler config
|
||||
@config
|
||||
end
|
||||
|
||||
# returns
|
||||
# [String] : Name of the provider service
|
||||
def name
|
||||
@provider_name
|
||||
end
|
||||
|
||||
# Pool Manager Methods
|
||||
|
||||
# inputs
|
||||
# [String] pool_name : Name of the pool
|
||||
# returns
|
||||
# Array[Hashtable]
|
||||
# Hash contains:
|
||||
# 'name' => [String] Name of VM
|
||||
def vms_in_pool(_pool_name)
|
||||
raise("#{self.class.name} does not implement vms_in_pool")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# vm_name: string
|
||||
# [String]pool_name : Name of the pool
|
||||
# [String] vm_name : Name of the VM
|
||||
# returns
|
||||
# [String] hostname = Name of the host computer running the vm. If this is not a Virtual Machine, it returns the vm_name
|
||||
def get_vm_host(_vm_name)
|
||||
# [String] : Name of the host computer running the vm. If this is not a Virtual Machine, it returns the vm_name
|
||||
def get_vm_host(_pool_name, _vm_name)
|
||||
raise("#{self.class.name} does not implement get_vm_host")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# vm_name: string
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] vm_name : Name of the VM
|
||||
# returns
|
||||
# [String] hostname = Name of the most appropriate host computer to run this VM. Useful for load balancing VMs in a cluster
|
||||
# If this is not a Virtual Machine, it returns the vm_name
|
||||
def find_least_used_compatible_host(_vm_name)
|
||||
# [String] : Name of the most appropriate host computer to run this VM. Useful for load balancing VMs in a cluster
|
||||
# If this is not a Virtual Machine, it returns the vm_name
|
||||
def find_least_used_compatible_host(_pool_name, _vm_name)
|
||||
raise("#{self.class.name} does not implement find_least_used_compatible_host")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# vm_name: string
|
||||
# dest_host_name: string (Name of the host to migrate `vm_name` to)
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] vm_name : Name of the VM to migrate
|
||||
# [String] dest_host_name : Name of the host to migrate `vm_name` to
|
||||
# returns
|
||||
# [Boolean] Returns true on success or false on failure
|
||||
def migrate_vm_to_host(_vm_name, _dest_host_name)
|
||||
# [Boolean] : true on success or false on failure
|
||||
def migrate_vm_to_host(_pool_name, _vm_name, _dest_host_name)
|
||||
raise("#{self.class.name} does not implement migrate_vm_to_host")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# vm_name: string
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] vm_name : Name of the VM to find
|
||||
# returns
|
||||
# nil if it doesn't exist
|
||||
# Hastable of the VM
|
||||
# [String] name = Name of the VM
|
||||
# [String] hostname = Name reported by Vmware tools (host.summary.guest.hostName)
|
||||
# [String] template = This is the name of template exposed by the API. It must _match_ the poolname
|
||||
# [String] poolname = Name of the pool the VM is located
|
||||
# [Time] boottime = Time when the VM was created/booted
|
||||
# [String] powerstate = Current power state of a VM. Valid values (as per vCenter API)
|
||||
# nil if VM doesn't exist
|
||||
# [Hastable] of the VM
|
||||
# [String] name : Name of the VM
|
||||
# [String] hostname : Name reported by Vmware tools (host.summary.guest.hostName)
|
||||
# [String] template : This is the name of template exposed by the API. It must _match_ the poolname
|
||||
# [String] poolname : Name of the pool the VM is located
|
||||
# [Time] boottime : Time when the VM was created/booted
|
||||
# [String] powerstate : Current power state of a VM. Valid values (as per vCenter API)
|
||||
# - 'PoweredOn','PoweredOff'
|
||||
def get_vm(_vm_name)
|
||||
def get_vm(_pool_name, _vm_name)
|
||||
raise("#{self.class.name} does not implement get_vm")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# pool : hashtable from config file
|
||||
# new_vmname : string Name the new VM should use
|
||||
# [String] pool : Name of the pool
|
||||
# [String] new_vmname : Name to give the new VM
|
||||
# returns
|
||||
# Hashtable of the VM as per get_vm
|
||||
def create_vm(_pool, _new_vmname)
|
||||
# [Hashtable] of the VM as per get_vm
|
||||
# Raises RuntimeError if the pool_name is not supported by the Provider
|
||||
def create_vm(_pool_name, _new_vmname)
|
||||
raise("#{self.class.name} does not implement create_vm")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# vm_name: string
|
||||
# pool: string
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] vm_name : Name of the VM to create the disk on
|
||||
# [Integer] disk_size : Size of the disk to create in Gigabytes (GB)
|
||||
# returns
|
||||
# boolean : true if success, false on error
|
||||
def destroy_vm(_vm_name, _pool)
|
||||
# [Boolean] : true if success, false if disk could not be created
|
||||
# Raises RuntimeError if the Pool does not exist
|
||||
# Raises RuntimeError if the VM does not exist
|
||||
def create_disk(_pool_name, _vm_name, _disk_size)
|
||||
raise("#{self.class.name} does not implement create_disk")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] new_vmname : Name of the VM to create the snapshot on
|
||||
# [String] new_snapshot_name : Name of the new snapshot to create
|
||||
# returns
|
||||
# [Boolean] : true if success, false if snapshot could not be created
|
||||
# Raises RuntimeError if the VM does not exist
|
||||
# Raises RuntimeError if the snapshot already exists
|
||||
def create_snapshot(_pool_name, _vm_name, _new_snapshot_name)
|
||||
raise("#{self.class.name} does not implement create_snapshot")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] new_vmname : Name of the VM to restore
|
||||
# [String] snapshot_name : Name of the snapshot to restore to
|
||||
# returns
|
||||
# [Boolean] : true if success, false if snapshot could not be revertted
|
||||
# Raises RuntimeError if the VM does not exist
|
||||
# Raises RuntimeError if the snapshot already exists
|
||||
def revert_snapshot(_pool_name, _vm_name, _snapshot_name)
|
||||
raise("#{self.class.name} does not implement revert_snapshot")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] vm_name : Name of the VM to destroy
|
||||
# returns
|
||||
# [Boolean] : true if success, false on error. Should returns true if the VM is missing
|
||||
def destroy_vm(_pool_name, _vm_name)
|
||||
raise("#{self.class.name} does not implement destroy_vm")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# vm : string
|
||||
# pool: string
|
||||
# timeout: int (Seconds)
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] vm_name : Name of the VM to check if ready
|
||||
# returns
|
||||
# result: boolean
|
||||
def vm_ready?(_vm, _pool, _timeout)
|
||||
# [Boolean] : true if ready, false if not
|
||||
def vm_ready?(_pool_name, _vm_name)
|
||||
raise("#{self.class.name} does not implement vm_ready?")
|
||||
end
|
||||
|
||||
# inputs
|
||||
# vm : string
|
||||
# [String] pool_name : Name of the pool
|
||||
# [String] vm_name : Name of the VM to check if it exists
|
||||
# returns
|
||||
# result: boolean
|
||||
def vm_exists?(vm)
|
||||
!get_vm(vm).nil?
|
||||
# [Boolean] : true if it exists, false if not
|
||||
def vm_exists?(pool_name, vm_name)
|
||||
!get_vm(pool_name, vm_name).nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ require 'spec_helper'
|
|||
# to enforce that certain methods are defined in the base classes
|
||||
|
||||
describe 'Vmpooler::PoolManager::Provider::Base' do
|
||||
let(:logger) { MockLogger.new }
|
||||
let(:metrics) { Vmpooler::DummyStatsd.new }
|
||||
let(:config) { {} }
|
||||
let(:provider_name) { 'base' }
|
||||
let(:provider_options) { { 'param' => 'value' } }
|
||||
|
||||
let(:fake_vm) {
|
||||
fake_vm = {}
|
||||
fake_vm['name'] = 'vm1'
|
||||
|
|
@ -16,11 +21,102 @@ describe 'Vmpooler::PoolManager::Provider::Base' do
|
|||
fake_vm
|
||||
}
|
||||
|
||||
subject { Vmpooler::PoolManager::Provider::Base.new(config) }
|
||||
subject { Vmpooler::PoolManager::Provider::Base.new(config, logger, metrics, provider_name, provider_options) }
|
||||
|
||||
# Helper attr_reader methods
|
||||
describe '#logger' do
|
||||
it 'should come from the provider initialization' do
|
||||
expect(subject.logger).to be(logger)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#metrics' do
|
||||
it 'should come from the provider initialization' do
|
||||
expect(subject.metrics).to be(metrics)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_options' do
|
||||
it 'should come from the provider initialization' do
|
||||
expect(subject.provider_options).to be(provider_options)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pool_config' do
|
||||
let(:poolname) { 'pool1' }
|
||||
let(:config) { YAML.load(<<-EOT
|
||||
---
|
||||
:pools:
|
||||
- name: '#{poolname}'
|
||||
alias: [ 'mockpool' ]
|
||||
template: 'Templates/pool1'
|
||||
folder: 'Pooler/pool1'
|
||||
datastore: 'datastore0'
|
||||
size: 5
|
||||
timeout: 10
|
||||
ready_ttl: 1440
|
||||
clone_target: 'cluster1'
|
||||
EOT
|
||||
)
|
||||
}
|
||||
context 'Given a pool that does not exist' do
|
||||
it 'should return nil' do
|
||||
expect(subject.pool_config('missing_pool')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'Given a pool that does exist' do
|
||||
it 'should return the pool\'s configuration' do
|
||||
result = subject.pool_config(poolname)
|
||||
expect(result['name']).to eq(poolname)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_config' do
|
||||
let(:poolname) { 'pool1' }
|
||||
let(:config) { YAML.load(<<-EOT
|
||||
---
|
||||
:providers:
|
||||
:#{provider_name}:
|
||||
option1: 'value1'
|
||||
EOT
|
||||
)
|
||||
}
|
||||
|
||||
context 'Given a misconfigured provider name' do
|
||||
let(:config) { YAML.load(<<-EOT
|
||||
---
|
||||
:providers:
|
||||
:bad_provider:
|
||||
option1: 'value1'
|
||||
option2: 'value1'
|
||||
EOT
|
||||
)
|
||||
}
|
||||
it 'should return nil' do
|
||||
expect(subject.provider_config).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'Given a correct provider name' do
|
||||
it 'should return the provider\'s configuration' do
|
||||
result = subject.provider_config
|
||||
expect(result['option1']).to eq('value1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#global_config' do
|
||||
it 'should come from the provider initialization' do
|
||||
expect(subject.global_config).to be(config)
|
||||
end
|
||||
end
|
||||
|
||||
# Pool Manager Methods
|
||||
describe '#name' do
|
||||
it 'should be base' do
|
||||
expect(subject.name).to eq('base')
|
||||
it "should come from the provider initialization" do
|
||||
expect(subject.name).to eq(provider_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -32,25 +128,25 @@ describe 'Vmpooler::PoolManager::Provider::Base' do
|
|||
|
||||
describe '#get_vm_host' do
|
||||
it 'should raise error' do
|
||||
expect{subject.get_vm_host('vm')}.to raise_error(/does not implement get_vm_host/)
|
||||
expect{subject.get_vm_host('pool', 'vm')}.to raise_error(/does not implement get_vm_host/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_least_used_compatible_host' do
|
||||
it 'should raise error' do
|
||||
expect{subject.find_least_used_compatible_host('vm')}.to raise_error(/does not implement find_least_used_compatible_host/)
|
||||
expect{subject.find_least_used_compatible_host('pool', 'vm')}.to raise_error(/does not implement find_least_used_compatible_host/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#migrate_vm_to_host' do
|
||||
it 'should raise error' do
|
||||
expect{subject.migrate_vm_to_host('vm','host')}.to raise_error(/does not implement migrate_vm_to_host/)
|
||||
expect{subject.migrate_vm_to_host('pool', 'vm','host')}.to raise_error(/does not implement migrate_vm_to_host/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_vm' do
|
||||
it 'should raise error' do
|
||||
expect{subject.get_vm('vm')}.to raise_error(/does not implement get_vm/)
|
||||
expect{subject.get_vm('pool', 'vm')}.to raise_error(/does not implement get_vm/)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -60,33 +156,51 @@ describe 'Vmpooler::PoolManager::Provider::Base' do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#create_disk' do
|
||||
it 'should raise error' do
|
||||
expect{subject.create_disk('pool', 'vm', 10)}.to raise_error(/does not implement create_disk/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_snapshot' do
|
||||
it 'should raise error' do
|
||||
expect{subject.create_snapshot('pool', 'vm', 'snapshot')}.to raise_error(/does not implement create_snapshot/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#revert_snapshot' do
|
||||
it 'should raise error' do
|
||||
expect{subject.revert_snapshot('pool', 'vm', 'snapshot')}.to raise_error(/does not implement revert_snapshot/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy_vm' do
|
||||
it 'should raise error' do
|
||||
expect{subject.destroy_vm('vm','pool')}.to raise_error(/does not implement destroy_vm/)
|
||||
expect{subject.destroy_vm('pool', 'vm')}.to raise_error(/does not implement destroy_vm/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#vm_ready?' do
|
||||
it 'should raise error' do
|
||||
expect{subject.vm_ready?('vm','pool','timeout')}.to raise_error(/does not implement vm_ready?/)
|
||||
expect{subject.vm_ready?('pool', 'vm')}.to raise_error(/does not implement vm_ready?/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#vm_exists?' do
|
||||
it 'should raise error' do
|
||||
expect{subject.vm_exists?('vm')}.to raise_error(/does not implement/)
|
||||
expect{subject.vm_exists?('pool', 'vm')}.to raise_error(/does not implement/)
|
||||
end
|
||||
|
||||
it 'should return true when get_vm returns an object' do
|
||||
allow(subject).to receive(:get_vm).with('vm').and_return(fake_vm)
|
||||
allow(subject).to receive(:get_vm).with('pool', 'vm').and_return(fake_vm)
|
||||
|
||||
expect(subject.vm_exists?('vm')).to eq(true)
|
||||
expect(subject.vm_exists?('pool', 'vm')).to eq(true)
|
||||
end
|
||||
|
||||
it 'should return false when get_vm returns nil' do
|
||||
allow(subject).to receive(:get_vm).with('vm').and_return(nil)
|
||||
allow(subject).to receive(:get_vm).with('pool', 'vm').and_return(nil)
|
||||
|
||||
expect(subject.vm_exists?('vm')).to eq(false)
|
||||
expect(subject.vm_exists?('pool', 'vm')).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue