jcloud/backbone/hypervisor.py
2025-04-12 17:39:38 +08:00

119 lines
4.0 KiB
Python

# Copyright (c) 2020, JINGROW
# For license information, please see license.txt
import platform
import subprocess
from pathlib import Path
class Hypervisor:
def __init__(self, shell=None):
self.shell = shell
def setup(self):
system = platform.system()
if system == "Linux":
self.preinstall()
self.install()
self.verify()
elif system == "Darwin":
self.verify_mac()
def build(self, size):
system = platform.system()
if system == "Linux":
self.build_cloud_init_linux()
elif system == "Darwin":
self.build_cloud_init_mac()
self.build_packer("backbone", size=size)
def build_cloud_init_linux(self):
cloud_init_yml = str(Path(__file__).parent.joinpath("packer", "cloud-init.yml"))
cloud_init_image = str(Path(__file__).parent.joinpath("packer", "cloud-init.img"))
self.shell.execute(f"cloud-localds {cloud_init_image} {cloud_init_yml}")
def build_cloud_init_mac(self):
# cloud-localds isn't available on macOS.
# So we do what it does ourselves
# user-data is the same as cloud-init.yml
# https://github.com/canonical/cloud-utils/blob/49e5dd7849ee3c662f3db35e857148d02e72694b/bin/cloud-localds#L168-L187
cloud_init_yml = str(Path(__file__).parent.joinpath("packer", "cloud-init.yml"))
user_data = str(Path(__file__).parent.joinpath("packer", "user-data"))
self.shell.execute(f"cp {cloud_init_yml} {user_data}")
# meta-data has some inconsequential values
# but the file is needed
meta_data = str(Path(__file__).parent.joinpath("packer", "meta-data"))
self.shell.execute(f"touch {meta_data}")
cloud_init_image = str(Path(__file__).parent.joinpath("packer", "cloud-init.img"))
# Reference: https://github.com/canonical/cloud-utils/blob/49e5dd7849ee3c662f3db35e857148d02e72694b/bin/cloud-localds#L235-L237
self.shell.execute(
f"mkisofs -joliet -rock -volid cidata -output {cloud_init_image} {user_data} {meta_data}"
)
def build_packer(self, template, size):
packer_template = str(Path(__file__).parent.joinpath("packer", f"{template}.json"))
packer = self.shell.execute(f"packer build -var 'disk_size={size}' {packer_template}")
if packer.returncode:
raise Exception("Build Failed")
box = str(Path(__file__).parent.joinpath("packer", "builds", f"{template}.box"))
add = self.shell.execute(f"vagrant box add {box} --name {template} --force")
if add.returncode:
raise Exception(f"Cannot add box {box}")
def build_scaleway(self, size):
self.build_cloud_init_scaleway()
self.build_packer("scaleway", size=size)
def build_cloud_init_scaleway(self):
cloud_init_yml = str(Path(__file__).parent.joinpath("packer", "cloud-init-scaleway.yml"))
cloud_init_image = str(Path(__file__).parent.joinpath("packer", "cloud-init-scaleway.img"))
self.shell.execute(f"cloud-localds {cloud_init_image} {cloud_init_yml}")
def up(self):
vagrant = self.shell.execute("vagrant init backbone")
vagrant = self.shell.execute("vagrant up --provider=libvirt")
if vagrant.returncode:
raise Exception("Cannot start hypervisor")
def ssh(self, command=None):
if command:
vagrant = self.shell.execute(f'vagrant ssh -c "{command}"')
else:
vagrant = self.shell.execute("vagrant ssh")
if vagrant.returncode:
raise Exception("Cannot ssh")
def preinstall(self):
kvm_ok = self.shell.execute("kvm-ok")
if kvm_ok.returncode:
raise Exception("Cannot use KVM")
def install(self):
kvm_install = self.shell.execute("sudo apt install qemu-kvm")
if kvm_install.returncode:
raise Exception("Cannot install KVM")
def verify(self):
kvm_connect = self.shell.execute("virsh list --all")
if kvm_connect.returncode:
raise Exception("Cannot connect to KVM")
def verify_mac(self):
kvm_connect = self.shell.execute("virsh list --all")
if kvm_connect.returncode:
raise Exception("Cannot connect to KVM")
class Shell:
def __init__(self, directory=None):
self.directory = directory
def execute(self, command, directory=None):
directory = directory or self.directory
return subprocess.run(
command, check=False, stderr=subprocess.STDOUT, cwd=directory, shell=True, text=True
)