[Kimchi-devel] [PATCH] [Kimchi 2/4] Add maxvcpus attribute to guests
Lucio Correia
luciojhc at linux.vnet.ibm.com
Fri Feb 5 17:42:30 UTC 2016
- Enable update of maxvcpus, vcpus and CPU topology
- Enable vCPU cold plug when topology is defined
- Add cpu_info validation to guest update
- Simplify _get_cpu_xml() code
- Remove change_numa verification
- Update docs and APIs with maxvcpus attribute
Signed-off-by: Lucio Correia <luciojhc at linux.vnet.ibm.com>
---
API.json | 7 +--
docs/API.md | 25 +++++++--
i18n.py | 1 -
model/vms.py | 171 +++++++++++++++++++++++++++++++++-----------------------
vmtemplate.py | 37 ++++--------
xmlutils/cpu.py | 6 +-
6 files changed, 136 insertions(+), 111 deletions(-)
diff --git a/API.json b/API.json
index d97a810..294be64 100644
--- a/API.json
+++ b/API.json
@@ -308,12 +308,7 @@
}
}
},
- "cpus": {
- "description": "The new number of virtual CPUs for the VM",
- "type": "integer",
- "minimum": 1,
- "error": "KCHTMPL0012E"
- },
+ "cpu_info": { "$ref": "#/kimchitype/cpu_info" },
"memory": {
"description": "The new amount (MB) of memory for the VM",
"type": "integer",
diff --git a/docs/API.md b/docs/API.md
index 070f938..a46e80e 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -117,7 +117,13 @@ server.
* io_throughput_peak: The highest recent value of 'io_throughput'.
* uuid: UUID of the VM.
* memory: The amount of memory assigned to the VM (in MB)
- * cpus: The number of CPUs assigned to the VM
+ * cpu_info: CPU-specific information.
+ * vcpus: The number of CPUs assigned to the VM
+ * maxvcpus: The maximum number of CPUs that can be assigned to the VM
+ * topology: Processor topology, includes:
+ * sockets - The maximum number of sockets to use.
+ * cores - The number of cores per socket.
+ * threads - The number of threads per core.
* screenshot: A link to a recent capture of the screen in PNG format
* icon: A link to an icon that represents the VM
* graphics: A dict to show detail of VM graphics.
@@ -138,12 +144,10 @@ server.
* groups: A list of system groups whose users have permission to access
the VM. Default is: empty (i.e. no groups given access).
* **DELETE**: Remove the Virtual Machine
-* **PUT**: update the parameters of existed VM
+* **PUT**: update the parameters of existing VM
* name: New name for this VM (only applied for shutoff VM)
* users: New list of system users.
* groups: New list of system groups.
- * cpus: New number of virtual cpus for this VM (if VM is running, new value
- will take effect in next reboot)
* memory: New amount of memory (MB) for this VM (if VM is running, new
value will take effect in next reboot)
* graphics: A dict to show detail of VM graphics.
@@ -152,6 +156,19 @@ server.
* passwdValidTo *(optional)*: lifetime for the console password. When
omitted the password will be valid just
for 30 seconds.
+ * cpu_info *(optional)*: CPU-specific information.
+ * maxvcpus *(optional)*: The maximum number of vCPUs that can be
+ assigned to the VM. If topology is specified, maxvcpus must be a
+ product of sockets, cores and threads.
+ * vcpus *(optional)*: The number of vCPUs assigned to the VM. Default
+ is 1, unless a CPU topology is specified. In that case, vcpus
+ must be a multiple of a product of cores and threads, and will
+ default to maxvcpus value.
+ * topology *(optional)*: Specify sockets, threads, and cores to run the
+ virtual CPU threads on. All three are required.
+ * sockets - The maximum number of sockets to use.
+ * cores - The number of cores per socket.
+ * threads - The number of threads per core.
* **POST**: *See Virtual Machine Actions*
diff --git a/i18n.py b/i18n.py
index bbe68b6..0b9fa39 100644
--- a/i18n.py
+++ b/i18n.py
@@ -130,7 +130,6 @@ messages = {
"KCHVM0071E": _("Memory value %(mem)s must be aligned to %(alignment)sMiB."),
"KCHVM0073E": _("Unable to update the following parameters while the VM is offline: %(params)s"),
"KCHVM0074E": _("Unable to update the following parameters while the VM is online: %(params)s"),
- "KCHVM0075E": _("Cannot change VCPU value because '%(vm)s' has a topology defined - sockets: %(sockets)s, cores: %(cores)s, threads: %(threads)s."),
"KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."),
"KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."),
diff --git a/model/vms.py b/model/vms.py
index 9aaf530..593d73b 100644
--- a/model/vms.py
+++ b/model/vms.py
@@ -41,8 +41,8 @@ from wok.model.tasks import TaskModel
from wok.rollbackcontext import RollbackContext
from wok.utils import add_task, convert_data_size, get_next_clone_name
from wok.utils import import_class, run_setfacl_set_attr, run_command, wok_log
-from wok.xmlutils.utils import xpath_get_text, xml_item_update
-from wok.xmlutils.utils import dictize
+from wok.xmlutils.utils import dictize, xpath_get_text, xml_item_insert
+from wok.xmlutils.utils import xml_item_remove, xml_item_update
from wok.plugins.kimchi import model
from wok.plugins.kimchi import vnc
@@ -60,6 +60,7 @@ from wok.plugins.kimchi.screenshot import VMScreenshot
from wok.plugins.kimchi.utils import template_name_from_uri
from wok.plugins.kimchi.vmtemplate import MAX_MEM_LIM, PPC_MEM_ALIGN
from wok.plugins.kimchi.xmlutils.cpu import get_cpu_xml, get_numa_xml
+from wok.plugins.kimchi.xmlutils.cpu import get_topology_xml
from wok.plugins.kimchi.xmlutils.disk import get_vm_disk_info, get_vm_disks
@@ -72,15 +73,12 @@ DOM_STATE_MAP = {0: 'nostate',
6: 'crashed',
7: 'pmsuspended'}
-VM_STATIC_UPDATE_PARAMS = {'name': './name', 'cpus': './vcpu'}
-
-VM_LIVE_UPDATE_PARAMS = {}
-
# update parameters which are updatable when the VM is online
VM_ONLINE_UPDATE_PARAMS = ['graphics', 'groups', 'memory', 'users']
+
# update parameters which are updatable when the VM is offline
-VM_OFFLINE_UPDATE_PARAMS = ['cpus', 'graphics', 'groups', 'memory', 'name',
- 'users']
+VM_OFFLINE_UPDATE_PARAMS = ['cpu_info', 'graphics', 'groups', 'memory',
+ 'name', 'users']
XPATH_DOMAIN_DISK = "/domain/devices/disk[@device='disk']/source/@file"
XPATH_DOMAIN_DISK_BY_FILE = "./devices/disk[@device='disk']/source[@file='%s']"
@@ -93,8 +91,11 @@ XPATH_DOMAIN_MEMORY_UNIT = '/domain/memory/@unit'
XPATH_DOMAIN_UUID = '/domain/uuid'
XPATH_DOMAIN_DEV_CPU_ID = '/domain/devices/spapr-cpu-socket/@id'
+XPATH_CPU = './cpu'
+XPATH_NAME = './name'
XPATH_NUMA_CELL = './cpu/numa/cell'
XPATH_TOPOLOGY = './cpu/topology'
+XPATH_VCPU = './vcpu'
# key: VM name; value: lock object
vm_locks = {}
@@ -241,23 +242,6 @@ class VMModel(object):
threads = xpath_get_text(xml, XPATH_TOPOLOGY + '/@threads')
return sockets and cores and threads
- def get_vm_max_sockets(self, dom):
- return int(xpath_get_text(dom.XMLDesc(0),
- XPATH_TOPOLOGY + '/@sockets')[0])
-
- def get_vm_sockets(self, dom):
- current_vcpu = dom.vcpusFlags(libvirt.VIR_DOMAIN_AFFECT_CURRENT)
- return (current_vcpu / self.get_vm_cores(dom) /
- self.get_vm_threads(dom))
-
- def get_vm_cores(self, dom):
- return int(xpath_get_text(dom.XMLDesc(0),
- XPATH_TOPOLOGY + '/@cores')[0])
-
- def get_vm_threads(self, dom):
- return int(xpath_get_text(dom.XMLDesc(0),
- XPATH_TOPOLOGY + '/@threads')[0])
-
def update(self, name, params):
lock = vm_locks.get(name)
if lock is None:
@@ -285,17 +269,6 @@ class VMModel(object):
raise InvalidParameter('KCHVM0074E',
{'params': ', '.join(ext_params)})
- if 'cpus' in params and DOM_STATE_MAP[dom.info()[0]] == 'shutoff':
- # user cannot change vcpu if topology is defined.
- curr_vcpu = dom.vcpusFlags(libvirt.VIR_DOMAIN_AFFECT_CURRENT)
- if self.has_topology(dom) and curr_vcpu != params['cpus']:
- raise InvalidOperation(
- 'KCHVM0075E',
- {'vm': dom.name(),
- 'sockets': self.get_vm_sockets(dom),
- 'cores': self.get_vm_cores(dom),
- 'threads': self.get_vm_threads(dom)})
-
self._live_vm_update(dom, params)
vm_name, dom = self._static_vm_update(name, dom, params)
return vm_name
@@ -751,6 +724,8 @@ class VMModel(object):
old_xml = new_xml = dom.XMLDesc(0)
params = copy.deepcopy(params)
+
+ # Update name
name = params.get('name')
nonascii_name = None
if name is not None:
@@ -760,37 +735,41 @@ class VMModel(object):
raise InvalidParameter("KCHVM0003E", msg_args)
params['name'], nonascii_name = get_ascii_nonascii_name(name)
-
- for key, val in params.items():
- change_numa = True
- if key in VM_STATIC_UPDATE_PARAMS:
- if type(val) == int:
- val = str(val)
- xpath = VM_STATIC_UPDATE_PARAMS[key]
- attrib = None
- if key == 'cpus':
- if self.has_topology(dom) or dom.isActive():
- change_numa = False
- continue
- # Update maxvcpu firstly
- new_xml = xml_item_update(new_xml, xpath,
- str(self._get_host_maxcpu()),
- attrib)
- # Update current vcpu
- attrib = 'current'
- new_xml = xml_item_update(new_xml, xpath, val, attrib)
+ new_xml = xml_item_update(new_xml, XPATH_NAME, name, None)
+
+ # Update CPU info
+ cpu_info = params.get('cpu_info', {})
+ cpu_info = self._update_cpu_info(new_xml, dom, cpu_info)
+
+ vcpus = str(cpu_info['vcpus'])
+ new_xml = xml_item_update(new_xml, XPATH_VCPU, vcpus, 'current')
+
+ maxvcpus = str(cpu_info['maxvcpus'])
+ new_xml = xml_item_update(new_xml, XPATH_VCPU, maxvcpus, None)
+
+ topology = cpu_info['topology']
+ if topology:
+ sockets = str(topology['sockets'])
+ cores = str(topology['cores'])
+ threads = str(topology['threads'])
+
+ if self.has_topology(dom):
+ # topology is being updated
+ xpath = XPATH_TOPOLOGY
+ new_xml = xml_item_update(new_xml, xpath, sockets, 'sockets')
+ new_xml = xml_item_update(new_xml, xpath, cores, 'cores')
+ new_xml = xml_item_update(new_xml, xpath, threads, 'threads')
+ else:
+ # topology is being added
+ new_xml = xml_item_insert(new_xml, XPATH_CPU,
+ get_topology_xml(topology))
+ elif self.has_topology(dom):
+ # topology is being undefined: remove it
+ new_xml = xml_item_remove(new_xml, XPATH_TOPOLOGY)
# Updating memory and NUMA if necessary, if vm is offline
- if not dom.isActive():
- if 'memory' in params:
- new_xml = self._update_memory_config(new_xml, params)
- elif 'cpus' in params and change_numa and \
- (xpath_get_text(new_xml, XPATH_NUMA_CELL + '/@memory') != []):
- new_xml = xml_item_update(
- new_xml,
- XPATH_NUMA_CELL,
- value='0',
- attr='cpus')
+ if 'memory' in params and not dom.isActive():
+ new_xml = self._update_memory_config(new_xml, params)
if 'graphics' in params:
new_xml = self._update_graphics(dom, new_xml, params)
@@ -834,10 +813,10 @@ class VMModel(object):
# apropriated
root = ET.fromstring(xml)
numa_mem = xpath_get_text(xml, XPATH_NUMA_CELL + '/@memory')
- vcpus = params.get('cpus')
+ maxvcpus = params.get('cpu_info', {}).get('maxvcpus')
if numa_mem == []:
- if vcpus is None:
- vcpus = int(xpath_get_text(xml, 'vcpu')[0])
+ if maxvcpus is None:
+ maxvcpus = int(xpath_get_text(xml, XPATH_VCPU)[0])
cpu = root.find('./cpu')
if cpu is None:
cpu = get_cpu_xml(0, params['memory'] << 10)
@@ -846,7 +825,7 @@ class VMModel(object):
numa_element = get_numa_xml(0, params['memory'] << 10)
cpu.insert(0, ET.fromstring(numa_element))
else:
- if vcpus is not None:
+ if maxvcpus is not None:
xml = xml_item_update(
xml,
XPATH_NUMA_CELL,
@@ -928,6 +907,37 @@ class VMModel(object):
return ET.tostring(root, encoding="utf-8")
+ def _update_cpu_info(self, new_xml, dom, new_info):
+ topology = {}
+ if self.has_topology(dom):
+ sockets = xpath_get_text(new_xml, XPATH_TOPOLOGY + '/@sockets')[0]
+ cores = xpath_get_text(new_xml, XPATH_TOPOLOGY + '/@cores')[0]
+ threads = xpath_get_text(new_xml, XPATH_TOPOLOGY + '/@threads')[0]
+ topology = {
+ 'sockets': int(sockets),
+ 'cores': int(cores),
+ 'threads': int(threads),
+ }
+
+ # if current is not defined in vcpu, vcpus is equal to maxvcpus
+ xml_maxvcpus = xpath_get_text(new_xml, 'vcpu')
+ maxvcpus = int(xml_maxvcpus[0])
+ xml_vcpus = xpath_get_text(new_xml, './vcpu/@current')
+ vcpus = int(xml_vcpus[0]) if xml_vcpus else maxvcpus
+
+ cpu_info = {
+ 'maxvcpus': maxvcpus,
+ 'vcpus': vcpus,
+ 'topology': topology,
+ }
+ cpu_info.update(new_info)
+
+ # Revalidate cpu info - may raise CPUInfo exceptions
+ cpu_model = CPUInfoModel(conn=self.conn)
+ cpu_model.check_cpu_info(cpu_info)
+
+ return cpu_info
+
def _live_vm_update(self, dom, params):
self._vm_update_access_metadata(dom, params)
if 'memory' in params and dom.isActive():
@@ -1140,8 +1150,27 @@ class VMModel(object):
res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
users, groups = self._get_access_info(dom)
+ xml = dom.XMLDesc(0)
+ maxvcpus = int(xpath_get_text(xml, XPATH_VCPU)[0])
+
+ cpu_info = {
+ 'vcpus': info[3],
+ 'maxvcpus': maxvcpus,
+ 'topology': {},
+ }
+
+ if self.has_topology(dom):
+ sockets = int(xpath_get_text(xml, XPATH_TOPOLOGY + '/@sockets')[0])
+ cores = int(xpath_get_text(xml, XPATH_TOPOLOGY + '/@cores')[0])
+ threads = int(xpath_get_text(xml, XPATH_TOPOLOGY + '/@threads')[0])
+
+ cpu_info['topology'] = {
+ 'sockets': sockets,
+ 'cores': cores,
+ 'threads': threads,
+ }
+
if state == 'shutoff':
- xml = dom.XMLDesc(0)
val = xpath_get_text(xml, XPATH_DOMAIN_MEMORY)[0]
unit_list = xpath_get_text(xml, XPATH_DOMAIN_MEMORY_UNIT)
if len(unit_list) > 0:
@@ -1157,7 +1186,7 @@ class VMModel(object):
'stats': res,
'uuid': dom.UUIDString(),
'memory': memory,
- 'cpus': info[3],
+ 'cpu_info': cpu_info,
'screenshot': screenshot,
'icon': icon,
# (type, listen, port, passwd, passwdValidTo)
diff --git a/vmtemplate.py b/vmtemplate.py
index 2f524a7..ef17ff6 100644
--- a/vmtemplate.py
+++ b/vmtemplate.py
@@ -324,12 +324,9 @@ class VMTemplate(object):
def _get_cpu_xml(self):
# Include CPU topology, if provided
- cpu_info = self.info.get('cpu_info')
- if cpu_info is not None:
- cpu_topo = cpu_info.get('topology')
- return get_cpu_xml(0,
- self.info.get('memory') << 10,
- cpu_topo)
+ cpu_topo = self.info.get('cpu_info', {}).get('topology', {})
+
+ return get_cpu_xml(0, self.info.get('memory') << 10, cpu_topo)
def _get_max_memory(self, guest_memory):
# Setting maxMemory of the VM, which will be lesser value between:
@@ -371,8 +368,6 @@ class VMTemplate(object):
libvirt_stream_protocols = kwargs.get('libvirt_stream_protocols', [])
cdrom_xml = self._get_cdrom_xml(libvirt_stream_protocols)
- max_vcpus = kwargs.get('max_vcpus', 1)
-
if not urlparse.urlparse(self.info.get('cdrom', "")).scheme in \
libvirt_stream_protocols and \
params.get('iso_stream', False):
@@ -402,23 +397,13 @@ class VMTemplate(object):
# set a hard limit using max_memory + 1GiB
params['hard_limit'] = params['max_memory'] + (1024 << 10)
- cpu_topo = self.info.get('cpu_info').get('topology')
- if (cpu_topo is not None):
- sockets = int(max_vcpus / (cpu_topo['cores'] *
- cpu_topo['threads']))
- self.info['cpu_info']['topology']['sockets'] = sockets
+ # vcpu element
+ cpus = params['cpu_info']['vcpus']
+ maxvcpus = params['cpu_info']['maxvcpus']
+ params['vcpus_xml'] = "<vcpu current='%d'>%d</vcpu>" % (cpus, maxvcpus)
- # Reduce maxvcpu to fit number of sockets if necessary
- total_max_vcpu = sockets * cpu_topo['cores'] * cpu_topo['threads']
- if total_max_vcpu != max_vcpus:
- max_vcpus = total_max_vcpu
-
- params['vcpus'] = "<vcpu current='%s'>%d</vcpu>" % \
- (params['cpus'], max_vcpus)
- else:
- params['vcpus'] = "<vcpu current='%s'>%d</vcpu>" % \
- (params['cpus'], max_vcpus)
- params['cpu_info'] = self._get_cpu_xml()
+ # cpu_info element
+ params['cpu_info_xml'] = self._get_cpu_xml()
xml = """
<domain type='%(domain)s'>
@@ -430,8 +415,8 @@ class VMTemplate(object):
</memtune>
<maxMemory slots='%(slots)s' unit='KiB'>%(max_memory)s</maxMemory>
<memory unit='MiB'>%(memory)s</memory>
- %(vcpus)s
- %(cpu_info)s
+ %(vcpus_xml)s
+ %(cpu_info_xml)s
<os>
<type arch='%(arch)s'>hvm</type>
<boot dev='hd'/>
diff --git a/xmlutils/cpu.py b/xmlutils/cpu.py
index 32c01a4..b3eaaae 100644
--- a/xmlutils/cpu.py
+++ b/xmlutils/cpu.py
@@ -1,7 +1,7 @@
#
# Project Kimchi
#
-# Copyright IBM, Corp. 2015
+# Copyright IBM, Corp. 2015-2016
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -45,7 +45,7 @@ def get_topology_xml(cpu_topo):
return ET.tostring(xml)
-def get_cpu_xml(cpus, memory, cpu_topo=None):
+def get_cpu_xml(cpus, memory, cpu_topo={}):
# Returns the libvirt CPU element based on given numa and topology
# CPU element will always have numa element
# <cpu>
@@ -55,6 +55,6 @@ def get_cpu_xml(cpus, memory, cpu_topo=None):
# <topology sockets='1' cores='2' threads='1'/>
# </cpu>
xml = E.cpu(ET.fromstring(get_numa_xml(cpus, memory)))
- if cpu_topo is not None:
+ if cpu_topo:
xml.insert(0, ET.fromstring(get_topology_xml(cpu_topo)))
return ET.tostring(xml)
--
1.9.1
More information about the Kimchi-devel
mailing list