[PATCH v6] Auto Topology for Guest Templates

This should be the final major revision for this. After more discussion with Aline, it seems we've moved very far from "no input from users" to "let users input the numbers, with some small restrctions." Aline said she would talk with the UI team, but I'll still put a small description here: 1. UI adds a new section on the template panel near vcpus. There's a checkbox with the text, "Manually configure guest CPU topology." If the users checks it - The UI calls GET /host/cpuinfo - The CPUs field becomes inactive, and will now be populated by the UI based on the sockets & cores (next item): CPUS = cores * threads - Two fields appear: A selection field for threads (powers of 2 : 0...log(threads_per_core, 2)). This section should be called either SMT (Power), or Threads (x86). The 2nd field is a plain text box titled Cores that takes any integer. Note: no 'automatic' option for threads/SMT. - The UI calls POST template with the topology. 2. If the user doesn't check the checkbox, there's nothing new to pass in for the template create. Note: Currently there is a failing test in mockmodel, due to the nature of the libvirt test driver. I'm looking into a workaround for the getCapabilities function. Christy Perez (1): Parts to allow Kimchi to configure the cpu topology. docs/API.md | 24 ++++++++ src/kimchi/control/cpuinfo.py | 37 ++++++++++++ src/kimchi/control/host.py | 2 + src/kimchi/i18n.py | 5 ++ src/kimchi/model/cpuinfo.py | 128 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/templates.py | 10 ++-- 6 files changed, 202 insertions(+), 4 deletions(-) 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 | 24 ++++++++ src/kimchi/control/cpuinfo.py | 37 ++++++++++++ src/kimchi/control/host.py | 2 + src/kimchi/i18n.py | 5 ++ src/kimchi/model/cpuinfo.py | 128 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/templates.py | 10 ++-- 6 files changed, 202 insertions(+), 4 deletions(-) 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 6c36bb1..d51e763 100644 --- a/docs/API.md +++ b/docs/API.md @@ -896,6 +896,30 @@ Contains the host sample data. *No actions defined* +### Resource: HostStats + +**URI:** /host/cpuinfo + +The cores and sockets of a hosts's CPU. Useful when sizing VMs to take +advantages of the perforamance benefits of SMT (Power) or Hyper-Threading (Intel). + +**Methods:** + +* **GET**: Retreives the sockets, cores, and threads values. + * threading_enabled: Whether CPU topology is supported on this system. + * sockets: The number of total sockets on a system. + * cores: The total number of cores per socket. + * threads_per_core: The threads per core. + +**Actions (PUT):** + +*No actions defined* + +**Actions (POST):** + +*No actions defined* + + ### Resource: HostStatsHistory **URI:** /host/stats/history 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 8f6b67e..a836401 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -318,4 +318,9 @@ "KCHSNAP0006E": _("Unable to delete snapshot '%(name)s' on virtual machine '%(vm)s'. Details: %(err)s"), "KCHSNAP0008E": _("Unable to retrieve current snapshot on virtual machine '%(vm)s'. Details: %(err)s"), "KCHSNAP0009E": _("Unable to revert virtual machine '%(vm)s' to snapshot '%(name)s'. Details: %(err)s"), + + "KCHCPUINF0001E": _("The number of vCPUs is too large for this system."), + "KCHCPUINF0002E": _("Invalid vCPU/topology combination."), + "KCHCPUINF0003E": _("This host (or current configuration) does not allow CPU topology."), + } diff --git a/src/kimchi/model/cpuinfo.py b/src/kimchi/model/cpuinfo.py new file mode 100644 index 0000000..9042f7e --- /dev/null +++ b/src/kimchi/model/cpuinfo.py @@ -0,0 +1,128 @@ +# 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, InvalidOperation +from kimchi.exception import OperationFailed +from kimchi.utils import kimchi_log, run_command + +ARCH = 'power' if platform.machine().startswith('ppc') else 'x86' +LEGACY_CPU_MAX = 4 + + +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: + - 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 OperationFailed("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 OperationFailed("Unable to get CPU topology capabilities: %s" + % e.message) + + if ARCH == 'power': + # IBM PowerPC + 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.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 check_topology(self, vcpus, topology): + """ + param vcpus: should be an integer + param iso_path: the path of the guest ISO + param topology: {'sockets': x, 'cores': x, 'threads': x} + """ + sockets = topology['sockets'] + cores = topology['cores'] + threads = topology['threads'] + + if not self.guest_threads_enabled: + raise InvalidOperation("KCHCPUINF0003E") + if vcpus != sockets * cores * threads: + raise InvalidParameter("KCHCPUINF0002E") + if vcpus > self.cores_available * self.threads_per_core: + raise InvalidParameter("KCHCPUINF0001E") + if threads > self.threads_per_core: + raise InvalidParameter("KCHCPUINF0002E") diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py index 6e1a571..36dff9f 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -25,6 +25,7 @@ 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.utils import pool_name_from_uri from kimchi.utils import probe_file_permission_as_user from kimchi.vmtemplate import VMTemplate @@ -57,11 +58,12 @@ def create(self, params): sockets = topology['sockets'] cores = topology['cores'] threads = topology['threads'] - vcpus = params.get('cpus') - if vcpus is None: + if params.get('cpus') is None: params['cpus'] = sockets * cores * threads - elif vcpus != sockets * cores * threads: - raise InvalidParameter("KCHTMPL0025E") + # check_topoology will raise the appropriate + # exception if a topology is invalid. + CPUInfoModel(conn=self.conn).\ + check_topology(params['cpus'], topology) else: params['cpu_info'] = dict() -- 1.9.3

On 11/19/2014 01:03 AM, Christy Perez wrote:
Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- docs/API.md | 24 ++++++++ src/kimchi/control/cpuinfo.py | 37 ++++++++++++ src/kimchi/control/host.py | 2 + src/kimchi/i18n.py | 5 ++ src/kimchi/model/cpuinfo.py | 128 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/templates.py | 10 ++-- 6 files changed, 202 insertions(+), 4 deletions(-) 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 6c36bb1..d51e763 100644 --- a/docs/API.md +++ b/docs/API.md @@ -896,6 +896,30 @@ Contains the host sample data.
*No actions defined*
+### Resource: HostStats + +**URI:** /host/cpuinfo + +The cores and sockets of a hosts's CPU. Useful when sizing VMs to take +advantages of the perforamance benefits of SMT (Power) or Hyper-Threading (Intel). + +**Methods:** + +* **GET**: Retreives the sockets, cores, and threads values. + * threading_enabled: Whether CPU topology is supported on this system. + * sockets: The number of total sockets on a system. + * cores: The total number of cores per socket. + * threads_per_core: The threads per core. + +**Actions (PUT):** + +*No actions defined* + +**Actions (POST):** + +*No actions defined* + + ### Resource: HostStatsHistory
**URI:** /host/stats/history 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 8f6b67e..a836401 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -318,4 +318,9 @@ "KCHSNAP0006E": _("Unable to delete snapshot '%(name)s' on virtual machine '%(vm)s'. Details: %(err)s"), "KCHSNAP0008E": _("Unable to retrieve current snapshot on virtual machine '%(vm)s'. Details: %(err)s"), "KCHSNAP0009E": _("Unable to revert virtual machine '%(vm)s' to snapshot '%(name)s'. Details: %(err)s"), + + "KCHCPUINF0001E": _("The number of vCPUs is too large for this system."), + "KCHCPUINF0002E": _("Invalid vCPU/topology combination."), + "KCHCPUINF0003E": _("This host (or current configuration) does not allow CPU topology."), + } diff --git a/src/kimchi/model/cpuinfo.py b/src/kimchi/model/cpuinfo.py new file mode 100644 index 0000000..9042f7e --- /dev/null +++ b/src/kimchi/model/cpuinfo.py @@ -0,0 +1,128 @@ +# 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, InvalidOperation +from kimchi.exception import OperationFailed +from kimchi.utils import kimchi_log, run_command + +ARCH = 'power' if platform.machine().startswith('ppc') else 'x86' +LEGACY_CPU_MAX = 4 + + +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: + - 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 OperationFailed("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 OperationFailed("Unable to get CPU topology capabilities: %s" + % e.message)
If we raise an exception on __init__() Kimchi server will not start up which is very bad IMO. In that case I'd suggest to return the same values returned when libvirt_topology is None
+ + if ARCH == 'power': + # IBM PowerPC + 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.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 check_topology(self, vcpus, topology): + """ + param vcpus: should be an integer + param iso_path: the path of the guest ISO + param topology: {'sockets': x, 'cores': x, 'threads': x} + """ + sockets = topology['sockets'] + cores = topology['cores'] + threads = topology['threads'] + + if not self.guest_threads_enabled: + raise InvalidOperation("KCHCPUINF0003E") + if vcpus != sockets * cores * threads: + raise InvalidParameter("KCHCPUINF0002E") + if vcpus > self.cores_available * self.threads_per_core: + raise InvalidParameter("KCHCPUINF0001E") + if threads > self.threads_per_core: + raise InvalidParameter("KCHCPUINF0002E") diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py index 6e1a571..36dff9f 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -25,6 +25,7 @@ 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.utils import pool_name_from_uri from kimchi.utils import probe_file_permission_as_user from kimchi.vmtemplate import VMTemplate @@ -57,11 +58,12 @@ def create(self, params): sockets = topology['sockets'] cores = topology['cores'] threads = topology['threads'] - vcpus = params.get('cpus') - if vcpus is None: + if params.get('cpus') is None: params['cpus'] = sockets * cores * threads - elif vcpus != sockets * cores * threads: - raise InvalidParameter("KCHTMPL0025E") + # check_topoology will raise the appropriate + # exception if a topology is invalid. + CPUInfoModel(conn=self.conn).\ + check_topology(params['cpus'], topology) else: params['cpu_info'] = dict()

On 11/19/2014 01:03 AM, Christy Perez wrote:
Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com> --- docs/API.md | 24 ++++++++ src/kimchi/control/cpuinfo.py | 37 ++++++++++++ src/kimchi/control/host.py | 2 + src/kimchi/i18n.py | 5 ++ src/kimchi/model/cpuinfo.py | 128 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/templates.py | 10 ++--
As you will resend, please add a simple test to verify the API. GET /host/cpu_info will return a JSON with the X expected keys, for example.
6 files changed, 202 insertions(+), 4 deletions(-) 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 6c36bb1..d51e763 100644 --- a/docs/API.md +++ b/docs/API.md @@ -896,6 +896,30 @@ Contains the host sample data.
*No actions defined*
+### Resource: HostStats + +**URI:** /host/cpuinfo + +The cores and sockets of a hosts's CPU. Useful when sizing VMs to take +advantages of the perforamance benefits of SMT (Power) or Hyper-Threading (Intel). + +**Methods:** + +* **GET**: Retreives the sockets, cores, and threads values. + * threading_enabled: Whether CPU topology is supported on this system. + * sockets: The number of total sockets on a system. + * cores: The total number of cores per socket. + * threads_per_core: The threads per core. + +**Actions (PUT):** + +*No actions defined* + +**Actions (POST):** + +*No actions defined* + + ### Resource: HostStatsHistory
**URI:** /host/stats/history 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 8f6b67e..a836401 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -318,4 +318,9 @@ "KCHSNAP0006E": _("Unable to delete snapshot '%(name)s' on virtual machine '%(vm)s'. Details: %(err)s"), "KCHSNAP0008E": _("Unable to retrieve current snapshot on virtual machine '%(vm)s'. Details: %(err)s"), "KCHSNAP0009E": _("Unable to revert virtual machine '%(vm)s' to snapshot '%(name)s'. Details: %(err)s"), + + "KCHCPUINF0001E": _("The number of vCPUs is too large for this system."), + "KCHCPUINF0002E": _("Invalid vCPU/topology combination."), + "KCHCPUINF0003E": _("This host (or current configuration) does not allow CPU topology."), + } diff --git a/src/kimchi/model/cpuinfo.py b/src/kimchi/model/cpuinfo.py new file mode 100644 index 0000000..9042f7e --- /dev/null +++ b/src/kimchi/model/cpuinfo.py @@ -0,0 +1,128 @@ +# 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, InvalidOperation +from kimchi.exception import OperationFailed +from kimchi.utils import kimchi_log, run_command + +ARCH = 'power' if platform.machine().startswith('ppc') else 'x86' +LEGACY_CPU_MAX = 4 + + +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: + - 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 OperationFailed("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 OperationFailed("Unable to get CPU topology capabilities: %s" + % e.message) + + if ARCH == 'power': + # IBM PowerPC + 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.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 check_topology(self, vcpus, topology): + """ + param vcpus: should be an integer + param iso_path: the path of the guest ISO + param topology: {'sockets': x, 'cores': x, 'threads': x} + """ + sockets = topology['sockets'] + cores = topology['cores'] + threads = topology['threads'] + + if not self.guest_threads_enabled: + raise InvalidOperation("KCHCPUINF0003E") + if vcpus != sockets * cores * threads: + raise InvalidParameter("KCHCPUINF0002E") + if vcpus > self.cores_available * self.threads_per_core: + raise InvalidParameter("KCHCPUINF0001E") + if threads > self.threads_per_core: + raise InvalidParameter("KCHCPUINF0002E") diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py index 6e1a571..36dff9f 100644 --- a/src/kimchi/model/templates.py +++ b/src/kimchi/model/templates.py @@ -25,6 +25,7 @@ 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.utils import pool_name_from_uri from kimchi.utils import probe_file_permission_as_user from kimchi.vmtemplate import VMTemplate @@ -57,11 +58,12 @@ def create(self, params): sockets = topology['sockets'] cores = topology['cores'] threads = topology['threads'] - vcpus = params.get('cpus') - if vcpus is None: + if params.get('cpus') is None: params['cpus'] = sockets * cores * threads - elif vcpus != sockets * cores * threads: - raise InvalidParameter("KCHTMPL0025E") + # check_topoology will raise the appropriate + # exception if a topology is invalid. + CPUInfoModel(conn=self.conn).\ + check_topology(params['cpus'], topology) else: params['cpu_info'] = dict()
participants (2)
-
Aline Manera
-
Christy Perez