openstack 使用cloud init 和 console-log, nbd或者libguestfs 获取VM中的硬件信息。
以获取PCI的信息为例。
基本代码:
pci.py
import base64 import guestfs from functools import partial import os import six import stat import subprocess import tempfile import time import xml.etree.ElementTree as ET from oslo_utils import encodeutils from tempest import config from tempest_lib.common.utils import data_utils def shell_command(cmd, log=False): # cmd = ['sudo', 'ip', 'netns', 'exec', ns, 'ssh', "%s@%s" %(name, ip), # "'%s;ifconfig eth1 %s/24'" % (name ip)] p = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if log: print out print err if len(err): return False return out VM_MOUNT_POINT = "/mnt" PCI_PATH = "/pci.info" RC_LOCAL = "/etc/rc.local" USER_DATA = ['#!/bin/sh -e', '# mount -a', 'mount /dev/vdb %s' % VM_MOUNT_POINT, 'touch %s%s' % (VM_MOUNT_POINT, PCI_PATH), 'lspci > %s%s' % (VM_MOUNT_POINT, PCI_PATH), '# umount /mnt/' 'exit 0'] PCIINFO_DELIMITER = "*" * 40 + "%s" + "*" * 40 PCIINFO_DELIMITER_BEGIN = PCIINFO_DELIMITER % "PCI INFO BEGIN" PCIINFO_DELIMITER_END = PCIINFO_DELIMITER % "PCI INFO END" CONSOLE_DATA = ['#!/bin/sh -e', 'sudo echo "%s"' % PCIINFO_DELIMITER_BEGIN, 'sudo lspci', 'sudo echo "%s"' % PCIINFO_DELIMITER_END, 'exit 0'] def gen_rc_local_file(pci_path=PCI_PATH): """please remove the file: os.remove(p) print gen_rc_local() """ l, p = tempfile.mkstemp("local", "rc") f = open(p, 'w+b') f.writelines(['#!/bin/sh -e\n', 'mount -a\n', 'touch %s%s\n' % (VM_MOUNT_POINT, pci_path), 'lspci > %s%s\n' % (VM_MOUNT_POINT, pci_path), 'umount /mnt/\n' 'exit 0\n']) os.chmod(p, stat.S_IRWXU + stat.S_IXGRP + stat.S_IXOTH) f.close() return p, pci_path def gen_rc_local_dict(pci_path=PCI_PATH): """ usage: personality = {} personality.append({ 'path': filepath, 'contents': cont, }) """ data = ['#!/bin/sh -e', 'mount -a', 'touch %s%s' % (VM_MOUNT_POINT, pci_path), 'lspci > %s%s' % (VM_MOUNT_POINT, pci_path), 'cd ~', 'umount /mnt/', 'exit 0'] data = "\n".join(data) if six.PY3 and isinstance(data, str): data = data.encode('utf-8') cont = base64.b64encode(data).decode('utf-8') return cont def gen_user_data(userdata=USER_DATA): if hasattr(userdata, 'read'): userdata = userdata.read() # NOTE(melwitt): Text file data is converted to bytes prior to # base64 encoding. The utf-8 encoding will fail for binary files. if six.PY3: try: userdata = userdata.encode("utf-8") except AttributeError: # In python 3, 'bytes' object has no attribute 'encode' pass else: try: userdata = encodeutils.safe_encode(userdata) except UnicodeDecodeError: pass userdata_b64 = base64.b64encode(userdata).decode('utf-8') return userdata_b64 def gen_etc_fstab(): """ usage: personality = {} personality.append({ 'path': filepath, 'contents': cont, }) """ data = ["/dev/root / auto rw,noauto 0 1", "proc /proc proc defaults 0 0", "devpts /dev/pts devpts defaults,gid=5,mode=620 0 0", "tmpfs /dev/shm tmpfs mode=0777 0 0", "sysfs /sys sysfs defaults 0 0", "tmpfs /run tmpfs rw,nosuid,relatime,size=200k,mode=755 0 0", "/dev/vdb /mnt/ auto defaults 0 0"] data = "\n".join(data) if six.PY3 and isinstance(data, str): data = data.encode('utf-8') cont = base64.b64encode(data).decode('utf-8') return cont def get_pci_info(disk, pci_path=PCI_PATH): """please remove the dir: os.rmdir(p) mount the disk by guestfs and get the pci info. need: $ sudo chmod g+rw /boot/vmlinuz-/boot/vmlinuz-`uname -r` $ sudo usermod -a -G root jenkins need to install these 2 packages $ sudo apt-get install libguestfs-tools $ sudo apt-get install python-guestfs need to add use to kvm group $ sudo usermod -a -G kvm jenkins or $ sudo chmod 0666 /dev/kvm ref http://libguestfs.org/guestfs-python.3.html for debug: $ export LIBGUESTFS_DEBUG=1 $ export LIBGUESTFS_TRACE=1 ref http://libguestfs.org/guestfs-faq.1.html#debugging-libguestfs """ p = tempfile.mkdtemp("guestfs", "mount") g = guestfs.GuestFS(python_return_dict=True) # g.add_drive_opts(disk, format="qcow2", readonly=1) g.add_drive_opts(disk, readonly=1) g.launch() g.mount('/dev/sda', '/') return g.read_lines(pci_path) def nbd_mount(f, disk, path=PCI_PATH): """please remove the dir: os.rmdir(p) mount the disk by guestfs and get the pci info. need: $ sudo rmmod nbd $ sudo modprobe nbd max_part=16 ref https://en.wikibooks.org/wiki/QEMU/Images ref https://blogs.gnome.org/muelli/2010/03/mounting-qemu-qcow2-image-using-nbd/ """ mount_point = "/mnt/nbd" cmd = ["sudo", "modinfo", "nbd"] out = shell_command(cmd) if "modinfo: ERROR:" in out: cmd = ["sudo", "modprobe", "nbd", "max_part=16"] shell_command(cmd) if not os.path.exists(mount_point): cmd = ["sudo", "mkdir", mount_point] shell_command(cmd) if os.path.ismount(mount_point): cmd = ["sudo", "umount", mount_point] shell_command(cmd) cmd = ["sudo", "qemu-nbd", "-d", "/dev/nbd0"] shell_command(cmd) cmd = ["sudo", "killall", "qemu-nbd"] shell_command(cmd) # cmd = ["sudo", "qemu-nbd", "-r", "-c", "/dev/nbd0", disk] cmd = ["sudo", "qemu-nbd", "-c", "/dev/nbd0", disk] shell_command(cmd) cmd = ["sudo", "mount", "/dev/nbd0", mount_point] shell_command(cmd) if not os.path.ismount(mount_point): cmd = ["sudo", "qemu-nbd", "-d", "/dev/nbd0"] shell_command(cmd) raise Exception("Can't mount the VM disk!") out = f(mount_point, path) cmd = ["sudo", "umount", mount_point] shell_command(cmd) cmd = ["sudo", "qemu-nbd", "-d", "/dev/nbd0"] shell_command(cmd) return out def cat_file(mount, pci_path): cmd = ["sudo", "cat", mount + pci_path] pci_info = shell_command(cmd, True) if pci_info is False: return pci_info return pci_info.splitlines() get_pci_info_by_nbd = partial(nbd_mount, cat_file) def x_file(mount, rc_path): cmd = ["sudo", "chmod", "a+x", mount + rc_path] out = shell_command(cmd) return out rc_local_add_x = partial(nbd_mount, x_file) def get_vda_path(xml): # tree = ET.parse('/etc/libvirt/qemu/instance-0000001f.xml') # root = tree.getroot() root = ET.fromstring(xml) disks = root.findall("./devices/disk[@device='disk'][@type='file']") for d in disks: t = d.find("target") if t.attrib.get("dev") == "vda": return d.find("./source").get("file") def get_config_drive_path(xml): # tree = ET.parse('/etc/libvirt/qemu/instance-0000001f.xml') # root = tree.getroot() root = ET.fromstring(xml) disks = root.findall("./devices/disk[@device='disk'][@type='file']") for d in disks: f = d.find("./source").get("file") if "disk.config" in f: return f def get_pci_output(get_console_output, server_id): output = get_console_output(server_id, -1)['output'] lines = output.split('\n') if (len(lines) > 0 and lines.count(PCIINFO_DELIMITER_BEGIN) > 0 and lines.count(PCIINFO_DELIMITER_END)): begin = lines.index(PCIINFO_DELIMITER_BEGIN) + 1 end = lines.index(PCIINFO_DELIMITER_END) return lines[begin : end] def retry_get_pci_output(get_console_output, server_id, retry=20): while retry > 0: out = get_pci_output(get_console_output, server_id) if out is None: retry = retry - 1 time.sleep(1) else: return out raise Exception("Can't get the pci.info from VM!")
使用cloud-init 和console-log获取硬件信息的代码的片段。
personality = [] user_data = pci.gen_user_data("\n".join(pci.CONSOLE_DATA)) server_with_pci = (self.create_test_server( wait_until='ACTIVE', adminPass=admin_pass, user_data=user_data, personality=personality, flavor=flavor_with_pci_id)) #print self.client.get_server_metadata_item(server_with_pci['id'],"addresses") addresses = self.client.show_server(server_with_pci['id'])['server'] password = 'cubswin:)' print "cirros@" + addresses["addresses"]["private"][0]["addr"] print password pci_info = pci.retry_get_pci_output( self.client.get_console_output, server_with_pci["id"]) # pci_info = pci.get_pci_info(disk) # pci_flag = linux_client.get_pci(pciid) expect_pci = filter(lambda x: pciid in x, pci_info) # self.assertTrue(pci_flag is not None) self.assertTrue(not not expect_pci) # pci_count = linux_client.get_pci_count(pciid) pci_count = len(expect_pci) # pci_count = pci_count.strip() self.assertEqual(1, pci_count)
使用 使用cloud-init 和 nbd获取时候,需要mount config.drive的磁盘。
使用 file inject 和nbd获取的时候,需要开关机器。