diff options
Diffstat (limited to 'yardstick/benchmark/contexts/standalone/model.py')
-rw-r--r-- | yardstick/benchmark/contexts/standalone/model.py | 273 |
1 files changed, 223 insertions, 50 deletions
diff --git a/yardstick/benchmark/contexts/standalone/model.py b/yardstick/benchmark/contexts/standalone/model.py index 0d58e91b0..a15426872 100644 --- a/yardstick/benchmark/contexts/standalone/model.py +++ b/yardstick/benchmark/contexts/standalone/model.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import absolute_import import os import re import time @@ -25,17 +24,19 @@ from netaddr import IPNetwork import xml.etree.ElementTree as ET from yardstick import ssh -from yardstick.common.constants import YARDSTICK_ROOT_PATH -from yardstick.common.yaml_loader import yaml_load +from yardstick.common import constants +from yardstick.common import exceptions +from yardstick.common import utils as common_utils +from yardstick.common import yaml_loader from yardstick.network_services.utils import PciAddress from yardstick.network_services.helpers.cpu import CpuSysCores -from yardstick.common.utils import write_file + LOG = logging.getLogger(__name__) VM_TEMPLATE = """ <domain type="kvm"> - <name>{vm_name}</name> + <name>{vm_name}</name> <uuid>{random_uuid}</uuid> <memory unit="MB">{memory}</memory> <currentMemory unit="MB">{memory}</currentMemory> @@ -45,7 +46,7 @@ VM_TEMPLATE = """ <vcpu cpuset='{cpuset}'>{vcpu}</vcpu> {cputune} <os> - <type arch="x86_64" machine="pc-i440fx-utopic">hvm</type> + <type arch="x86_64" machine="{machine}">hvm</type> <boot dev="hd" /> </os> <features> @@ -80,9 +81,39 @@ VM_TEMPLATE = """ <source bridge="br-int" /> <model type='virtio'/> </interface> - </devices> + <serial type='pty'> + <target port='0'/> + </serial> + <console type='pty'> + <target type='serial' port='0'/> + </console> + </devices> </domain> """ + +USER_DATA_TEMPLATE = """ +cat > {user_file} <<EOF +#cloud-config +preserve_hostname: false +hostname: {host} +users: +{user_config} +EOF +""" + +NETWORK_DATA_TEMPLATE = """ +cat > {network_file} <<EOF +#cloud-config +version: 2 +ethernets: + ens3: + match: + macaddress: {mac_address} + addresses: + - {ip_address} +EOF +""" + WAIT_FOR_BOOT = 30 @@ -100,12 +131,17 @@ class Libvirt(object): @staticmethod def virsh_create_vm(connection, cfg): - err = connection.execute("virsh create %s" % cfg)[0] - LOG.info("VM create status: %s", err) + LOG.info('VM create, XML config: %s', cfg) + status, _, error = connection.execute('virsh create %s' % cfg) + if status: + raise exceptions.LibvirtCreateError(error=error) @staticmethod def virsh_destroy_vm(vm_name, connection): - connection.execute("virsh destroy %s" % vm_name) + LOG.info('VM destroy, VM name: %s', vm_name) + status, _, error = connection.execute('virsh destroy %s' % vm_name) + if status: + LOG.warning('Error destroying VM %s. Error: %s', vm_name, error) @staticmethod def _add_interface_address(interface, pci_address): @@ -126,7 +162,8 @@ class Libvirt(object): return vm_pci @classmethod - def add_ovs_interface(cls, vpath, port_num, vpci, vports_mac, xml): + def add_ovs_interface(cls, vpath, port_num, vpci, vports_mac, xml_str, + queues): """Add a DPDK OVS 'interface' XML node in 'devices' node <devices> @@ -150,7 +187,7 @@ class Libvirt(object): vhost_path = ('{0}/var/run/openvswitch/dpdkvhostuser{1}'. format(vpath, port_num)) - root = ET.parse(xml) + root = ET.fromstring(xml_str) pci_address = PciAddress(vpci.strip()) device = root.find('devices') @@ -168,17 +205,17 @@ class Libvirt(object): model.set('type', 'virtio') driver = ET.SubElement(interface, 'driver') - driver.set('queues', '4') + driver.set('queues', str(queues)) host = ET.SubElement(driver, 'host') host.set('mrg_rxbuf', 'off') cls._add_interface_address(interface, pci_address) - root.write(xml) + return ET.tostring(root) @classmethod - def add_sriov_interfaces(cls, vm_pci, vf_pci, vf_mac, xml): + def add_sriov_interfaces(cls, vm_pci, vf_pci, vf_mac, xml_str): """Add a SR-IOV 'interface' XML node in 'devices' node <devices> @@ -201,7 +238,7 @@ class Libvirt(object): -sr_iov-how_sr_iov_libvirt_works """ - root = ET.parse(xml) + root = ET.fromstring(xml_str) device = root.find('devices') interface = ET.SubElement(device, 'interface') @@ -218,20 +255,47 @@ class Libvirt(object): pci_vm_address = PciAddress(vm_pci.strip()) cls._add_interface_address(interface, pci_vm_address) - root.write(xml) + return ET.tostring(root) @staticmethod - def create_snapshot_qemu(connection, index, vm_image): - # build snapshot image - image = "/var/lib/libvirt/images/%s.qcow2" % index - connection.execute("rm %s" % image) - qemu_template = "qemu-img create -f qcow2 -o backing_file=%s %s" - connection.execute(qemu_template % (vm_image, image)) + def create_snapshot_qemu(connection, index, base_image): + """Create the snapshot image for a VM using a base image - return image + :param connection: SSH connection to the remote host + :param index: index of the VM to be spawn + :param base_image: path of the VM base image in the remote host + :return: snapshot image path + """ + vm_image = '/var/lib/libvirt/images/%s.qcow2' % index + connection.execute('rm -- "%s"' % vm_image) + status, _, _ = connection.execute('test -r %s' % base_image) + if status: + if not os.access(base_image, os.R_OK): + raise exceptions.LibvirtQemuImageBaseImageNotPresent( + vm_image=vm_image, base_image=base_image) + # NOTE(ralonsoh): done in two steps to avoid root permission + # issues. + LOG.info('Copy %s from execution host to remote host', base_image) + file_name = os.path.basename(os.path.normpath(base_image)) + connection.put_file(base_image, '/tmp/%s' % file_name) + status, _, error = connection.execute( + 'mv -- "/tmp/%s" "%s"' % (file_name, base_image)) + if status: + raise exceptions.LibvirtQemuImageCreateError( + vm_image=vm_image, base_image=base_image, error=error) + + LOG.info('Convert image %s to %s', base_image, vm_image) + qemu_cmd = ('qemu-img create -f qcow2 -o backing_file=%s %s' % + (base_image, vm_image)) + status, _, error = connection.execute(qemu_cmd) + if status: + raise exceptions.LibvirtQemuImageCreateError( + vm_image=vm_image, base_image=base_image, error=error) + return vm_image @classmethod - def build_vm_xml(cls, connection, flavor, cfg, vm_name, index): + def build_vm_xml(cls, connection, flavor, vm_name, index, cdrom_img): + """Build the XML from the configuration parameters""" memory = flavor.get('ram', '4096') extra_spec = flavor.get('extra_specs', {}) cpu = extra_spec.get('hw:cpu_cores', '2') @@ -243,6 +307,7 @@ class Libvirt(object): cpuset = Libvirt.pin_vcpu_for_perf(connection, hw_socket) cputune = extra_spec.get('cputune', '') + machine = extra_spec.get('machine_type', 'pc-i440fx-xenial') mac = StandaloneContextHelper.get_mac_address(0x00) image = cls.create_snapshot_qemu(connection, index, flavor.get("images", None)) @@ -253,11 +318,13 @@ class Libvirt(object): memory=memory, vcpu=vcpu, cpu=cpu, numa_cpus=numa_cpus, socket=socket, threads=threads, - vm_image=image, cpuset=cpuset, cputune=cputune) + vm_image=image, cpuset=cpuset, + machine=machine, cputune=cputune) - write_file(cfg, vm_xml) + # Add CD-ROM device + vm_xml = Libvirt.add_cdrom(cdrom_img, vm_xml) - return [vcpu, mac] + return vm_xml, mac @staticmethod def update_interrupts_hugepages_perf(connection): @@ -277,6 +344,82 @@ class Libvirt(object): cpuset = "%s,%s" % (cores, threads) return cpuset + @classmethod + def write_file(cls, file_name, xml_str): + """Dump a XML string to a file""" + root = ET.fromstring(xml_str) + et = ET.ElementTree(element=root) + et.write(file_name, encoding='utf-8', method='xml') + + @classmethod + def add_cdrom(cls, file_path, xml_str): + """Add a CD-ROM disk XML node in 'devices' node + + <devices> + <disk type='file' device='cdrom'> + <driver name='qemu' type='raw'/> + <source file='/var/lib/libvirt/images/data.img'/> + <target dev='hdb'/> + <readonly/> + </disk> + ... + </devices> + """ + + root = ET.fromstring(xml_str) + device = root.find('devices') + + disk = ET.SubElement(device, 'disk') + disk.set('type', 'file') + disk.set('device', 'cdrom') + + driver = ET.SubElement(disk, 'driver') + driver.set('name', 'qemu') + driver.set('type', 'raw') + + source = ET.SubElement(disk, 'source') + source.set('file', file_path) + + target = ET.SubElement(disk, 'target') + target.set('dev', 'hdb') + + ET.SubElement(disk, 'readonly') + return ET.tostring(root) + + @staticmethod + def gen_cdrom_image(connection, file_path, vm_name, vm_user, key_filename, mac, ip): + """Generate ISO image for CD-ROM """ + + user_config = [" - name: {user_name}", + " ssh_authorized_keys:", + " - {pub_key_str}"] + if vm_user != "root": + user_config.append(" sudo: ALL=(ALL) NOPASSWD:ALL") + + meta_data = "/tmp/meta-data" + user_data = "/tmp/user-data" + network_data = "/tmp/network-config" + with open(".".join([key_filename, "pub"]), "r") as pub_key_file: + pub_key_str = pub_key_file.read().rstrip() + user_conf = os.linesep.join(user_config).format(pub_key_str=pub_key_str, user_name=vm_user) + + cmd_lst = [ + "touch %s" % meta_data, + USER_DATA_TEMPLATE.format(user_file=user_data, host=vm_name, user_config=user_conf), + NETWORK_DATA_TEMPLATE.format(network_file=network_data, mac_address=mac, + ip_address=ip), + "genisoimage -output {0} -volid cidata -joliet -r {1} {2} {3}".format(file_path, + meta_data, + user_data, + network_data), + "rm {0} {1} {2}".format(meta_data, user_data, network_data), + ] + for cmd in cmd_lst: + LOG.info(cmd) + status, _, error = connection.execute(cmd) + if status: + raise exceptions.LibvirtQemuImageCreateError(error=error) + class StandaloneContextHelper(object): """ This class handles all the common code for standalone @@ -286,8 +429,9 @@ class StandaloneContextHelper(object): super(StandaloneContextHelper, self).__init__() @staticmethod - def install_req_libs(connection, extra_pkgs=[]): - pkgs = ["qemu-kvm", "libvirt-bin", "bridge-utils", "numactl", "fping"] + def install_req_libs(connection, extra_pkgs=None): + extra_pkgs = extra_pkgs or [] + pkgs = ["qemu-kvm", "libvirt-bin", "bridge-utils", "numactl", "fping", "genisoimage"] pkgs.extend(extra_pkgs) cmd_template = "dpkg-query -W --showformat='${Status}\\n' \"%s\"|grep 'ok installed'" for pkg in pkgs: @@ -303,7 +447,7 @@ class StandaloneContextHelper(object): return driver @classmethod - def get_nic_details(cls, connection, networks, dpdk_nic_bind): + def get_nic_details(cls, connection, networks, dpdk_devbind): for key, ports in networks.items(): if key == "mgmt": continue @@ -313,11 +457,11 @@ class StandaloneContextHelper(object): driver = cls.get_kernel_module(connection, phy_ports, phy_driver) # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe - bind_cmd = "{dpdk_nic_bind} --force -b {driver} {port}" + bind_cmd = "{dpdk_devbind} --force -b {driver} {port}" lshw_cmd = "lshw -c network -businfo | grep '{port}'" link_show_cmd = "ip -s link show {interface}" - cmd = bind_cmd.format(dpdk_nic_bind=dpdk_nic_bind, + cmd = bind_cmd.format(dpdk_devbind=dpdk_devbind, driver=driver, port=ports['phy_port']) connection.execute(cmd) @@ -350,25 +494,18 @@ class StandaloneContextHelper(object): return pf_vfs - def read_config_file(self): - """Read from config file""" - - with open(self.file_path) as stream: - LOG.info("Parsing pod file: %s", self.file_path) - cfg = yaml_load(stream) - return cfg - def parse_pod_file(self, file_path, nfvi_role='Sriov'): self.file_path = file_path nodes = [] nfvi_host = [] try: - cfg = self.read_config_file() + cfg = yaml_loader.read_yaml_file(self.file_path) except IOError as io_error: if io_error.errno != errno.ENOENT: raise - self.file_path = os.path.join(YARDSTICK_ROOT_PATH, file_path) - cfg = self.read_config_file() + self.file_path = os.path.join(constants.YARDSTICK_ROOT_PATH, + file_path) + cfg = yaml_loader.read_yaml_file(self.file_path) nodes.extend([node for node in cfg["nodes"] if str(node["role"]) != nfvi_role]) nfvi_host.extend([node for node in cfg["nodes"] if str(node["role"]) == nfvi_role]) @@ -418,8 +555,41 @@ class StandaloneContextHelper(object): ip = cls.get_mgmt_ip(connection, node["mac"], mgmtip, node) if ip: node["ip"] = ip + client = ssh.SSH.from_node(node) + LOG.debug("OS version: %s", + common_utils.get_os_version(client)) + LOG.debug("Kernel version: %s", + common_utils.get_kernel_version(client)) + vnfs_data = common_utils.get_sample_vnf_info(client) + for vnf_name, vnf_data in vnfs_data.items(): + LOG.debug("VNF name: '%s', commit ID/branch: '%s'", + vnf_name, vnf_data["branch_commit"]) + LOG.debug("%s", vnf_data["md5_result"]) return nodes + @classmethod + def check_update_key(cls, connection, node, vm_name, id_name, cdrom_img, mac): + # Generate public/private keys if private key file is not provided + user_name = node.get('user') + if not user_name: + node['user'] = 'root' + user_name = node.get('user') + if not node.get('key_filename'): + key_filename = ''.join( + [constants.YARDSTICK_ROOT_PATH, + 'yardstick/resources/files/yardstick_key-', + id_name, '-', vm_name]) + ssh.SSH.gen_keys(key_filename) + node['key_filename'] = key_filename + # Update image with public key + key_filename = node.get('key_filename') + ip_netmask = "{0}/{1}".format(node.get('ip'), node.get('netmask')) + ip_netmask = "{0}/{1}".format(node.get('ip'), + IPNetwork(ip_netmask).prefixlen) + Libvirt.gen_cdrom_image(connection, cdrom_img, vm_name, user_name, key_filename, mac, + ip_netmask) + return node + class Server(object): """ This class handles geting vnf nodes @@ -432,7 +602,7 @@ class Server(object): for key, vfs in vnf["network_ports"].items(): if key == "mgmt": - mgmtip = str(IPNetwork(vfs['cidr']).ip) + mgmt_cidr = IPNetwork(vfs['cidr']) continue vf = ports[vfs[0]] @@ -449,14 +619,15 @@ class Server(object): }) index = index + 1 - return mgmtip, interfaces + return mgmt_cidr, interfaces @classmethod def generate_vnf_instance(cls, flavor, ports, ip, key, vnf, mac): - mgmtip, interfaces = cls.build_vnf_interfaces(vnf, ports) + mgmt_cidr, interfaces = cls.build_vnf_interfaces(vnf, ports) result = { - "ip": mgmtip, + "ip": str(mgmt_cidr.ip), + "netmask": str(mgmt_cidr.netmask), "mac": mac, "host": ip, "user": flavor.get('user', 'root'), @@ -499,7 +670,7 @@ class OvsDeploy(object): StandaloneContextHelper.install_req_libs(self.connection, pkgs) def ovs_deploy(self): - ovs_deploy = os.path.join(YARDSTICK_ROOT_PATH, + ovs_deploy = os.path.join(constants.YARDSTICK_ROOT_PATH, "yardstick/resources/scripts/install/", self.OVS_DEPLOY_SCRIPT) if os.path.isfile(ovs_deploy): @@ -515,4 +686,6 @@ class OvsDeploy(object): cmd = "sudo -E %s --ovs='%s' --dpdk='%s' -p='%s'" % (remote_ovs_deploy, ovs, dpdk, http_proxy) - self.connection.execute(cmd) + exit_status, _, stderr = self.connection.execute(cmd) + if exit_status: + raise exceptions.OVSDeployError(stderr=stderr) |