[libvirt] [Libvirt-test-API][PATCH 1/6] add createWithFlags() API and modify migrate() function to align with libvirt Python migration definition

--- lib/domainAPI.py | 57 ++++++++++++++++++++++++++++++++++++------------------ 1 files changed, 38 insertions(+), 19 deletions(-) diff --git a/lib/domainAPI.py b/lib/domainAPI.py index 0c725f8..5667c20 100644 --- a/lib/domainAPI.py +++ b/lib/domainAPI.py @@ -40,18 +40,6 @@ append_path(result.group(0)) import exception -# DomainMigrateFlags -VIR_MIGRATE_LIVE = 1 - -# DomainState -VIR_DOMAIN_NOSTATE = 0 -VIR_DOMAIN_RUNNING = 1 -VIR_DOMAIN_BLOCKED = 2 -VIR_DOMAIN_PAUSED = 3 -VIR_DOMAIN_SHUTDOWN = 4 -VIR_DOMAIN_SHUTOFF = 5 -VIR_DOMAIN_CRASHED = 6 - class DomainAPI(object): def __init__(self, connection): self.conn = connection @@ -182,6 +170,16 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code) + def start_with_flags(self, domname, flags = 0): + try: + dom_obj = self.get_defined_obj(domname) + retval = dom_obj.createWithFlags(flags) + return retval + except libvirt.libvirtError, e: + message = e.get_error_message() + code = e.get_error_code() + raise exception.LibvirtAPI(message, code) + def suspend(self, domname): try: dom_obj = self.get_domain_by_name(domname) @@ -223,10 +221,10 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code) - def migrate(self, domname, dconn, flags = VIR_MIGRATE_LIVE): + def migrate(self, domname, dconn, flags, dname = None, uri = None, bandwidth = 0): try: dom_obj = self.get_domain_by_name(domname) - retval = dom_obj.migrate(dconn, flags, None, None, 0) + retval = dom_obj.migrate(dconn, flags, dname, uri, 0) return retval except libvirt.libvirtError, e: message = e.get_error_message() @@ -484,7 +482,7 @@ class DomainAPI(object): dom_obj = self.get_domain_by_name(domname) state = dom_obj.info() if state[0] == VIR_DOMAIN_NOSTATE: - dom_state = 'no state' + dom_state = 'nostate' elif state[0] == VIR_DOMAIN_RUNNING: dom_state = 'running' elif state[0] == VIR_DOMAIN_BLOCKED: @@ -581,7 +579,7 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code) - def isActive(self, domname): + def is_active(self, domname): try: dom_obj = self.get_domain_by_name(domname) return dom_obj.isActive() @@ -590,7 +588,7 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code) - def isPersistent(self, domname): + def is_persistent(self, domname): try: dom_obj = self.get_domain_by_name(domname) return dom_obj.isPersistent() @@ -662,10 +660,10 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code) - def migrate_to_uri(self, domname, duri, dname, bandwidth, flag = 0): + def migrate_to_uri(self, domname, duri, flags, dname = None, bandwidth = 0): try: dom_obj = self.get_domain_by_name(domname) - return dom_obj.migrateToURI(duri, dname, bandwidth, flag) + return dom_obj.migrateToURI(duri, flags, dname, bandwidth) except libvirt.libvirtError, e: message = e.get_error_message() code = e.get_error_code() @@ -761,3 +759,24 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code) + +# DomainState +VIR_DOMAIN_NOSTATE = 0 +VIR_DOMAIN_RUNNING = 1 +VIR_DOMAIN_BLOCKED = 2 +VIR_DOMAIN_PAUSED = 3 +VIR_DOMAIN_SHUTDOWN = 4 +VIR_DOMAIN_SHUTOFF = 5 +VIR_DOMAIN_CRASHED = 6 + + +# virDomainMigrateFlags +VIR_MIGRATE_LIVE = 1 +VIR_MIGRATE_PEER2PEER = 2 +VIR_MIGRATE_TUNNELLED = 4 +VIR_MIGRATE_PERSIST_DEST = 8 +VIR_MIGRATE_UNDEFINE_SOURCE = 16 +VIR_MIGRATE_PAUSED = 32 +VIR_MIGRATE_NON_SHARED_DISK = 64 +VIR_MIGRATE_NON_SHARED_INC = 128 + -- 1.7.1

--- repos/domain/create.py | 53 ++++++++++++++++++++++++++++++++++++----------- 1 files changed, 40 insertions(+), 13 deletions(-) diff --git a/repos/domain/create.py b/repos/domain/create.py index 343aba2..a06a2d3 100644 --- a/repos/domain/create.py +++ b/repos/domain/create.py @@ -12,6 +12,7 @@ nicmodel ifacetype source + flags """ __author__ = 'Guannan Ren: gren@redhat.com' @@ -42,6 +43,9 @@ from utils.Python import utils from utils.Python import xmlbuilder from exception import LibvirtAPI +NONE = 0 +START_PAUSED = 1 + def usage(): print '''usage: mandatory arguments:guesttype guestname @@ -54,8 +58,14 @@ def usage(): nicmodel ifacetype source + flags ''' +def return_close(conn, logger, ret): + conn.close() + logger.info("closed hypervisor connection") + return ret + def check_params(params): """Verify inputing parameter dictionary""" logger = params['logger'] @@ -78,6 +88,13 @@ def create(params): guestname = params['guestname'] test_result = False + flags = None + if params.has_key('flags'): + flags = params['flags'] + if flags != "none" and flags != "start_paused": + logger.error("flags value either \"none\" or \"start_paused\""); + return 1 + # Connect to local hypervisor connection URI util = utils.Utils() uri = util.get_uri('127.0.0.1') @@ -98,16 +115,26 @@ def create(params): # Create domain from xml try: - try: - domobj.create(domxml) - except LibvirtAPI, e: - logger.error("API error message: %s, error code is %s" % - (e.response()['message'], e.response()['code'])) - logger.error("fail to create domain %s" % guestname) - return 1 - finally: - conn.close() - logger.info("closed hypervisor connection") + if not flags or flags == "none": + domobj.create(domxml, NONE) + elif flags == "start_paused": + domobj.create(domxml, START_PAUSED) + else: + logger.error("flags error") + except LibvirtAPI, e: + logger.error("API error message: %s, error code is %s" % + (e.response()['message'], e.response()['code'])) + logger.error("fail to create domain %s" % guestname) + return return_close(conn, logger, 1) + + if flags == "start_paused": + state = domobj.get_state(guestname) + if state == "paused": + logger.info("guest start with state paused successfully") + return return_close(conn, logger, 0) + else: + logger.error("guest state error") + return return_close(conn, logger, 1) logger.info("get the mac address of vm %s" % guestname) mac = util.get_dom_mac_addr(guestname) @@ -132,9 +159,9 @@ def create(params): if timeout == 0: logger.info("fail to power on vm %s" % guestname) test_result = False - return 1 + return return_close(conn, logger, 1) if test_result: - return 0 + return return_close(conn, logger, 0) else: - return 1 + return return_close(conn, logger, 1) -- 1.7.1

On 06/28/2011 01:54 PM, Guannan.ren wrote:
--- repos/domain/create.py | 53 ++++++++++++++++++++++++++++++++++++----------- 1 files changed, 40 insertions(+), 13 deletions(-)
diff --git a/repos/domain/create.py b/repos/domain/create.py index 343aba2..a06a2d3 100644 --- a/repos/domain/create.py +++ b/repos/domain/create.py @@ -12,6 +12,7 @@ nicmodel ifacetype source + flags """
__author__ = 'Guannan Ren:gren@redhat.com' @@ -42,6 +43,9 @@ from utils.Python import utils from utils.Python import xmlbuilder from exception import LibvirtAPI
+NONE = 0 +START_PAUSED = 1 You may forget to set a variable for the VIR_DOMAIN_START_AUTODESTROY flag.
START_AUTODESTORY = 2
+ def usage(): print '''usage: mandatory arguments:guesttype guestname @@ -54,8 +58,14 @@ def usage(): nicmodel ifacetype source + flags '''
+def return_close(conn, logger, ret): + conn.close() + logger.info("closed hypervisor connection") + return ret + def check_params(params): """Verify inputing parameter dictionary""" logger = params['logger'] @@ -78,6 +88,13 @@ def create(params): guestname = params['guestname'] test_result = False
+ flags = None + if params.has_key('flags'): + flags = params['flags'] + if flags != "none" and flags != "start_paused": + logger.error("flags value either \"none\" or \"start_paused\""); + return 1 + # Connect to local hypervisor connection URI util = utils.Utils() uri = util.get_uri('127.0.0.1') @@ -98,16 +115,26 @@ def create(params):
# Create domain from xml try: - try: - domobj.create(domxml) - except LibvirtAPI, e: - logger.error("API error message: %s, error code is %s" % - (e.response()['message'], e.response()['code'])) - logger.error("fail to create domain %s" % guestname) - return 1 - finally: - conn.close() - logger.info("closed hypervisor connection") + if not flags or flags == "none": + domobj.create(domxml, NONE) + elif flags == "start_paused": + domobj.create(domxml, START_PAUSED) + else: + logger.error("flags error") Also, needs to add a condition here for the flag which value is 2. + except LibvirtAPI, e: + logger.error("API error message: %s, error code is %s" % + (e.response()['message'], e.response()['code'])) + logger.error("fail to create domain %s" % guestname) + return return_close(conn, logger, 1) + + if flags == "start_paused": + state = domobj.get_state(guestname) + if state == "paused": + logger.info("guest start with state paused successfully") + return return_close(conn, logger, 0) + else: + logger.error("guest state error") + return return_close(conn, logger, 1)
logger.info("get the mac address of vm %s" % guestname) mac = util.get_dom_mac_addr(guestname) @@ -132,9 +159,9 @@ def create(params): if timeout == 0: logger.info("fail to power on vm %s" % guestname) test_result = False - return 1 + return return_close(conn, logger, 1)
if test_result: - return 0 + return return_close(conn, logger, 0) else: - return 1 + return return_close(conn, logger, 1)

--- repos/domain/start.py | 32 ++++++++++++++++++++++++++++++-- 1 files changed, 30 insertions(+), 2 deletions(-) diff --git a/repos/domain/start.py b/repos/domain/start.py index 34906e5..39ac47f 100644 --- a/repos/domain/start.py +++ b/repos/domain/start.py @@ -31,6 +31,9 @@ from lib import domainAPI from utils.Python import utils from exception import LibvirtAPI +NONE = 0 +START_PAUSED = 1 + def return_close(conn, logger, ret): conn.close() logger.info("closed hypervisor connection") @@ -62,7 +65,8 @@ def start(params): {'logger': logger, 'guestname': guestname} logger -- an object of utils/Python/log.py - guestname -- same as the domain name + mandatory arguments : guestname -- same as the domain name + optional arguments : flags -- domain create flags <none|start_paused> Return 0 on SUCCESS or 1 on FAILURE """ @@ -72,6 +76,13 @@ def start(params): domname = params['guestname'] logger = params['logger'] + flags = None + if params.has_key('flags'): + flags = params['flags'] + if flags != 'none' and flags != 'start_paused': + logger.error("flags value either \"none\" or \"start_paused\""); + return 1 + # Connect to local hypervisor connection URI util = utils.Utils() uri = util.get_uri('127.0.0.1') @@ -84,12 +95,29 @@ def start(params): logger.info('start domain') try: - dom_obj.start(domname) + if flags == "none": + dom_obj.start_with_flags(domname, NONE) + elif flags == "start_paused": + dom_obj.start_with_flags(domname, START_PAUSED) + elif not flags: + dom_obj.start(domname) + else: + logger.error("flags error") + return (conn, logger, 1) except LibvirtAPI, e: logger.error(str(e)) logger.error("start failed") return return_close(conn, logger, 1) + if flags == "start_paused": + state = dom_obj.get_state(domname) + if state == "paused": + logger.info("guest start with state paused successfully") + return return_close(conn, logger, 0) + else: + logger.error("guest state error") + return return_close(conn, logger, 1) + while timeout: time.sleep(10) timeout -= 10 -- 1.7.1

On 06/28/2011 01:54 PM, Guannan.ren wrote:
--- repos/domain/start.py | 32 ++++++++++++++++++++++++++++++-- 1 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/repos/domain/start.py b/repos/domain/start.py index 34906e5..39ac47f 100644 --- a/repos/domain/start.py +++ b/repos/domain/start.py @@ -31,6 +31,9 @@ from lib import domainAPI from utils.Python import utils from exception import LibvirtAPI
+NONE = 0 +START_PAUSED = 1 + def return_close(conn, logger, ret): conn.close() logger.info("closed hypervisor connection") @@ -62,7 +65,8 @@ def start(params): {'logger': logger, 'guestname': guestname}
logger -- an object of utils/Python/log.py - guestname -- same as the domain name + mandatory arguments : guestname -- same as the domain name + optional arguments : flags -- domain create flags<none|start_paused>
Return 0 on SUCCESS or 1 on FAILURE """ @@ -72,6 +76,13 @@ def start(params): domname = params['guestname'] logger = params['logger']
+ flags = None + if params.has_key('flags'): + flags = params['flags'] + if flags != 'none' and flags != 'start_paused': + logger.error("flags value either \"none\" or \"start_paused\""); + return 1 + # Connect to local hypervisor connection URI util = utils.Utils() uri = util.get_uri('127.0.0.1') @@ -84,12 +95,29 @@ def start(params): logger.info('start domain')
try: - dom_obj.start(domname) + if flags == "none": + dom_obj.start_with_flags(domname, NONE) + elif flags == "start_paused": + dom_obj.start_with_flags(domname, START_PAUSED) + elif not flags: + dom_obj.start(domname) + else: + logger.error("flags error") + return (conn, logger, 1) except LibvirtAPI, e: logger.error(str(e)) logger.error("start failed") return return_close(conn, logger, 1)
+ if flags == "start_paused": + state = dom_obj.get_state(domname) + if state == "paused": + logger.info("guest start with state paused successfully") + return return_close(conn, logger, 0) + else: + logger.error("guest state error") + return return_close(conn, logger, 1) + while timeout: time.sleep(10) timeout -= 10 The same issue with [PATCH 2/6].
- nzhang

