Support per-VM migration type selectors

Allow a VM to specify its migration type as a default choice. The valid
options are "default" (i.e. behave as now), "live" which forces a live
migration only, and "shutdown" which forces a shutdown migration only.
The new option is treated as a VM meta option and is set to default if
not found.
This commit is contained in:
Joshua Boniface 2020-10-29 11:31:32 -04:00
parent d2c0d868c4
commit ec0b8acf90
11 changed files with 236 additions and 61 deletions

View File

@ -0,0 +1,28 @@
"""PVC version 0.9.0
Revision ID: 3efe890e1d87
Revises: 3bc6117ea44d
Create Date: 2020-10-29 11:49:58.756626
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '3efe890e1d87'
down_revision = '3bc6117ea44d'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('system_template', sa.Column('migration_method', sa.Text(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('system_template', 'migration_method')
# ### end Alembic commands ###

View File

@ -834,6 +834,9 @@ class API_VM_Root(Resource):
node_autostart: node_autostart:
type: boolean type: boolean
description: Whether to autostart the VM when its node returns to ready domain state description: Whether to autostart the VM when its node returns to ready domain state
migration_method:
type: string
description: The preferred migration method (live, shutdown, none)
description: description:
type: string type: string
description: The description of the VM description: The description of the VM
@ -1036,6 +1039,7 @@ class API_VM_Root(Resource):
{ 'name': 'node' }, { 'name': 'node' },
{ 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" }, { 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" },
{ 'name': 'autostart' }, { 'name': 'autostart' },
{ 'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified" },
{ 'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified" }, { 'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified" },
]) ])
@Authenticator @Authenticator
@ -1077,6 +1081,16 @@ class API_VM_Root(Resource):
type: boolean type: boolean
required: false required: false
description: Whether to autostart the VM when its node returns to ready domain state description: Whether to autostart the VM when its node returns to ready domain state
- in: query
name: migration_method
type: string
required: false
description: The preferred migration method (live, shutdown, none)
default: none
enum:
- live
- shutdown
- none
responses: responses:
200: 200:
description: OK description: OK
@ -1094,7 +1108,8 @@ class API_VM_Root(Resource):
reqargs.get('node', None), reqargs.get('node', None),
reqargs.get('limit', None), reqargs.get('limit', None),
reqargs.get('selector', 'mem'), reqargs.get('selector', 'mem'),
bool(strtobool(reqargs.get('autostart', 'false'))) bool(strtobool(reqargs.get('autostart', 'false'))),
reqargs.get('migration_method', 'none')
) )
api.add_resource(API_VM_Root, '/vm') api.add_resource(API_VM_Root, '/vm')
@ -1125,6 +1140,7 @@ class API_VM_Element(Resource):
{ 'name': 'node' }, { 'name': 'node' },
{ 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" }, { 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" },
{ 'name': 'autostart' }, { 'name': 'autostart' },
{ 'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified" },
{ 'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified" }, { 'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified" },
]) ])
@Authenticator @Authenticator
@ -1168,6 +1184,16 @@ class API_VM_Element(Resource):
type: boolean type: boolean
required: false required: false
description: Whether to autostart the VM when its node returns to ready domain state description: Whether to autostart the VM when its node returns to ready domain state
- in: query
name: migration_method
type: string
required: false
description: The preferred migration method (live, shutdown, none)
default: none
enum:
- live
- shutdown
- none
responses: responses:
200: 200:
description: OK description: OK
@ -1185,7 +1211,8 @@ class API_VM_Element(Resource):
reqargs.get('node', None), reqargs.get('node', None),
reqargs.get('limit', None), reqargs.get('limit', None),
reqargs.get('selector', 'mem'), reqargs.get('selector', 'mem'),
bool(strtobool(reqargs.get('autostart', 'false'))) bool(strtobool(reqargs.get('autostart', 'false'))),
reqargs.get('migration_method', 'none')
) )
@RequestParser([ @RequestParser([
@ -1296,6 +1323,9 @@ class API_VM_Metadata(Resource):
node_autostart: node_autostart:
type: string type: string
description: Whether to autostart the VM when its node returns to ready domain state description: Whether to autostart the VM when its node returns to ready domain state
migration_method:
type: string
description: The preferred migration method (live, shutdown, none)
404: 404:
description: Not found description: Not found
schema: schema:
@ -1309,6 +1339,7 @@ class API_VM_Metadata(Resource):
{ 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" }, { 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" },
{ 'name': 'autostart' }, { 'name': 'autostart' },
{ 'name': 'profile' }, { 'name': 'profile' },
{ 'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified" },
]) ])
@Authenticator @Authenticator
def post(self, vm, reqargs): def post(self, vm, reqargs):
@ -1343,6 +1374,16 @@ class API_VM_Metadata(Resource):
type: string type: string
required: false required: false
description: The PVC provisioner profile for the VM description: The PVC provisioner profile for the VM
- in: query
name: migration_method
type: string
required: false
description: The preferred migration method (live, shutdown, none)
default: none
enum:
- live
- shutdown
- none
responses: responses:
200: 200:
description: OK description: OK
@ -1360,7 +1401,8 @@ class API_VM_Metadata(Resource):
reqargs.get('limit', None), reqargs.get('limit', None),
reqargs.get('selector', None), reqargs.get('selector', None),
reqargs.get('autostart', None), reqargs.get('autostart', None),
reqargs.get('profile', None) reqargs.get('profile', None),
reqargs.get('migration_method', None)
) )
api.add_resource(API_VM_Metadata, '/vm/<vm>/meta') api.add_resource(API_VM_Metadata, '/vm/<vm>/meta')
@ -4057,6 +4099,9 @@ class API_Provisioner_Template_System_Root(Resource):
node_autostart: node_autostart:
type: boolean type: boolean
description: Whether to start VM with node ready state (one-time) description: Whether to start VM with node ready state (one-time)
migration_method:
type: string
description: The preferred migration method (live, shutdown, none)
parameters: parameters:
- in: query - in: query
name: limit name: limit
@ -4084,7 +4129,8 @@ class API_Provisioner_Template_System_Root(Resource):
{ 'name': 'vnc_bind' }, { 'name': 'vnc_bind' },
{ 'name': 'node_limit' }, { 'name': 'node_limit' },
{ 'name': 'node_selector' }, { 'name': 'node_selector' },
{ 'name': 'node_autostart' } { 'name': 'node_autostart' },
{ 'name': 'migration_method' }
]) ])
@Authenticator @Authenticator
def post(self, reqargs): def post(self, reqargs):
@ -4139,6 +4185,11 @@ class API_Provisioner_Template_System_Root(Resource):
type: boolean type: boolean
required: false required: false
description: Whether to start VM with node ready state (one-time) description: Whether to start VM with node ready state (one-time)
- in: query
name: migration_method
type: string
required: false
description: The preferred migration method (live, shutdown, none)
responses: responses:
200: 200:
description: OK description: OK
@ -4185,7 +4236,8 @@ class API_Provisioner_Template_System_Root(Resource):
vnc_bind, vnc_bind,
reqargs.get('node_limit', None), reqargs.get('node_limit', None),
reqargs.get('node_selector', None), reqargs.get('node_selector', None),
node_autostart node_autostart,
reqargs.get('migration_method', None),
) )
api.add_resource(API_Provisioner_Template_System_Root, '/provisioner/template/system') api.add_resource(API_Provisioner_Template_System_Root, '/provisioner/template/system')
@ -4222,7 +4274,8 @@ class API_Provisioner_Template_System_Element(Resource):
{ 'name': 'vnc_bind' }, { 'name': 'vnc_bind' },
{ 'name': 'node_limit' }, { 'name': 'node_limit' },
{ 'name': 'node_selector' }, { 'name': 'node_selector' },
{ 'name': 'node_autostart' } { 'name': 'node_autostart' },
{ 'name': 'migration_method' }
]) ])
@Authenticator @Authenticator
def post(self, template, reqargs): def post(self, template, reqargs):
@ -4272,6 +4325,11 @@ class API_Provisioner_Template_System_Element(Resource):
type: boolean type: boolean
required: false required: false
description: Whether to start VM with node ready state (one-time) description: Whether to start VM with node ready state (one-time)
- in: query
name: migration_method
type: string
required: false
description: The preferred migration method (live, shutdown, none)
responses: responses:
200: 200:
description: OK description: OK
@ -4318,7 +4376,8 @@ class API_Provisioner_Template_System_Element(Resource):
vnc_bind, vnc_bind,
reqargs.get('node_limit', None), reqargs.get('node_limit', None),
reqargs.get('node_selector', None), reqargs.get('node_selector', None),
node_autostart node_autostart,
reqargs.get('migration_method', None),
) )
@RequestParser([ @RequestParser([
@ -4329,7 +4388,8 @@ class API_Provisioner_Template_System_Element(Resource):
{ 'name': 'vnc_bind' }, { 'name': 'vnc_bind' },
{ 'name': 'node_limit' }, { 'name': 'node_limit' },
{ 'name': 'node_selector' }, { 'name': 'node_selector' },
{ 'name': 'node_autostart' } { 'name': 'node_autostart' },
{ 'name': 'migration_method' }
]) ])
@Authenticator @Authenticator
def put(self, template, reqargs): def put(self, template, reqargs):
@ -4371,6 +4431,10 @@ class API_Provisioner_Template_System_Element(Resource):
name: node_autostart name: node_autostart
type: boolean type: boolean
description: Whether to start VM with node ready state (one-time) description: Whether to start VM with node ready state (one-time)
- in: query
name: migration_method
type: string
description: The preferred migration method (live, shutdown, none)
responses: responses:
200: 200:
description: OK description: OK
@ -4392,7 +4456,8 @@ class API_Provisioner_Template_System_Element(Resource):
reqargs.get('vnc_bind'), reqargs.get('vnc_bind'),
reqargs.get('node_limit', None), reqargs.get('node_limit', None),
reqargs.get('node_selector', None), reqargs.get('node_selector', None),
reqargs.get('node_autostart', None) reqargs.get('node_autostart', None),
reqargs.get('migration_method', None)
) )
@Authenticator @Authenticator

View File

@ -431,7 +431,7 @@ def vm_list(node=None, state=None, limit=None, is_fuzzy=True):
return retdata, retcode return retdata, retcode
def vm_define(xml, node, limit, selector, autostart): def vm_define(xml, node, limit, selector, autostart, migration_method):
""" """
Define a VM from Libvirt XML in the PVC cluster. Define a VM from Libvirt XML in the PVC cluster.
""" """
@ -443,7 +443,7 @@ def vm_define(xml, node, limit, selector, autostart):
return { 'message': 'XML is malformed or incorrect: {}'.format(e) }, 400 return { 'message': 'XML is malformed or incorrect: {}'.format(e) }, 400
zk_conn = pvc_common.startZKConnection(config['coordinators']) zk_conn = pvc_common.startZKConnection(config['coordinators'])
retflag, retdata = pvc_vm.define_vm(zk_conn, new_cfg, node, limit, selector, autostart, profile=None) retflag, retdata = pvc_vm.define_vm(zk_conn, new_cfg, node, limit, selector, autostart, migration_method, profile=None)
pvc_common.stopZKConnection(zk_conn) pvc_common.stopZKConnection(zk_conn)
if retflag: if retflag:
@ -475,7 +475,8 @@ def get_vm_meta(vm):
'name': vm, 'name': vm,
'node_limit': retdata['node_limit'], 'node_limit': retdata['node_limit'],
'node_selector': retdata['node_selector'], 'node_selector': retdata['node_selector'],
'node_autostart': retdata['node_autostart'] 'node_autostart': retdata['node_autostart'],
'migration_method': retdata['migration_method']
} }
else: else:
retcode = 404 retcode = 404
@ -490,7 +491,7 @@ def get_vm_meta(vm):
return retdata, retcode return retdata, retcode
def update_vm_meta(vm, limit, selector, autostart, provisioner_profile): def update_vm_meta(vm, limit, selector, autostart, provisioner_profile, migration_method):
""" """
Update metadata of a VM. Update metadata of a VM.
""" """
@ -500,7 +501,7 @@ def update_vm_meta(vm, limit, selector, autostart, provisioner_profile):
autostart = bool(strtobool(autostart)) autostart = bool(strtobool(autostart))
except: except:
autostart = False autostart = False
retflag, retdata = pvc_vm.modify_vm_metadata(zk_conn, vm, limit, selector, autostart, provisioner_profile) retflag, retdata = pvc_vm.modify_vm_metadata(zk_conn, vm, limit, selector, autostart, provisioner_profile, migration_method)
pvc_common.stopZKConnection(zk_conn) pvc_common.stopZKConnection(zk_conn)
if retflag: if retflag:

View File

@ -35,9 +35,10 @@ class DBSystemTemplate(db.Model):
node_limit = db.Column(db.Text) node_limit = db.Column(db.Text)
node_selector = db.Column(db.Text) node_selector = db.Column(db.Text)
node_autostart = db.Column(db.Boolean, nullable=False) node_autostart = db.Column(db.Boolean, nullable=False)
migration_method = db.Column(db.Text)
ova = db.Column(db.Integer, db.ForeignKey("ova.id"), nullable=True) ova = db.Column(db.Integer, db.ForeignKey("ova.id"), nullable=True)
def __init__(self, name, vcpu_count, vram_mb, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, ova=None): def __init__(self, name, vcpu_count, vram_mb, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, migration_method, ova=None):
self.name = name self.name = name
self.vcpu_count = vcpu_count self.vcpu_count = vcpu_count
self.vram_mb = vram_mb self.vram_mb = vram_mb
@ -47,6 +48,7 @@ class DBSystemTemplate(db.Model):
self.node_limit = node_limit self.node_limit = node_limit
self.node_selector = node_selector self.node_selector = node_selector
self.node_autostart = node_autostart self.node_autostart = node_autostart
self.migration_method = migration_method
self.ova = ova self.ova = ova
def __repr__(self): def __repr__(self):

View File

@ -214,14 +214,14 @@ def template_list(limit):
# #
# Template Create functions # Template Create functions
# #
def create_template_system(name, vcpu_count, vram_mb, serial=False, vnc=False, vnc_bind=None, node_limit=None, node_selector=None, node_autostart=False, ova=None): def create_template_system(name, vcpu_count, vram_mb, serial=False, vnc=False, vnc_bind=None, node_limit=None, node_selector=None, node_autostart=False, migration_method=None, ova=None):
if list_template_system(name, is_fuzzy=False)[-1] != 404: if list_template_system(name, is_fuzzy=False)[-1] != 404:
retmsg = { 'message': 'The system template "{}" already exists.'.format(name) } retmsg = { 'message': 'The system template "{}" already exists.'.format(name) }
retcode = 400 retcode = 400
return retmsg, retcode return retmsg, retcode
query = "INSERT INTO system_template (name, vcpu_count, vram_mb, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, ova) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s);" query = "INSERT INTO system_template (name, vcpu_count, vram_mb, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, migration_method, ova) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);"
args = (name, vcpu_count, vram_mb, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, ova) args = (name, vcpu_count, vram_mb, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, migration_method, ova)
conn, cur = open_database(config) conn, cur = open_database(config)
try: try:
@ -359,7 +359,7 @@ def create_template_storage_element(name, disk_id, pool, source_volume=None, dis
# #
# Template Modify functions # Template Modify functions
# #
def modify_template_system(name, vcpu_count=None, vram_mb=None, serial=None, vnc=None, vnc_bind=None, node_limit=None, node_selector=None, node_autostart=None): def modify_template_system(name, vcpu_count=None, vram_mb=None, serial=None, vnc=None, vnc_bind=None, node_limit=None, node_selector=None, node_autostart=None, migration_method=None):
if list_profile(name, is_fuzzy=False)[-1] != 200: if list_profile(name, is_fuzzy=False)[-1] != 200:
retmsg = { 'message': 'The system template "{}" does not exist.'.format(name) } retmsg = { 'message': 'The system template "{}" does not exist.'.format(name) }
retcode = 400 retcode = 400
@ -420,6 +420,9 @@ def modify_template_system(name, vcpu_count=None, vram_mb=None, serial=None, vnc
retcode = 400 retcode = 400
fields.append({'field': 'node_autostart', 'data': node_autostart}) fields.append({'field': 'node_autostart', 'data': node_autostart})
if migration_method is not None:
fields.append({'field': 'migration_method', 'data': migration_method})
conn, cur = open_database(config) conn, cur = open_database(config)
try: try:
for field in fields: for field in fields:
@ -1403,7 +1406,8 @@ def create_vm(self, vm_name, vm_profile, define_vm=True, start_vm=True, script_r
node_limit = node_limit.split(',') node_limit = node_limit.split(',')
node_selector = vm_data['system_details']['node_selector'] node_selector = vm_data['system_details']['node_selector']
node_autostart = vm_data['system_details']['node_autostart'] node_autostart = vm_data['system_details']['node_autostart']
retcode, retmsg = pvc_vm.define_vm(zk_conn, vm_schema.strip(), target_node, node_limit, node_selector, node_autostart, vm_profile, initial_state='provision') migration_method = vm_data['system_details']['migration_method']
retcode, retmsg = pvc_vm.define_vm(zk_conn, vm_schema.strip(), target_node, node_limit, node_selector, node_autostart, migration_method, vm_profile, initial_state='provision')
print(retmsg) print(retmsg)
else: else:
print("Skipping VM definition") print("Skipping VM definition")

View File

@ -717,9 +717,10 @@ def format_list_template_system(template_data):
template_serial_length = 7 template_serial_length = 7
template_vnc_length = 4 template_vnc_length = 4
template_vnc_bind_length = 10 template_vnc_bind_length = 10
template_node_limit_length = 9 template_node_limit_length = 6
template_node_selector_length = 11 template_node_selector_length = 9
template_node_autostart_length = 11 template_node_autostart_length = 10
template_migration_method_length = 10
for template in template_data: for template in template_data:
# template_name column # template_name column
@ -762,17 +763,22 @@ def format_list_template_system(template_data):
_template_node_autostart_length = len(str(template['node_autostart'])) + 1 _template_node_autostart_length = len(str(template['node_autostart'])) + 1
if _template_node_autostart_length > template_node_autostart_length: if _template_node_autostart_length > template_node_autostart_length:
template_node_autostart_length = _template_node_autostart_length template_node_autostart_length = _template_node_autostart_length
# template_migration_method column
_template_migration_method_length = len(str(template['migration_method'])) + 1
if _template_migration_method_length > template_migration_method_length:
template_migration_method_length = _template_migration_method_length
# Format the string (header) # Format the string (header)
template_list_output_header = '{bold}{template_name: <{template_name_length}} {template_id: <{template_id_length}} \ template_list_output_header = '{bold}{template_name: <{template_name_length}} {template_id: <{template_id_length}} \
{template_vcpu: <{template_vcpu_length}} \ {template_vcpu: <{template_vcpu_length}} \
{template_vram: <{template_vram_length}} \ {template_vram: <{template_vram_length}} \
Consoles: {template_serial: <{template_serial_length}} \ Console: {template_serial: <{template_serial_length}} \
{template_vnc: <{template_vnc_length}} \ {template_vnc: <{template_vnc_length}} \
{template_vnc_bind: <{template_vnc_bind_length}} \ {template_vnc_bind: <{template_vnc_bind_length}} \
Metadata: {template_node_limit: <{template_node_limit_length}} \ Meta: {template_node_limit: <{template_node_limit_length}} \
{template_node_selector: <{template_node_selector_length}} \ {template_node_selector: <{template_node_selector_length}} \
{template_node_autostart: <{template_node_autostart_length}}{end_bold}'.format( {template_node_autostart: <{template_node_autostart_length}} \
{template_migration_method: <{template_migration_method_length}}{end_bold}'.format(
template_name_length=template_name_length, template_name_length=template_name_length,
template_id_length=template_id_length, template_id_length=template_id_length,
template_vcpu_length=template_vcpu_length, template_vcpu_length=template_vcpu_length,
@ -783,6 +789,7 @@ Metadata: {template_node_limit: <{template_node_limit_length}} \
template_node_limit_length=template_node_limit_length, template_node_limit_length=template_node_limit_length,
template_node_selector_length=template_node_selector_length, template_node_selector_length=template_node_selector_length,
template_node_autostart_length=template_node_autostart_length, template_node_autostart_length=template_node_autostart_length,
template_migration_method_length=template_migration_method_length,
bold=ansiprint.bold(), bold=ansiprint.bold(),
end_bold=ansiprint.end(), end_bold=ansiprint.end(),
template_state_colour='', template_state_colour='',
@ -796,7 +803,8 @@ Metadata: {template_node_limit: <{template_node_limit_length}} \
template_vnc_bind='VNC bind', template_vnc_bind='VNC bind',
template_node_limit='Limit', template_node_limit='Limit',
template_node_selector='Selector', template_node_selector='Selector',
template_node_autostart='Autostart' template_node_autostart='Autostart',
template_migration_method='Migration'
) )
# Keep track of nets we found to be valid to cut down on duplicate API hits # Keep track of nets we found to be valid to cut down on duplicate API hits
@ -813,7 +821,8 @@ Metadata: {template_node_limit: <{template_node_limit_length}} \
{template_vnc_bind: <{template_vnc_bind_length}} \ {template_vnc_bind: <{template_vnc_bind_length}} \
{template_node_limit: <{template_node_limit_length}} \ {template_node_limit: <{template_node_limit_length}} \
{template_node_selector: <{template_node_selector_length}} \ {template_node_selector: <{template_node_selector_length}} \
{template_node_autostart: <{template_node_autostart_length}}{end_bold}'.format( {template_node_autostart: <{template_node_autostart_length}} \
{template_migration_method: <{template_migration_method_length}}{end_bold}'.format(
template_name_length=template_name_length, template_name_length=template_name_length,
template_id_length=template_id_length, template_id_length=template_id_length,
template_vcpu_length=template_vcpu_length, template_vcpu_length=template_vcpu_length,
@ -824,6 +833,7 @@ Metadata: {template_node_limit: <{template_node_limit_length}} \
template_node_limit_length=template_node_limit_length, template_node_limit_length=template_node_limit_length,
template_node_selector_length=template_node_selector_length, template_node_selector_length=template_node_selector_length,
template_node_autostart_length=template_node_autostart_length, template_node_autostart_length=template_node_autostart_length,
template_migration_method_length=template_migration_method_length,
bold='', bold='',
end_bold='', end_bold='',
template_name=str(template['name']), template_name=str(template['name']),
@ -835,7 +845,8 @@ Metadata: {template_node_limit: <{template_node_limit_length}} \
template_vnc_bind=str(template['vnc_bind']), template_vnc_bind=str(template['vnc_bind']),
template_node_limit=str(template['node_limit']), template_node_limit=str(template['node_limit']),
template_node_selector=str(template['node_selector']), template_node_selector=str(template['node_selector']),
template_node_autostart=str(template['node_autostart']) template_node_autostart=str(template['node_autostart']),
template_migration_method=str(template['migration_method'])
) )
) )

View File

@ -80,19 +80,20 @@ def vm_list(config, limit, target_node, target_state):
else: else:
return False, response.json().get('message', '') return False, response.json().get('message', '')
def vm_define(config, xml, node, node_limit, node_selector, node_autostart): def vm_define(config, xml, node, node_limit, node_selector, node_autostart, migration_method):
""" """
Define a new VM on the cluster Define a new VM on the cluster
API endpoint: POST /vm API endpoint: POST /vm
API arguments: xml={xml}, node={node}, limit={node_limit}, selector={node_selector}, autostart={node_autostart} API arguments: xml={xml}, node={node}, limit={node_limit}, selector={node_selector}, autostart={node_autostart}, migration_method={migration_method}
API schema: {"message":"{data}"} API schema: {"message":"{data}"}
""" """
params = { params = {
'node': node, 'node': node,
'limit': node_limit, 'limit': node_limit,
'selector': node_selector, 'selector': node_selector,
'autostart': node_autostart 'autostart': node_autostart,
'migration_method': migration_method
} }
data = { data = {
'xml': xml 'xml': xml
@ -129,12 +130,12 @@ def vm_modify(config, vm, xml, restart):
return retstatus, response.json().get('message', '') return retstatus, response.json().get('message', '')
def vm_metadata(config, vm, node_limit, node_selector, node_autostart, provisioner_profile): def vm_metadata(config, vm, node_limit, node_selector, node_autostart, migration_method, provisioner_profile):
""" """
Modify PVC metadata of a VM Modify PVC metadata of a VM
API endpoint: GET /vm/{vm}/meta, POST /vm/{vm}/meta API endpoint: GET /vm/{vm}/meta, POST /vm/{vm}/meta
API arguments: limit={node_limit}, selector={node_selector}, autostart={node_autostart}, profile={provisioner_profile} API arguments: limit={node_limit}, selector={node_selector}, autostart={node_autostart}, migration_method={migration_method} profile={provisioner_profile}
API schema: {"message":"{data}"} API schema: {"message":"{data}"}
""" """
params = dict() params = dict()
@ -149,6 +150,9 @@ def vm_metadata(config, vm, node_limit, node_selector, node_autostart, provision
if node_autostart is not None: if node_autostart is not None:
params['autostart'] = node_autostart params['autostart'] = node_autostart
if migration_method is not None:
params['migration_method'] = migration_method
if provisioner_profile is not None: if provisioner_profile is not None:
params['profile'] = provisioner_profile params['profile'] = provisioner_profile
@ -391,24 +395,30 @@ def format_info(config, domain_information, long_output):
ainformation.append('') ainformation.append('')
ainformation.append('{}Failure reason:{} {}'.format(ansiprint.purple(), ansiprint.end(), domain_information['failed_reason'])) ainformation.append('{}Failure reason:{} {}'.format(ansiprint.purple(), ansiprint.end(), domain_information['failed_reason']))
if not domain_information['node_selector']: if not domain_information.get('node_selector'):
formatted_node_selector = "False" formatted_node_selector = "False"
else: else:
formatted_node_selector = domain_information['node_selector'] formatted_node_selector = domain_information['node_selector']
if not domain_information['node_limit']: if not domain_information.get('node_limit'):
formatted_node_limit = "False" formatted_node_limit = "False"
else: else:
formatted_node_limit = ', '.join(domain_information['node_limit']) formatted_node_limit = ', '.join(domain_information['node_limit'])
if not domain_information['node_autostart']: if not domain_information.get('node_autostart'):
formatted_node_autostart = "False" formatted_node_autostart = "False"
else: else:
formatted_node_autostart = domain_information['node_autostart'] formatted_node_autostart = domain_information['node_autostart']
if not domain_information.get('migration_method'):
formatted_migration_method = "default"
else:
formatted_migration_method = domain_information['migration_method']
ainformation.append('{}Migration selector:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_selector)) ainformation.append('{}Migration selector:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_selector))
ainformation.append('{}Node limit:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_limit)) ainformation.append('{}Node limit:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_limit))
ainformation.append('{}Autostart:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_autostart)) ainformation.append('{}Autostart:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_autostart))
ainformation.append('{}Migration Method:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_migration_method))
# Network list # Network list
net_list = [] net_list = []

View File

@ -593,11 +593,16 @@ def cli_vm():
'-a/-A', '--autostart/--no-autostart', 'node_autostart', is_flag=True, default=False, '-a/-A', '--autostart/--no-autostart', 'node_autostart', is_flag=True, default=False,
help='Start VM automatically on next unflush/ready state of home node; unset by daemon once used.' help='Start VM automatically on next unflush/ready state of home node; unset by daemon once used.'
) )
@click.option(
'-m', '--method', 'migration_method', default='none', show_default=True,
type=click.Choice(['none','live','shutdown']),
help='The preferred migration method of the VM between nodes; saved with VM.'
)
@click.argument( @click.argument(
'vmconfig', type=click.File() 'vmconfig', type=click.File()
) )
@cluster_req @cluster_req
def vm_define(vmconfig, target_node, node_limit, node_selector, node_autostart): def vm_define(vmconfig, target_node, node_limit, node_selector, node_autostart, migration_method):
""" """
Define a new virtual machine from Libvirt XML configuration file VMCONFIG. Define a new virtual machine from Libvirt XML configuration file VMCONFIG.
""" """
@ -613,7 +618,7 @@ def vm_define(vmconfig, target_node, node_limit, node_selector, node_autostart):
except: except:
cleanup(False, 'Error: XML is malformed or invalid') cleanup(False, 'Error: XML is malformed or invalid')
retcode, retmsg = pvc_vm.vm_define(config, new_cfg, target_node, node_limit, node_selector, node_autostart) retcode, retmsg = pvc_vm.vm_define(config, new_cfg, target_node, node_limit, node_selector, node_autostart, migration_method)
cleanup(retcode, retmsg) cleanup(retcode, retmsg)
############################################################################### ###############################################################################
@ -633,6 +638,11 @@ def vm_define(vmconfig, target_node, node_limit, node_selector, node_autostart):
'-a/-A', '--autostart/--no-autostart', 'node_autostart', is_flag=True, default=None, '-a/-A', '--autostart/--no-autostart', 'node_autostart', is_flag=True, default=None,
help='Start VM automatically on next unflush/ready state of home node; unset by daemon once used.' help='Start VM automatically on next unflush/ready state of home node; unset by daemon once used.'
) )
@click.option(
'-m', '--method', 'migration_method', default='none', show_default=True,
type=click.Choice(['none','live','shutdown']),
help='The preferred migration method of the VM between nodes; saved with VM.'
)
@click.option( @click.option(
'-p', '--profile', 'provisioner_profile', default=None, show_default=False, '-p', '--profile', 'provisioner_profile', default=None, show_default=False,
help='PVC provisioner profile name for VM.' help='PVC provisioner profile name for VM.'
@ -641,15 +651,15 @@ def vm_define(vmconfig, target_node, node_limit, node_selector, node_autostart):
'domain' 'domain'
) )
@cluster_req @cluster_req
def vm_meta(domain, node_limit, node_selector, node_autostart, provisioner_profile): def vm_meta(domain, node_limit, node_selector, node_autostart, migration_method, provisioner_profile):
""" """
Modify the PVC metadata of existing virtual machine DOMAIN. At least one option to update must be specified. DOMAIN may be a UUID or name. Modify the PVC metadata of existing virtual machine DOMAIN. At least one option to update must be specified. DOMAIN may be a UUID or name.
""" """
if node_limit is None and node_selector is None and node_autostart is None and provisioner_profile is None: if node_limit is None and node_selector is None and node_autostart is None and migration_method is None and provisioner_profile is None:
cleanup(False, 'At least one metadata option must be specified to update.') cleanup(False, 'At least one metadata option must be specified to update.')
retcode, retmsg = pvc_vm.vm_metadata(config, domain, node_limit, node_selector, node_autostart, provisioner_profile) retcode, retmsg = pvc_vm.vm_metadata(config, domain, node_limit, node_selector, node_autostart, migration_method, provisioner_profile)
cleanup(retcode, retmsg) cleanup(retcode, retmsg)
############################################################################### ###############################################################################
@ -2301,8 +2311,14 @@ def provisioner_template_system_list(limit):
is_flag=True, default=False, is_flag=True, default=False,
help='Autostart VM with their parent Node on first/next boot.' help='Autostart VM with their parent Node on first/next boot.'
) )
@click.option(
'--migration-method', 'migration_method',
type=click.Choice(['none','live','shutdown'], case_sensitive=False),
default=None, # Use cluster default
help='The preferred migration method of the VM between nodes'
)
@cluster_req @cluster_req
def provisioner_template_system_add(name, vcpus, vram, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart): def provisioner_template_system_add(name, vcpus, vram, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, migration_method):
""" """
Add a new system template NAME to the PVC cluster provisioner. Add a new system template NAME to the PVC cluster provisioner.
""" """
@ -2320,6 +2336,8 @@ def provisioner_template_system_add(name, vcpus, vram, serial, vnc, vnc_bind, no
params['node_selector'] = node_selector params['node_selector'] = node_selector
if node_autostart: if node_autostart:
params['node_autostart'] = node_autostart params['node_autostart'] = node_autostart
if migration_method:
params['migration_method'] = migration_method
retcode, retdata = pvc_provisioner.template_add(config, params, template_type='system') retcode, retdata = pvc_provisioner.template_add(config, params, template_type='system')
cleanup(retcode, retdata) cleanup(retcode, retdata)
@ -2369,8 +2387,14 @@ def provisioner_template_system_add(name, vcpus, vram, serial, vnc, vnc_bind, no
is_flag=True, default=None, is_flag=True, default=None,
help='Autostart VM with their parent Node on first/next boot.' help='Autostart VM with their parent Node on first/next boot.'
) )
@click.option(
'--migration-method', 'migration_method',
type=click.Choice(['none','live','shutdown'], case_sensitive=False),
default=None, # Use cluster default
help='The preferred migration method of the VM between nodes'
)
@cluster_req @cluster_req
def provisioner_template_system_modify(name, vcpus, vram, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart): def provisioner_template_system_modify(name, vcpus, vram, serial, vnc, vnc_bind, node_limit, node_selector, node_autostart, migration_method):
""" """
Add a new system template NAME to the PVC cluster provisioner. Add a new system template NAME to the PVC cluster provisioner.
""" """
@ -2383,6 +2407,7 @@ def provisioner_template_system_modify(name, vcpus, vram, serial, vnc, vnc_bind,
params['node_limit'] = node_limit params['node_limit'] = node_limit
params['node_selector'] = node_selector params['node_selector'] = node_selector
params['node_autostart'] = node_autostart params['node_autostart'] = node_autostart
params['migration_method'] = migration_method
retcode, retdata = pvc_provisioner.template_modify(config, params, name, template_type='system') retcode, retdata = pvc_provisioner.template_modify(config, params, name, template_type='system')
cleanup(retcode, retdata) cleanup(retcode, retdata)

View File

@ -236,6 +236,10 @@ def getInformationFromXML(zk_conn, uuid):
domain_node_autostart = zkhandler.readdata(zk_conn, '/domains/{}/node_autostart'.format(uuid)) domain_node_autostart = zkhandler.readdata(zk_conn, '/domains/{}/node_autostart'.format(uuid))
except: except:
domain_node_autostart = None domain_node_autostart = None
try:
domain_migration_method = zkhandler.readdata(zk_conn, '/domains/{}/migration_method'.format(uuid))
except:
domain_migration_method = None
if not domain_node_limit: if not domain_node_limit:
domain_node_limit = None domain_node_limit = None
@ -282,6 +286,7 @@ def getInformationFromXML(zk_conn, uuid):
'node_limit': domain_node_limit, 'node_limit': domain_node_limit,
'node_selector': domain_node_selector, 'node_selector': domain_node_selector,
'node_autostart': bool(strtobool(domain_node_autostart)), 'node_autostart': bool(strtobool(domain_node_autostart)),
'migration_method': domain_migration_method,
'description': domain_description, 'description': domain_description,
'profile': domain_profile, 'profile': domain_profile,
'memory': int(domain_memory), 'memory': int(domain_memory),

View File

@ -157,7 +157,7 @@ def flush_locks(zk_conn, domain):
return success, message return success, message
def define_vm(zk_conn, config_data, target_node, node_limit, node_selector, node_autostart, profile=None, initial_state='stop'): def define_vm(zk_conn, config_data, target_node, node_limit, node_selector, node_autostart, migration_method=None, profile=None, initial_state='stop'):
# Parse the XML data # Parse the XML data
try: try:
parsed_xml = lxml.objectify.fromstring(config_data) parsed_xml = lxml.objectify.fromstring(config_data)
@ -206,6 +206,7 @@ def define_vm(zk_conn, config_data, target_node, node_limit, node_selector, node
'/domains/{}/node_limit'.format(dom_uuid): formatted_node_limit, '/domains/{}/node_limit'.format(dom_uuid): formatted_node_limit,
'/domains/{}/node_selector'.format(dom_uuid): node_selector, '/domains/{}/node_selector'.format(dom_uuid): node_selector,
'/domains/{}/node_autostart'.format(dom_uuid): node_autostart, '/domains/{}/node_autostart'.format(dom_uuid): node_autostart,
'/domains/{}/migration_method'.format(dom_uuid): migration_method,
'/domains/{}/failedreason'.format(dom_uuid): '', '/domains/{}/failedreason'.format(dom_uuid): '',
'/domains/{}/consolelog'.format(dom_uuid): '', '/domains/{}/consolelog'.format(dom_uuid): '',
'/domains/{}/rbdlist'.format(dom_uuid): formatted_rbd_list, '/domains/{}/rbdlist'.format(dom_uuid): formatted_rbd_list,
@ -215,7 +216,7 @@ def define_vm(zk_conn, config_data, target_node, node_limit, node_selector, node
return True, 'Added new VM with Name "{}" and UUID "{}" to database.'.format(dom_name, dom_uuid) return True, 'Added new VM with Name "{}" and UUID "{}" to database.'.format(dom_name, dom_uuid)
def modify_vm_metadata(zk_conn, domain, node_limit, node_selector, node_autostart, provisioner_profile): def modify_vm_metadata(zk_conn, domain, node_limit, node_selector, node_autostart, provisioner_profile, migration_method):
dom_uuid = getDomainUUID(zk_conn, domain) dom_uuid = getDomainUUID(zk_conn, domain)
if not dom_uuid: if not dom_uuid:
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain) return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
@ -240,6 +241,11 @@ def modify_vm_metadata(zk_conn, domain, node_limit, node_selector, node_autostar
'/domains/{}/profile'.format(dom_uuid): provisioner_profile '/domains/{}/profile'.format(dom_uuid): provisioner_profile
}) })
if migration_method is not None:
zkhandler.writedata(zk_conn, {
'/domains/{}/migration_method'.format(dom_uuid): migration_method
})
return True, 'Successfully modified PVC metadata of VM "{}".'.format(domain) return True, 'Successfully modified PVC metadata of VM "{}".'.format(domain)
def modify_vm(zk_conn, domain, restart, new_vm_config): def modify_vm(zk_conn, domain, restart, new_vm_config):
@ -781,9 +787,15 @@ def format_info(zk_conn, domain_information, long_output):
else: else:
formatted_node_autostart = domain_information['node_autostart'] formatted_node_autostart = domain_information['node_autostart']
if not domain_information['migration_method']:
formatted_migration_method = "False"
else:
formatted_migration_method = domain_information['migration_method']
ainformation.append('{}Migration selector:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_selector)) ainformation.append('{}Migration selector:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_selector))
ainformation.append('{}Node limit:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_limit)) ainformation.append('{}Node limit:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_limit))
ainformation.append('{}Autostart:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_autostart)) ainformation.append('{}Autostart:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_autostart))
ainformation.append('{}Migration Method:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_migration_method))
# Network list # Network list
net_list = [] net_list = []

View File

@ -113,7 +113,11 @@ class VMInstance(object):
try: try:
self.pinpolicy = zkhandler.readdata(self.zk_conn, '/domains/{}/pinpolicy'.format(self.domuuid)) self.pinpolicy = zkhandler.readdata(self.zk_conn, '/domains/{}/pinpolicy'.format(self.domuuid))
except: except:
self.pinpolicy = "None" self.pinpolicy = "none"
try:
self.migration_method = zkhandler.readdata(self.zk_conn, '/domains/{}/migration_method'.format(self.domuuid))
except:
self.migration_method = 'none'
# These will all be set later # These will all be set later
self.instart = False self.instart = False
@ -349,11 +353,16 @@ class VMInstance(object):
zkhandler.writedata(self.zk_conn, { '/domains/{}/state'.format(self.domuuid): 'start' }) zkhandler.writedata(self.zk_conn, { '/domains/{}/state'.format(self.domuuid): 'start' })
# Migrate the VM to a target host # Migrate the VM to a target host
def migrate_vm(self, force_live=False): def migrate_vm(self, force_live=False, force_shutdown=False):
# Wait for any previous migration # Wait for any previous migration
while self.inmigrate: while self.inmigrate:
time.sleep(0.1) time.sleep(0.1)
if self.migration_method == 'live':
force_live = True
elif self.migration_method == 'shutdown':
force_shutdown = True
self.inmigrate = True self.inmigrate = True
self.logger.out('Migrating VM to node "{}"'.format(self.node), state='i', prefix='Domain {}'.format(self.domuuid)) self.logger.out('Migrating VM to node "{}"'.format(self.node), state='i', prefix='Domain {}'.format(self.domuuid))
@ -466,6 +475,7 @@ class VMInstance(object):
abort_migrate('Target node changed during preparation') abort_migrate('Target node changed during preparation')
return return
if not force_shutdown:
# A live migrate is attemped 3 times in succession # A live migrate is attemped 3 times in succession
ticks = 0 ticks = 0
while True: while True:
@ -477,6 +487,8 @@ class VMInstance(object):
time.sleep(0.5) time.sleep(0.5)
if ticks > 2: if ticks > 2:
break break
else:
migrate_live_result = False
if not migrate_live_result: if not migrate_live_result:
if force_live: if force_live: