On 11/24/2014 09:07 AM, Aline Manera wrote:
On 11/20/2014 06:30 PM, Christy Perez wrote:
> This patch adds a host/cpuinfo API so that the topology can be
> retrieved and then used as a guideline for what is legal
> for this system.
>
> The original plan for this function was to abstract everything
> away from users, but over many iterations, it became clear that
> due to differences in architectures, etc., it is simpler to ask
> a user to input cores and threads for a VM, with a few minimal
> restrictions on threads/core and cores.
>
> As a result, the final version of the patch for the backend
> contains only a small amount of validation for new templates.
>
> Signed-off-by: Christy Perez <christy(a)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..fbae6ce
> --- /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
> +
Seems there are parts of the license header missing here.
ACK
> +import platform
> +
> +from xml.etree import ElementTree as ET
> +
> +from kimchi.exception import InvalidParameter, InvalidOperation
> +from kimchi.utils import kimchi_log, run_command
> +
> +ARCH = 'power' if platform.machine().startswith('ppc') else
'x86'
> +LEGACY_CPU_MAX = 4
> +
> +
> +def get_topo_capabilities(connect):
> + """
> + This helper function exists solely to be overridden for
> + mockmodel tests. Since other modules use getCapabilies(),
> + it can't be overridden directly.
> + """
> + xml = connect.getCapabilities()
> + capabilities = ET.fromstring(xml)
> + return
capabilities.find('host').find('cpu').find('topology')
> +
> +
> +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):
> + def set_not_supported():
> + 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
> +
You can set those values by default and only change them when needed.
ACK
> + self.conn = kargs['conn']
> + libvirt_topology = None
> + try:
> + connect = self.conn.get()
> + except Exception as e:
> + kimchi_log.error("cpuinfo: Unable to get qemu connection:
> %s"
> + % e.message)
> + set_not_supported()
> +
> + try:
> + libvirt_topology = get_topo_capabilities(connect)
> + if libvirt_topology is None:
> + set_not_supported()
> + return
> + except Exception as e:
> + kimchi_log.info("Unable to get CPU topology capabilities:
> %s"
> + % e.message)
> + set_not_supported()
> + return
> +
Then all that become:
try:
connect = self.conn.get()
libvirt_topology = get_topo_capabilities()
except Exception, e:
kimchi_log.into(....)
I'd still need the check "if libvirt_topology is None:," but I can take
out a try/except block. Sending this change in v8.
> + 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()
>