--- repos/domain/migrate.py | 335 +++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 335 insertions(+), 0 deletions(-) create mode 100644 repos/domain/migrate.py diff --git a/repos/domain/migrate.py b/repos/domain/migrate.py new file mode 100644 index 0000000..3f80bd0 --- /dev/null +++ b/repos/domain/migrate.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python +"""this script is for migration testing + domain:migrate + target_machine + 10.66.5.5 + username + root + password + redhat + guestname + rhel6 + prestate + running + poststate + running + presrcconfig + false + postsrcconfig + false + predstconfig + false + postdstconfig + false + flags + 0|live + +prestate and poststate is the domain state: <running|paused> +presrconfig, postsrconfig, predstconfig, postdstconfig is <true|false> +flags is the migration flags combination <0|peer2peer|tunnelled|live|paused \ + |persist_dest|undefine_source|> + +""" +__author__ = 'Guannan Ren: gren@redhat.com' +__date__ = 'Sun June 26, 2011' +__version__ = '0.1.0' +__credits__ = 'Copyright (C) 2011 Red Hat, Inc.' +__all__ = ['usage', 'migrate'] + +import os +import re +import sys +import pexpect +import string +import commands + +def append_path(path): + """Append root path of package""" + if path in sys.path: + pass + else: + sys.path.append(path) + +pwd = os.getcwd() +result = re.search('(.*)libvirt-test-API', pwd) +append_path(result.group(0)) + +from lib import connectAPI +from lib.domainAPI import * +from utils.Python import utils +from utils.Python import xmlbuilder +from exception import LibvirtAPI + +SSH_KEYGEN = "ssh-keygen -t rsa" +SSH_COPY_ID = "ssh-copy-id" +GUEST_XML = "/etc/libvirt/qemu/%s.xml" + +def exec_command(logger, command, flag): + """execute shell command + """ + status, ret = commands.getstatusoutput(command) + if not flag and status: + logger.error("executing "+ "\"" + command + "\"" + " failed") + logger.error(ret) + return status, ret + + +def env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger): + + logger.info("destroy and undefine %s on both side if it exsits", guestname) + exec_command(logger, "virsh destroy %s" % guestname, 1) + exec_command(logger, "virsh undefine %s" % guestname, 1) + REMOTE_DESTROY = "ssh %s \"virsh destroy %s\"" % (target_machine, guestname) + exec_command(logger, REMOTE_DESTROY, 1) + REMOTE_UNDEFINE = "ssh %s \"virsh undefine %s\"" % (target_machine, guestname) + exec_command(logger, REMOTE_UNDEFINE, 1) + + src.close() + logger.info("close local hypervisor connection") + dst.close() + logger.info("close remote hypervisor connection") + + REMOVE_SSH = "ssh %s \"rm -rf /root/.ssh/*\"" % (target_machine) + logger.info("remove ssh key on remote machine") + status, ret = exec_command(logger, REMOVE_SSH, 0) + if status: + logger.error("failed to remove ssh key") + + REMOVE_LOCAL_SSH = "rm -rf /root/.ssh/*" + logger.info("remove local ssh key") + status, ret = exec_command(logger, REMOVE_LOCAL_SSH, 0) + if status: + logger.error("failed to remove local ssh key") + + +def check_params(params): + """check out the arguments requried for migration""" + logger = params['logger'] + keys = ['target_machine', 'username', 'password', 'guestname', 'flags'] + for key in keys: + if key not in params: + logger.error("Argument %s is required" % key) + return 1 + return 0 + +def ssh_keygen(logger): + """using pexpect to generate RSA""" + logger.info("generate ssh RSA \"%s\"" % SSH_KEYGEN) + child = pexpect.spawn(SSH_KEYGEN) + while True: + index = child.expect(['Enter file in which to save the key ', + 'Enter passphrase ', + 'Enter same passphrase again: ', + pexpect.EOF, + pexpect.TIMEOUT]) + if index == 0: + child.sendline("\r") + elif index == 1: + child.sendline("\r") + elif index == 2: + child.sendline("\r") + elif index == 3: + logger.debug(string.strip(child.before)) + child.close() + return 0 + elif index == 4: + logger.error("ssh_keygen timeout") + logger.debug(string.strip(child.before)) + child.close() + return 1 + + return 0 + +def ssh_tunnel(hostname, username, password, logger): + """setup a tunnel to a give host""" + logger.info("setup ssh tunnel with host %s" % hostname) + user_host = "%s@%s" % (username, hostname) + child = pexpect.spawn(SSH_COPY_ID, [ user_host]) + while True: + index = child.expect(['yes\/no', 'password: ', + pexpect.EOF, + pexpect.TIMEOUT]) + if index == 0: + child.sendline("yes") + elif index == 1: + child.sendline(password) + elif index == 2: + logger.debug(string.strip(child.before)) + child.close() + return 0 + elif index == 3: + logger.error("setup tunnel timeout") + logger.debug(string.strip(child.before)) + child.close() + return 1 + + return 0 + +def remote_guest_define(target_machine, username, guestname, logger): + """copy guest xml description to target machine and define it""" + xml_file = GUEST_XML % guestname + + if not os.path.exists(xml_file): + logger.error("guest %s xml file doesn't exsits" % guestname) + return 1 + + SCP_CMD = "scp %s %s@%s:/tmp" %(xml_file, username, target_machine) + status, ret = exec_command(logger, SCP_CMD, 0) + if status: + logger.error("copy guest file failed") + return 1 + + VIRSH_DEFINE = "ssh %s \"virsh define /tmp/%s.xml\"" % (target_machine, guestname) + status, ret = exec_command(logger, VIRSH_DEFINE, 0) + if status: + logger.error("faied to define guest on target machine") + return 1 + + return 0 + +def migrate(params): + """ migrate a guest back and forth between two machines""" + logger = params['logger'] + params_check_result = check_params(params) + if params_check_result: + return 1 + + target_machine = params['target_machine'] + username = params['username'] + password = params['password'] + guestname = params['guestname'] + poststate = params['poststate'] + presrcconfig = params['presrcconfig'] + postsrcconfig = params['postsrcconfig'] + predstconfig = params['predstconfig'] + postdstconfig = params['postdstconfig'] + flags = params['flags'] + + + logger.info("the flags is %s" % flags) + flags_string = flags.split("|") + + migflags = 0 + for flag in flags_string: + if flag == '0': + migflags |= 0 + elif flag == 'peer2peer': + migflags |= VIR_MIGRATE_PEER2PEER + elif flag == 'tunnelled': + migflags |= VIR_MIGRATE_TUNNELLED + elif flag == 'live': + migflags |= VIR_MIGRATE_LIVE + elif flag == 'persist_dest': + migflags |= VIR_MIGRATE_PERSIST_DEST + elif flag == 'undefine_source': + migflags |= VIR_MIGRATE_UNDEFINE_SOURCE + elif flag == 'paused': + migflags |= VIR_MIGRATE_PAUSED + else: + logger.error("unknown flag") + return 1 + + #generate ssh key pair + ret = ssh_keygen(logger) + if ret: + logger.error("failed to generate RSA key") + return 1 + #setup ssh tunnel with target machine + ret = ssh_tunnel(target_machine, username, password, logger) + if ret: + logger.error("faild to setup ssh tunnel with target machine %s" % target_machine) + return 1 + + commands.getstatusoutput("ssh-add") + + srcuri = "qemu:///system" + dsturi = "qemu+ssh://%s/system" % target_machine + + # Connect to local hypervisor connection URI + util = utils.Utils() + conn = connectAPI.ConnectAPI() + src = conn.open(srcuri) + dst = conn.open(dsturi) + + srcdom = DomainAPI(src) + dstdom = DomainAPI(dst) + + if predstconfig == "true": + ret = remote_guest_define(target_machine, username, guestname, logger) + if ret: + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + + try: + if(migflags & VIR_MIGRATE_PEER2PEER): + logger.info("use migrate_to_uri() API to migrate") + srcdom.migrate_to_uri(guestname, dsturi, migflags) + else: + logger.info("use migrate() to migrate") + srcdom.migrate(guestname, dst, migflags) + except LibvirtAPI, e: + logger.error("API error message: %s, error code is %s" % \ + (e.response()['message'], e.response()['code'])) + logger.error("Migration Failed") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + if postsrcconfig == "true": + if srcdom.is_active(guestname): + logger.error("Source VM is still active") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + if not srcdom.is_persistent(guestname): + logger.error("Source VM missing config") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + else: + guest_names = srcdom.get_list() + guest_names += srcdom.get_defined_list() + if guestname in guest_names: + logger.error("Source VM still exists") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + if not dstdom.is_active(guestname): + logger.error("Dst VM is not active") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + if postdstconfig == "true": + if not dstdom.is_persistent(guestname): + logger.error("Dst VM missing config") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + dstdom_state = dstdom.get_state(guestname) + if dstdom_state != poststate: + logger.error("Dst VM wrong state %s, should be %s", dstdom_state, poststate) + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + logger.info("Migration PASS") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 0 + + + + + + + + + + + + + + + + + + + + + -- 1.7.1

On 06/28/2011 01:54 PM, Guannan.ren wrote:
--- repos/domain/migrate.py | 335 +++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 335 insertions(+), 0 deletions(-) create mode 100644 repos/domain/migrate.py
diff --git a/repos/domain/migrate.py b/repos/domain/migrate.py new file mode 100644 index 0000000..3f80bd0 --- /dev/null +++ b/repos/domain/migrate.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python +"""this script is for migration testing + domain:migrate + target_machine + 10.66.5.5 + username + root + password + redhat + guestname + rhel6 + prestate + running + poststate + running + presrcconfig + false + postsrcconfig + false + predstconfig + false + postdstconfig + false + flags + 0|live + +prestate and poststate is the domain state:<running|paused> +presrconfig, postsrconfig, predstconfig, postdstconfig is<true|false> +flags is the migration flags combination<0|peer2peer|tunnelled|live|paused \ + |persist_dest|undefine_source|> + +""" +__author__ = 'Guannan Ren: gren@redhat.com' +__date__ = 'Sun June 26, 2011' +__version__ = '0.1.0' +__credits__ = 'Copyright (C) 2011 Red Hat, Inc.' +__all__ = ['usage', 'migrate'] + +import os +import re +import sys +import pexpect +import string +import commands + +def append_path(path): + """Append root path of package""" + if path in sys.path: + pass + else: + sys.path.append(path) + +pwd = os.getcwd() +result = re.search('(.*)libvirt-test-API', pwd) +append_path(result.group(0)) + +from lib import connectAPI +from lib.domainAPI import * +from utils.Python import utils +from utils.Python import xmlbuilder +from exception import LibvirtAPI + +SSH_KEYGEN = "ssh-keygen -t rsa" +SSH_COPY_ID = "ssh-copy-id" +GUEST_XML = "/etc/libvirt/qemu/%s.xml" + +def exec_command(logger, command, flag): + """execute shell command + """ + status, ret = commands.getstatusoutput(command) + if not flag and status: + logger.error("executing "+ "\"" + command + "\"" + " failed") + logger.error(ret) + return status, ret + + +def env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger): + + logger.info("destroy and undefine %s on both side if it exsits", guestname) + exec_command(logger, "virsh destroy %s" % guestname, 1) + exec_command(logger, "virsh undefine %s" % guestname, 1) + REMOTE_DESTROY = "ssh %s \"virsh destroy %s\"" % (target_machine, guestname) + exec_command(logger, REMOTE_DESTROY, 1) + REMOTE_UNDEFINE = "ssh %s \"virsh undefine %s\"" % (target_machine, guestname) + exec_command(logger, REMOTE_UNDEFINE, 1) + + src.close() + logger.info("close local hypervisor connection") + dst.close() + logger.info("close remote hypervisor connection") + + REMOVE_SSH = "ssh %s \"rm -rf /root/.ssh/*\"" % (target_machine) + logger.info("remove ssh key on remote machine") + status, ret = exec_command(logger, REMOVE_SSH, 0) + if status: + logger.error("failed to remove ssh key") + + REMOVE_LOCAL_SSH = "rm -rf /root/.ssh/*" + logger.info("remove local ssh key") + status, ret = exec_command(logger, REMOVE_LOCAL_SSH, 0) + if status: + logger.error("failed to remove local ssh key") + + +def check_params(params): + """check out the arguments requried for migration""" + logger = params['logger'] + keys = ['target_machine', 'username', 'password', 'guestname', 'flags'] + for key in keys: + if key not in params: + logger.error("Argument %s is required" % key) + return 1 + return 0 + +def ssh_keygen(logger): + """using pexpect to generate RSA""" + logger.info("generate ssh RSA \"%s\"" % SSH_KEYGEN) + child = pexpect.spawn(SSH_KEYGEN) + while True: + index = child.expect(['Enter file in which to save the key ', + 'Enter passphrase ', + 'Enter same passphrase again: ', + pexpect.EOF, + pexpect.TIMEOUT]) + if index == 0: + child.sendline("\r") + elif index == 1: + child.sendline("\r") + elif index == 2: + child.sendline("\r") + elif index == 3: + logger.debug(string.strip(child.before)) + child.close() + return 0 + elif index == 4: + logger.error("ssh_keygen timeout") + logger.debug(string.strip(child.before)) + child.close() + return 1 + + return 0 + +def ssh_tunnel(hostname, username, password, logger): + """setup a tunnel to a give host""" + logger.info("setup ssh tunnel with host %s" % hostname) + user_host = "%s@%s" % (username, hostname) + child = pexpect.spawn(SSH_COPY_ID, [ user_host]) + while True: + index = child.expect(['yes\/no', 'password: ', + pexpect.EOF, + pexpect.TIMEOUT]) + if index == 0: + child.sendline("yes") + elif index == 1: + child.sendline(password) + elif index == 2: + logger.debug(string.strip(child.before)) + child.close() + return 0 + elif index == 3: + logger.error("setup tunnel timeout") + logger.debug(string.strip(child.before)) + child.close() + return 1 + + return 0 + +def remote_guest_define(target_machine, username, guestname, logger): + """copy guest xml description to target machine and define it""" + xml_file = GUEST_XML % guestname + + if not os.path.exists(xml_file): + logger.error("guest %s xml file doesn't exsits" % guestname) + return 1 + + SCP_CMD = "scp %s %s@%s:/tmp" %(xml_file, username, target_machine) + status, ret = exec_command(logger, SCP_CMD, 0) + if status: + logger.error("copy guest file failed") + return 1 + + VIRSH_DEFINE = "ssh %s \"virsh define /tmp/%s.xml\"" % (target_machine, guestname) + status, ret = exec_command(logger, VIRSH_DEFINE, 0) + if status: + logger.error("faied to define guest on target machine") + return 1 + + return 0 + +def migrate(params): + """ migrate a guest back and forth between two machines""" + logger = params['logger'] + params_check_result = check_params(params) + if params_check_result: + return 1 + + target_machine = params['target_machine'] + username = params['username'] + password = params['password'] + guestname = params['guestname'] + poststate = params['poststate'] + presrcconfig = params['presrcconfig'] + postsrcconfig = params['postsrcconfig'] + predstconfig = params['predstconfig'] + postdstconfig = params['postdstconfig'] + flags = params['flags'] + + + logger.info("the flags is %s" % flags) + flags_string = flags.split("|") + + migflags = 0 + for flag in flags_string: + if flag == '0': + migflags |= 0 + elif flag == 'peer2peer': + migflags |= VIR_MIGRATE_PEER2PEER + elif flag == 'tunnelled': + migflags |= VIR_MIGRATE_TUNNELLED + elif flag == 'live': + migflags |= VIR_MIGRATE_LIVE + elif flag == 'persist_dest': + migflags |= VIR_MIGRATE_PERSIST_DEST + elif flag == 'undefine_source': + migflags |= VIR_MIGRATE_UNDEFINE_SOURCE + elif flag == 'paused': + migflags |= VIR_MIGRATE_PAUSED + else: + logger.error("unknown flag") + return 1 + + #generate ssh key pair + ret = ssh_keygen(logger) + if ret: + logger.error("failed to generate RSA key") + return 1 + #setup ssh tunnel with target machine + ret = ssh_tunnel(target_machine, username, password, logger) + if ret: + logger.error("faild to setup ssh tunnel with target machine %s" % target_machine) + return 1 + + commands.getstatusoutput("ssh-add") + + srcuri = "qemu:///system" + dsturi = "qemu+ssh://%s/system" % target_machine + + # Connect to local hypervisor connection URI + util = utils.Utils() + conn = connectAPI.ConnectAPI() + src = conn.open(srcuri) + dst = conn.open(dsturi) + + srcdom = DomainAPI(src) + dstdom = DomainAPI(dst) + + if predstconfig == "true": + ret = remote_guest_define(target_machine, username, guestname, logger) + if ret: + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + + try: + if(migflags& VIR_MIGRATE_PEER2PEER): + logger.info("use migrate_to_uri() API to migrate") + srcdom.migrate_to_uri(guestname, dsturi, migflags) + else: + logger.info("use migrate() to migrate") + srcdom.migrate(guestname, dst, migflags) + except LibvirtAPI, e: + logger.error("API error message: %s, error code is %s" % \ + (e.response()['message'], e.response()['code'])) + logger.error("Migration Failed") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + if postsrcconfig == "true": + if srcdom.is_active(guestname): + logger.error("Source VM is still active") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + if not srcdom.is_persistent(guestname): + logger.error("Source VM missing config") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + else: + guest_names = srcdom.get_list() + guest_names += srcdom.get_defined_list() + if guestname in guest_names: + logger.error("Source VM still exists") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + if not dstdom.is_active(guestname): + logger.error("Dst VM is not active") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + if postdstconfig == "true": + if not dstdom.is_persistent(guestname): + logger.error("Dst VM missing config") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + dstdom_state = dstdom.get_state(guestname) + if dstdom_state != poststate: + logger.error("Dst VM wrong state %s, should be %s", dstdom_state, poststate) + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 1 + + logger.info("Migration PASS") + env_clean(src, dst, srcdom, dstdom, target_machine, guestname, logger) + return 0 + + + + + + + + + + + + + + + + + + + + + ACK, but too many redundant blank lines at the end of file.
- nzhang

