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:
2021-09-12 15:41:05 -04:00
parent 46f1d761f6
commit e962743e51
6 changed files with 500 additions and 83 deletions

View File

@ -105,14 +105,14 @@ def getDomainName(zkhandler, domain):
# Helper functions
#
def change_state(zkhandler, dom_uuid, new_state):
lock = zkhandler.exclusivelock(('domain.state', dom_uuid))
with lock:
zkhandler.write([
(('domain.state', dom_uuid), new_state)
])
lock = zkhandler.exclusivelock(('domain.state', dom_uuid))
with lock:
zkhandler.write([
(('domain.state', dom_uuid), new_state)
])
# Wait for 1/2 second to allow state to flow to all nodes
time.sleep(0.5)
# Wait for 1/2 second to allow state to flow to all nodes
time.sleep(0.5)
#
@ -262,6 +262,94 @@ def define_vm(zkhandler, config_data, target_node, node_limit, node_selector, no
return True, 'Added new VM with Name "{}" and UUID "{}" to database.'.format(dom_name, dom_uuid)
def attach_vm_device(zkhandler, domain, device_spec_xml):
# Validate that VM exists in cluster
dom_uuid = getDomainUUID(zkhandler, domain)
if not dom_uuid:
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Verify that the VM is in a stopped state; freeing locks is not safe otherwise
state = zkhandler.read(('domain.state', dom_uuid))
if state != 'start':
return False, 'ERROR: VM "{}" is not in started state; live-add unneccessary.'.format(domain)
# Tell the cluster to attach the device
attach_device_string = 'attach_device {} {}'.format(dom_uuid, device_spec_xml)
zkhandler.write([
('base.cmd.domain', attach_device_string)
])
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock('base.cmd.domain')
with lock:
try:
result = zkhandler.read('base.cmd.domain').split()[0]
if result == 'success-attach_device':
message = 'Attached device on VM "{}"'.format(domain)
success = True
else:
message = 'ERROR: Failed to attach device on VM "{}"; check node logs for details.'.format(domain)
success = False
except Exception:
message = 'ERROR: Command ignored by node.'
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock('base.cmd.domain')
with lock:
time.sleep(0.5)
zkhandler.write([
('base.cmd.domain', '')
])
return success, message
def detach_vm_device(zkhandler, domain, device_spec_xml):
# Validate that VM exists in cluster
dom_uuid = getDomainUUID(zkhandler, domain)
if not dom_uuid:
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Verify that the VM is in a stopped state; freeing locks is not safe otherwise
state = zkhandler.read(('domain.state', dom_uuid))
if state != 'start':
return False, 'ERROR: VM "{}" is not in started state; live-add unneccessary.'.format(domain)
# Tell the cluster to detach the device
detach_device_string = 'detach_device {} {}'.format(dom_uuid, device_spec_xml)
zkhandler.write([
('base.cmd.domain', detach_device_string)
])
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock('base.cmd.domain')
with lock:
try:
result = zkhandler.read('base.cmd.domain').split()[0]
if result == 'success-detach_device':
message = 'Attached device on VM "{}"'.format(domain)
success = True
else:
message = 'ERROR: Failed to detach device on VM "{}"; check node logs for details.'.format(domain)
success = False
except Exception:
message = 'ERROR: Command ignored by node.'
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock('base.cmd.domain')
with lock:
time.sleep(0.5)
zkhandler.write([
('base.cmd.domain', '')
])
return success, message
def modify_vm_metadata(zkhandler, domain, node_limit, node_selector, node_autostart, provisioner_profile, migration_method):
dom_uuid = getDomainUUID(zkhandler, domain)
if not dom_uuid: