Différences entre les versions de « Migration VM de kvm vers hyperv »

De BlaxWiki
Aller à la navigationAller à la recherche
 
(6 versions intermédiaires par le même utilisateur non affichées)
Ligne 39 : Ligne 39 :
<pre>
<pre>
0. Ajout des drivers dans l'initrd
0. Ajout des drivers dans l'initrd
Se connecter à la VM et faire un :
Se connecter à la VM et :
Modifier la ligne INITRD_MODULES de /etc/sysconfig/kernel pour y rajouter : hv_storvsc hv_netvsc hv_vmbus
ou
Régénérer les initrd avec : mkinitrd
dracut -f --add-drivers "hv_vmbus hv_storvsc hv_netvsc hv_utils hv_balloon hyperv-keyboard hyperv_fb hid-hyperv"
dracut -f --add-drivers "hv_vmbus hv_storvsc hv_netvsc hv_utils hv_balloon hyperv-keyboard hyperv_fb hid-hyperv"
Modifier /etc/default/grub et supprimer les options suivantes si elles sont présentes : rd.lvm.lv=centos_centos7/swap rd.lvm.lv=centos_centos7/root
Faire grub2-mkconfig -o /boot/grub2/grub.cfg
Verifier qu'il n'y a plus de référence en refaisant : grep rd.lvm.lv /boot/grub2/grub.cfg


1. Vérifications et arret de la VM
1. Vérifications et arret de la VM
Ligne 129 : Ligne 137 :


=== Script KVM ===
=== Script KVM ===
<pre>
mig.py --windows --dry vm1
Au cas où il y a un problème après la copie de TOUS les disques, il est possible de relancer avec l'option --no-copy
Si on a besoin de faire des modifications a la main avant l'import, il est possible d'inhiber le processus de unmount/kpart -dv/losetup pour garder le montage avec l'option --no-clean. Il faudra faire les opérations manuellement 
Si le script dit VM not found on this HV, il faut faire un virsh define /etc/libvirt/qemu/le-nom-de-la-vm.xml et relancer le script
</pre>
<pre>
<pre>


#!/usr/bin/env python
#!/usr/bin/env python
"""Script migration VM de KVM vers Hyper-V"""
# pylint: disable=invalid-name,logging-format-interpolation,global-statement,multiple-imports,line-too-long,missing-docstring,anomalous-backslash-in-string
from optparse import OptionParser
from optparse import OptionParser
import logging, subprocess, os, errno, atexit, time, re, tempfile
import logging, subprocess, os, atexit, re, tempfile, time
import xml.etree.ElementTree
import xml.etree.ElementTree


logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
force_flag=False
force_flag = False
dryRun_flag = False
#
srcVM = ""
dstPath = ""
noCopy_flag = False
doNotClean_flag = False
exportedRaw = []
mountedPartPath = []


#
srcVM=""
dstPath=""
noCopy_flag=False
doNotClean_flag=False
exportedRaw=[]
mountedPartPath=[]


def main():
def main():
     global srcVM, noCopy_flag, doNotClean_flag
     global srcVM, noCopy_flag, doNotClean_flag, dryRun_flag
 
    ## Parse args
     parser = OptionParser(usage="usage: %prog [options] VMname", version="%prog 1.0")
     parser = OptionParser(usage="usage: %prog [options] VMname", version="%prog 1.0")
     parser.add_option("-f", "--force",
     parser.add_option("-f", "--force", action="store_true", dest="force_flag", default=False, help="force")
                      action="store_true",
     parser.add_option("-n", "--no-copy", action="store_true", dest="noCopy_flag", default=False, help="Do not copy disk")
                      dest="force_flag",
     parser.add_option("-d", "--debug", action="store_true", dest="debug_flag", default=False, help="Print debug")
                      default=False,
     parser.add_option("-a", "--no-clean", action="store_true", dest="noclean_flag", default=False, help="No umount/no kpartx -d")
                      help="force")
    parser.add_option("-b", "--dry", action="store_true", dest="dryrun_flag", default=False, help="Do not modify files in export")
     parser.add_option("-n", "--no-copy",
    parser.add_option("-w", "--windows", action="store_true", dest="windows_flag", default=False, help="windows VM")
                      action="store_true",
                      dest="noCopy_flag",
                      default=False,
                      help="Do not copy disk")
     parser.add_option("-d", "--debug",
                      action="store_true",
                      dest="debug_flag",
                      default=False,
                      help="Print debug")
     parser.add_option("-X", "--no-clean",
                      action="store_true",
                      dest="noclean_flag",
                      default=False,
                      help="No umount/no kpartx -d")


     (options, args) = parser.parse_args()
     (options, args) = parser.parse_args()
Ligne 176 : Ligne 185 :
         parser.error("wrong number of arguments")
         parser.error("wrong number of arguments")


    logging.debug("Starting script")
     srcVM = args[0]
     srcVM=args[0]
     noCopy_flag = options.noCopy_flag
     noCopy_flag=options.noCopy_flag
     doNotClean_flag = options.noclean_flag
     doNotClean_flag=options.noclean_flag
    dryRun_flag = options.dryrun_flag
     if options.debug_flag:
     if options.debug_flag:
         logging.getLogger().setLevel(logging.DEBUG)
         logging.getLogger().setLevel(logging.DEBUG)
    # Start of scripts
    logging.debug("Starting script")
    # Check if VM exists in libvirt
     check_VM()
     check_VM()
    # Export xml
     exportXML()
     exportXML()
     copyDisk()
     copyDisk()
     systemPart=getSystemPartition()
 
    if not systemPart:
     if not options.windows_flag:
        logging.critical("Could not find system partition")
        systemPartition = getSystemPartition()
        exit()
        if not systemPartition:
    linux_vers=detectLinuxVersion(systemPart)
            logging.critical("Could not find system partition")
    if linux_vers == "unknown":
            exit()
        logging.critical("Could not determine Linux Version")
 
        exit()
        # Detection version Linux
    logging.info("VM - Linux Version : {0}".format(linux_vers))
        linuxVersion = detectLinuxVersion(systemPartition)
    filesPath=dstPath+"/files"
        if linuxVersion == "unknown":
    if not os.path.exists(filesPath):
            logging.critical("Could not determine Linux Version")
        try:
            os.makedirs(filesPath)
        except OSError as e:
            logging.critical("main: Error creating {0} : {1}".format(filesPath,e))
             exit()
             exit()
        logging.info("VM - Linux Version : {0}".format(linuxVersion))
        saveFolder = dstPath+"/files"
        if not os.path.exists(saveFolder):
            try:
                os.makedirs(saveFolder)
            except OSError as e:
                logging.critical("main: Error creating {0} : {1}".format(saveFolder, e))
                exit()


            #TODO : faire le reste
         # Changement FSTAB
         # modifie le fstab
         changeFstab(systemPartition, saveFolder)
         #refait le truc udev
        #installe un nouveau kernel
        #refait le grub.conf bien


        # Ajout proxy yum.conf
        changePackageManager(systemPartition, saveFolder, linuxVersion)
        # Install hypervtools
        installTools(systemPartition, linuxVersion)
        # Update kernel
        updateKernel(systemPartition, linuxVersion)
        # Update initramfs
        changeInitrd(systemPartition, linuxVersion)
        # Changement vda dans GRUB
        changeGrub(systemPartition, saveFolder, linuxVersion)
        # Ajout fstrim
        addFstrim(systemPartition, linuxVersion)
    os.system("sync")
    time.sleep(3)


def check_VM():
def check_VM():
     """Check existence of the VM"""
     """Check existence of the VM"""
     logging.debug("check_VM: Checking VM existence with virsh list")
     logging.debug("check_VM: Checking VM existence with virsh list")
     proc = subprocess.Popen(['/usr/bin/virsh','list --all --name'], stdout=subprocess.PIPE)
     proc = subprocess.Popen(['/usr/bin/virsh', 'list --all --name'], stdout=subprocess.PIPE)
     tmp = proc.stdout.read()
     tmp = proc.stdout.read()
     VMs = tmp.splitlines()
     VMs = tmp.splitlines()
Ligne 232 : Ligne 264 :
             os.makedirs(dstPath)
             os.makedirs(dstPath)
         except OSError as e:
         except OSError as e:
             logging.critical("exportXML: Error creating {0} : {1}".format(dstPath,e))
             logging.critical("exportXML: Error creating {0} : {1}".format(dstPath, e))
             exit()
             exit()
     else:
     else:
Ligne 242 : Ligne 274 :
     """Read XML conf to find all disk and copy them to export"""
     """Read XML conf to find all disk and copy them to export"""
     global exportedRaw
     global exportedRaw
     disks=[]
     disks = []
     logging.info("copyDisk: Searching for disk in XML")
     logging.info("copyDisk: Searching for disk in XML")
     tree = xml.etree.ElementTree.parse("{0}/{1}.xml".format(dstPath,srcVM))
     tree = xml.etree.ElementTree.parse("{0}/{1}.xml".format(dstPath, srcVM))
     root = tree.getroot()
     root = tree.getroot()
     for vmdisk in root.findall("./devices/disk"):
     for vmdisk in root.findall("./devices/disk"):
         if vmdisk.attrib['device'] == 'disk':
         if vmdisk.attrib['device'] == 'disk':
                srcDiskPath=vmdisk.find('source').attrib['dev']
            srcDiskPath = vmdisk.find('source').attrib['dev']
                logging.info("copyDisk: Found disk {0}".format(srcDiskPath))
            logging.info("copyDisk: Found disk {0}".format(srcDiskPath))
                disks.append(srcDiskPath)
            disks.append(srcDiskPath)
     for disk in disks:
     for disk in disks:
         dstRaw="{0}/{1}.raw".format(dstPath,os.path.basename(disk))
         dstRaw = "{0}/{1}.raw".format(dstPath, os.path.basename(disk))
         logging.info("copyDisk : Copying {0} to {1}".format(disk,dstRaw))
         logging.info("copyDisk : Copying {0} to {1}".format(disk, dstRaw))
         exportedRaw.append(dstRaw)
         exportedRaw.append(dstRaw)
         if noCopy_flag:
         if noCopy_flag:
             logging.info("copyDisk : option no-copy - skipping disk")
             logging.info("copyDisk : option no-copy - skipping disk")
         else:
         else:
             #os.system("/bin/dd if={0} of={1} bs=2M".format(disk,dstRaw))
             os.system("/bin/dd if={0} of={1} bs=2M".format(disk, dstRaw))
            logging.info("copyDisk : temp")
 


def getSystemPartition():
def getSystemPartition():
Ligne 266 : Ligne 298 :
     for raw in exportedRaw:
     for raw in exportedRaw:
         logging.debug("mountSystemPartition : Searching {0}".format(raw))
         logging.debug("mountSystemPartition : Searching {0}".format(raw))
         mappings=run_kpartx(raw)
         mappings = run_kpartx(raw)
         for mapping in mappings:
         for mapping in mappings:
             path="/dev/mapper/{0}".format(mapping)
             path = "/dev/mapper/{0}".format(mapping)
             # if kpartx fails
             # if kpartx fails
             if not os.path.exists(path):
             if not os.path.exists(path):
Ligne 274 : Ligne 306 :
                 exit()
                 exit()
             # Verify that there is a filesystem on the mapping
             # Verify that there is a filesystem on the mapping
             proc = subprocess.Popen(['/usr/bin/file','-s',path], stdout=subprocess.PIPE)
            # pylint: disable=no-member
            path = os.path.join(os.path.dirname(path), os.readlink(path)) # Needed for centos 7
             proc = subprocess.Popen(['/usr/bin/file', '-sk', path], stdout=subprocess.PIPE)
             tmp = proc.stdout.read()
             tmp = proc.stdout.read()
             if "filesystem" not in tmp:
             if "filesystem" not in tmp:
                 logging.debug("mountSystemPartition : ignoring {0} file return : \"{1}\"".format(path,tmp))
                 logging.debug("mountSystemPartition : ignoring {0} file return : \"{1}\"".format(path, tmp))
             else:
             else:
                 tmpDir=tempfile.mkdtemp()
                 tmpDir = tempfile.mkdtemp()
                 logging.debug("mountSystemPartition : mounting {0} file on {1}".format(path,tmpDir))
                 logging.debug("mountSystemPartition : mounting {0} file on {1}".format(path, tmpDir))
                 os.system("/bin/mount {0} {1}".format(path,tmpDir))
                 os.system("/bin/mount {0} {1}".format(path, tmpDir))
                 if not doNotClean_flag: atexit.register(undo_mount,tmpDir)
                 if not doNotClean_flag:
                    atexit.register(undo_mount, tmpDir)


                 if os.path.exists(tmpDir+"/etc/passwd"):
                 if os.path.exists(tmpDir+"/etc/passwd"):
Ligne 292 : Ligne 327 :
     """Run kpartx and return a list of partition mappings"""
     """Run kpartx and return a list of partition mappings"""
     cleanMappings = []
     cleanMappings = []
     proc = subprocess.Popen(['/sbin/kpartx','-av','{0}'.format(raw)], stdout=subprocess.PIPE)
     proc = subprocess.Popen(['/sbin/kpartx', '-asv', '{0}'.format(raw)], stdout=subprocess.PIPE)
     tmp = proc.stdout.read()
     tmp = proc.stdout.read()
     output = tmp.splitlines()
     output = tmp.splitlines()
     if not doNotClean_flag: atexit.register(undo_kpartx,raw)
     if not doNotClean_flag:
        atexit.register(undo_kpartx, raw)
     mapping_regex = re.compile(r'^add map ')
     mapping_regex = re.compile(r'^add map ')
     mappings = filter(mapping_regex.search, output)
     mappings = filter(mapping_regex.search, output)
     for mapping in mappings:
     for mapping in mappings:
         m=mapping.split()[2]
         m = mapping.split()[2]
         logging.debug("run_kpartx : Mapping created : {0}".format(m))
         logging.debug("run_kpartx : Mapping created : {0}".format(m))
         cleanMappings.append(m)
         cleanMappings.append(m)
    if cleanMappings:
        m = re.search(r'(loop[0-9]+)', cleanMappings[0])
        if not doNotClean_flag:
            atexit.register(undo_losetup, '/dev/'+m.group(0))
            atexit.register(undo_kpartx, '/dev/'+m.group(0))
     return cleanMappings
     return cleanMappings


def undo_kpartx(raw):
def undo_kpartx(raw):
     logging.debug("undo_kpartx : unmapping {0}".format(raw))
     logging.debug("undo_kpartx : unmapping {0}".format(raw))
     time.sleep(1)
     os.system("sync")
     os.system("kpartx -d {0} > /dev/null".format(raw))
     os.system("kpartx -s -d {0} > /dev/null".format(raw))
 
def undo_losetup(raw):
    logging.debug("undo_losetup : unmapping {0}".format(raw))
    os.system("losetup -d {0} > /dev/null".format(raw))


def undo_mount(tmpDir):
def undo_mount(tmpDir):
     logging.debug("undo_mount : unmounting {0}".format(tmpDir))
     logging.debug("undo_mount : unmounting {0}".format(tmpDir))
    os.system("sync")
    time.sleep(3)
     os.system("/bin/umount {0}".format(tmpDir))
     os.system("/bin/umount {0}".format(tmpDir))
    os.system("sync")
     os.system("/bin/rmdir {0}".format(tmpDir))
     os.system("/bin/rmdir {0}".format(tmpDir))


Ligne 317 : Ligne 365 :
     """return centos6/centos7/debian7 or unknown"""
     """return centos6/centos7/debian7 or unknown"""
     if os.path.exists(root+"/etc/redhat-release"):
     if os.path.exists(root+"/etc/redhat-release"):
         file=open(root+"/etc/redhat-release", "r")
         relfile = open(root+"/etc/redhat-release", "r")
         line=file.readline()
         line = relfile.readline()
         file.close()
         relfile.close()
         if line.startswith("CentOS release 6"):
         if line.startswith("CentOS release 6"):
             return "centos6"
             return "centos6"
Ligne 325 : Ligne 373 :
             return "centos7"
             return "centos7"
     if os.path.exists(root+"/etc/debian_version"):
     if os.path.exists(root+"/etc/debian_version"):
         file=open(root+"/etc/debian_version", "r")
         relfile = open(root+"/etc/debian_version", "r")
         line=file.readline()
         line = relfile.readline()
         file.close()
         relfile.close()
         if line.startswith("7."):
         if line.startswith("7."):
             return "debian7"
             return "debian7"
     return "unknown"
     return "unknown"


def changeFstab(root,filespath):
def changeFstab(systemPartition, saveFolder):
     logging.debug("changeFstab : Copying original file to {0}/fstab.orig".format(filespath))
     changeFile(systemPartition, saveFolder, "/etc/fstab", [["/dev/vd", "/dev/sd"]])
    os.system("/bin/cp {0}/etc/fstab {1}/fstab.orig".format(root,filespath))


