KVM虚拟机实现在线迁移

经过2周的实现终于实现了KVM虚拟机动态迁移,实现一个服务器下的虚拟机迁移到另一台服务器上
以下是项目的主要的实现方法,步骤大致如下:
1.检测base池的存在,也就是虚拟机模板镜像是否存在;
2.先创建空的磁盘文件;
3.创建虚拟机filter;
4.虚拟机启动状态检测;
5.最后调用libvirt函数实现在线迁移;
6.当然还有最后的清理操作,包括:源虚拟机的删除、磁盘文件的删除、数据库的改变等操作;

# 执行迁移操作的service
def excuteVmMigrateService(host_id, vname, moveNodeId,vmType):
    log_debug.debug('excuteVmMigrateService begin!')
    result = 'error'
    try:
        compute = Compute.objects.get(id=host_id)
        conn = conn_wvmInstance(compute, vname)
        status = conn.get_status()
    except:
        log_debug.debug('no host or inst')
        return result

    try:
        # 连接数据库,根据节点id获取虚拟机对象
        instance = Instance.objects.get(node_id=host_id, vm_name=vname)
    except:
        log_debug.debug('conn vm sql error')
        return result

    # ----------------1.获取虚拟机xml配置文件、磁盘配置路径以及检测base池   开始--------------
    try:
        log_debug.debug('get vm xml,path info start')
        inst_xml = conn._XMLDesc(VIR_DOMAIN_XML_SECURE)
        # 获取指定节点的配置项
        jsonPath= json.loads(getMigratePathByVmType(vmType))

        add_file_path = jsonPath['addFilePath']
        volumPath = jsonPath['volumPath']
        basePath = jsonPath['basePath']
        origpath = jsonPath['origpath']

        basePathVm = instance.baseimg
        # 从数据库中得到虚拟机base镜像名称
        image_name = basePathVm.split('/')[-1]
        log_debug.debug('vm basePath=%s,image_name=%s' % (basePathVm,image_name))
        if image_name == 'NONE' or image_name == '':
            if vmType == '2':
                image_name = sys_config['df_teaimg']
            elif vmType == '3':
                image_name = sys_config['df_serverimg']

        # 检测迁移的目标节点base池是否存在镜像,不存在则从当前模板镜像目录进行拷贝
        if basePath:
            is_base_exist(moveNodeId, origpath, basePath, image_name)

        moveNode = Compute.objects.get(id=moveNodeId)
        moveIp = moveNode.hostname
        virconn = connection_manager.get_connection(moveNode.hostname, moveNode.login, moveNode.password, moveNode.type)
    except Exception, ex:  # 所有异常
        print Exception, ":", ex
        log_debug.debug('migrated domain happen exception!')
        result = 'exception'
        return result
    # ----------------获取虚拟机磁盘配置路径以及检测base池    结束--------------

    # ----------------2.获取虚拟机磁盘信息开始--------------
    try:
        log_debug.debug('get vm disk info start')
        disk_devices = conn.get_disk_device()  # 取得虚拟机的磁盘信息
        disk_json = [] # 用来存放base以及增量文件,一一对应
        for disk_device in disk_devices:
            disk_add_name = disk_device['image'].split('/')[-1]
            if '-' in disk_device['image']:
                disk_base_name = (disk_device['image']).split('-', 1)[-1]
            elif '_' in disk_device['image']:
                disk_base_name = (disk_device['image']).split('_', 3)[-1]
            disk_json.append({'disk_add_name': disk_add_name, 'disk_base_name': disk_base_name})

        log_debug.debug('disk_json=%s' % disk_json)
        disk_amount = len(disk_devices) # 磁盘数量
        log_debug.debug('vm disk_amount=%s' % disk_amount)
    except:
        log_debug.debug('get vm disk info except!')
        result = 'exception'
        return result
    # ----------------获取虚拟机磁盘信息   结束--------------

    # --------------------3.创建虚拟机磁盘  开始---------------------------
    try:
        log_debug.debug('create vm disk start')
        if disk_amount == 1:
            log_debug.debug('------------------------  disk amout == 1')
            # 得到c盘base名称以及增量文件名称
            c_add_name = disk_json[0]['disk_add_name']
            c_base_name = disk_json[0]['disk_base_name']
            rel = create_volum_by_qemu_img(moveNode.hostname, basePath, c_base_name, add_file_path, c_add_name)
            if rel != 'success':
                result = 'error'
                return result
        else:
            log_debug.debug('------------------------- disk amout > 1')
            # 得到c盘base名称以及增量文件名称
            c_add_name = disk_json[0]['disk_add_name']
            c_base_name = disk_json[0]['disk_base_name']
            # 创建c盘
            rel = create_volum_by_qemu_img(moveNode.hostname, basePath, c_base_name, add_file_path, c_add_name)
            if rel == 'success':
                disk_json.pop(0)  # 过滤c盘
                for ex_json in disk_json:
                    disk_base_ex = ex_json['disk_base_name']
                    add_name_ex = ex_json['disk_add_name']
                    # 创建扩展盘,包含d盘在内
                    rel_ex = create_volum_by_qemu_img(moveNode.hostname, volumPath, disk_base_ex, volumPath, add_name_ex)
                    refresh_pool(moveNode)
                    if rel_ex == 'success':
                        continue
                    else:
                        result = 'error'
                        return result
            else:
                result = 'error'
                return result
    except:
        log_debug.debug('create vm disk except!')
        result = 'exception'
        return result
    # ----------------------------创建磁盘  结束-----------------------


    # ----------------4.定义虚拟机的fileter 开始------------------
    try:
        log_debug.debug('define vm fileter start')
        filter_uuid = virconn.nwfilterLookupByName('%s_filter' % vname).UUIDString()
    except:
        log_debug.debug('get filter_uuid happen except!')
        filter_uuid = util.randomUUID()

    single_nwfilter = getVm2AndVm3FilterXml(vname, filter_uuid)

    try:
        virconn.nwfilterDefineXML(single_nwfilter[1])
    except:
        log_debug.debug('create filter error!')
        result = 'exception'
        return result
    # ----------------定义虚拟机的fileter  结束------------------

    # ----------------5.判断虚拟机启动状态  开始-------------------
    try:
        log_debug.debug('check vm  status=%s' % status)
        # 迁移要求虚拟机是启动状态,否则报错
        if status:
            if status == 5:
                conn.start()
            elif status == 3:
                conn.resume()
                # return HttpResponse('success')
        else:
            log_debug.debug('start vm fail')
            result = 'startFail'
            return result
    except:
        log_debug.debug('check vm status except!')
        result = 'exception'
        return result
    # ----------------判断虚拟机启动状态  结束-------------------

    # ----------------6.最后执行虚拟机迁移操作  开始-----------------------
    try:
        log_debug.debug('excute migrate vm start')
        #启动状态,执行迁移操作
        source_conn = libvirt.open('qemu+tcp://%s/system' % conn.host)
        dom = source_conn.lookupByName(vname)
        dest_conn = libvirt.open('qemu+tcp://%s/system' % moveIp)
        flg = 'tcp://%s' % moveIp
        new_dom = dom.migrate2(dest_conn, inst_xml, libvirt.VIR_MIGRATE_PERSIST_DEST  | libvirt.VIR_MIGRATE_LIVE | libvirt.VIR_MIGRATE_NON_SHARED_INC, None, flg, 0)
        refresh_pool(moveNode)
        log_debug.debug('refresh pool success')
        # 注:迁移成功后自动在/etc/libvirt/qemu/目录下生成xml配置文件
        if new_dom != None:
            dest_conn.close()
            conn.close()
            log_debug.debug('migrated vm success')
            result = 'success'
            return result
        else:
            log_debug.debug('migrated domain error')
            result = 'error'
            return result
    except Exception, ex:  # 所有异常
        print Exception, ":", ex
        log_debug.debug('excute vm migrated exception!')
        result = 'exception'
        return result
    # ----------------执行虚拟机迁移操作  结束-----------------------
    return result
