Converting GNU/Linux Generation 1 VMs to Generation 2 on Hyper-V 2012 R2 – Part 3

Happy new year!

Continuing with this series on Converting GNU/Linux generation 1 VMs to generation 2 on Hyper-V 2012 R2, I will go over this subjects:

Today I’m going to cover how to convert a GNU/Linux generation 1 VM to a generation 2 VM and take full advantage of all the benefits that comes with generation 2 VMs on Hyper-V 2012 R2.

Sorry for the delay in this post but while documenting the procedure that I will go over below, I found and reported a bug in the Linux kernel provided with CentOS 7. I thought that it didn’t make sense to publish this article if you will hit a critical bug once your VM is converted to generation 2, isn’t it?

To make a long story short, with any CentOS 7 kernel before 3.10.0-327.3.1.el7.x86_64 if you try to hot add or remove any SCSI device to your VM or if you suspend it (only if the VM is generation 2), the Linux kernel will panic and you will need to reset the VM to recover it. Yes, it’s a very serious bug, but fortunately it’s already resolved. If you are interested in having more details you can find more information here.

Having said that, let’s start with the recipe to convert your generation 1 VM running GNU/Linux into a generation 2 VM.

I tried this steps several times and already converted many VMs with it, so far without any post-convertion issues or data loss but I encourage you to backup your VM before starting with it, just in case. For this example let’s call our original generation 1 VM, VMG1 and our new generation 2 VM, VMG2:

  1. Backup VMG1.
  2. Create a new generation 2 VM called VMG2, replicating the configuration from VMG1 in terms of vCPUs, memory, vNICs, etc. If you are using static MACs for the vNICs in VMG1 keep in mind that you may need to replicate that information to VMG2 too in order to avoid any connectivity problems once your VM is converted.
    Also make sure you disable the Secure Boot feature on VMG2 that is enabled by default, by unchecking the checkbox in VMG2 settings, under Hardware –> Firmware.
    disable_sec_boot
  3. Shutdown VMG1 in order to expand the guest OS system VHDX. It’s not possible to do this online since in generation 1 VMs the system disk is IDE and it’s not possible to extend IDE disks online at least in Hyper-V 2012 R2 or any previous version.
  4. Expand VMG1 guest OS system disk by 1GB. You can do this from Hyper-V Manager itself or with Powershell.
  5. After expanding the disk, create a snapshot/checkpoint of VMG1 so if anything goes wrong is pretty easy and fast to rollback the changes and start over.
  6. Now start VMG1.
  7. This step is a little bit longer and delicate so please pay special attention.
    Once the OS is up and running, login as root and run fdisk on the system disk and create a 200MB partition at the end of the disk. You can create a larger partition but for most systems 200MB should be enough. We are doing it at the end of the disk since later we will want to use the spare space to expand the root filesystem so we don’t waste 800MB of precious storage space. If you don’t want to use that space, then it’s fine to create the partition right after the last one on disk, which is more simpler.
    To do the math, we will need to check the sector size and how many sectors the disk has, in my case is a 41GB virtual disk with 85983232 sectors of 512 bytes each. If you will use  200MB for the new EFI partition then the hole between the last partition on the disk and the new one will be of 800MB or in other words 409600 sectors, since 409600 / 512 = 800MB
    With this numbers you can calculate the starting sector of the new partition: Starting Sector = Total Sectors – (409600 + 1)
    Then with this information in fdisk you should follow the next steps (you can interrupt the procedure at anytime without commiting any changes to the disk, pressing q):

    press n to create a new partition
    press p to make it primary
    press <enter> to accept the default partition number suggested, ,and write it down because we will need it in a moment. It should be a number between 1 and 4.For our example: 3
    type the value of <Starting Sector> to indicate which will be the first sector number for the new partition or just press <enter> to create the new partition right after the last one. For our example: 85573631.
    press <enter> to accept the default end sector that in our case will mean the last sector available on the disk
    

    Now that the new partition is created at the end of the disk, we need to change the partition type from Linux (which is the default) to EFI (ef in fdisk):

    press t to change the partition type
    type <new partition number created above> to select the partition we created above (remember I told you to write it down?)
    press ef to change the partition type to EFI

    Then we want to mark the new EFI partition as bootable:

    press a to toggle the booteable flag
    type <new partition number created above> to select the partition we created above

    Check that all the changes are correct by listing the new partition table layout, this is very important:

    press p to show the partition table layout

    And finally we will commit all the changes to the disk partition:

    press w to write the changes to the disk and exit fdisk
  8. Now we need to execute partprobe so the kernel will re-read the partition table and be aware of the changes we made with fdisk:
    # partprobe
  9. Now we will create a new directory called efi in /boot that will serve as a mount point for our new efi partition:
    # mkdir /boot/efi
  10. Install dosfstools if it’s not already installed, so we can format the new EFI partition as FAT:
    # yum install dosfstools
  11. Now we will format the partition with FAT filesystem and label it as “efi-boot”. You can label it as you wish as far as you don’t label it “EFI”, this is very important. Replace <device> with the disk device we are modifying (sda, sdb, etc) and the partition number we created above (ie: sda3):
    # mkfs.vfat -F 16 -n efi-boot /dev/<device>
  12. Next step is to add the new EFI partition to fstab so it will be mounted on next boot automatically:
    # echo $(blkid /dev/sda3 | cut -d " " -f 4 | sed -e 's/\"//g')' /boot/efi    vfat    umask=0077,shortname=winnt    0 1' >> /etc/fstab
  13. Now we are going to mount it and verify that is correctly mounted on /boot/efi:
    # mount -a
    # mount | grep sda3

    The output should look like this one (pay attention to the “on /boot/efi” part highlighted below):

    /dev/sda3 on /boot/efi type vfat (rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro)
  14. After mounting the new partition and verifying that is mounted in the correct mount point, we will create the EFI directories:
    # mkdir -p /boot/efi/EFI/Boot
  15. We will install Grub with EFI support and if you didn’t do it yet also the Hyper-V guest additions that are a must:
    # yum install grub2-efi efibootmgr grub2-efi-modules shim hyperv-daemons
  16. Now we will configure Grub for EFI:
    # grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
    # sed -i -e 's/linux16/linuxefi/g' /boot/efi/EFI/centos/grub.cfg
    # sed -i -e 's/initrd16/initrdefi/g' /boot/efi/EFI/centos/grub.cfg
  17. Next we will shutdown VMG1:
    # systemctl poweroff
  18. Now delete the snapshot created in step 5 and let Hyper-V merge the changes to the disk.
  19. Once the changes are merged, remove the system VHDX from VMG1. We are now ready to move the VHDX to VMG2 that we prepared in advance in step 2.
  20. Add the system VHDX that we removed from VMG1 in the previous step, to VMG2.
  21. Spin-up VMG2 and it should startup just fine.

