- 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(a)linux.vnet.ibm.com>
---
API.json | 7 ++++
docs/API.md | 17 +++++++-
i18n.py | 1 -
model/vms.py | 128 +++++++++++++++++++++++++++++++++++++---------------------
vmtemplate.py | 30 ++++----------
5 files changed, 110 insertions(+), 73 deletions(-)
diff --git a/API.json b/API.json
index 4876cc0..a3d99f8 100644
--- a/API.json
+++ b/API.json
@@ -299,12 +299,19 @@
}
}
},
+ "cpu_info": { "$ref":
"#/kimchitype/cpu_info" },
"cpus": {
"description": "The new number of virtual CPUs for the
VM",
"type": "integer",
"minimum": 1,
"error": "KCHTMPL0012E"
},
+ "maxvcpus": {
+ "description": "The maximum number of virtual CPUs
that can be assigned to the VM",
+ "type": "integer",
+ "minimum": 1,
+ "error": "KCHTMPL0012E"
+ },
"memory": {
"description": "The new amount (MB) of memory for the
VM",
"type": "integer",
diff --git a/docs/API.md b/docs/API.md
index 4790d08..8dbc27c 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -118,6 +118,7 @@ server.
* 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
+ * maxvcpus: The maximum number of CPUs that can be assigned to the VM
* 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 +139,13 @@ 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
+ * cpus: New number of CPUs for this VM (if VM is running, new value
will take effect in next reboot)
+ * maxvcpus: The maximum number of CPUs that can be assigned to the VM
* 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 +154,17 @@ server.
* passwdValidTo *(optional)*: lifetime for the console password. When
omitted the password will be valid just
for 30 seconds.
+ * cpu_info: CPU-specific information.
+ * topology: Specify sockets, threads, and cores to run the virtual CPU
+ threads on.
+ All three are required in order to specify cpu topology.
+ * sockets - The maximum number of sockets to use.
+ * cores - The number of cores per socket.
+ * threads - The number of threads per core.
+ If CPU topology is specified, *cpus* and *maxvcpus* also must be
+ specified. Make sure *maxvcpus* is equal to the product of sockets,
+ cores and threads, and *cpus* is a multiple of a product of cores
+ and threads.
* **POST**: *See Virtual Machine Actions*
diff --git a/i18n.py b/i18n.py
index bb6f3d1..59870e8 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..fa3bcbb 100644
--- a/model/vms.py
+++ b/model/vms.py
@@ -41,7 +41,7 @@ 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 xpath_get_text, xml_item_remove, xml_item_update
from wok.xmlutils.utils import dictize
from wok.plugins.kimchi import model
@@ -72,15 +72,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 = ['cpus', 'cpu_info', 'graphics',
'groups',
+ 'maxvcpus', '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 +90,10 @@ XPATH_DOMAIN_MEMORY_UNIT = '/domain/memory/@unit'
XPATH_DOMAIN_UUID = '/domain/uuid'
XPATH_DOMAIN_DEV_CPU_ID = '/domain/devices/spapr-cpu-socket/@id'
+XPATH_NAME = './name'
XPATH_NUMA_CELL = './cpu/numa/cell'
XPATH_TOPOLOGY = './cpu/topology'
+XPATH_VCPU = './vcpu'
# key: VM name; value: lock object
vm_locks = {}
@@ -285,17 +284,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 +739,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 +750,42 @@ class VMModel(object):
raise InvalidParameter("KCHVM0003E", msg_args)
params['name'], nonascii_name = get_ascii_nonascii_name(name)
+ new_xml = xml_item_update(new_xml, XPATH_NAME, nonascii_name, None)
+
+ # Update vcpus
+ cpus = params.get('cpus', None)
+ if cpus:
+ attrib = 'current'
+ new_xml = xml_item_update(new_xml, XPATH_VCPU, str(cpus), attrib)
+
+ # Update maxvcpus
+ maxvcpus = params.get('maxvcpus', None)
+ if maxvcpus:
+ new_xml = xml_item_update(new_xml, XPATH_VCPU, str(maxvcpus), None)
+
+ # Update topology
+ if 'cpu_info' in params:
+ topology = params.get('cpu_info', {}).get('topology', None)
+
+ if topology:
+ sockets = str(topology['sockets'])
+ cores = str(topology['cores'])
+ threads = str(topology['threads'])
+
+ 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 may have been removed
+ new_xml = xml_item_remove(new_xml, xpath)
- 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)
+ # Revalidate cpu info - may raise CPUInfo exceptions
+ self._validate_cpu_info(new_xml, dom)
# 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)
@@ -928,6 +923,27 @@ class VMModel(object):
return ET.tostring(root, encoding="utf-8")
+ def _validate_cpu_info(self, new_xml, dom):
+ topology = None
+ 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),
+ }
+
+ params = {
+ 'maxvcpus': int(xpath_get_text(new_xml, 'vcpu')[0]),
+ 'cpus': int(xpath_get_text(new_xml, './vcpu/@current')[0]),
+ 'cpu_info': {'topology': topology}
+ }
+
+ cpu_model = CPUInfoModel(conn=self.conn)
+ cpu_model.check_cpu_info(params)
+
def _live_vm_update(self, dom, params):
self._vm_update_access_metadata(dom, params)
if 'memory' in params and dom.isActive():
@@ -1140,8 +1156,24 @@ 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 = None
+ 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:
@@ -1158,6 +1190,8 @@ class VMModel(object):
'uuid': dom.UUIDString(),
'memory': memory,
'cpus': info[3],
+ 'cpu_info': cpu_info,
+ 'maxvcpus': maxvcpus,
'screenshot': screenshot,
'icon': icon,
# (type, listen, port, passwd, passwdValidTo)
diff --git a/vmtemplate.py b/vmtemplate.py
index 2f524a7..e1571b0 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', None)
+
+ 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,22 +397,11 @@ 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
+ params['vcpus'] = "<vcpu
current='%d'>%d</vcpu>" % \
+ (params['cpus'], params['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)
+ # cpu_info element
params['cpu_info'] = self._get_cpu_xml()
xml = """
--
1.9.1