Host snapshot functionality

This commit is contained in:
Scott Schneider 2015-07-14 09:57:47 -07:00
parent 68d02f50a2
commit 1c3045fd65
4 changed files with 180 additions and 5 deletions

View file

@ -50,6 +50,14 @@ module Vmpooler
put '/vm/:hostname/?' do put '/vm/:hostname/?' do
call env.merge("PATH_INFO" => "/api/v#{api_version}/vm/#{params[:hostname]}") call env.merge("PATH_INFO" => "/api/v#{api_version}/vm/#{params[:hostname]}")
end end
post '/vm/:hostname/snapshot/?' do
call env.merge("PATH_INFO" => "/api/v#{api_version}/vm/#{params[:hostname]}/snapshot")
end
post '/vm/:hostname/snapshot/:snapshot/?' do
call env.merge("PATH_INFO" => "/api/v#{api_version}/vm/#{params[:hostname]}/snapshot/#{params[:snapshot]}")
end
end end
end end
end end

View file

@ -413,6 +413,11 @@ module Vmpooler
result[params[:hostname]]['tags'] ||= {} result[params[:hostname]]['tags'] ||= {}
result[params[:hostname]]['tags'][$1] = rdata[key] result[params[:hostname]]['tags'][$1] = rdata[key]
end end
if key.match('^snapshot\:(.+?)$')
result[params[:hostname]]['snapshots'] ||= []
result[params[:hostname]]['snapshots'].push($1)
end
end end
if config['domain'] if config['domain']
@ -509,6 +514,47 @@ module Vmpooler
JSON.pretty_generate(result) JSON.pretty_generate(result)
end end
post "#{api_prefix}/vm/:hostname/snapshot/?" do
content_type :json
status 404
result = { 'ok' => false }
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
if backend.exists('vmpooler__vm__' + params[:hostname])
result[params[:hostname]] = {}
o = [('a'..'z'), ('0'..'9')].map(&:to_a).flatten
result[params[:hostname]]['snapshot'] = o[rand(25)] + (0...31).map { o[rand(o.length)] }.join
backend.sadd('vmpooler__tasks__snapshot', params[:hostname] + ':' + result[params[:hostname]]['snapshot'])
status 202
result['ok'] = true
end
JSON.pretty_generate(result)
end
post "#{api_prefix}/vm/:hostname/snapshot/:snapshot/?" do
content_type :json
status 404
result = { 'ok' => false }
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
if backend.exists('vmpooler__vm__' + params[:hostname]) and backend.hget('vmpooler__vm__' + params[:hostname], 'snapshot:' + params[:snapshot])
backend.sadd('vmpooler__tasks__snapshot-revert', params[:hostname] + ':' + params[:snapshot])
status 202
result['ok'] = true
end
JSON.pretty_generate(result)
end
end end
end end
end end

View file

@ -286,6 +286,100 @@ module Vmpooler
end end
end end
def create_vm_snapshot(vm, snapshot_name)
Thread.new do
_create_vm_snapshot(vm, snapshot_name)
end
end
def _create_vm_snapshot(vm, snapshot_name)
host = $vsphere['snapshot_manager'].find_vm(vm) ||
$vsphere['snapshot_manager'].find_vm_heavy(vm)[vm]
if (host) && ((! snapshot_name.nil?) || (! snapshot_name.empty?))
$logger.log('s', "[ ] [snapshot_manager] '#{vm}' is being snapshotted")
start = Time.now
host.CreateSnapshot_Task(
name: snapshot_name,
description: 'vmpooler',
memory: true,
quiesce: true
).wait_for_completion
finish = '%.2f' % (Time.now - start)
$redis.hset('vmpooler__vm__' + vm, 'snapshot:' + snapshot_name, Time.now.to_s)
$logger.log('s', "[+] [snapshot_manager] '#{vm}' snapshot created in #{finish} seconds")
end
end
def revert_vm_snapshot(vm, snapshot_name)
Thread.new do
_revert_vm_snapshot(vm, snapshot_name)
end
end
def _revert_vm_snapshot(vm, snapshot_name)
host = $vsphere['snapshot_manager'].find_vm(vm) ||
$vsphere['snapshot_manager'].find_vm_heavy(vm)[vm]
if host
snapshot = $vsphere['snapshot_manager'].find_snapshot(host, snapshot_name)
if snapshot
$logger.log('s', "[ ] [snapshot_manager] '#{vm}' is being reverted to snapshot '#{snapshot_name}'")
start = Time.now
snapshot.RevertToSnapshot_Task.wait_for_completion
finish = '%.2f' % (Time.now - start)
$logger.log('s', "[<] [snapshot_manager] '#{vm}' reverted to snapshot in #{finish} seconds")
end
end
end
def check_snapshot_queue
$logger.log('d', "[*] [snapshot_manager] starting worker thread")
$vsphere['snapshot_manager'] ||= Vmpooler::VsphereHelper.new
$threads['snapshot_manager'] = Thread.new do
loop do
_check_snapshot_queue
sleep(5)
end
end
end
def _check_snapshot_queue
vm = $redis.spop('vmpooler__tasks__snapshot')
unless vm.nil?
begin
vm_name, snapshot_name = vm.split(':')
create_vm_snapshot(vm_name, snapshot_name)
rescue
$logger.log('s', "[!] [snapshot_manager] snapshot appears to have failed")
end
end
vm = $redis.spop('vmpooler__tasks__snapshot-revert')
unless vm.nil?
begin
vm_name, snapshot_name = vm.split(':')
revert_vm_snapshot(vm_name, snapshot_name)
rescue
$logger.log('s', "[!] [snapshot_manager] snapshot revert appears to have failed")
end
end
end
def check_pool(pool) def check_pool(pool)
$logger.log('d', "[*] [#{pool['name']}] starting worker thread") $logger.log('d', "[*] [#{pool['name']}] starting worker thread")
@ -455,14 +549,19 @@ module Vmpooler
$redis.set('vmpooler__tasks__clone', 0) $redis.set('vmpooler__tasks__clone', 0)
loop do loop do
if ! $threads['snapshot_manager']
check_snapshot_queue
elsif ! $threads['snapshot_manager'].alive?
$logger.log('d', "[!] [snapshot_manager] worker thread died, restarting")
check_snapshot_queue
end
$config[:pools].each do |pool| $config[:pools].each do |pool|
if ! $threads[pool['name']] if ! $threads[pool['name']]
check_pool(pool) check_pool(pool)
else elsif ! $threads[pool['name']].alive?
unless $threads[pool['name']].alive? $logger.log('d', "[!] [#{pool['name']}] worker thread died, restarting")
$logger.log('d', "[!] [#{pool['name']}] worker thread died, restarting") check_pool(pool)
check_pool(pool)
end
end end
end end

View file

@ -99,6 +99,14 @@ module Vmpooler
base base
end end
def find_snapshot(vm, snapshotname)
if vm.snapshot
get_snapshot_list(vm.snapshot.rootSnapshotList, snapshotname)
else
return []
end
end
def find_vm(vmname) def find_vm(vmname)
begin begin
@connection.serviceInstance.CurrentTime @connection.serviceInstance.CurrentTime
@ -178,6 +186,20 @@ module Vmpooler
) )
end end
def get_snapshot_list(tree, snapshotname)
snapshot = []
tree.each do |child|
if child.name == snapshotname
snapshot ||= child.snapshot
else
snapshot ||= get_snapshot_list(child.childSnapshotList, snapshotname)
end
end
snapshot
end
def close def close
@connection.close @connection.close
end end