By default, Kimchi always sets the best Template configuration for user
based on a single input: ISO file or Image file.
So you don't need to create an API (auto-topology) for it. It will be
the default behavior.
The flow is:
- Create a Template which will get the default CPU topology according to
host OS and ISO OS.
POST /templates {name: my-tmpl, cdrom: <my-iso>}
- Update the CPU topology according to your needs:
PUT /templates/my-tmpl {cpu_info: {...}}
Ok?
See more comments inline below:
On 11/03/2014 12:36 AM, Christy Perez wrote:
Signed-off-by: Christy Perez <christy(a)linux.vnet.ibm.com>
---
docs/API.md | 2 +
src/kimchi/API.json | 6 ++
src/kimchi/control/cpuinfo.py | 37 +++++++++
src/kimchi/i18n.py | 2 +
src/kimchi/model/cpuinfo.py | 176 ++++++++++++++++++++++++++++++++++++++++++
src/kimchi/model/host.py | 1 +
src/kimchi/model/templates.py | 13 +++-
7 files changed, 236 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 9c06f85..4e449b3 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -226,6 +226,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..3a295ad
--- /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.uri_fmt = "/host/cpu_info"
+
+ @property
+ def data(self):
+ return {'arch': self.info['arch'],
+ 'threading_enabled':
self.info['guest_threads_enabled'],
+ 'sockets': self.info['sockets'],
+ 'cores_present': self.info['cores_present'],
+ 'cores_online': self.info['cores_available'],
+ 'threads_per_core': self.info['threads_per_core']
+ }
----
All that will not needed.
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index f789259..7669dbc 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -146,6 +146,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..12f5899
--- /dev/null
+++ b/src/kimchi/model/cpuinfo.py
As this file will not needed, you can move the functions needed to
identify the topology to
a new module model/cputopology.py or something like it.
@@ -0,0 +1,176 @@
+# 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
+import libvirt
+import psutil
+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):
+ """ 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
+ """
+ libvirt_topology = None
+ try:
+ connect = libvirt.open(None)
Use LibvirtConnection to establish a connection to libvirt.
One of the args on "kargs" is "conn" which is already an
LibvirtConnection instance.
+ 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')
Only rc!=0 is not enough to check it?
And isn't there a libvirt API to ppc64_cpu?
+ if rc or out is not 'SMT is off':
+ # SMT has to be disabled for guest to use threads as CPUs.
+ self.guest_threads_enabled = False
+ out, error, rc = run_command('ppc64_cpu',
'--cores_present')
+ if not rc:
+ self.cores_present = out.split()[-1]
+ out, error, rc = run_command('ppc64_cpu', '--cores_on')
+ if not rc:
+ self.cores_available = out.split()[-1]
+ out, error, rc = run_command('ppc64_cpu',
'--threads_per_core')
+ if not rc:
+ self.threads_per_core = out.split()[-1]
+ # This next line doesn't make sense as written, but takes advantage
+ # of the fact that libvirt for ppc reports all the cores on the
+ # system as the cores value in topology (e.g. sockets: 1, cores:
+ # 128, threads: 1)
+ # @TODO: When libvirt on Power starts returning the correct info,
+ # we should be able to remove the seperate logic (or at least put
+ # in a check for the libvirt version, b/c that will break this).
+ self.cores_per_socket = \
+ libvirt.topology.get('cores')/self.threads_per_core
+ # Nothing actually uses 'sockets,' but set it anyway
+ self.sockets = 1
+ 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 = psutil.NUM_CPUS
+ self.threads_per_core = int(libvirt_topology.get('threads'))
+
+ def lookup(self):
+ """
+ The UI can use this data to decide what to display:
+ - SMT (arch = ppc) or HT (arch = x86)
+ - If HT:
+ 1. the UI can look at cores_present and
+ warn the user if he/she creates a VM with more
+ vCPUs than cores (bad performance).
+ 2. The UI could include a suggestion of 'cores_present'
+ or fewer vCPUs next to the CPU box.
+ 3. There could be an actual limit on the number of vCPUs.
+ A user can use this data to decide how to best manually
+ create a VM or template.
+ """
+ return {
+ 'arch': self.arch,
+ 'guest_threads_enabled': self.guest_threads_enabled,
+ 'sockets': self.sockets,
+ 'cores_per_socket': self.cores_per_socket,
+ 'cores_present': self.cores_present,
+ 'cores_availalble': 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, or 0, if no preference (x86).
+ return: topology numbers. If all zero, SMT/hyperthreading not
+ supported.
+ """
+
+ """
+ Note to remove later ... This will be called from the UI,
+ If Power, smtx should be set.
+
+ On x86, spreading threads over cores is better than grouping.
+ IOW, for vcpus = 2, sockets = 1, cores = 2, threads = 1 is
+ better than vcpus=2, sockets = 1, cores = 1, threads = 2.
+ """
+
+ 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
+
+ if vcpus == 1:
+ return {"sockets": sockets, "cores": cores,
"threads": threads}
+
+ # creating a VM using REST APIs
+ if self.arch is 'ppc' and smtx == 0:
+ smtx = self.threads_per_core
+
+ if smtx: # Power
+ cores = vcpus/smtx
+ threads = smtx
+
+ else: # x86
+ # The scheduler will move these around, but we'll split
+ # it up anyway.
+ cores = self.cores_available
+ threads = vcpus/cores
+
+ return {"sockets": sockets, "cores": cores,
"threads": threads}
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..e402260 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
@@ -50,18 +51,28 @@ 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 = 1
Why are you assuming it? Isn't this value same of params['cpus'] ?
The default values come from osinfo.py and we should respect them.
+ topology = CPUInfoModel().get_rec_topology(vcpus)
+ if topology:
+ params['cpu_info']['topology'] = topology
+ else:
+ raise InvalidOperation("KCHTMPL0029E")
+
else:
params['cpu_info'] = dict()