(POOLER-70) Add initial VM Provider service

Previously all of the VM provisioning code was intertwined with the VM lifecycle
code e.g. The VSphere specific code is mixed with Redis code.  This makes it
impossible to add aditional providers or disable VSphere integration.  This
commit begins the process to refactor the VSphere code out of the lifecycle code
by introducing the concept of VM Providers.  A Provider will contain the logic/
code to manage VMs i.e. create/destroy/inquire.  Therefore the Pool Manager can
query a strict interface into one or more Providers.  Initially only a VSphere
provider will be available.

This commit adds the base class for all providers and describes the API or
contract that the Pool Manager will use to manage VMs.
This commit is contained in:
Glenn Sarti 2017-03-01 20:49:09 -08:00
parent 03ad7bfa46
commit c502f92cd3
4 changed files with 207 additions and 1 deletions

View file

@ -12,7 +12,7 @@ module Vmpooler
require 'yaml'
require 'set'
%w(api graphite logger pool_manager vsphere_helper statsd dummy_statsd).each do |lib|
%w(api graphite logger pool_manager vsphere_helper statsd dummy_statsd providers).each do |lib|
begin
require "vmpooler/#{lib}"
rescue LoadError

View file

@ -0,0 +1,7 @@
%w( base vsphere ).each do |lib|
begin
require "vmpooler/providers/#{lib}"
rescue LoadError
require File.expand_path(File.join(File.dirname(__FILE__), 'providers', lib))
end
end

View file

@ -0,0 +1,107 @@
module Vmpooler
class PoolManager
class Provider
class Base
# These defs must be overidden in child classes
def initialize(options)
@provider_options = options
end
# returns
# [String] Name of the provider service
def name
'base'
end
# inputs
# pool : hashtable from config file
# returns
# hashtable
# name : name of the device <---- TODO is this all?
def vms_in_pool(pool)
fail "#{self.class.name} does not implement vms_in_pool"
end
# inputs
# vm_name: string
# 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)
fail "#{self.class.name} does not implement get_vm_host"
end
# inputs
# vm_name: string
# 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)
fail "#{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)
# returns
# [Boolean] Returns true on success or false on failure
def migrate_vm_to_host(vm_name, dest_host_name)
fail "#{self.class.name} does not implement migrate_vm_to_host"
end
# inputs
# vm_name: string
# 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)
# - 'PoweredOn','PoweredOff'
def get_vm(vm_name)
fail "#{self.class.name} does not implement get_vm"
end
# inputs
# pool : hashtable from config file
# new_vmname : string Name the new VM should use
# returns
# Hashtable of the VM as per get_vm
def create_vm(pool,new_vmname)
fail "#{self.class.name} does not implement create_vm"
end
# inputs
# vm_name: string
# pool: string
# returns
# boolean : true if success, false on error
def destroy_vm(vm_name,pool)
fail "#{self.class.name} does not implement destroy_vm"
end
# inputs
# vm : string
# pool: string
# timeout: int (Seconds)
# returns
# result: boolean
def is_vm_ready?(vm,pool,timeout)
fail "#{self.class.name} does not implement is_vm_ready?"
end
# inputs
# vm : string
# returns
# result: boolean
def vm_exists?(vm)
!get_vm(vm).nil?
end
end
end
end
end

View file

@ -0,0 +1,92 @@
require 'spec_helper'
# This spec does not really exercise code paths but is merely used
# to enforce that certain methods are defined in the base classes
describe 'Vmpooler::PoolManager::Provider::Base' do
let(:config) { {} }
let(:fake_vm) {
fake_vm = {}
fake_vm['name'] = 'vm1'
fake_vm['hostname'] = 'vm1'
fake_vm['template'] = 'pool1'
fake_vm['boottime'] = Time.now
fake_vm['powerstate'] = 'PoweredOn'
fake_vm
}
subject { Vmpooler::PoolManager::Provider::Base.new(config) }
describe '#name' do
it 'should be base' do
expect(subject.name).to eq('base')
end
end
describe '#vms_in_pool' do
it 'should raise error' do
expect{subject.vms_in_pool('pool')}.to raise_error(/does not implement vms_in_pool/)
end
end
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/)
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/)
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/)
end
end
describe '#get_vm' do
it 'should raise error' do
expect{subject.get_vm('vm')}.to raise_error(/does not implement get_vm/)
end
end
describe '#create_vm' do
it 'should raise error' do
expect{subject.create_vm('pool','newname')}.to raise_error(/does not implement create_vm/)
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/)
end
end
describe '#is_vm_ready?' do
it 'should raise error' do
expect{subject.is_vm_ready?('vm','pool','timeout')}.to raise_error(/does not implement is_vm_ready?/)
end
end
describe '#vm_exists?' do
it 'should raise error' do
expect{subject.vm_exists?('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)
expect(subject.vm_exists?('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)
expect(subject.vm_exists?('vm')).to eq(false)
end
end
end