Add VM device hot attach/detach support
Adds a new API endpoint to support hot attach/detach of devices, and the corresponding client-side logic to use this endpoint when doing VM network/storage add/remove actions. The live attach is now the default behaviour for these types of additions and removals, and can be disabled if needed. Closes #141
This commit is contained in:
@ -28,6 +28,8 @@ from threading import Thread
|
||||
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from re import match
|
||||
|
||||
import daemon_lib.common as common
|
||||
|
||||
import pvcnoded.objects.VMConsoleWatcherInstance as VMConsoleWatcherInstance
|
||||
@ -163,6 +165,34 @@ class VMInstance(object):
|
||||
(('domain.console.vnc', self.domuuid), '')
|
||||
])
|
||||
|
||||
# Attach a device to the running domain
|
||||
def attach_device(self, xml_spec):
|
||||
if not self.dom:
|
||||
self.logger.out('Cannot attach device to non-running domain', state='w', prefix='Domain {}'.format(self.domuuid))
|
||||
return False
|
||||
|
||||
try:
|
||||
self.logger.out('Attaching new device to VM', state='i', prefix='Domain {}'.format(self.domuuid))
|
||||
self.dom.attachDevice(xml_spec)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.out('Failed to attach device: {}'.format(e), state='e', prefix='Domain {}'.format(self.domuuid))
|
||||
return False
|
||||
|
||||
# Detach a device from the running domain
|
||||
def detach_device(self, xml_spec):
|
||||
if not self.dom:
|
||||
self.logger.out('Cannot detach device from non-running domain', state='w', prefix='Domain {}'.format(self.domuuid))
|
||||
return False
|
||||
|
||||
try:
|
||||
self.logger.out('Detaching device from VM', state='i', prefix='Domain {}'.format(self.domuuid))
|
||||
self.dom.detachDevice(xml_spec)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.out('Failed to detach device: {}'.format(e), state='e', prefix='Domain {}'.format(self.domuuid))
|
||||
return False
|
||||
|
||||
# Start up the VM
|
||||
def start_vm(self):
|
||||
# Start the log watcher
|
||||
@ -851,30 +881,51 @@ class VMInstance(object):
|
||||
# Primary command function
|
||||
def vm_command(zkhandler, logger, this_node, data):
|
||||
# Get the command and args
|
||||
command, args = data.split()
|
||||
command, dom_uuid, *args = data.split()
|
||||
|
||||
# Flushing VM RBD locks
|
||||
if command == 'flush_locks':
|
||||
dom_uuid = args
|
||||
if match('success-.*', command) or match('failure-.*', command):
|
||||
return
|
||||
|
||||
# Verify that the VM is set to run on this node
|
||||
if this_node.d_domain[dom_uuid].getnode() == this_node.name:
|
||||
# Lock the command queue
|
||||
zk_lock = zkhandler.writelock('base.cmd.domain')
|
||||
with zk_lock:
|
||||
# Flush the lock
|
||||
result = VMInstance.flush_locks(zkhandler, logger, dom_uuid, this_node)
|
||||
# Command succeeded
|
||||
if result:
|
||||
# Update the command queue
|
||||
zkhandler.write([
|
||||
('base.cmd.domain', 'success-{}'.format(data))
|
||||
])
|
||||
# Command failed
|
||||
else:
|
||||
# Update the command queue
|
||||
zkhandler.write([
|
||||
('base.cmd.domain', 'failure-{}'.format(data))
|
||||
])
|
||||
# Wait 1 seconds before we free the lock, to ensure the client hits the lock
|
||||
time.sleep(1)
|
||||
logger.out('Getting command "{}" for domain "{}"'.format(command, dom_uuid), state='i')
|
||||
|
||||
# Verify that the VM is set to run on this node
|
||||
domain = this_node.d_domain.get(dom_uuid, None)
|
||||
if domain is None:
|
||||
return False
|
||||
|
||||
if domain.getnode() != this_node.name:
|
||||
return
|
||||
|
||||
# Lock the command queue
|
||||
zk_lock = zkhandler.writelock('base.cmd.domain')
|
||||
with zk_lock:
|
||||
# Flushing VM RBD locks
|
||||
if command == 'flush_locks':
|
||||
result = VMInstance.flush_locks(zkhandler, logger, dom_uuid, this_node)
|
||||
# Attaching a device
|
||||
elif command == 'attach_device':
|
||||
xml_spec = ' '.join(args)
|
||||
result = domain.attach_device(xml_spec)
|
||||
# Detaching a device
|
||||
elif command == 'detach_device':
|
||||
xml_spec = ' '.join(args)
|
||||
result = domain.detach_device(xml_spec)
|
||||
# Command not defined
|
||||
else:
|
||||
result = False
|
||||
|
||||
# Command succeeded
|
||||
if result:
|
||||
# Update the command queue
|
||||
zkhandler.write([
|
||||
('base.cmd.domain', 'success-{}'.format(data))
|
||||
])
|
||||
# Command failed
|
||||
else:
|
||||
# Update the command queue
|
||||
zkhandler.write([
|
||||
('base.cmd.domain', 'failure-{}'.format(data))
|
||||
])
|
||||
|
||||
# Wait 1 seconds before we free the lock, to ensure the client hits the lock
|
||||
time.sleep(1)
|
||||
|
Reference in New Issue
Block a user