--- cases/migrate.conf | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 97 insertions(+), 0 deletions(-) create mode 100644 cases/migrate.conf diff --git a/cases/migrate.conf b/cases/migrate.conf new file mode 100644 index 0000000..2d8157a --- /dev/null +++ b/cases/migrate.conf @@ -0,0 +1,97 @@ +# This is a sample for migration testing, it assumes that there is a preinstalled rhel6 guest image file +# named rhel6 located in shared storage. Then on both host side, it needs to mount the shared folder onto +# the default libvirt image folder: /var/lib/libvirt/images + +domain:create + guestname + $defaultos + guesttype + $defaulthv + +domain:migrate + target_machine + $target_machine + username + $target_user + password + $target_password + guestname + $defaultos + prestate + running + poststate + running + presrcconfig + false + postsrcconfig + false + predstconfig + false + postdstconfig + false + flags + 0|live + + +domain:create + guestname + $defaultos + guesttype + $defaulthv + +domain:migrate + target_machine + $target_machine + username + $target_user + password + $target_password + guestname + $defaultos + prestate + running + poststate + running + presrcconfig + false + postsrcconfig + false + predstconfig + false + postdstconfig + false + flags + peer2peer|live + + + +domain:create + guestname + $defaultos + guesttype + $defaulthv + +domain:migrate + target_machine + $target_machine + username + $target_user + password + $target_password + guestname + $$defaultos + prestate + running + poststate + running + presrcconfig + false + postsrcconfig + false + predstconfig + false + postdstconfig + false + flags + peer2peer|tunnelled|live + -- 1.7.1