def changeGrub(systemPartition, saveFolder, linux_vers):
    if linux_vers == "centos6":
        [code, stdout] = execInChroot(systemPartition, "rpm -qa kernel | sort -n | tail -1 | sed 's/kernel-//'")
        stdout = stdout.rstrip()
        changeFile(systemPartition, saveFolder, "/boot/grub/menu.lst", [["/dev/vd", "/dev/sd"],
                                                                        ["clocksource=kvm-clock", ""],
                                                                        ["console=ttyS0,115200", ""],
                                                                        ["^title.*", "title CentOS ({0})".format(stdout)],
                                                                        ["/boot/vmlinuz[^\s]*", "/boot/vmlinuz-{0}".format(stdout)],
                                                                        ["/boot/init[^\s]*", "/boot/initramfs-{0}.img".format(stdout)]
                                                                      ])
        changeFile(systemPartition, saveFolder, "/boot/grub/device.map", [["/dev/vd", "/dev/sd"]])
    elif linux_vers == "centos7":
        [code, stdout] = execInChroot(systemPartition, "rpm -qa kernel | sort -n | tail -1 | sed 's/kernel-//'")
        stdout = stdout.rstrip()
        # Clean probleme template agarik
        changeFile(systemPartition, saveFolder, "/etc/default/grub", [["rd.lvm.lv=centos_centos7/swap", ""],
                                                                      ["rd.lvm.lv=centos_centos7/root", ""]
                                                                    ])
        changeFile(systemPartition, saveFolder, "/boot/grub2/grub.cfg", [["/dev/vd", "/dev/sd"],
                                                                        ["clocksource=kvm-clock", ""],
                                                                        ["console=ttyS0,115200", ""],
                                                                        ["^menuentry.*", "menuentry 'CentOS Linux ({0}) 7 (Core) ' {{".format(stdout)],
                                                                        ["/boot/vmlinuz[^\s]*", "/boot/vmlinuz-{0}".format(stdout)],
                                                                        ["/boot/init[^\s]*", "/boot/initramfs-{0}.img".format(stdout)]
                                                                        ])
        changeFile(systemPartition, saveFolder, "/boot/grub2/device.map", [["/dev/vd", "/dev/sd"]])
    elif linux_vers == "debian7":
        [code, stdout] = execInChroot(systemPartition, "dpkg -l linux-image-3\* | grep '^ii' | sort -n | tail -1 | cut -d\  -f3 | sed s/linux-image-//")
        stdout = stdout.rstrip()
        changeFile(systemPartition, saveFolder, "/boot/grub/grub.cfg", [["/dev/vd", "/dev/sd"],
                                                                        ["^menuentry.*", "menuentry 'Debian GNU/Linux, with Linux {0}' --class debian --class gnu-linux --class gnu --class os {{".format(stdout)],
                                                                        ["/boot/vmlinuz[^\s]*", "/boot/vmlinuz-{0}".format(stdout)],
                                                                        ["/boot/init[^\s]*", "/boot/initrd.img-{0}".format(stdout)]
                                                                      ])
        changeFile(systemPartition, saveFolder, "/boot/grub/device.map", [["/dev/vd", "/dev/sd"]])
def changeFile(systemPartition, saveFolder, filepath, regexList):
    """
    Change systemPartition/filepath with a copy of the original file in saveFolder
    regexList is a list of one or multiple ["regex","replaceExpr"]
    """
    filename = os.path.basename(filepath)
    saveOrig = "{0}/{1}.orig".format(saveFolder, filename)
    saveMod = "{0}/{1}".format(saveFolder, filename)
    logging.info("changeFile : Copying original file {0} to {1}".format(filename, saveOrig))
    os.system("/bin/cp {0}/{1} {2}".format(systemPartition, filepath, saveOrig))
    if not os.path.exists(saveOrig):
        logging.critical("changeFile : {0} does not exist".format(saveOrig))
        exit()
    if os.path.exists(saveMod):
        logging.debug("changeFile : {0} exist, renaming with .old suffix".format(saveOrig))
        os.rename(saveMod, saveMod+".old")
    fin = open(saveOrig, 'r')
    fout = open(saveMod, 'w')
    for iline in fin:
        line = iline
        for regex in regexList:
            line = re.sub(regex[0], regex[1], line)
        if iline != line:
            logging.debug("changeFile : Replaced\n    {0}  by\n    {1}  in {2}".format(iline, line, filename))
        fout.write(line)
    # try:
    #    fout.writelines(fin.readlines())
    # except Exception as E:
    #    raise E
    fin.close()
    fout.close()
    if not dryRun_flag:
        os.system("/bin/cp {0} {1}/{2}".format(saveMod, systemPartition, filepath))
def changePackageManager(systemPartition, saveFolder, linux_vers):
    if linux_vers == "centos6":
        changeFile(systemPartition, saveFolder, "/etc/yum.conf", [[r"^releasever=.*", ""],
                                                                  [r"^proxy=.*", ""],
                                                                  [r"^keepcache=.*", ""],
                                                                ])
        if not dryRun_flag:
            os.system("echo 'proxy=http://192.168.26.11:3128' >> {0}/etc/yum.conf".format(systemPartition))
            os.system("echo 'releasever=6' >> {0}/etc/yum.conf".format(systemPartition))
            os.system("echo 'keepcache=1' >> {0}/etc/yum.conf".format(systemPartition))
            execInChroot(systemPartition, "yum clean all")
    elif linux_vers == "centos7":
        changeFile(systemPartition, saveFolder, "/etc/yum.conf", [[r"^releasever=.*", ""],
                                                                  [r"^proxy=.*", ""],
                                                                ])
        if not dryRun_flag:
            os.system("echo 'proxy=http://192.168.26.11:3128' >> {0}/etc/yum.conf".format(systemPartition))
            os.system("echo 'releasever=7' >> {0}/etc/yum.conf".format(systemPartition))
            os.system("echo 'keepcache=1' >> {0}/etc/yum.conf".format(systemPartition))
            execInChroot(systemPartition, "yum clean all")
    elif linux_vers == "debian7":
        if not dryRun_flag:
            os.system("echo 'Acquire::http::Proxy \"http://192.168.26.15:3128\";' > {0}/etc/apt/apt.conf.d/99HttpProxy".format(systemPartition))
            execInChroot(systemPartition, "apt-get clean")
            execInChroot(systemPartition, "apt-get  install -y debian-keyring debian-archive-keyring")
def installTools(systemPartition, linuxVersion):
    logging.info("installTools : Installing hyperv-tools")
    if (linuxVersion == "centos6") or (linuxVersion == "centos7"):
        execInChroot(systemPartition, "yum --disablerepo=\"*\" --enablerepo=base,updates install -y hyperv-daemons")
    elif linuxVersion == "debian7":
        execInChroot(systemPartition, "apt-get update")
        [code, output] = execInChroot(systemPartition, "apt-get install -y hyperv-daemons")
        logging.debug("installTools : return code {0}".format(code))
        logging.debug("installTools : return output {0}".format(output))
def updateKernel(systemPartition, linuxVersion):
    logging.info("updateKernel : Updating kernel")
    if (linuxVersion == "centos6") or (linuxVersion == "centos7"):
        execInChroot(systemPartition, "yum --disablerepo=\"*\" --enablerepo=base,updates update -y kernel")
    elif linuxVersion == "debian7":
        execInChroot(systemPartition, "apt-get update")
        [code, output] = execInChroot(systemPartition, "apt-get upgrade -y linux-image-amd64")
        logging.debug("updateKernel : return code {0}".format(code))
        logging.debug("updateKernel : return output {0}".format(output))
def changeInitrd(systemPartition, linuxVersion):
    logging.info("changeInitrd : Adding hyperv modules to dracut.conf.d/hyperv.conf")
    if not dryRun_flag:
        if linuxVersion == "centos7":
            execInChroot(systemPartition, "echo 'add_drivers=\"$add_drivers hv_vmbus hv_storvsc hv_netvsc hv_utils hv_balloon hyperv-keyboard hyperv_fb hid-hyperv\"' > /etc/dracut.conf.d/hyperv.conf")
            execInChroot(systemPartition, '/bin/bash -c \'for i in /boot/initramfs-*.x86_64.img; do dracut -N -f $i $(basename $i | sed "s/initramfs-//" | sed "s/\.img//"); done\'')
        if linuxVersion == "centos6":
            execInChroot(systemPartition, "echo 'add_drivers=\"$add_drivers hv_vmbus hv_storvsc hv_netvsc hv_utils hv_balloon hyperv-keyboard hyperv_fb hid-hyperv\"' > /etc/dracut.conf.d/hyperv.conf")
            execInChroot(systemPartition, '/bin/bash -c \'for i in /boot/initramfs-*.x86_64.img; do dracut -f $i $(basename $i | sed "s/initramfs-//" | sed "s/\.img//"); done\'')
def execInChroot(systemPartition, command):
    if dryRun_flag:
        logging.debug("execInChroot : DryRun : not executed : command {0}".format(command))
        return [0, ""]
    else:
        logging.debug("execInChroot : Executing command {0}".format(command))
        proc = subprocess.Popen(['chroot {0} {1}'.format(systemPartition, command)], stdout=subprocess.PIPE, shell=True)
        return [proc.returncode, proc.stdout.read()]
def addFstrim(systemPartition, linuxVersion):
    logging.info("addFstrim : Enabling fstrim")
    if not dryRun_flag:
        if linuxVersion == "centos7":
            execInChroot(systemPartition, "ln -s /usr/lib/systemd/system/fstrim.timer /etc/systemd/system/multi-user.target.wants/")
        if (linuxVersion == "centos6") or (linuxVersion == "debian7"):
            os.system("echo \"lsblk -o mountpoint | grep '^/' | xargs -n1 fstrim\" > "+systemPartition+"/etc/cron.weekly/fstrim")
            #execInChroot(systemPartition, "echo \"grep '/dev/sd' /proc/mounts  | awk '{print \$2}' | xargs -n1 fstrim\" > /etc/cron.weekly/fstrim")
            execInChroot(systemPartition, "chmod +x /etc/cron.weekly/fstrim")


if __name__ == '__main__':
if __name__ == '__main__':
     main()
     main()
 
[root@hv1.ircem Scripts]#
 
</pre>
</pre>


