(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:
Glenn Sarti 2017-03-28 19:12:17 -07:00
parent 901ddde7c3
commit 821dcf45c2
2 changed files with 266 additions and 62 deletions

View file

@ -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
# [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(_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

View file

@ -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