On 06/28/2011 01:54 PM, Guannan.ren wrote:
--- cases/migrate.conf | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 97 insertions(+), 0 deletions(-) create mode 100644 cases/migrate.conf
diff --git a/cases/migrate.conf b/cases/migrate.conf new file mode 100644 index 0000000..2d8157a --- /dev/null +++ b/cases/migrate.conf @@ -0,0 +1,97 @@ +# This is a sample for migration testing, it assumes that there is a preinstalled rhel6 guest image file +# named rhel6 located in shared storage. Then on both host side, it needs to mount the shared folder onto +# the default libvirt image folder: /var/lib/libvirt/images + +domain:create + guestname + $defaultos + guesttype + $defaulthv + +domain:migrate + target_machine + $target_machine + username + $target_user + password + $target_password + guestname + $defaultos + prestate + running + poststate + running + presrcconfig + false + postsrcconfig + false + predstconfig + false + postdstconfig + false + flags + 0|live + + +domain:create + guestname + $defaultos + guesttype + $defaulthv + +domain:migrate + target_machine + $target_machine + username + $target_user + password + $target_password + guestname + $defaultos + prestate + running + poststate + running + presrcconfig + false + postsrcconfig + false + predstconfig + false + postdstconfig + false + flags + peer2peer|live + + + +domain:create + guestname + $defaultos + guesttype + $defaulthv + +domain:migrate + target_machine + $target_machine + username + $target_user + password + $target_password + guestname + $$defaultos Redundant '$' sign for variable, should be $defaultos. + prestate + running + poststate + running + presrcconfig + false + postsrcconfig + false + predstconfig + false + postdstconfig + false + flags + peer2peer|tunnelled|live +

--- env.cfg | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diff --git a/env.cfg b/env.cfg index e1237de..b5e404a 100644 --- a/env.cfg +++ b/env.cfg @@ -135,3 +135,9 @@ testnic = eth1 # a PCI device to use for attach/detach/reset tests # for example testpci = 00:19.0 testpci = + +# The IP address of migration target machine +target_machine= +target_user = root +target_password = + -- 1.7.1