=== Script HyperV ===
=== Script HyperV (old deprecated) ===
<pre>
<pre>
# Import VM
# Import VM
Ligne 419 : Ligne 617 :
</pre>
</pre>


=== Script HyperV-Bisnew ===
<pre>
Prerequis : installer un qemu sur l hyperV : https://qemu.weilnetz.de/w64/
Migration_KVM.ps1
Choisir la VM VM1
- Une fois le script terminé, éditer la VM, ajouter un lecteur cdrom sur le controller IDE, attacher l'ISO "D:\ISOs\systemrescue-8.03-amd64.iso"
- Selectionner le CDROM en tant que premier choix de boot
- Sur le system rescue :
monter /dev/vg_gipmds/iv dans /mnt
editer /mnt/etc/fstab pour remplacer /dev/vda2 par /dev/sda2
- enlever le CD et redémarrer
Cas connu : Error: "This document already has a 'DocumentElement' node." -> il y a plusieurs xml dans le dossier d&#8217;export, il faut supprimer le mauvais
Allumer la VM sur le nouvel HV; En cas de problème de boot, faire Echap lors du démarrage de grub, appuyer sur e pour editer la premiere entrée, vérifier que les lignes correspondent bien a celle du fichier /export/<nom de la VM>/files/menu.lst ou  /export/<nom de la VM>/files/grub.cfg
<pre>


=== Script HyperV-Bis ===
<pre>
<pre>
#Prerequis : installer un qemu sur le hv01 : https://qemu.weilnetz.de/w64/
#          : faire un share cifs a monter depuis le kvm
# Import xml de libvirt
# Import xml de libvirt
# convert raw en VHD
# convert raw en VHD
Ligne 432 : Ligne 648 :
# Demandes des informations
# Demandes des informations
Write-Output "Choisir le chemin de la VM a importer :"
Write-Output "Choisir le chemin de la VM a importer :"
$InputVmSrcPath=ls Z: | Select FullName | Out-GridView -OutputMode Single -Title "Choisir le chemin de la VM a importer :"
$InputVmSrcPath=ls D:\Migration | Select FullName | Out-GridView -OutputMode Single -Title "Choisir le chemin de la VM a importer :"
Write-Output $InputVmSrcPath.Fullname
Write-Output $InputVmSrcPath.Fullname


Ligne 441 : Ligne 657 :


Write-Output "Choisir le chemin d'export :"
Write-Output "Choisir le chemin d'export :"
$InputVmDestPath=ls C:\ClusterStorage | Select FullName | Out-GridView -OutputMode Single -Title "Choisir où exporter la VM :"
$InputVmDestPath=Get-Item "D:\VMs"
Write-Output $InputVmDestPath.Fullname
Write-Output $InputVmDestPath.Fullname


Ligne 474 : Ligne 690 :
}
}


### Création dossiers
### Création dossiers
Write-Output "Création des dossiers dans le ClusterStorage"
Write-Output "Création des dossiers dans le ClusterStorage"
New-Item -ItemType "Directory" -path $InputVmDestPath.FullName -Name $VMName -ErrorAction Ignore
New-Item -ItemType "Directory" -path $InputVmDestPath.FullName -Name $VMName -ErrorAction Ignore
Ligne 493 : Ligne 709 :
         if ($VMInterface.mac.address) {
         if ($VMInterface.mac.address) {
             Write-Output "Ajout interface $($VMInterface.mac.address) VLAN $($Matches.vlan)"
             Write-Output "Ajout interface $($VMInterface.mac.address) VLAN $($Matches.vlan)"
             $adp=$VM | Add-VMNetworkAdapter -StaticMacAddress $VMInterface.mac.address -SwitchName 'vswitch_csn' -Name "Interface VLAN $($Matches.vlan)" -Passthru -Verbose
             $adp=$VM | Add-VMNetworkAdapter -StaticMacAddress $VMInterface.mac.address -SwitchName $Matches.sw -Name "Interface VLAN $($Matches.vlan)" -Passthru -Verbose
         }
         }
         else {
         else {
             Write-Output "Ajout interface VLAN $($Matches.vlan)"
             Write-Output "Ajout interface VLAN $($Matches.vlan)"
             $adp=$VM | Add-VMNetworkAdapter -DynamicMacAddress $true -SwitchName 'vswitch_csn' -Name "Interface VLAN $($Matches.vlan)" -Passthru -Verbose
             $adp=$VM | Add-VMNetworkAdapter -DynamicMacAddress $true -SwitchName $Matches.sw -Name "Interface VLAN $($Matches.vlan)" -Passthru -Verbose
         }
         }
         $adp | Set-VMNetworkAdapterVlan -Access -VlanId $Matches.vlan -Verbose
         $adp | Set-VMNetworkAdapterVlan -Access -VlanId $Matches.vlan -Verbose
Ligne 513 : Ligne 729 :
             $name=$Matches.name
             $name=$Matches.name
             Write-Output "$name.raw : Conversion du raw en VHD avec qemu-convert"   
             Write-Output "$name.raw : Conversion du raw en VHD avec qemu-convert"   
             & qemu-img.exe convert "$($InputVmSrcPath.FullName)\$name.raw" -O vpc -o subformat=fixed "$($InputVmSrcPath.FullName)\$name.vhd"
             & "C:\Program Files\qemu\qemu-img.exe" convert "$($InputVmSrcPath.FullName)\$name.raw" -O vpc -o subformat=fixed "$($InputVmSrcPath.FullName)\$name.vhd"
              
              
             Write-Output "$name.vhd : Suppression du sparse flag"
             Write-Output "$name.vhd : Suppression du sparse flag"
Ligne 533 : Ligne 749 :
   }
   }
}
}
### Ajout Cluster
Write-Output "Ajout de la VM au cluster"
$VM  | Add-ClusterVirtualMachineRole
Write-Output "VM ajoutée"


Write-Output "Fin des opérations du script"
Write-Output "Fin des opérations du script"

Version actuelle datée du 1 septembre 2021 à 14:33

Ce script sert à migrer une VM qui se trouve sur un Kvm vers un HyperV

Infos / Prérequis[modifier]


1. Sur le kvm source, vérifier que le montage cifs vers l hyperv est OK (sinon mount -t cifs -o user=admin-agarik //172.30.107.99/Z$ /export)

2. Shutdown de la VM : 

3. Lancement du script d'export sur le KVM hébergeant la machine, executer le script :
/export/Scripts/mig.py --debug nom-de-la-VM
Au cas où il y a un problème après la copie de TOUS les disques, il est possible de relancer avec l'option --no-copy
Si on a besoin de faire des modifications a la main avant l'import, il est possible d'inhiber le processus de unmount/kpart -dv/losetup pour garder le montage avec l'option --no-clean. Il faudra faire les opérations manuellement  
Si le script dit VM not found on this HV, il faut faire un virsh define /etc/libvirt/qemu/le-nom-de-la-vm.xml et relancer le script

4. Imports dans hyperv
Aller sur HV01.csn.fr,
Cliquer sur le raccourci "Import depuis KVM" sur hv01 et choissisez la VM et le cluster volume
Laissez les opérations se dérouler
Si le script met plus de 15minutes à s'executer sans changer d'état, il faut le stopper, nettoyer la configuration de VM sur hv01 si elle s'est déja créée et relancer le script 
En cas de soucis, laisser la fenetre powershell avec l'erreur ouverte pour pouvoir Debug
Cas connu : Error: "This document already has a 'DocumentElement' node." -> il y a plusieurs xml dans le dossier d’export, il faut supprimer le mauvais
Cas connu : un problème a la création d’une interface réseau -> il faut éditer le xml (depuis le kvm) et ajouter l’adresse mac de l’AO au niveau de l’interface, exemple :
		<interface type='bridge'>
			<mac address='52:54:00:e1:df:a9'/>

Allumer la VM sur le nouvel HV
En cas de problème de boot, faire Echap lors du démarrage de grub, appuyer sur e pour editer la premiere entrée, vérifier que les lignes correspondent bien a celle du fichier /export/<nom de la VM>/files/menu.lst ou  /export/<nom de la VM>/files/grub.cfg
 << Fichier: 1) appuyé sur e.png >>  << Fichier: 2) editer kernet et initrd.png >>  << Fichier: 3) editer kernel.png >>  << Fichier: 4) editer initrd + boot.png >> 
Si ça marche toujours pas, on passe au rollback. Tu éteints la VM sur l’hyper-V, tu redémarres la VM sur le kvm avec pcs et tu fais préviens le cdp/srs et aussi le client
---

Actions manuelles[modifier]

On décrit ici les actions manuelles à faire

0. Ajout des drivers dans l'initrd
Se connecter à la VM et :
Modifier la ligne INITRD_MODULES de /etc/sysconfig/kernel pour y rajouter : hv_storvsc hv_netvsc hv_vmbus
ou 
Régénérer les initrd avec : mkinitrd
dracut -f --add-drivers "hv_vmbus hv_storvsc hv_netvsc hv_utils hv_balloon hyperv-keyboard hyperv_fb hid-hyperv"