Except for the fdisk part that is very delicate and that is better to do it manually, the rest of the steps can be scripted to reduce the human error margin and also to speed up the process. You can find an example script below.

I hope you enjoyed this article.
In my next post I will show you how to utilize the small space hole between partitions, if you decided to put the new partition at the end of the disk.

Script to automate the EFI partition preparation and bootloader configuration:

#!/bin/bash 
#
# Author: Fernando Casas Schössow <fernando@fercasas.com> 
# URL: www.fercasas.com 
# Usage: prepare_efi.sh <device>
# Example: prepare_efi.sh sda3 


DEVICE="$1" 
 

if [ ! "$DEVICE" == "" ]; then 

    # Tell the kernel to re-read partition table 
    partprobe 

    # Create efi diretory to mount the new EFI partition 
    mkdir /boot/efi 
     
    # Install mkfs.vfat to format the new EFI partition 
    yum install dosfstools -y 
     
    # Format the new EFI partition with FAT 
    mkfs.vfat -F 16 -n efi-boot /dev/$DEVICE 

    # Add the new EFI partition to fstab so it will be automatically mounted on boot 
    echo $(blkid /dev/$DEVICE | cut -d " " -f 4 | sed -e 's/\"//g')' /boot/efi    vfat    umask=0077,shortname=winnt    0 1' >> /etc/fstab 

    # Mount the new EFI partition 
    mount -a 

    # Create Boot directory under /boot/efi/EFI to hold the EFI bootloader files 
    mkdir -p /boot/efi/EFI/Boot 

    # Install grub for EFI 
    yum install grub2-efi grub2-efi-modules shim efibootmgr hyperv-daemons -y 

    # Generate and modify grub configuration files for UEFI to properly boot in UEFI systems 
    grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg 
    sed -i -e 's/linux16/linuxefi/g' /boot/efi/EFI/centos/grub.cfg 
    sed -i -e 's/initrd16/initrdefi/g' /boot/efi/EFI/centos/grub.cfg 

else 

    echo "ERROR: You need to specify the device id for the partition that will hold the EFI information." 

fi