On 06/28/2011 01:54 PM, Guannan.ren wrote:
--- env.cfg | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-)
diff --git a/env.cfg b/env.cfg index e1237de..b5e404a 100644 --- a/env.cfg +++ b/env.cfg @@ -135,3 +135,9 @@ testnic = eth1 # a PCI device to use for attach/detach/reset tests # for example testpci = 00:19.0 testpci = + +# The IP address of migration target machine +target_machine= +target_user = root +target_password = + ACK.
- nzhang

It will be nice if you could explain what the patch series does with "git send-email --compose", and also each patch should have a description, as your patches are not tiny patch which one can get the sense at a glance. Only a patch subject is hard for one reviewing it. Regards Osier 于 2011年06月28日 13:54, Guannan.ren 写道:
--- lib/domainAPI.py | 57 ++++++++++++++++++++++++++++++++++++------------------ 1 files changed, 38 insertions(+), 19 deletions(-)
diff --git a/lib/domainAPI.py b/lib/domainAPI.py index 0c725f8..5667c20 100644 --- a/lib/domainAPI.py +++ b/lib/domainAPI.py @@ -40,18 +40,6 @@ append_path(result.group(0))
import exception
-# DomainMigrateFlags -VIR_MIGRATE_LIVE = 1 - -# DomainState -VIR_DOMAIN_NOSTATE = 0 -VIR_DOMAIN_RUNNING = 1 -VIR_DOMAIN_BLOCKED = 2 -VIR_DOMAIN_PAUSED = 3 -VIR_DOMAIN_SHUTDOWN = 4 -VIR_DOMAIN_SHUTOFF = 5 -VIR_DOMAIN_CRASHED = 6 - class DomainAPI(object): def __init__(self, connection): self.conn = connection @@ -182,6 +170,16 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code)
+ def start_with_flags(self, domname, flags = 0): + try: + dom_obj = self.get_defined_obj(domname) + retval = dom_obj.createWithFlags(flags) + return retval + except libvirt.libvirtError, e: + message = e.get_error_message() + code = e.get_error_code() + raise exception.LibvirtAPI(message, code) + def suspend(self, domname): try: dom_obj = self.get_domain_by_name(domname) @@ -223,10 +221,10 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code)
- def migrate(self, domname, dconn, flags = VIR_MIGRATE_LIVE): + def migrate(self, domname, dconn, flags, dname = None, uri = None, bandwidth = 0): try: dom_obj = self.get_domain_by_name(domname) - retval = dom_obj.migrate(dconn, flags, None, None, 0) + retval = dom_obj.migrate(dconn, flags, dname, uri, 0) return retval except libvirt.libvirtError, e: message = e.get_error_message() @@ -484,7 +482,7 @@ class DomainAPI(object): dom_obj = self.get_domain_by_name(domname) state = dom_obj.info() if state[0] == VIR_DOMAIN_NOSTATE: - dom_state = 'no state' + dom_state = 'nostate' elif state[0] == VIR_DOMAIN_RUNNING: dom_state = 'running' elif state[0] == VIR_DOMAIN_BLOCKED: @@ -581,7 +579,7 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code)
- def isActive(self, domname): + def is_active(self, domname): try: dom_obj = self.get_domain_by_name(domname) return dom_obj.isActive() @@ -590,7 +588,7 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code)
- def isPersistent(self, domname): + def is_persistent(self, domname): try: dom_obj = self.get_domain_by_name(domname) return dom_obj.isPersistent() @@ -662,10 +660,10 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code)
- def migrate_to_uri(self, domname, duri, dname, bandwidth, flag = 0): + def migrate_to_uri(self, domname, duri, flags, dname = None, bandwidth = 0): try: dom_obj = self.get_domain_by_name(domname) - return dom_obj.migrateToURI(duri, dname, bandwidth, flag) + return dom_obj.migrateToURI(duri, flags, dname, bandwidth) except libvirt.libvirtError, e: message = e.get_error_message() code = e.get_error_code() @@ -761,3 +759,24 @@ class DomainAPI(object): code = e.get_error_code() raise exception.LibvirtAPI(message, code)
+ +# DomainState +VIR_DOMAIN_NOSTATE = 0 +VIR_DOMAIN_RUNNING = 1 +VIR_DOMAIN_BLOCKED = 2 +VIR_DOMAIN_PAUSED = 3 +VIR_DOMAIN_SHUTDOWN = 4 +VIR_DOMAIN_SHUTOFF = 5 +VIR_DOMAIN_CRASHED = 6 + + +# virDomainMigrateFlags +VIR_MIGRATE_LIVE = 1 +VIR_MIGRATE_PEER2PEER = 2 +VIR_MIGRATE_TUNNELLED = 4 +VIR_MIGRATE_PERSIST_DEST = 8 +VIR_MIGRATE_UNDEFINE_SOURCE = 16 +VIR_MIGRATE_PAUSED = 32 +VIR_MIGRATE_NON_SHARED_DISK = 64 +VIR_MIGRATE_NON_SHARED_INC = 128 +

On 06/28/2011 05:03 PM, Osier Yang wrote:
It will be nice if you could explain what the patch series does with "git send-email --compose", and also each patch should have a description, as your patches are not tiny patch which one can get the sense at a glance.
Only a patch subject is hard for one reviewing it.
Regards Osier
于 2011年06月28日 13:54, Guannan.ren 写道:
ok, thanks
participants (4)
-
Guannan Ren
-
Guannan.ren
-
Nan Zhang
-
Osier Yang