# 通过命令的方式创建磁盘增量文件
def create_volum_by_qemu_img(nodeIp,basePath,c_base_name,add_file_path,c_add_name):
    result = 'success'
    cmd = "ssh %s 'qemu-img create -f qcow2 -b %s/%s %s/%s'" % (nodeIp, basePath, c_base_name, add_file_path, c_add_name)
    log_debug.debug('create qemu-img shell cmd=%s' % cmd)
    try:
        subprocess.call(cmd, shell=True)
        log_debug.debug('create  qemu-img success')
        return result
    except:
        log_debug.debug('create  qemu-img error')
        result = 'error'
        return result
    return result
# 迁移虚拟机确认的service,包括删除之前节点的虚拟机以及修改数据库
def migrateConfirmService(host_id, vname,moveNodeId):
    log_debug.debug('migrateConfirmService begin')
    result = 'error'
    try:
        compute = Compute.objects.get(id=host_id)
        conn = conn_wvmInstance(compute, vname)
    except:
        log_debug.debug('no host or inst')
        return result

    try:
        moveNode = Compute.objects.get(id=moveNodeId)
        moveConn = conn_wvmInstance(moveNode, vname)
    except:
        log_debug.debug('no move_host or move_inst')
        return result


    try:
        # 连接数据库,根据节点id获取虚拟机对象
        instance = Instance.objects.get(node_id=host_id, vm_name=vname)
    except:
        log_debug.debug('conn vm sql error')
        return result

    if conn.get_status() == 1:
        conn.force_shutdown()
    try:
        # 删除磁盘,包含C盘以及扩展盘
        conn.delete_disk()
        # 迁移成功,修改数据库的虚拟机所在的节点
        instance.node_id = moveNodeId
        # 修改端口 以及虚拟机的运行状态
        port = moveConn.get_console_port()
        log_debug.debug('vm new port=%s' % port)
        instance.vm_port = port
        instance.vm_status = "running"
        instance.save()

        # 删除2类型以及3类型自定义的filter
        if instance.vm_type == '2' or instance.vm_type == '3':
            virconn = connection_manager.get_connection(compute.hostname, compute.login,compute.password,compute.type)
            try:
                virconn.nwfilterLookupByName('%s_filter' % vname).undefine()
            except:
                log_debug.debug("undeine %s_filter faild" % vname)
        # 最后删除虚拟机
        conn.delete()
        result = 'success'
        log_debug.debug('delete vm success')
    except Exception, ex:
        print Exception, ":", ex
        log_debug.debug('delete migrate vm fail')
        result = 'exception'
    return result

总结:1.虚拟机迁移要求磁盘文件为增量文件的方式,也就是说创建磁盘的方式要是增量文件的方式,通过克隆的方式进过测试不支持迁移;
            2.迁移之前要求虚拟机没有创建快照并且虚拟机是启动的状态,否则迁移失败;
           3.迁移的过程实际上是磁盘文件的拷贝过程,因此从源节点到目的节点上迁移之前中,源节点要先创建虚拟机空的磁盘文件,在进行迁移操作;
           4.迁移完成之后虚拟机的状态为启动状态并且端口号也发生的变化;

你可能感兴趣的:(KVM_Libvirt)