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!")
                                                      
View Code

 

使用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)
View Code

 

使用 使用cloud-init 和 nbd获取时候,需要mount config.drive的磁盘。

使用 file inject 和nbd获取的时候,需要开关机器。

posted @ 2016-03-24 16:46  lvmxh  阅读(591)  评论(0编辑  收藏  举报