[PATCH v3] Auto Topology for Guest Templates

This patchset is a better functioning, but not final, version of the topology logic. Aline asked that I send another version with details for the UI will interact with the cpu_info API. The flow will be: - GET /host/cpu_info: curl -k -u user -H 'Content-Type: application/json' \ -H 'Accept: application/json' https://localhost:8001/host/cpuinfo { "cores":2, "threading_enabled":true, "sockets":1, "threads_per_core":2 } Note: I removed the cores_online and cores_present, and changed it to just cores. I think the other was too much info. - Based on that information, plus the host arch, the UI can display - If host arch is Power: - Display a menu labled SMT, with up to four check-boxes. The check-boxes will be labeled SMT1, SMT2, SMT4, and SMT8. The max SMT value displayed will be dependant upon threads_per_core from above. The default checkbox should be the greatest SMT value (e.g. SMT8). - Display the same box for CPUs as before. I had initially thought about having a drop-down for CPUs instead of the empty field, (with only legal vCPU amounts, like 1,2,4,8, etc.), but that will get complicated with different types of processors, and also be too tedious to use. So, unless someone objects, I think the next best thing to do, is just slightly adjust the number of vCPUs the user asked for in order to make sure that the number is a legal number to use with a topology (since vCPUS must equal sockets * cores * threads). Note: See below return from get_rec_topology. If different than what the user entered, this new vcpus should replace the number entered by the user. If some kind of effect could be shown when it's changed, that might be helpful. - Else: - Display a menu labeled Hyper-Threading, with only a checkbox labeled HT. Note: The same comments for vCPUS as above apply here. The number maybe need to change based on the CPU capabilities. - The user's selections from those fields is sent to the get_rec_topology method, which will then return two values: {'topology': {'sockets': sockets, 'cores': cores, 'threads': threads}, 'vcpus': best_vcpus} These values can then be used to create the template: curl -k -u user -X POST -H 'Content-Typ: application/json' -H 'Accept: application/json' \ https://localhost:8001/templates -d'{"name": "test_topo", "cdrom": "/isos/DVD_name.iso", \ "cpus":4, "cpu_info": {"topology": {"sockets": 1, "cores": 2, "threads":2}}}' What doesn't work right now: - Some error messages still need to be created. - API doc needs to be added. Christy Perez (1): Parts to allow Kimchi to configure the cpu topology. docs/API.md | 2 + src/kimchi/API.json | 6 ++ src/kimchi/control/cpuinfo.py | 37 +++++++++ src/kimchi/control/host.py | 2 + src/kimchi/i18n.py | 2 + src/kimchi/model/cpuinfo.py | 175 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/host.py | 1 + src/kimchi/model/templates.py | 16 +++- 8 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 src/kimchi/control/cpuinfo.py create mode 100644 src/kimchi/model/cpuinfo.py -- 1.9.3

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- docs/API.md | 2 + src/kimchi/API.json | 6 ++ src/kimchi/control/cpuinfo.py | 37 +++++++++ src/kimchi/control/host.py | 2 + src/kimchi/i18n.py | 2 + src/kimchi/model/cpuinfo.py | 175 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/host.py | 1 + src/kimchi/model/templates.py | 16 +++- 8 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 src/kimchi/control/cpuinfo.py create mode 100644 src/kimchi/model/cpuinfo.py diff --git a/docs/API.md b/docs/API.md index 9b866f3..865c728 100644 --- a/docs/API.md +++ b/docs/API.md @@ -233,6 +233,8 @@ Represents a snapshot of the Virtual Machine's primary monitor. * threads - The number of threads per core. If specifying both cpus and CPU topology, make sure cpus is equal to the product of sockets, cores, and threads. + * auto-topology: Allow Kimchi to determine the best topology + based on desired vCPUs and host capabilities. ### Sub-Collection: Virtual Machine Network Interfaces diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 0ad36ab..b405889 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -54,6 +54,12 @@ "error": "KCHTMPL0026E" } } + }, + "auto_topology": { + "description": "Auto-configure guest CPU topology.", + "type": "boolean", + "required": false, + "error": "KCHTMPL0028E" } } } diff --git a/src/kimchi/control/cpuinfo.py b/src/kimchi/control/cpuinfo.py new file mode 100644 index 0000000..415dd3d --- /dev/null +++ b/src/kimchi/control/cpuinfo.py @@ -0,0 +1,37 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +from kimchi.control.base import Resource + + +class CPUInfo(Resource): + def __init__(self, model): + super(CPUInfo, self).__init__(model) + self.admin_methods = ['GET'] + self.role_key = 'host' + self.uri_fmt = "/host/cpuinfo" + + @property + def data(self): + return {'threading_enabled': self.info['guest_threads_enabled'], + 'sockets': self.info['sockets'], + 'cores': self.info['cores_available'], + 'threads_per_core': self.info['threads_per_core'] + } diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index 4362da7..9f73653 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -17,6 +17,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +from kimchi.control.cpuinfo import CPUInfo from kimchi.control.base import Collection, Resource, SimpleCollection from kimchi.control.utils import UrlSubNode from kimchi.exception import NotFoundError @@ -39,6 +40,7 @@ def __init__(self, model, id=None): self.users = Users(self.model) self.groups = Groups(self.model) self.swupdate = self.generate_action_handler_task('swupdate') + self.cpuinfo = CPUInfo(self.model) @property def data(self): diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index e823f2b..1ace42c 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -150,6 +150,8 @@ "KCHTMPL0025E": _("When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."), "KCHTMPL0026E": _("When specifying CPU topology, each element must be an integer greater than zero."), "KCHTMPL0027E": _("Invalid disk image format. Valid formats: bochs, cloop, cow, dmg, qcow, qcow2, qed, raw, vmdk, vpc."), + "KCHTMPL0028E": _("The auto_topology value must be either True or False."), + "KCHTMPL0029E": _("This host (or current configuration) does not allow CPU topology."), "KCHPOOL0001E": _("Storage pool %(name)s already exists"), "KCHPOOL0002E": _("Storage pool %(name)s does not exist"), diff --git a/src/kimchi/model/cpuinfo.py b/src/kimchi/model/cpuinfo.py new file mode 100644 index 0000000..28dfb17 --- /dev/null +++ b/src/kimchi/model/cpuinfo.py @@ -0,0 +1,175 @@ +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +import platform +from xml.etree import ElementTree as ET + +from kimchi.exception import InvalidParameter +from kimchi.utils import kimchi_log, run_command + + +class CPUInfoModel(object): + """ + Get information about a CPU for hyperthreading (on x86) + or SMT (on POWER) for logic when creating templates and VMs. + """ + + def __init__(self, **kargs): + self.conn = kargs['conn'] + + """ + Since there are so many similar-seeming variables: + - arch = either ppc or x86 (Intel and AMD) + - guest_threads_enabled = Can a user specify topology? + - sockets = total number of sockets in the system. While nothing + uses this value, it's part of topology so we'll track it. + - cores_present = total number of cores in the system + Note: Cores is often synonymous with CPUs. + - cores_available = cores online (in case some were offlined) + - cores_per_socket = max cores value for topology + - threads_per_core = max threads value for topology + """ + + self.conn = kargs['conn'] + + libvirt_topology = None + try: + connect = self.conn.get() + except Exception as e: + raise Exception("Unable to get qemu connection: %s" % e.message) + try: + xml = connect.getCapabilities() + capabilities = ET.fromstring(xml) + libvirt_topology = capabilities.find('host').find('cpu').\ + find('topology') + if libvirt_topology is None: + kimchi_log.info("cpu_info topology not supported.") + self.guest_threads_enabled = False + self.sockets = 0 + self.cores_present = 0 + self.cores_available = 0 + self.cores_per_socket = 0 + self.threads_per_core = 0 + self.max_threads = 0 + return + except Exception as e: + raise("Unable to get CPU topology capabilities: %s" % e.message) + + if platform.machine().startswith('ppc'): + # IBM PowerPC + self.arch = 'ppc' + self.guest_threads_enabled = True + out, error, rc = run_command(['ppc64_cpu', '--smt']) + if rc or 'on' in out: + # SMT has to be disabled for guest to use threads as CPUs. + # rc is always zero, whether SMT is off or on. + self.guest_threads_enabled = False + out, error, rc = run_command(['ppc64_cpu', '--cores-present']) + if not rc: + self.cores_present = int(out.split()[-1]) + out, error, rc = run_command(['ppc64_cpu', '--cores-on']) + if not rc: + self.cores_available = int(out.split()[-1]) + out, error, rc = run_command(['ppc64_cpu', '--threads-per-core']) + if not rc: + self.threads_per_core = int(out.split()[-1]) + self.sockets = self.cores_present/self.threads_per_core + self.cores_per_socket = self.cores_present/self.sockets + else: + # Intel or AMD + self.arch = 'x86' + self.guest_threads_enabled = True + self.sockets = int(libvirt_topology.get('sockets')) + self.cores_per_socket = int(libvirt_topology.get('cores')) + self.cores_present = self.cores_per_socket * self.sockets + self.cores_available = self.cores_present + self.threads_per_core = int(libvirt_topology.get('threads')) + + def lookup(self, ident): + return { + 'guest_threads_enabled': self.guest_threads_enabled, + 'sockets': self.sockets, + 'cores_per_socket': self.cores_per_socket, + 'cores_present': self.cores_present, + 'cores_available': self.cores_available, + 'threads_per_core': self.threads_per_core, + } + + def get_rec_topology(self, vcpus, guest=None, smtx=0): + """ + Kimchi will provide a recommended topoology based on the + number of virtual CPUs desired and guest OS. + + param vcpus: should be an integer + param guest: should be in the form 'distro:version' + param thread_pref: a power of 2 (0, if no preference or x86). + return: topology numbers. None, if SMT/HT not enabled. + """ + + if not self.guest_threads_enabled: + return None + if vcpus > self.cores_available * self.threads_per_core: + # This check should go into template create too? + # Or should we allow this? + raise InvalidParameter("The number of vcpus is too large \ + for this system") # @TODO: msgid + + sockets = 1 + cores = 1 + threads = 1 + best_vcpus = vcpus + + if best_vcpus == 1: + return {'topology': {'sockets': sockets, 'cores': cores, + 'threads': threads}, + 'vcpus': best_vcpus} + + if self.arch is 'ppc': + if best_vcpus % self.threads_per_core: + # Having fewer threads than threads/core will give poor + # performance. Since we're auto-configuring things, + # let's give the user a better vcpu value. + best_vcpus = best_vcpus + \ + (self.threads_per_core - + (best_vcpus % self.threads_per_core)) + + if best_vcpus <= self.threads_per_core: + threads = best_vcpus + return {'topology': {'sockets': sockets, 'cores': cores, + 'threads': threads}, + 'vcpus': best_vcpus} + + # creating a template using REST APIs (non-UI) + if self.arch is 'ppc' and smtx == 0: + smtx = self.threads_per_core + + if smtx > 0: # Power + cores = best_vcpus/smtx + threads = smtx + else: # x86 + # Because of the check above, we know that more vcpus + # than will fit on one core were requested. Spread + # them over as many cores as needed, as opposed to packing + # threads onto fewer cores. + # cores= best_vcpus/self.cores_available + # threads = self.threads_per_core + cores = min(self.cores_available, best_vcpus) + threads = best_vcpus / cores + # In case vcpus was something like '3' on a dual-thread proc, + # make it a legal vcpu number: + best_vcpus = cores * threads + + return {'topology': {'sockets': sockets, 'cores': cores, + 'threads': threads}, + 'vcpus': best_vcpus} diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py index 8cddcdc..7912311 100644 --- a/src/kimchi/model/host.py +++ b/src/kimchi/model/host.py @@ -53,6 +53,7 @@ def __init__(self, **kargs): self.task = TaskModel(**kargs) self.host_info = self._get_host_info() + # @TODO: Think about moving this to the new cpuinfo class def _get_ppc_cpu_info(self): res = {} with open('/proc/cpuinfo') as f: diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py index ff1070d..f436f45 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -25,6 +25,8 @@ from kimchi.exception import InvalidOperation, InvalidParameter from kimchi.exception import NotFoundError, OperationFailed from kimchi.kvmusertests import UserTests +from kimchi.model.cpuinfo import CPUInfoModel +from kimchi.osinfo import common_spec from kimchi.utils import pool_name_from_uri from kimchi.utils import probe_file_permission_as_user from kimchi.vmtemplate import VMTemplate @@ -50,18 +52,30 @@ def create(self, params): cpu_info = params.get('cpu_info') if cpu_info: + vcpus = params.get('cpus') topology = cpu_info.get('topology') + auto_topology = cpu_info.get('auto_topology') # Check, even though currently only topology # is supported. if topology: sockets = topology['sockets'] cores = topology['cores'] threads = topology['threads'] - vcpus = params.get('cpus') if vcpus is None: params['cpus'] = sockets * cores * threads elif vcpus != sockets * cores * threads: raise InvalidParameter("KCHTMPL0025E") + elif auto_topology and auto_topology is True: + if vcpus is None: + vcpus = common_spec['cpus'] + rec_topology = CPUInfoModel( + conn=self.conn).get_rec_topology(vcpus) + if rec_topology: + params['cpus'] = rec_topology['vcpus'] + params['cpu_info']['topology'] = rec_topology['topology'] + else: + raise InvalidOperation("KCHTMPL0029E") + else: params['cpu_info'] = dict() -- 1.9.3
participants (1)
-
Christy Perez