From c502f92cd342e867299ea84150142ca00c1bfef1 Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Wed, 1 Mar 2017 20:49:09 -0800 Subject: [PATCH] (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. --- lib/vmpooler.rb | 2 +- lib/vmpooler/providers.rb | 7 ++ lib/vmpooler/providers/base.rb | 107 +++++++++++++++++++++++++++++++ spec/unit/providers/base_spec.rb | 92 ++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 lib/vmpooler/providers.rb create mode 100644 lib/vmpooler/providers/base.rb create mode 100644 spec/unit/providers/base_spec.rb diff --git a/lib/vmpooler.rb b/lib/vmpooler.rb index 26c3faf..953dac1 100644 --- a/lib/vmpooler.rb +++ b/lib/vmpooler.rb @@ -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 diff --git a/lib/vmpooler/providers.rb b/lib/vmpooler/providers.rb new file mode 100644 index 0000000..eb35436 --- /dev/null +++ b/lib/vmpooler/providers.rb @@ -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 diff --git a/lib/vmpooler/providers/base.rb b/lib/vmpooler/providers/base.rb new file mode 100644 index 0000000..4e7877b --- /dev/null +++ b/lib/vmpooler/providers/base.rb @@ -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 diff --git a/spec/unit/providers/base_spec.rb b/spec/unit/providers/base_spec.rb new file mode 100644 index 0000000..24f2b7c --- /dev/null +++ b/spec/unit/providers/base_spec.rb @@ -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