Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/image/qcow2ova/prep/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ func resize2fs(device string) error {
return nil
}

// growBtrfs resizes the mounted Btrfs volume to max size
func growBtrfs(mountPoint string, size string) error {
exitcode, out, err := utils.RunCMD("btrfs", "filesystem", "resize", size, mountPoint)
if exitcode != 0 {
return fmt.Errorf("failed to grow btrfs volume: %s, exitcode: %d, stdout: %s, err: %s", mountPoint, exitcode, out, err)
}
return nil
}

func mount(opts, src, target string) error {
exitcode, out, err := utils.RunCMD("mount", "-o", opts, src, target)
if exitcode != 0 {
Expand Down
115 changes: 60 additions & 55 deletions cmd/image/qcow2ova/prep/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,22 @@ import (
"github.com/ppc64le-cloud/pvsadm/pkg/utils"
)

var (
hostPartitions = []string{"/proc", "/dev", "/sys", "/var/run/", "/etc/machine-id"}
)
var hostPartitions = []string{"/proc", "/dev", "/sys", "/run", "/etc/machine-id"}

// prepare is a function prepares the CentOS or RHEL image for capturing, this includes
// - Installs the cloud-init
// - Install and configure multipath for rootfs
// - Install all the required modules for PowerVM
// - Sets the root password
func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error {
// Setup loop device and cleanup on exit
lo, err := setupLoop(volume)
if err != nil {
return err
}
defer removeLoop(lo)

err = partprobe(lo)
if err != nil {
if err = partprobe(lo); err != nil {
return err
}

Expand All @@ -51,63 +49,73 @@ func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error {
return err
}

partDev := lo + "p" + partition

err = mount("nouuid", partDev, mnt)
partDev := fmt.Sprintf("%sp%s", lo, partition)
fsType, err := getFSType(partDev)
if err != nil {
return err
}
defer Umount(mnt)

err = growpart(lo, partition)
if err != nil {
return err
}
switch fsType {
case "btrfs":
if err = mount("defaults,subvol=root", partDev, filepath.Join(mnt)); err != nil {
return err
}
defer Umount(mnt)
case "ext2", "ext3", "ext4", "xfs":
if err = mount("nouuid", partDev, mnt); err != nil {
return err
}
defer Umount(mnt)
}

fsType, err := getFSType(partDev)
if err != nil {
// Resize partition
if err = growpart(lo, partition); err != nil {
return err
}

switch fsType {
case "xfs":
err = xfsGrow(partDev)
if err != nil {
if err = xfsGrow(partDev); err != nil {
return err
}
case "ext2", "ext3", "ext4":
err = resize2fs(partDev)
if err != nil {
if err = resize2fs(partDev); err != nil {
return err
}
case "btrfs":
if err = growBtrfs(filepath.Join(mnt, "root"), "max"); err != nil {
return err
}
default:
return fmt.Errorf("unable to handle the %s filesystem for %s", fsType, partDev)
}

// Get the boot partition name and Mount boot partition
fstabPath := filepath.Join(mnt, "etc", "fstab")

//get the boot partition name
deviceuuid, err := bootDeviceuuid(fstabPath)
Comment thread
bkhadars marked this conversation as resolved.
if err != nil {
return err
}

if deviceuuid != "" {
bootDev, err := findDevice(deviceuuid)
if err != nil {
return err
}
err = mount("nouuid", bootDev, filepath.Join(mnt, "boot"))
if err != nil {
bootMount := filepath.Join(mnt, "boot")
if deviceuuid, err := bootDeviceuuid(fstabPath); err == nil && deviceuuid != "" {
if bootDev, err := findDevice(deviceuuid); err == nil {
if fsType == "btrfs" {
if err = mount("defaults", bootDev, bootMount); err != nil {
return err
}
} else {
if err = mount("defaults,nouuid", bootDev, bootMount); err != nil {
return err

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please check if "defaults" work for other filesystem types as well ?

}
}
defer Umount(bootMount)
} else {
return err
}
defer Umount(filepath.Join(mnt, "boot"))
} else if err != nil {
return err
}

// Verify /boot is mounted properly and files are present.
bootDirFiles := []string{"config-*.ppc64le", "efi", "grub2", "initramfs-*.ppc64le.img", "loader", "symvers-*.ppc64le.*", "System.map-*.ppc64le", "vmlinuz-*.ppc64le"}
for _, file := range bootDirFiles {
exist, err := checkFileExists(filepath.Join(mnt, "boot", file))
exist, err := checkFileExists(filepath.Join(bootMount, file))
if err != nil {
return fmt.Errorf("error while validating contents of /boot directory. %v", err)
}
Expand All @@ -123,35 +131,32 @@ func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error {
return err
}
}
defer UmountHostPartitions(mnt)
defer UmountHostPartitions(mnt, dist)

setupStr, err := Render(dist, rhnuser, rhnpasswd, rootpasswd)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(mnt, "setup.sh"), []byte(setupStr), 0744)
if err != nil {
if setupStr, err := Render(dist, rhnuser, rhnpasswd, rootpasswd); err == nil {
if err = os.WriteFile(filepath.Join(mnt, "setup.sh"), []byte(setupStr), 0744); err != nil {
return err
}
} else {
return err
}

err = os.WriteFile(filepath.Join(mnt, "/etc/cloud/cloud.cfg"), []byte(CloudConfig), 0644)
if err != nil {
return err
files := map[string]string{
"/etc/cloud/cloud.cfg": CloudConfig,
"/etc/cloud/ds-identify.cfg": dsIdentify,
}

err = os.WriteFile(filepath.Join(mnt, "/etc/cloud/ds-identify.cfg"), []byte(dsIdentify), 0644)
if err != nil {
return err
for path, content := range files {
if err := os.WriteFile(filepath.Join(mnt, path), []byte(content), 0644); err != nil {
return err
}
}

err = Chroot(mnt)
if err != nil {
if err = Chroot(mnt); err != nil {
return err
}
defer ExitChroot()

err = os.Chdir("/")
if err != nil {
if err = os.Chdir("/"); err != nil {
return err
}

Expand All @@ -163,7 +168,7 @@ func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error {
return nil
}

func UmountHostPartitions(mnt string) {
func UmountHostPartitions(mnt, dist string) {
for _, p := range hostPartitions {
Umount(filepath.Join(mnt, p))
}
Expand All @@ -176,7 +181,7 @@ func Prepare4capture(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) e
//}
//defer os.Chdir(cwd)
switch dist := strings.ToLower(dist); dist {
case "rhel", "centos":
case "rhel", "centos", "fedora":
return prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd)
case "coreos":
klog.Info("No image preparation required for the coreos.")
Expand Down
21 changes: 16 additions & 5 deletions cmd/image/qcow2ova/prep/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ yum install -y cloud-init
yum reinstall grub2-common -y
rm -rf /etc/systemd/system/multi-user.target.wants/firewalld.service
rpm -vih --nodeps https://public.dhe.ibm.com/software/server/POWER/Linux/yum/download/ibm-power-repo-latest.noarch.rpm
sed -i 's/^more \/opt\/ibm\/lop\/notice/#more \/opt\/ibm\/lop\/notice/g' /opt/ibm/lop/configure
echo 'y' | /opt/ibm/lop/configure
{{if eq .Dist "rhel"}}
# Disable the AT repository due to slowness in nature
yum-config-manager --disable Advance_Toolchain
Expand All @@ -49,6 +47,14 @@ yum-config-manager --disable Advance_Toolchain
yum-config-manager --add-repo=https://public.dhe.ibm.com/software/server/POWER/Linux/yum/IBM/RHEL/$(rpm -E %{rhel})/ppc64le/
rpm --import https://public.dhe.ibm.com/software/server/POWER/Linux/yum/IBM/RHEL/$(rpm -E %{rhel})/ppc64le/repodata/repomd.xml.key
{{end}}
{{if eq .Dist "fedora"}}
yum-config-manager --add-repo=https://public.dhe.ibm.com/software/server/POWER/Linux/yum/IBM/$(rpm -E %{dist_vendor})/ppc64le/
rpm --import https://public.dhe.ibm.com/software/server/POWER/Linux/yum/IBM/$(rpm -E %{dist_vendor})/ppc64le/repodata/repomd.xml.key
{{end}}
sed -i -E 's/^(more \/opt\/ibm\/lop\/notice|less \/opt\/ibm\/lop\/notice)/#\1/' /opt/ibm/lop/configure

echo 'y' | /opt/ibm/lop/configure

Comment thread
bkhadars marked this conversation as resolved.
yum install powerpc-utils librtas DynamicRM devices.chrp.base.ServiceRM rsct.opt.storagerm rsct.core rsct.basic rsct.core src -y
yum install -y device-mapper-multipath
cat <<EOF > /etc/multipath.conf
Expand All @@ -66,10 +72,15 @@ defaults {
}
EOF
sed -i 's/GRUB_TIMEOUT=.*$/GRUB_TIMEOUT=60/g' /etc/default/grub
sed -i 's/GRUB_CMDLINE_LINUX=.*$/GRUB_CMDLINE_LINUX="console=tty0 console=hvc0,115200n8 biosdevname=0 crashkernel=auto rd.shell rd.debug rd.driver.pre=dm_multipath log_buf_len=1M "/g' /etc/default/grub
sed -i 's/^\(GRUB_CMDLINE_LINUX_DEFAULT\|GRUB_CMDLINE_LINUX\)=.*$/GRUB_CMDLINE_LINUX="console=tty0 console=hvc0,115200n8 biosdevname=0 crashkernel=auto rd.shell rd.debug rd.driver.pre=dm_multipath log_buf_len=1M autorelabel=1 "/g' /etc/default/grub
echo 'force_drivers+=" dm-multipath "' >/etc/dracut.conf.d/10-mp.conf
dracut --regenerate-all --force
for kernel in $(rpm -q kernel | sort -V | sed 's/kernel-//')
{{if eq .Dist "fedora"}}
kernel_pkg="kernel-core"
{{else}}
kernel_pkg="kernel"
{{end}}
for kernel in $(rpm -q $kernel_pkg | sort -V | sed "s/$kernel_pkg-//")
do
echo "Generating initramfs for kernel version: ${kernel}"
dracut --kver ${kernel} --force --add multipath --include /etc/multipath /etc/multipath --include /etc/multipath.conf /etc/multipath.conf
Expand All @@ -85,7 +96,7 @@ subscription-manager clean
rpm -e ibm-power-repo-*.noarch

mv /etc/resolv.conf.orig /etc/resolv.conf || true
touch /.autorelabel
setfiles -F /etc/selinux/targeted/contexts/files/file_contexts /
`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already part of the code


var CloudConfig = `# latest file from cloud-init-22.1-1.el8.noarch
Expand Down
32 changes: 26 additions & 6 deletions cmd/image/qcow2ova/qcow2ova.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,24 @@ Examples:
# Converts the CentOS image from the local filesystem with size 50GB
pvsadm image qcow2ova --image-name centos-82 --image-dist centos --image-size 50 --image-url /root/CentOS-8-GenericCloud-8.2.2004-20200611.2.ppc64le.qcow2

# Converts the Fedora image from the local filesystem with size 50GB
pvsadm image qcow2ova --image-name fedora-41 --image-dist fedora --image-size 50 --image-url /root/Fedora-Cloud-Base-Generic-41-1.4.ppc64le.qcow2

# Converts the RHEL image from local filesystem
pvsadm image qcow2ova --image-name rhel-82-29oct --image-dist rhel --rhn-user joesmith@example.com --rhn-password someValidPassword --image-url ./rhel-8.2-update-2-ppc64le-kvm.qcow2

# Converts the CentOS image from the local filesystem with OS password set
pvsadm image qcow2ova --image-name centos-82 --image-dist centos --os-password s0meC0mplexPassword --image-url /root/CentOS-8-GenericCloud-8.2.2004-20200611.2.ppc64le.qcow2

## Converts the Fedora image from the local filesystem with OS password set
pvsadm image qcow2ova --image-name fedora-41 --image-dist fedora --os-password s0meC0mplexPassword --image-url /root/Fedora-Cloud-Base-Generic-41-1.4.ppc64le.qcow2

# Converts the CentOS image from the local filesystem without OS password
pvsadm image qcow2ova --image-name centos-82 --image-dist centos --image-url /root/CentOS-8-GenericCloud-8.2.2004-20200611.2.ppc64le.qcow2 --skip-os-password

# Converts the Fedora image from the local filesystem without OS password
pvsadm image qcow2ova --image-name fedora-41 --image-dist fedora --image-url /root/Fedora-Cloud-Base-Generic-41-1.4.ppc64le.qcow2 --skip-os-password

# Customize the image preparation script for RHEL/CentOS distro, e.g: add additional yum repository or packages, change name servers etc.
# Step 1 - Dump the default image preparation template
pvsadm image qcow2ova --prep-template-default > image-prep.template
Expand All @@ -68,14 +77,13 @@ Examples:
# Step 3 - Run the qcow2ova with the modified cloud config template
pvsadm image qcow2ova --image-name centos-82 --image-dist centos --image-url /root/CentOS-8-GenericCloud-8.2.2004-20200611.2.ppc64le.qcow2 --cloud-config user_cloud.config



Qcow2 images location:

# CentOS 8: https://cloud.centos.org/centos/8-stream/ppc64le/images/
# CentOS 9: https://cloud.centos.org/centos/9-stream/ppc64le/images/
# Old Centos: https://cloud.centos.org/centos/8/ppc64le/images/
# RHEL image: https://access.redhat.com/downloads/content/279/ver=/rhel---8/8.3/ppc64le/product-software
# Fedora image: https://archive.fedoraproject.org/pub/fedora-secondary/releases/41/Cloud/ppc64le/images
# RHCOS images: https://mirror.openshift.com/pub/openshift-v4/ppc64le/dependencies/rhcos/

`,
Expand Down Expand Up @@ -113,11 +121,22 @@ Qcow2 images location:
prep.CloudConfig = string(content)

}
if !utils.Contains([]string{"rhel", "centos", "coreos"}, strings.ToLower(opt.ImageDist)) {
klog.Errorln("--image-dist is a mandatory flag and one of these [rhel, centos, coreos]")
if !utils.Contains([]string{"rhel", "centos", "fedora", "coreos"}, strings.ToLower(opt.ImageDist)) {
klog.Errorln("--image-dist is a mandatory flag and valid --image-dist options are [rhel, centos, fedora, coreos]")
os.Exit(1)
}

if opt.ImageDist == "fedora" {
supported, err := utils.IsBtrfsSupported()
if err != nil {
return err
}
if !supported {
klog.Errorln("to create fedora ova image btrfs support should be available in the kernel]")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request to make this little more clear,

os.Exit(1)
}
}

//Read the RHNUser and RHNPassword if empty
if opt.ImageDist == "rhel" && (opt.RHNUser == "" || opt.RHNPassword == "") {
var err error
Expand Down Expand Up @@ -179,6 +198,7 @@ Qcow2 images location:

}

klog.Info("before perflight check validations")
// preflight checks validations
return validate.Validate()
},
Expand Down Expand Up @@ -209,7 +229,7 @@ Qcow2 images location:
<-c
klog.Info("Received an interrupt, exiting.")
prep.ExitChroot()
prep.UmountHostPartitions(mnt)
prep.UmountHostPartitions(mnt, opt.ImageDist)
_ = prep.Umount(mnt)
_ = os.RemoveAll(tmpDir)
os.Exit(1)
Expand Down Expand Up @@ -292,7 +312,7 @@ Qcow2 images location:
func init() {
Cmd.Flags().StringVar(&pkg.ImageCMDOptions.ImageName, "image-name", "", "Name of the resultant OVA image")
Cmd.Flags().StringVar(&pkg.ImageCMDOptions.ImageURL, "image-url", "", "URL or absolute local file path to the <QCOW2>.gz image")
Cmd.Flags().StringVar(&pkg.ImageCMDOptions.ImageDist, "image-dist", "", "Image Distribution(supported: rhel, centos, coreos)")
Cmd.Flags().StringVar(&pkg.ImageCMDOptions.ImageDist, "image-dist", "", "Image Distribution(supported: rhel, centos, fedora, coreos)")
Cmd.Flags().Uint64Var(&pkg.ImageCMDOptions.ImageSize, "image-size", 11, "Size (in GB) of the resultant OVA image")
Cmd.Flags().Int64Var(&pkg.ImageCMDOptions.TargetDiskSize, "target-disk-size", 120, "Size (in GB) of the target disk volume where OVA will be copied")
Cmd.Flags().StringVar(&pkg.ImageCMDOptions.RHNUser, "rhn-user", "", "RedHat Subscription username. Required when Image distribution is rhel")
Expand Down
5 changes: 5 additions & 0 deletions cmd/image/qcow2ova/validate/tools/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package tools

import (
"os/exec"
"strings"

"github.com/ppc64le-cloud/pvsadm/pkg"
"k8s.io/klog/v2"
)

Expand All @@ -34,6 +36,9 @@ func (p *Rule) String() string {
}

func (p *Rule) Verify() error {
if strings.ToLower(pkg.ImageCMDOptions.ImageDist) == "fedora" {
commands["btrfs"] = "yum install btrfs-progs -y"
}
for command := range commands {
path, err := exec.LookPath(command)
if err != nil {
Expand Down
Loading