Modifier /etc/default/grub et supprimer les options suivantes si elles sont présentes : rd.lvm.lv=centos_centos7/swap rd.lvm.lv=centos_centos7/root
Faire grub2-mkconfig -o /boot/grub2/grub.cfg
Verifier qu'il n'y a plus de référence en refaisant : grep rd.lvm.lv /boot/grub2/grub.cfg


1. Vérifications et arret de la VM
Vérifier que la VM n'existe pas déja sur le cluster hyperv
Sur le kvm source, Vérifier que le montage cifs vers hv01 est OK : grep cifs /proc/mounts
	Si ce n'est pas le cas : 
	   si on est sur kvm1 ou kvm2 : mount -t cifs -o user=admin-agarik //172.30.107.99/Z$ /export 
	   si on est sur kvm3 ou kvm4 : mount -t cifs -o user=admin-agarik //172.30.107.57/Z$ /export 
	   si on est sur kvm5 ou kvm6 : mount -t cifs -o user=admin-agarik,domain=hv01 //172.30.107.167/Z$ /export
	Le mot de passe est dans la fiche de hv01 (compte qui n'est pas celui du domaine)

Créer un dossier au nom de la VM dans /export
shutdown VM : 
	- si kvm1-2 ou kvm3-4 : clusvcadm -s vm:nom-de-la-VM puis clusvcadm -d vm:nom-de-la-VM
	- si kvm5-6 : pcs resource disable nom-de-la-VM
Vérifier que la VM ne tourne plus : virsh list | grep nom-de-la-VM sur les deux noeuds du cluster


2. Copie de la VM sur hv01 via le montage cifs
Faire un dump de la conf de la VM dans le dossier :
    virsh dumpxml nom-de-la-VM > /export/nom-de-la-VM/nom-de-la-VM.xml

Pour chaque disque de la VM :
  dd if=/dev/vol_kvmX/nom-du-lv of=/export/nom-de-la-VM/nom-du-lv.raw bs=2M

3. Modifications de la VM

3.1 Modification lettre de lecteur
Faire un kpartx avec le disque system copié : kpart -av /export/nom-de-la-VM/nom-du-lv.raw
Monter la partition systeme et la partition boot si il y en a une :
   mount /dev/mapper/loopXXXXX /mnt
Faire un : chroot /mnt /bin/bash
changer :
 - /etc/fstab
		Remplacer tous les vd* (vda, vbd etc) par sd* (sda, sdb, etc)
		Noter a quoi correspond chaque HD dans un coin
Si centos 6 : 
 - /boot/grub/menu.lst
		Dans la ligne kernel remplacer le vd* (vda, vbd etc) par sd* (sda, sdb, etc)
		supprimer le clocksource=kvm-clock si présent
		supprimer le console=ttyS0,115200 si présent
 - /boot/grub/device.map
		Remplacer tous les vd* (vda, vbd etc) par hd* (hda, hdb, etc)
Si centos 7 :
 - /boot/grub2/grub.cfg
 		Dans la ligne kernel remplacer le vd* (sda, vbd etc) par sd* (sda, sdb, etc)
		supprimer le clocksource=kvm-clock si présent
		supprimer le console=ttyS0,115200 si présent

 
3.2 Démontage chroot/umount/kpartx
quitter le chroot
Faire un umount de la partition
Defaire un kpartx avec le disque system copié : kpart -dv /export/nom-de-la-VM/nom-du-lv.raw

3.3 Contournement bug kparts
faire un losetup -a 
Noter les interfaces loop0 loop1 qui sont affichées
Pour chaque loop0/loop1
faire un kpartx -dv /dev/loop0
puis faire un losetup -d /dev/loop0


4. Imports dans hyperv
Aller sur HV01.csn.fr,
Cliquer sur le raccourci "Import depuis KVM" sur hv01 et choissisez la VM et le cluster volume
Laissez les opérations se dérouler
Si le script met plus de 15minutes à s'executer sans changer d'état, il faut le stopper, nettoyer la configuration de VM sur hv01 si elle s'est déja créée et relancer le script 
Allumer la VM sur le nouvel HV
En cas de soucis, laisser la fenetre powershell avec l'erreur ouverte pour pouvoir Debug


5. Installation kernel récent / integration tools
En direct sur la VM
SI centos 7 :
 dans /etc/sysconfig/grub : modifier la ligne GRUB_CMDLINE_LINUX pour supprimer : rd.lvm.lv=centos_centos7/swap rd.lvm.lv=centos_centos7/root


 - mettre a jour le kernel (uniquement le kernel et paquets correspondant au kernel (firmware etc)), normalement :  yum update kernel
 Si il y a des erreurs 403 il est possible qu'il soit nécessaire de passer par les proxy agarik infra :
   - Si centos : Mettre dans /etc/yum.conf : proxy=http://192.168.26.11:3128
   - Si debian : export http_proxy=http://192.168.26.11:3128 
   Il va surement faloir modifier la configuration de proxy1%infra pour autoriser la VM a acceder aux depots centos - voir https://agawiki.agarik.eu/Equipement/proxy1.so.infra.agarik.com#head-ad26feae7f93d077881d86c1581ce321448a38a8
   Si erreur 404 : mettre releasever=6 dans /etc/yum.conf
 - installer les outils d'intégrations : yum install -y hyperv-daemons ou apt-get install hyperv-daemons
 - redémarrer

Script KVM[modifier]

mig.py --windows --dry vm1
Au cas où il y a un problème après la copie de TOUS les disques, il est possible de relancer avec l'option --no-copy
Si on a besoin de faire des modifications a la main avant l'import, il est possible d'inhiber le processus de unmount/kpart -dv/losetup pour garder le montage avec l'option --no-clean. Il faudra faire les opérations manuellement  
Si le script dit VM not found on this HV, il faut faire un virsh define /etc/libvirt/qemu/le-nom-de-la-vm.xml et relancer le script



#!/usr/bin/env python
"""Script migration VM de KVM vers Hyper-V"""
# pylint: disable=invalid-name,logging-format-interpolation,global-statement,multiple-imports,line-too-long,missing-docstring,anomalous-backslash-in-string


from optparse import OptionParser
import logging, subprocess, os, atexit, re, tempfile, time
import xml.etree.ElementTree

logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
force_flag = False
dryRun_flag = False
#
srcVM = ""
dstPath = ""
noCopy_flag = False
doNotClean_flag = False
exportedRaw = []
mountedPartPath = []


def main():
    global srcVM, noCopy_flag, doNotClean_flag, dryRun_flag

    ## Parse args
    parser = OptionParser(usage="usage: %prog [options] VMname", version="%prog 1.0")
    parser.add_option("-f", "--force", action="store_true", dest="force_flag", default=False, help="force")
    parser.add_option("-n", "--no-copy", action="store_true", dest="noCopy_flag", default=False, help="Do not copy disk")
    parser.add_option("-d", "--debug", action="store_true", dest="debug_flag", default=False, help="Print debug")
    parser.add_option("-a", "--no-clean", action="store_true", dest="noclean_flag", default=False, help="No umount/no kpartx -d")
    parser.add_option("-b", "--dry", action="store_true", dest="dryrun_flag", default=False, help="Do not modify files in export")
    parser.add_option("-w", "--windows", action="store_true", dest="windows_flag", default=False, help="windows VM")

    (options, args) = parser.parse_args()

    if len(args) != 1:
        parser.error("wrong number of arguments")

    srcVM = args[0]
    noCopy_flag = options.noCopy_flag
    doNotClean_flag = options.noclean_flag
    dryRun_flag = options.dryrun_flag
    if options.debug_flag:
        logging.getLogger().setLevel(logging.DEBUG)

    # Start of scripts
    logging.debug("Starting script")

    # Check if VM exists in libvirt
    check_VM()
    # Export xml
    exportXML()
    copyDisk()

    if not options.windows_flag:
        systemPartition = getSystemPartition()
        if not systemPartition:
            logging.critical("Could not find system partition")
            exit()

        # Detection version Linux
        linuxVersion = detectLinuxVersion(systemPartition)
        if linuxVersion == "unknown":
            logging.critical("Could not determine Linux Version")
            exit()
        logging.info("VM - Linux Version : {0}".format(linuxVersion))
        saveFolder = dstPath+"/files"
        if not os.path.exists(saveFolder):
            try:
                os.makedirs(saveFolder)
            except OSError as e:
                logging.critical("main: Error creating {0} : {1}".format(saveFolder, e))
                exit()

        # Changement FSTAB
        changeFstab(systemPartition, saveFolder)

        # Ajout proxy yum.conf
        changePackageManager(systemPartition, saveFolder, linuxVersion)

        # Install hypervtools
        installTools(systemPartition, linuxVersion)
        # Update kernel
        updateKernel(systemPartition, linuxVersion)
        # Update initramfs
        changeInitrd(systemPartition, linuxVersion)
        # Changement vda dans GRUB
        changeGrub(systemPartition, saveFolder, linuxVersion)
        # Ajout fstrim
        addFstrim(systemPartition, linuxVersion)

    os.system("sync")
    time.sleep(3)

def check_VM():
    """Check existence of the VM"""
    logging.debug("check_VM: Checking VM existence with virsh list")
    proc = subprocess.Popen(['/usr/bin/virsh', 'list --all --name'], stdout=subprocess.PIPE)
    tmp = proc.stdout.read()
    VMs = tmp.splitlines()
    logging.debug("check_VM: Found {0} VM on HV".format(len(VMs)))
    if srcVM in VMs:
        logging.info("check_VM: Found VM : {0}".format(srcVM))
    else:
        logging.critical("check_VM: VM {0} not found on this HV".format(srcVM))
        exit()

def exportXML():
    """Export libvirt XML config to /export"""
    global dstPath
    dstPath = "/export/{0}".format(srcVM)
    logging.debug("exportXML: Checking dir")
    if not os.path.exists(dstPath):
        logging.debug("exportXML: {0} does not exist, creating it".format(dstPath))
        try:
            os.makedirs(dstPath)
        except OSError as e:
            logging.critical("exportXML: Error creating {0} : {1}".format(dstPath, e))
            exit()
    else:
        logging.debug("exportXML: {0} exists".format(dstPath))
    logging.info("exportXML: exporting VM conf")
    os.system("/usr/bin/virsh dumpxml {0} > {1}/{0}.xml".format(srcVM, dstPath))

def copyDisk():
    """Read XML conf to find all disk and copy them to export"""
    global exportedRaw
    disks = []
    logging.info("copyDisk: Searching for disk in XML")
    tree = xml.etree.ElementTree.parse("{0}/{1}.xml".format(dstPath, srcVM))
    root = tree.getroot()
    for vmdisk in root.findall("./devices/disk"):
        if vmdisk.attrib['device'] == 'disk':
            srcDiskPath = vmdisk.find('source').attrib['dev']
            logging.info("copyDisk: Found disk {0}".format(srcDiskPath))
            disks.append(srcDiskPath)
    for disk in disks:
        dstRaw = "{0}/{1}.raw".format(dstPath, os.path.basename(disk))
        logging.info("copyDisk : Copying {0} to {1}".format(disk, dstRaw))
        exportedRaw.append(dstRaw)
        if noCopy_flag:
            logging.info("copyDisk : option no-copy - skipping disk")
        else:
            os.system("/bin/dd if={0} of={1} bs=2M".format(disk, dstRaw))


def getSystemPartition():
    """Mount the system partition of the VM and return the path"""
    logging.info("mountSystemPartition : Searching & mounting VM root partition")
    for raw in exportedRaw:
        logging.debug("mountSystemPartition : Searching {0}".format(raw))
        mappings = run_kpartx(raw)
        for mapping in mappings:
            path = "/dev/mapper/{0}".format(mapping)
            # if kpartx fails
            if not os.path.exists(path):
                logging.critical("mountSystemPartition : {0} does not exist ! kpartx error?".format(path))
                exit()
            # Verify that there is a filesystem on the mapping
            # pylint: disable=no-member
            path = os.path.join(os.path.dirname(path), os.readlink(path)) # Needed for centos 7
            proc = subprocess.Popen(['/usr/bin/file', '-sk', path], stdout=subprocess.PIPE)
            tmp = proc.stdout.read()
            if "filesystem" not in tmp:
                logging.debug("mountSystemPartition : ignoring {0} file return : \"{1}\"".format(path, tmp))
            else:
                tmpDir = tempfile.mkdtemp()
                logging.debug("mountSystemPartition : mounting {0} file on {1}".format(path, tmpDir))
                os.system("/bin/mount {0} {1}".format(path, tmpDir))
                if not doNotClean_flag:
                    atexit.register(undo_mount, tmpDir)

                if os.path.exists(tmpDir+"/etc/passwd"):
                    logging.info("mountSystemPartition : Found system partition : {0}".format(path))
                    return tmpDir
    return

def run_kpartx(raw):
    """Run kpartx and return a list of partition mappings"""
    cleanMappings = []
    proc = subprocess.Popen(['/sbin/kpartx', '-asv', '{0}'.format(raw)], stdout=subprocess.PIPE)
    tmp = proc.stdout.read()
    output = tmp.splitlines()
    if not doNotClean_flag:
        atexit.register(undo_kpartx, raw)
    mapping_regex = re.compile(r'^add map ')
    mappings = filter(mapping_regex.search, output)
    for mapping in mappings:
        m = mapping.split()[2]
        logging.debug("run_kpartx : Mapping created : {0}".format(m))
        cleanMappings.append(m)
    if cleanMappings:
        m = re.search(r'(loop[0-9]+)', cleanMappings[0])
        if not doNotClean_flag:
            atexit.register(undo_losetup, '/dev/'+m.group(0))
            atexit.register(undo_kpartx, '/dev/'+m.group(0))
    return cleanMappings

def undo_kpartx(raw):
    logging.debug("undo_kpartx : unmapping {0}".format(raw))
    os.system("sync")
    os.system("kpartx -s -d {0} > /dev/null".format(raw))

def undo_losetup(raw):
    logging.debug("undo_losetup : unmapping {0}".format(raw))
    os.system("losetup -d {0} > /dev/null".format(raw))

def undo_mount(tmpDir):
    logging.debug("undo_mount : unmounting {0}".format(tmpDir))
    os.system("sync")
    time.sleep(3)
    os.system("/bin/umount {0}".format(tmpDir))
    os.system("sync")
    os.system("/bin/rmdir {0}".format(tmpDir))

def detectLinuxVersion(root):
    """return centos6/centos7/debian7 or unknown"""
    if os.path.exists(root+"/etc/redhat-release"):
        relfile = open(root+"/etc/redhat-release", "r")
        line = relfile.readline()
        relfile.close()
        if line.startswith("CentOS release 6"):
            return "centos6"
        if line.startswith("CentOS Linux release 7"):
            return "centos7"
    if os.path.exists(root+"/etc/debian_version"):
        relfile = open(root+"/etc/debian_version", "r")
        line = relfile.readline()
        relfile.close()
        if line.startswith("7."):
            return "debian7"
    return "unknown"

def changeFstab(systemPartition, saveFolder):
    changeFile(systemPartition, saveFolder, "/etc/fstab", [["/dev/vd", "/dev/sd"]])

def changeGrub(systemPartition, saveFolder, linux_vers):
    if linux_vers == "centos6":
        [code, stdout] = execInChroot(systemPartition, "rpm -qa kernel | sort -n | tail -1 | sed 's/kernel-//'")
        stdout = stdout.rstrip()
        changeFile(systemPartition, saveFolder, "/boot/grub/menu.lst", [["/dev/vd", "/dev/sd"],
                                                                        ["clocksource=kvm-clock", ""],
                                                                        ["console=ttyS0,115200", ""],
                                                                        ["^title.*", "title CentOS ({0})".format(stdout)],
                                                                        ["/boot/vmlinuz[^\s]*", "/boot/vmlinuz-{0}".format(stdout)],
                                                                        ["/boot/init[^\s]*", "/boot/initramfs-{0}.img".format(stdout)]
                                                                       ])

        changeFile(systemPartition, saveFolder, "/boot/grub/device.map", [["/dev/vd", "/dev/sd"]])
    elif linux_vers == "centos7":
        [code, stdout] = execInChroot(systemPartition, "rpm -qa kernel | sort -n | tail -1 | sed 's/kernel-//'")
        stdout = stdout.rstrip()
        # Clean probleme template agarik
        changeFile(systemPartition, saveFolder, "/etc/default/grub", [["rd.lvm.lv=centos_centos7/swap", ""],
                                                                      ["rd.lvm.lv=centos_centos7/root", ""]
                                                                     ])
        changeFile(systemPartition, saveFolder, "/boot/grub2/grub.cfg", [["/dev/vd", "/dev/sd"],
                                                                         ["clocksource=kvm-clock", ""],
                                                                         ["console=ttyS0,115200", ""],
                                                                         ["^menuentry.*", "menuentry 'CentOS Linux ({0}) 7 (Core) ' {{".format(stdout)],
                                                                         ["/boot/vmlinuz[^\s]*", "/boot/vmlinuz-{0}".format(stdout)],
                                                                         ["/boot/init[^\s]*", "/boot/initramfs-{0}.img".format(stdout)]
                                                                        ])
        changeFile(systemPartition, saveFolder, "/boot/grub2/device.map", [["/dev/vd", "/dev/sd"]])
    elif linux_vers == "debian7":
        [code, stdout] = execInChroot(systemPartition, "dpkg -l linux-image-3\* | grep '^ii' | sort -n | tail -1 | cut -d\  -f3 | sed s/linux-image-//")
        stdout = stdout.rstrip()
        changeFile(systemPartition, saveFolder, "/boot/grub/grub.cfg", [["/dev/vd", "/dev/sd"],
                                                                        ["^menuentry.*", "menuentry 'Debian GNU/Linux, with Linux {0}' --class debian --class gnu-linux --class gnu --class os {{".format(stdout)],
                                                                        ["/boot/vmlinuz[^\s]*", "/boot/vmlinuz-{0}".format(stdout)],
                                                                        ["/boot/init[^\s]*", "/boot/initrd.img-{0}".format(stdout)]
                                                                       ])

        changeFile(systemPartition, saveFolder, "/boot/grub/device.map", [["/dev/vd", "/dev/sd"]])
def changeFile(systemPartition, saveFolder, filepath, regexList):
    """
    Change systemPartition/filepath with a copy of the original file in saveFolder
    regexList is a list of one or multiple ["regex","replaceExpr"]
    """
    filename = os.path.basename(filepath)

    saveOrig = "{0}/{1}.orig".format(saveFolder, filename)
    saveMod = "{0}/{1}".format(saveFolder, filename)
    logging.info("changeFile : Copying original file {0} to {1}".format(filename, saveOrig))
    os.system("/bin/cp {0}/{1} {2}".format(systemPartition, filepath, saveOrig))
    if not os.path.exists(saveOrig):
        logging.critical("changeFile : {0} does not exist".format(saveOrig))
        exit()
    if os.path.exists(saveMod):
        logging.debug("changeFile : {0} exist, renaming with .old suffix".format(saveOrig))
        os.rename(saveMod, saveMod+".old")
    fin = open(saveOrig, 'r')
    fout = open(saveMod, 'w')

    for iline in fin:
        line = iline
        for regex in regexList:
            line = re.sub(regex[0], regex[1], line)

        if iline != line:

            logging.debug("changeFile : Replaced\n    {0}   by\n    {1}   in {2}".format(iline, line, filename))
        fout.write(line)
    # try:
    #     fout.writelines(fin.readlines())
    # except Exception as E:
    #     raise E

    fin.close()
    fout.close()
    if not dryRun_flag:
        os.system("/bin/cp {0} {1}/{2}".format(saveMod, systemPartition, filepath))

def changePackageManager(systemPartition, saveFolder, linux_vers):
    if linux_vers == "centos6":
        changeFile(systemPartition, saveFolder, "/etc/yum.conf", [[r"^releasever=.*", ""],
                                                                  [r"^proxy=.*", ""],
                                                                  [r"^keepcache=.*", ""],
                                                                 ])
        if not dryRun_flag:
            os.system("echo 'proxy=http://192.168.26.11:3128' >> {0}/etc/yum.conf".format(systemPartition))
            os.system("echo 'releasever=6' >> {0}/etc/yum.conf".format(systemPartition))
            os.system("echo 'keepcache=1' >> {0}/etc/yum.conf".format(systemPartition))
            execInChroot(systemPartition, "yum clean all")

    elif linux_vers == "centos7":
        changeFile(systemPartition, saveFolder, "/etc/yum.conf", [[r"^releasever=.*", ""],
                                                                  [r"^proxy=.*", ""],
                                                                 ])
        if not dryRun_flag:
            os.system("echo 'proxy=http://192.168.26.11:3128' >> {0}/etc/yum.conf".format(systemPartition))
            os.system("echo 'releasever=7' >> {0}/etc/yum.conf".format(systemPartition))
            os.system("echo 'keepcache=1' >> {0}/etc/yum.conf".format(systemPartition))
            execInChroot(systemPartition, "yum clean all")
    elif linux_vers == "debian7":
        if not dryRun_flag:
            os.system("echo 'Acquire::http::Proxy \"http://192.168.26.15:3128\";' > {0}/etc/apt/apt.conf.d/99HttpProxy".format(systemPartition))
            execInChroot(systemPartition, "apt-get clean")
            execInChroot(systemPartition, "apt-get  install -y debian-keyring debian-archive-keyring")

def installTools(systemPartition, linuxVersion):
    logging.info("installTools : Installing hyperv-tools")
    if (linuxVersion == "centos6") or (linuxVersion == "centos7"):
        execInChroot(systemPartition, "yum --disablerepo=\"*\" --enablerepo=base,updates install -y hyperv-daemons")
    elif linuxVersion == "debian7":
        execInChroot(systemPartition, "apt-get update")
        [code, output] = execInChroot(systemPartition, "apt-get install -y hyperv-daemons")
        logging.debug("installTools : return code {0}".format(code))
        logging.debug("installTools : return output {0}".format(output))

def updateKernel(systemPartition, linuxVersion):
    logging.info("updateKernel : Updating kernel")
    if (linuxVersion == "centos6") or (linuxVersion == "centos7"):
        execInChroot(systemPartition, "yum --disablerepo=\"*\" --enablerepo=base,updates update -y kernel")
    elif linuxVersion == "debian7":
        execInChroot(systemPartition, "apt-get update")
        [code, output] = execInChroot(systemPartition, "apt-get upgrade -y linux-image-amd64")
        logging.debug("updateKernel : return code {0}".format(code))
        logging.debug("updateKernel : return output {0}".format(output))

def changeInitrd(systemPartition, linuxVersion):
    logging.info("changeInitrd : Adding hyperv modules to dracut.conf.d/hyperv.conf")
    if not dryRun_flag:
        if linuxVersion == "centos7":
            execInChroot(systemPartition, "echo 'add_drivers=\"$add_drivers hv_vmbus hv_storvsc hv_netvsc hv_utils hv_balloon hyperv-keyboard hyperv_fb hid-hyperv\"' > /etc/dracut.conf.d/hyperv.conf")
            execInChroot(systemPartition, '/bin/bash -c \'for i in /boot/initramfs-*.x86_64.img; do dracut -N -f $i $(basename $i | sed "s/initramfs-//" | sed "s/\.img//"); done\'')
        if linuxVersion == "centos6":
            execInChroot(systemPartition, "echo 'add_drivers=\"$add_drivers hv_vmbus hv_storvsc hv_netvsc hv_utils hv_balloon hyperv-keyboard hyperv_fb hid-hyperv\"' > /etc/dracut.conf.d/hyperv.conf")
            execInChroot(systemPartition, '/bin/bash -c \'for i in /boot/initramfs-*.x86_64.img; do dracut -f $i $(basename $i | sed "s/initramfs-//" | sed "s/\.img//"); done\'')

def execInChroot(systemPartition, command):
    if dryRun_flag:
        logging.debug("execInChroot : DryRun : not executed : command {0}".format(command))
        return [0, ""]
    else:
        logging.debug("execInChroot : Executing command {0}".format(command))
        proc = subprocess.Popen(['chroot {0} {1}'.format(systemPartition, command)], stdout=subprocess.PIPE, shell=True)
        return [proc.returncode, proc.stdout.read()]

def addFstrim(systemPartition, linuxVersion):
    logging.info("addFstrim : Enabling fstrim")
    if not dryRun_flag:
        if linuxVersion == "centos7":
            execInChroot(systemPartition, "ln -s /usr/lib/systemd/system/fstrim.timer /etc/systemd/system/multi-user.target.wants/")
        if (linuxVersion == "centos6") or (linuxVersion == "debian7"):
            os.system("echo \"lsblk -o mountpoint | grep '^/' | xargs -n1 fstrim\" > "+systemPartition+"/etc/cron.weekly/fstrim")
            #execInChroot(systemPartition, "echo \"grep '/dev/sd' /proc/mounts  | awk '{print \$2}' | xargs -n1 fstrim\" > /etc/cron.weekly/fstrim")
            execInChroot(systemPartition, "chmod +x /etc/cron.weekly/fstrim")

if __name__ == '__main__':
    main()
[root@hv1.ircem Scripts]#

Script HyperV (old deprecated)[modifier]

# Import VM
# convert vhdx
# Move VM
# Ajout VM dans cluster


$ErrorActionPreference = "Stop"

# Demandes des informations
Write-Output "Choisir le chemin de la VM a importer :"
$InputVmSrcPath=ls Z: | Select FullName | Out-GridView -OutputMode Single -Title "Choisir le chemin de la VM a importer :"
Write-Output $InputVmSrcPath.Fullname

if (-not (Test-Path -Path "$($InputVmSrcPath.FullName)\Virtual Machines\*.xml")) {
   Write-Error -Message "Erreur : XML de la VM non trouvé dans le dossier Virtual Machines"
   exit 
}

Write-Output "Choisir le chemin d'export :"
$InputVmDestPath=ls C:\ClusterStorage | Select FullName | Out-GridView -OutputMode Single -Title "Choisir où exporter la VM :"
Write-Output $InputVmDestPath.Fullname

### Import
Write-Output "Début vérification compatibilité"
$ConfigPath="$($InputVmSrcPath.FullName)\Virtual Machines\*.xml" | Resolve-Path
Write-Output "Utilisation config : $ConfigPath"
$report=Compare-VM -Register -Path $ConfigPath 
$report.Incompatibilities | Where-Object {$_.MessageID –eq 33012} | ForEach-Object {Connect-VMNetworkAdapter $_.Source –Switchname “Vswitch_CSN”}

Write-Output "Import de la VM"
$VM=Import-VM -CompatibilityReport $report
Write-Output "Fin import de la VM $($VM.Name)"

### Update config
Write-Output "Update configuration de la VM"
Update-VMVersion -Name $VM.Name -Force
Write-Output "Fin update"


### Création dossiers
Write-Output "Création des dossiers dans le ClusterStorage"
New-Item -ItemType "Directory" -path $InputVmDestPath.FullName -Name $VM.Name -ErrorAction Ignore
New-Item  -ItemType "Directory" -path "$($InputVmDestPath.FullName)\$($VM.Name)" -Name "Snapshots" -ErrorAction Ignore
New-Item  -ItemType "Directory" -path "$($InputVmDestPath.FullName)\$($VM.Name)" -Name "Virtual Hard Disks" -ErrorAction Ignore
New-Item  -ItemType "Directory" -path "$($InputVmDestPath.FullName)\$($VM.Name)" -Name "Virtual Machines" -ErrorAction Ignore
Write-Output "Fin de la création des dossiers"

### Conversion disque
Foreach ($vhd in ($VM | Get-VMHardDiskDrive)) {
    $vhdName=$vhd.Path | Split-path -Leaf -Resolve 
    $vhdxPath="$($InputVmDestPath.FullName)\$($VM.Name)\Virtual Hard Disks\$($vhdName)x"
    Write-Output "Conversion du disque $($vhd.Path) vers $vhdxPath"
    Convert-VHD -Path $($vhd.Path) -DestinationPath $vhdxPath -VHDType "Fixed"
    Write-Output "Fin conversion de $($vhd.Path)"
    Write-Output "Remplacement du VHD dans la configuration de VM"
    $vhd | Set-VMHardDiskDrive -Path $vhdxPath
}

### Déplacement VM
Write-Output "Déplacement de la VM dans $($InputVmDestPath.FullName)"
$VM | Move-VMStorage -VirtualMachinePath "$($InputVmDestPath.FullName)\$($VM.Name)\Virtual Machines" `
                     -SmartPagingFilePath "$($InputVmDestPath.FullName)\$($VM.Name)\Virtual Machines" `
                     -SnapshotFilePath "$($InputVmDestPath.FullName)\$($VM.Name)\Snapshots"
Write-Output "Fin déplacement"

### Ajout Cluster
Write-Output "Ajout de la VM au cluster"
$VM  | Add-ClusterVirtualMachineRole
Write-Output "VM ajoutée"

Write-Output "Fin des opérations du script"
Write-Warning "Il faut mettre manuellement les VLANS sur les interfaces réseaux de la VM"

Script HyperV-Bisnew[modifier]

Prerequis : installer un qemu sur l hyperV : https://qemu.weilnetz.de/w64/
Migration_KVM.ps1
Choisir la VM VM1
- Une fois le script terminé, éditer la VM, ajouter un lecteur cdrom sur le controller IDE, attacher l'ISO "D:\ISOs\systemrescue-8.03-amd64.iso"
- Selectionner le CDROM en tant que premier choix de boot
- Sur le system rescue :
monter /dev/vg_gipmds/iv dans /mnt
editer /mnt/etc/fstab pour remplacer /dev/vda2 par /dev/sda2
- enlever le CD et redémarrer

Cas connu : Error: "This document already has a 'DocumentElement' node." -> il y a plusieurs xml dans le dossier d’export, il faut supprimer le mauvais

Allumer la VM sur le nouvel HV; En cas de problème de boot, faire Echap lors du démarrage de grub, appuyer sur e pour editer la premiere entrée, vérifier que les lignes correspondent bien a celle du fichier /export/<nom de la VM>/files/menu.lst ou  /export/<nom de la VM>/files/grub.cfg
<pre>

<pre>
#Prerequis : installer un qemu sur le hv01 : https://qemu.weilnetz.de/w64/
#          : faire un share cifs a monter depuis le kvm

# Import xml de libvirt
# convert raw en VHD
# convert vhd en vhdx
# Ajout VM dans cluster


$ErrorActionPreference = "Stop"

# Demandes des informations
Write-Output "Choisir le chemin de la VM a importer :"
$InputVmSrcPath=ls D:\Migration | Select FullName | Out-GridView -OutputMode Single -Title "Choisir le chemin de la VM a importer :"
Write-Output $InputVmSrcPath.Fullname

if (-not (Test-Path -Path "$($InputVmSrcPath.FullName)\*.xml")) {
   Write-Error -Message "Erreur : XML de la VM non trouvé"
   exit 
}

Write-Output "Choisir le chemin d'export :"
$InputVmDestPath=Get-Item "D:\VMs"
Write-Output $InputVmDestPath.Fullname

### Lecture XML
$ConfigPath="$($InputVmSrcPath.FullName)\*.xml" | Resolve-Path
[xml]$XmlDocument = Get-Content -Path $ConfigPath
$VMName=$XmlDocument.domain.name
$VMCpu=$XmlDocument.domain.vcpu.'#text'
$VMMemory="$($XmlDocument.domain.memory.'#text')$($($XmlDocument.domain.memory.unit).Replace('iB','B'))"/1
if (($VMMemory/2MB) % 2 -ne 0) { ## The VM memory size need to be a multiple of 2MB
    $VMMemory=([Math]::Ceiling($VMMemory/2MB)*2MB)
}

$VMDisks=$XmlDocument.domain.devices.disk
$VMInterfaces=$XmlDocument.domain.devices.interface
$VMPath="$($InputVmDestPath.FullName)\$VMName"

### Check presence fichier disks
Foreach ($VMDisk in $VMDisks) {
    if ($VMDisk.device -eq 'disk') {
        if ($VMDisk.source.dev -match '/dev/(?<volume>[^/]+)/(?<name>.+)') {
            $name=$Matches.name
            if (Test-Path -PathType Leaf -Path "$($InputVmSrcPath.FullName)\$name.raw") {
                Write-Output "$name.raw trouvé"
            }
            else {
                Write-Error "Fichier $name.raw non trouvé dans $($InputVmSrcPath.FullName) !"
                Exit
            }
        }
    }
}

### Création dossiers
Write-Output "Création des dossiers dans le ClusterStorage"
New-Item -ItemType "Directory" -path $InputVmDestPath.FullName -Name $VMName -ErrorAction Ignore
New-Item  -ItemType "Directory" -path $VMPath -Name "Snapshots" -ErrorAction Ignore
New-Item  -ItemType "Directory" -path $VMPath -Name "Virtual Hard Disks" -ErrorAction Ignore
New-Item  -ItemType "Directory" -path $VMPath -Name "Virtual Machines" -ErrorAction Ignore
Write-Output "Fin de la création des dossiers"

### Creation VM
$VM=New-VM -Name $VMName -Generation 1 -MemoryStartupBytes $VMMemory -Path $InputVmDestPath.FullName -BootDevice IDE -Verbose
$VM | Set-VMProcessor -Count $VMCpu -verbose
$VM | Remove-VMNetworkAdapter
$VM | Get-VMDvdDrive | Remove-VMDvdDrive

### Ajout interfaces
Foreach ($VMInterface in $VMInterfaces) {
    if ($VMInterface.source.bridge -match '(?<sw>[a-zA-Z]+)(?<vlan>\d+)') {
        if ($VMInterface.mac.address) {
            Write-Output "Ajout interface $($VMInterface.mac.address) VLAN $($Matches.vlan)"
            $adp=$VM | Add-VMNetworkAdapter -StaticMacAddress $VMInterface.mac.address -SwitchName $Matches.sw -Name "Interface VLAN $($Matches.vlan)" -Passthru -Verbose
        }
        else {
            Write-Output "Ajout interface VLAN $($Matches.vlan)"
            $adp=$VM | Add-VMNetworkAdapter -DynamicMacAddress $true -SwitchName $Matches.sw -Name "Interface VLAN $($Matches.vlan)" -Passthru -Verbose
        }
        $adp | Set-VMNetworkAdapterVlan -Access -VlanId $Matches.vlan -Verbose
    }
    else {
        Write-Error "Erreur ajout interface $($VMInterface.mac.address)"
    }
    
}

### Ajout disks
Foreach ($VMDisk in $VMDisks) {
    if ($VMDisk.device -eq 'disk') {
        if ($VMDisk.source.dev -match '/dev/(?<volume>[^/]+)/(?<name>.+)') {
            $name=$Matches.name
            Write-Output "$name.raw : Conversion du raw en VHD avec qemu-convert"   
            & "C:\Program Files\qemu\qemu-img.exe" convert "$($InputVmSrcPath.FullName)\$name.raw" -O vpc -o subformat=fixed "$($InputVmSrcPath.FullName)\$name.vhd"
            
            Write-Output "$name.vhd : Suppression du sparse flag"
            & fsutil sparse setflag "$($InputVmSrcPath.FullName)\$name.vhd" 0
            
            Write-Output "$name.vhd : Conversion du VHD en VHDX + mise dans ClusterVolume"
            $VHDXPath="$VMPath\Virtual Hard Disks\$name.vhdx" 
            Convert-VHD -Path "$($InputVmSrcPath.FullName)\$name.vhd" -DestinationPath $vhdxPath -VHDType "Fixed"
            
            Write-Output "Ajout du disque $name dans $VHDXPath"
            $VM | Add-VMHardDiskDrive -Path $VHDXPath 
        }
        else {
            Write-Warning "Impossible de matcher automatiquement $($VMDisk.source.dev). Faire a la main"
        }
    }
    else {
        Write-Output "Ignore ajout type non-disk"
   }
}

Write-Output "Fin des opérations du script"