diff --git a/API.md b/API.md index 78b3d6d..ebec9d2 100644 --- a/API.md +++ b/API.md @@ -226,6 +226,46 @@ $ curl -X DELETE --url vmpooler.company.com/api/v1/vm/fq6qlpjlsskycq6 } ``` +#### Adding additional disk(s) + +##### POST /vm/<hostname>/disk/<size> + +Add an additional disk to a running VM. + +```` +$ curl -X POST -H X-AUTH-TOKEN:a9znth9dn01t416hrguu56ze37t790bl --url vmpooler.company.com/api/v1/vm/fq6qlpjlsskycq6/disk/8 +```` +````json +{ + "ok": true, + "fq6qlpjlsskycq6": { + "disk": "+8gb" + } +} +```` + +Provisioning and attaching disks can take a moment, but once the task completes it will be reflected in a `GET /vm/` query: + +```` +$ curl --url vmpooler.company.com/api/v1/vm/fq6qlpjlsskycq6 +```` +````json +{ + "ok": true, + "fq6qlpjlsskycq6": { + "template": "debian-7-x86_64", + "lifetime": 2, + "running": 0.08, + "state": "running", + "disk": [ + "+8gb" + ], + "domain": "delivery.puppetlabs.net" + } +} + +```` + #### VM snapshots ##### POST /vm/<hostname>/snapshot diff --git a/lib/vmpooler/api/reroute.rb b/lib/vmpooler/api/reroute.rb index 4f574a3..ea62ad1 100644 --- a/lib/vmpooler/api/reroute.rb +++ b/lib/vmpooler/api/reroute.rb @@ -62,6 +62,10 @@ module Vmpooler post '/vm/:hostname/snapshot/:snapshot/?' do call env.merge("PATH_INFO" => "/api/v#{api_version}/vm/#{params[:hostname]}/snapshot/#{params[:snapshot]}") end + + put '/vm/:hostname/disk/:size/?' do + call env.merge("PATH_INFO" => "/api/v#{api_version}/vm/#{params[:hostname]}/disk/#{params[:size]}") + end end end end diff --git a/lib/vmpooler/api/v1.rb b/lib/vmpooler/api/v1.rb index a042351..1046593 100644 --- a/lib/vmpooler/api/v1.rb +++ b/lib/vmpooler/api/v1.rb @@ -455,6 +455,10 @@ module Vmpooler end end + if rdata['disk'] + result[params[:hostname]]['disk'] = rdata['disk'].split(':') + end + if config['domain'] result[params[:hostname]]['domain'] = config['domain'] end @@ -552,6 +556,29 @@ module Vmpooler JSON.pretty_generate(result) end + post "#{api_prefix}/vm/:hostname/disk/:size/?" do + content_type :json + + need_token! if Vmpooler::API.settings.config[:auth] + + status 404 + result = { 'ok' => false } + + params[:hostname] = hostname_shorten(params[:hostname], config['domain']) + + if ((params[:size].to_i > 0 )and (backend.exists('vmpooler__vm__' + params[:hostname]))) + result[params[:hostname]] = {} + result[params[:hostname]]['disk'] = "+#{params[:size]}gb" + + backend.sadd('vmpooler__tasks__disk', params[:hostname] + ':' + params[:size]) + + status 202 + result['ok'] = true + end + + JSON.pretty_generate(result) + end + post "#{api_prefix}/vm/:hostname/snapshot/?" do content_type :json diff --git a/lib/vmpooler/pool_manager.rb b/lib/vmpooler/pool_manager.rb index eb9504f..065ec34 100644 --- a/lib/vmpooler/pool_manager.rb +++ b/lib/vmpooler/pool_manager.rb @@ -299,6 +299,47 @@ module Vmpooler end end + def create_vm_disk(vm, disk_size) + Thread.new do + _create_vm_disk(vm, disk_size) + end + end + + def _create_vm_disk(vm, disk_size) + host = $vsphere['disk_manager'].find_vm(vm) || + $vsphere['disk_manager'].find_vm_heavy(vm)[vm] + + if (host) && ((! disk_size.nil?) && (! disk_size.empty?) && (disk_size.to_i > 0)) + $logger.log('s', "[ ] [disk_manager] '#{vm}' is attaching a #{disk_size}gb disk") + + start = Time.now + + template = $redis.hget('vmpooler__vm__' + vm, 'template') + datastore = nil + + $config[:pools].each do |pool| + if pool['name'] == template + datastore = pool['datastore'] + end + end + + if ((! datastore.nil?) && (! datastore.empty?)) + $vsphere['disk_manager'].add_disk(host, disk_size, datastore) + + rdisks = $redis.hget('vmpooler__vm__' + vm, 'disk') + disks = rdisks ? rdisks.split(':') : [] + disks.push("+#{disk_size}gb") + $redis.hset('vmpooler__vm__' + vm, 'disk', disks.join(':')) + + finish = '%.2f' % (Time.now - start) + + $logger.log('s', "[+] [disk_manager] '#{vm}' attached #{disk_size}gb disk in #{finish} seconds") + else + $logger.log('s', "[+] [disk_manager] '#{vm}' failed to attach disk") + end + end + end + def create_vm_snapshot(vm, snapshot_name) Thread.new do _create_vm_snapshot(vm, snapshot_name) @@ -309,7 +350,7 @@ module Vmpooler host = $vsphere['snapshot_manager'].find_vm(vm) || $vsphere['snapshot_manager'].find_vm_heavy(vm)[vm] - if (host) && ((! snapshot_name.nil?) || (! snapshot_name.empty?)) + if (host) && ((! snapshot_name.nil?) && (! snapshot_name.empty?)) $logger.log('s', "[ ] [snapshot_manager] '#{vm}' is being snapshotted") start = Time.now @@ -356,6 +397,32 @@ module Vmpooler end end + def check_disk_queue + $logger.log('d', "[*] [disk_manager] starting worker thread") + + $vsphere['disk_manager'] ||= Vmpooler::VsphereHelper.new + + $threads['disk_manager'] = Thread.new do + loop do + _check_disk_queue + sleep(5) + end + end + end + + def _check_disk_queue + vm = $redis.spop('vmpooler__tasks__disk') + + unless vm.nil? + begin + vm_name, disk_size = vm.split(':') + create_vm_disk(vm_name, disk_size) + rescue + $logger.log('s', "[!] [disk_manager] disk creation appears to have failed") + end + end + end + def check_snapshot_queue $logger.log('d', "[*] [snapshot_manager] starting worker thread") @@ -544,6 +611,13 @@ module Vmpooler $redis.set('vmpooler__tasks__clone', 0) loop do + if ! $threads['disk_manager'] + check_disk_queue + elsif ! $threads['disk_manager'].alive? + $logger.log('d', "[!] [disk_manager] worker thread died, restarting") + check_disk_queue + end + if ! $threads['snapshot_manager'] check_snapshot_queue elsif ! $threads['snapshot_manager'].alive?