Class: RSpecSystem::NodeSet::Vsphere
- Includes:
- Log
- Defined in:
- lib/rspec-system/node_set/vsphere.rb
Overview
A NodeSet implementation for VSphere
Constant Summary collapse
- PROVIDER_TYPE =
'vsphere'
Instance Attribute Summary collapse
-
#vmconf ⇒ Object
readonly
Returns the value of attribute vmconf.
Attributes inherited from Base
#config, #custom_prefabs_path, #destroy, #nodes, #setname
NodeSet Methods collapse
-
#connect ⇒ void
Connect to the nodes.
-
#launch ⇒ void
Launch the nodes.
-
#teardown ⇒ void
Shutdown the NodeSet by shutting down all nodes.
Instance Method Summary collapse
-
#initialize(setname, config, custom_prefabs_path, options) ⇒ Vsphere
constructor
Creates a new instance of RSpecSystem::NodeSet::Vsphere.
-
#load_fog_config(path = ENV['HOME'] + '/.fog') ⇒ Object
private
Retrieves fog configuration if it exists.
-
#with_vsphere_connection(&block) ⇒ Object
private
This is a DSL based wrapper that provides connection and disconnection handling for the VSphere client API.
Methods included from Log
#bold, #color, #formatter, #log, #output
Methods inherited from Base
#configure, #default_node, #provider_type, #randmac, #random_string, #rcp, #run, #setup, #ssh_connect, #ssh_exec!, #tmppath
Constructor Details
#initialize(setname, config, custom_prefabs_path, options) ⇒ Vsphere
Creates a new instance of RSpecSystem::NodeSet::Vsphere
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/rspec-system/node_set/vsphere.rb', line 23 def initialize(setname, config, custom_prefabs_path, ) super # Valid supported ENV variables = [:host, :user, :pass, :dest_dir, :template_dir, :rpool, :cluster, :ssh_keys, :datacenter, :node_timeout, :node_tries, :node_sleep, :connect_timeout, :connect_tries] # Devise defaults, use fog configuration from file system if it exists defaults = load_fog_config() defaults = defaults.merge({ :node_timeout => 1200, :node_tries => 10, :node_sleep => 30 + rand(60), :connect_timeout => 60, :connect_tries => 10, }) # Traverse the ENV variables and load them into our config automatically @vmconf = defaults ENV.each do |k,v| next unless k =~/^RS(PEC)?_VSPHERE_/ var = k.sub(/^RS(PEC)?_VSPHERE_/, '').downcase.to_sym unless .include?(var) log.info("Ignoring unknown environment variable #{k}") next end @vmconf[var] = v end # Initialize node storage if not already RSpec.configuration.rs_storage[:nodes] ||= {} end |
Instance Attribute Details
#vmconf ⇒ Object (readonly)
Returns the value of attribute vmconf.
15 16 17 |
# File 'lib/rspec-system/node_set/vsphere.rb', line 15 def vmconf @vmconf end |
Instance Method Details
#connect ⇒ void
This method returns an undefined value.
Connect to the nodes
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/rspec-system/node_set/vsphere.rb', line 238 def connect nodes.each do |k,v| rs_storage = RSpec.configuration.rs_storage[:nodes][k] raise RuntimeError, "No internal storage for node #{k}" if rs_storage.nil? ipaddress = rs_storage[:ipaddress] raise RuntimeError, "No ipaddress provided from launch phase for node #{k}" if ipaddress.nil? chan = ssh_connect(:host => k, :user => 'root', :net_ssh_options => { :keys => vmconf[:ssh_keys].split(":"), :host_name => ipaddress, }) RSpec.configuration.rs_storage[:nodes][k][:ssh] = chan end nil end |
#launch ⇒ void
This method returns an undefined value.
Launch the nodes
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/rspec-system/node_set/vsphere.rb', line 124 def launch with_vsphere_connection do |dc| # Traverse folders to find target folder for new vm's and template # folders. Automatically create the destination folder if it doesn't # exist. dest_folder = dc.vmFolder.traverse!(vmconf[:dest_dir], RbVmomi::VIM::Folder) raise "Destination folder #{vmconf[:dest_dir]} not found" if dest_folder.nil? template_folder = dc.vmFolder.traverse(vmconf[:template_dir], RbVmomi::VIM::Folder) raise "Template folder #{vmconf[:template_dir]} not found" if template_folder.nil? # Find resource pool and prepare clone spec for cloning further down. rp = dc.find_compute_resource(vmconf[:cluster]). resourcePool. traverse(vmconf[:rpool]) relocateSpec = RbVmomi::VIM.VirtualMachineRelocateSpec( :diskMoveType => :moveChildMostDiskBacking, :pool => rp ) spec = RbVmomi::VIM.VirtualMachineCloneSpec( :location => relocateSpec, :powerOn => true, :template => false ) log.info "Launching VSphere instances one by one" nodes.each do |k,v| RSpec.configuration.rs_storage[:nodes][k] ||= {} # Obtain the template name to use ps = v.provider_specifics['vsphere'] raise 'No provider specifics for this prefab' if ps.nil? template = ps['template'] raise "No template specified for this prefab" if template.nil? # Traverse to find template VM object vm = template_folder.find(template, RbVmomi::VIM::VirtualMachine) raise "Cannot template find template #{template} in folder #{vmconf[:template_dir]}" if vm.nil? # Create a random name for the new VM vm_name = "rspec-system-#{k}-#{random_string(10)}" RSpec.configuration.rs_storage[:nodes][k][:vm] = vm_name log.info "Launching VSphere instance #{k} with template #{vmconf[:template_dir]}/#{template} as #{vmconf[:dest_dir]}/#{vm_name}" ipaddress = nil newvm = nil tries = 0 start_time = Time.now begin timeout(vmconf[:node_timeout]) do log.info "Cloning new VSphere vm #{vm_name} in folder #{vmconf[:dest_dir]}" vm.CloneVM_Task( :folder => dest_folder, :name => vm_name, :spec => spec ).wait_for_completion time1 = Time.now log.info "Cloning complete, took #{time1 - start_time} seconds" newvm = dest_folder.find(vm_name, RbVmomi::VIM::VirtualMachine) raise "Cannot find newly built virtual machine #{vm_name} in folder #{vmconf[:dest_dir]}" if newvm.nil? while(newvm.guest.guestState != 'running') do sleep 4 log.info "#{k}> Waiting for vm to run ..." end time2 = Time.now log.info "#{k}> Time in seconds for VM to run: #{time2 - time1}" while((ipaddress = newvm.guest.ipAddress) == nil) do sleep 4 log.info "#{k}> Waiting for ip address ..." end time3 = Time.now log.info "#{k}> Time in seconds waiting for IP: #{time3 - time2}" end RSpec.configuration.rs_storage[:nodes][k][:ipaddress] = ipaddress rescue Timeout::Error, SystemCallError => e tries += 1 log.error("VM launch attempt #{tries} failed with: " + e.) if tries < vmconf[:node_tries] log.info("Destroying any VM's, sleeping then trying again ...") begin newvm.PowerOffVM_Task.wait_for_completion rescue RbVmomi::Fault => e log.error "Fault attempting to power off node #{k}, #{e.}" ensure begin newvm.Destroy_Task.wait_for_completion rescue RbVmomi::Fault => e log.error "Fault attempting to destroy node #{k}, #{e.}" end end sleep_time = vmconf[:node_sleep] log.info("Sleeping #{sleep_time} seconds before trying again ...") sleep sleep_time retry else log.error("Failed to create VM and already retried #{tries} times, throwing exception") raise e end end time2 = Time.now log.info "#{k}> Took #{time2 - start_time} seconds to boot instance" end end nil end |
#load_fog_config(path = ENV['HOME'] + '/.fog') ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Retrieves fog configuration if it exists
60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/rspec-system/node_set/vsphere.rb', line 60 def load_fog_config(path = ENV['HOME'] + '/.fog') creds = {} if File.exists?(path) fog = YAML.load_file(path) fog[:default] ||= {} creds = { :host => fog[:default][:vsphere_server], :user => fog[:default][:vsphere_username], :pass => fog[:default][:vsphere_password], } end return creds end |
#teardown ⇒ void
This method returns an undefined value.
Shutdown the NodeSet by shutting down all nodes.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/rspec-system/node_set/vsphere.rb', line 259 def teardown with_vsphere_connection do |dc| nodes.each do |k,v| storage = RSpec.configuration.rs_storage[:nodes][k] if storage.nil? log.info "No entry for node #{k}, no teardown necessary" next end ssh = storage[:ssh] unless ssh.nil? or ssh.closed? ssh.close end if destroy log.info "Destroying instance #{k}" vm_name = storage[:vm] if vm_name == nil log.error "No vm object for #{k}" next end # Traverse folders to find target folder for new vm's vm_folder = dc.vmFolder.traverse(vmconf[:dest_dir], RbVmomi::VIM::Folder) raise "VirtualMachine folder #{vmconf[:dest_dir]} not found" if vm_folder.nil? vm = vm_folder.find(vm_name, RbVmomi::VIM::VirtualMachine) raise "VirtualMachine #{vm_name} not found in #{vmconf[:dest_dir]}" if vm.nil? begin vm.PowerOffVM_Task.wait_for_completion rescue RbVmomi::Fault => e log.error "Fault attempting to power off node #{k}, #{e.}" ensure begin vm.Destroy_Task.wait_for_completion rescue RbVmomi::Fault => e log.error "Fault attempting to destroy node #{k}, #{e.}" end end else next end end end nil end |
#with_vsphere_connection(&block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This is a DSL based wrapper that provides connection and disconnection handling for the VSphere client API.
The connection handling automatically retries upon failure.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/rspec-system/node_set/vsphere.rb', line 81 def with_vsphere_connection(&block) vim = nil dc = nil tries = 0 begin timeout(vmconf[:connect_timeout]) do vim = RbVmomi::VIM.connect( :host => vmconf[:host], :user => vmconf[:user], :password => vmconf[:pass], :ssl => true, :insecure => true ) end rescue => e tries += 1 log.error("Failure to connect (attempt #{tries})") if tries < vmconf[:connect_tries] log.info("Retry connection") retry end log.info("Failed to connect after #{tries} attempts, throwing exception") raise e end begin dc = vim.serviceInstance.find_datacenter(vmconf[:datacenter]) rescue => e log.error("Unable to retrieve datacenter #{vmconf[:datacenter]}") raise e end block.call(dc) vim.close end |