This patch changes the backend in order to allow user to edit max memory
tag. It changes the memory API to return and receive:
'memory': {'current': XXX, 'maxmemory': YYY}.
Other changes include:
- maxMemory xml tag is not created by default anymore;
- maxMemory xml tag is only created if user passes maxmemory;
- if memory and maxmemory are equal, than maxMemory tag is removed;
- keeps the limit to max memory and memory to 1TiB;
- adds tests to json and modify documentation;
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
API.json | 7 +-
docs/API.md | 11 ++-
i18n.py | 3 +-
model/vms.py | 246 ++++++++++++++++++++++++++++++++---------------------------
4 files changed, 146 insertions(+), 121 deletions(-)
diff --git a/API.json b/API.json
index f9a4ced..3c56c93 100644
--- a/API.json
+++ b/API.json
@@ -329,12 +329,7 @@
}
},
"cpu_info": { "$ref":
"#/kimchitype/cpu_info" },
- "memory": {
- "description": "The new amount (MB) of memory for the
VM",
- "type": "integer",
- "minimum": 512,
- "error": "KCHTMPL0013E"
- }
+ "memory": { "$ref": "#/kimchitype/memory"
}
},
"additionalProperties": false
},
diff --git a/docs/API.md b/docs/API.md
index 33d51ac..7cb7952 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -116,7 +116,10 @@ server.
writes across all virtual disks (kb/s).
* 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)
+ * memory: The memory parameters of the VM in the unit of MiB.
+ * current: The amount of memory that is assigned to the VM.
+ * maxmemory: The maximum total of memory that the VM can have. Amount
+ over current will be used exclusively for memory hotplug
* 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
@@ -148,8 +151,10 @@ server.
* name: New name for this VM (only applied for shutoff VM)
* users: New list of system users.
* groups: New list of system groups.
- * memory: New amount of memory (MB) for this VM (if VM is running, new
- value will take effect in next reboot)
+ * memory: New memory parameters of the VM in the unit of MiB.
+ Provide one or both.
+ * current: New amount of memory that will be assigned to the VM.
+ * maxmemory: New maximum total of memory that the VM can have.
* graphics: A dict to show detail of VM graphics.
* passwd *(optional)*: console password. When omitted a random password
willbe generated.
diff --git a/i18n.py b/i18n.py
index 36d4310..33b9c9d 100644
--- a/i18n.py
+++ b/i18n.py
@@ -100,7 +100,7 @@ messages = {
"KCHVM0039E": _("Cannot resume VM '%(name)s' because it is not
paused."),
"KCHVM0040E": _("Unable to resume VM '%(name)s'. Details:
%(err)s"),
"KCHVM0041E": _("Memory assigned is higher then the maximum allowed in
the host: %(maxmem)sMib."),
- "KCHVM0042E": _("VM '%(name)s' does not support live memory
update. Update the memory with the machine offline to enable this feature."),
+ "KCHVM0042E": _("Guest '%(name)s' does not support live memory
update. Please, with the guest offline, set Maximum Memory with a value greater then
Memory to enable this feature."),
"KCHVM0043E": _("Only increase memory is allowed in active
VMs"),
"KCHVM0044E": _("For live memory update, new memory value must be
equal old memory value plus multiples of 1024 Mib"),
"KCHVM0045E": _("There are not enough free slots of 1024 Mib in the
guest."),
@@ -134,6 +134,7 @@ messages = {
"KCHVM0077E": _("Impossible to get the serial console of
%(name)s"),
"KCHVM0078E": _("Memory or Maximum Memory value is higher than amount
supported by the host: %(memHost)sMiB."),
"KCHVM0079E": _("Memory or Maximum Memory value is higher than maximum
amount recommended: 1TiB"),
+ "KCHVM0080E": _("Cannot update Maximum Memory when guest is
running."),
"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 5fb27d3..5702f80 100644
--- a/model/vms.py
+++ b/model/vms.py
@@ -52,8 +52,8 @@ from wok.plugins.kimchi.kvmusertests import UserTests
from wok.plugins.kimchi.model.config import CapabilitiesModel
from wok.plugins.kimchi.model.cpuinfo import CPUInfoModel
from wok.plugins.kimchi.model.featuretests import FeatureTests
-from wok.plugins.kimchi.model.templates import TemplateModel
-from wok.plugins.kimchi.model.templates import MAX_MEM_LIM, PPC_MEM_ALIGN
+from wok.plugins.kimchi.model.templates import PPC_MEM_ALIGN
+from wok.plugins.kimchi.model.templates import TemplateModel, validate_memory
from wok.plugins.kimchi.model.utils import get_ascii_nonascii_name, get_vm_name
from wok.plugins.kimchi.model.utils import get_metadata_node
from wok.plugins.kimchi.model.utils import remove_metadata_node
@@ -97,6 +97,7 @@ XPATH_NAME = './name'
XPATH_NUMA_CELL = './cpu/numa/cell'
XPATH_TOPOLOGY = './cpu/topology'
XPATH_VCPU = './vcpu'
+XPATH_MAX_MEMORY = './maxMemory'
# key: VM name; value: lock object
vm_locks = {}
@@ -251,16 +252,12 @@ class VMModel(object):
vm_locks[name] = lock
with lock:
- # make sure memory is alingned in 256MiB in PowerPC
- distro, _, _ = platform.linux_distribution()
- if 'memory' in params and distro == "IBM_PowerKVM":
- if params['memory'] % PPC_MEM_ALIGN != 0:
- raise InvalidParameter('KCHVM0071E',
- {'param': "Memory",
- 'mem':
str(params['memory']),
- 'alignment': str(PPC_MEM_ALIGN)})
-
dom = self.get_vm(name, self.conn)
+ # You can only change <maxMemory> offline, updating guest XML
+ if ("memory" in params) and ('maxmemory' in
params['memory']) and\
+ (DOM_STATE_MAP[dom.info()[0]] != 'shutoff'):
+ raise InvalidParameter("KCHVM0080E")
+
if DOM_STATE_MAP[dom.info()[0]] == 'shutoff':
ext_params = set(params.keys()) - set(VM_OFFLINE_UPDATE_PARAMS)
if len(ext_params) > 0:
@@ -725,14 +722,14 @@ class VMModel(object):
def _static_vm_update(self, vm_name, dom, params):
old_xml = new_xml = dom.XMLDesc(0)
-
params = copy.deepcopy(params)
# Update name
name = params.get('name')
nonascii_name = None
+ state = DOM_STATE_MAP[dom.info()[0]]
+
if name is not None:
- state = DOM_STATE_MAP[dom.info()[0]]
if state != 'shutoff':
msg_args = {'name': vm_name, 'new_name':
params['name']}
raise InvalidParameter("KCHVM0003E", msg_args)
@@ -771,7 +768,8 @@ class VMModel(object):
new_xml = xml_item_remove(new_xml, XPATH_TOPOLOGY)
# Updating memory and NUMA if necessary, if vm is offline
- if 'memory' in params and not dom.isActive():
+ if (not dom.isActive() and 'memory' in params and
+ params['memory'] != {}):
new_xml = self._update_memory_config(new_xml, params)
if 'graphics' in params:
@@ -811,103 +809,113 @@ class VMModel(object):
return (nonascii_name if nonascii_name is not None else vm_name, dom)
def _update_memory_config(self, xml, params):
- # Checks if NUMA memory is already configured, if not, checks if CPU
- # element is already configured (topology). Then add NUMA element as
- # apropriated
root = ET.fromstring(xml)
- numa_mem = xpath_get_text(xml, XPATH_NUMA_CELL + '/@memory')
- maxvcpus = params.get('cpu_info', {}).get('maxvcpus')
- if numa_mem == []:
- 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)
- root.insert(0, ET.fromstring(cpu))
- else:
- numa_element = get_numa_xml(0, params['memory'] << 10)
- cpu.insert(0, ET.fromstring(numa_element))
+ # MiB to KiB
+ hasMem = 'current' in params['memory']
+ hasMaxMem = 'maxmemory' in params['memory']
+ oldMem = int(xpath_get_text(xml, XPATH_DOMAIN_MEMORY)[0]) >> 10
+ maxMemTag = root.find(XPATH_MAX_MEMORY)
+ if maxMemTag is not None:
+ oldMaxMem = int(xpath_get_text(xml, XPATH_MAX_MEMORY)[0]) >> 10
else:
- if maxvcpus is not None:
- xml = xml_item_update(
- xml,
- XPATH_NUMA_CELL,
- value='0',
- attr='cpus')
- root = ET.fromstring(xml_item_update(xml, XPATH_NUMA_CELL,
- str(params['memory'] <<
10),
- attr='memory'))
-
- # Remove currentMemory, automatically set later by libvirt
- currentMem = root.find('.currentMemory')
- if currentMem is not None:
- root.remove(currentMem)
-
- memory = root.find('.memory')
- # Update/Adds maxMemory accordingly
- if not self.caps.mem_hotplug_support:
- if memory is not None:
- memory.text = str(params['memory'] << 10)
+ oldMaxMem = oldMem
+ newMem = (params['memory'].get('current', oldMem)) << 10
+ newMaxMem = (params['memory'].get('maxmemory', oldMaxMem))
<< 10
+
+ validate_memory({'current': newMem >> 10,
+ 'maxmemory': newMaxMem >> 10})
+
+ def _get_slots(mem, maxMem):
+ slots = (maxMem - mem) >> 10 >> 10
+ # Libvirt does not accepts slots <= 1
+ if slots < 0:
+ raise InvalidParameter("KCHTMPL0031E",
+ {'mem': str(mem >> 10),
+ 'maxmem': str(maxMem >> 10)})
+ elif slots == 0:
+ slots = 1
+
+ # max 32 slots on Power
+ distro, _, _ = platform.linux_distribution()
+ if distro == "IBM_PowerKVM" and slots > 32:
+ slots = 32
+ return slots
+ # End of _get_slots
+
+ # There is an issue in Libvirt/Qemu, where Guest does not start if
+ # memory and max memory are the same. So we decided to remove max
+ # memory and only add it if user explicitly provides it, willing to
+ # do memory hotplug
+ if hasMaxMem:
+ # Conditions:
+ if (maxMemTag is None) and (newMem != newMaxMem):
+ # Creates the maxMemory tag
+ max_mem_xml = E.maxMemory(
+ str(newMaxMem),
+ unit='Kib',
+ slots=str(_get_slots(newMem, newMaxMem)))
+ root.insert(0, max_mem_xml)
+ elif (maxMemTag is None) and (newMem == newMaxMem):
+ # Nothing to do
+ pass
+ elif (maxMemTag is not None) and (newMem != newMaxMem):
+ # Just update value in max memory tag
+ maxMemTag.text = str(newMaxMem)
+ maxMemTag.set('slots', str(_get_slots(newMem, newMaxMem)))
+ elif (maxMemTag is not None) and (newMem == newMaxMem):
+ # Remove the tag
+ root.remove(maxMemTag)
+
+ # Update memory, if necessary
+ if hasMem:
+ # Remove currentMemory, automatically set later by libvirt, with
+ # memory value
+ currentMem = root.find('.currentMemory')
+ if currentMem is not None:
+ root.remove(currentMem)
+
+ memory = root.find('.memory')
+ # If host/guest does not support memory hot plug, then there is
+ # NUMA configure, we must update the tag directly
+ if not self.caps.mem_hotplug_support:
+ if memory is not None:
+ memory.text = str(newMem)
+ else:
+ if memory is not None:
+ # Libvirt is going to set the value automatically with
+ # the value configured in NUMA tag
+ root.remove(memory)
+
+ if (maxMemTag is not None) and (not hasMaxMem):
+ if newMem == newMaxMem:
+ root.remove(maxMemTag)
+ else:
+ maxMemTag.set('slots', str(_get_slots(newMem, newMaxMem)))
+
+ # Set NUMA parameterers and create it if does not exist
+ # CPU tag DO NOT exist? Create it
+ cpu = root.find(XPATH_CPU)
+ if cpu is None:
+ cpu = get_cpu_xml(0, newMem)
+ root.insert(0, ET.fromstring(cpu))
else:
- if memory is not None:
- root.remove(memory)
-
- def _get_slots(maxMem):
- slots = (maxMem - params['memory']) >> 10
- # Libvirt does not accepts slots <= 1
- if slots < 0:
- raise OperationFailed("KCHVM0041E",
- {'maxmem': str(maxMem)})
- elif slots == 0:
- slots = 1
-
- distro, _, _ = platform.linux_distribution()
- if distro == "IBM_PowerKVM":
- # max 32 slots on Power
- if slots > 32:
- slots = 32
- return slots
- # End of _get_slots
-
- def _get_newMaxMem():
- # Setting max memory to 4x memory requested, host total memory,
- # or 1 TB. This should avoid problems with live migration
- newMaxMem = MAX_MEM_LIM
- hostMem = self.conn.get().getInfo()[1] << 10
- if hostMem < newMaxMem:
- newMaxMem = hostMem
- mem = params.get('memory', 0)
- if (mem != 0) and (((mem * 4) << 10) < newMaxMem):
- newMaxMem = (mem * 4) << 10
-
- distro, _, _ = platform.linux_distribution()
- if distro == "IBM_PowerKVM":
- # max memory 256MiB alignment
- newMaxMem -= (newMaxMem % 256)
- return newMaxMem
-
- maxMem = root.find('.maxMemory')
- if maxMem is not None:
- root.remove(maxMem)
-
- # Setting maxMemory
- newMaxMem = _get_newMaxMem()
- slots = _get_slots(newMaxMem >> 10)
- max_mem_xml = E.maxMemory(
- str(newMaxMem),
- unit='Kib',
- slots=str(slots))
- root.insert(0, max_mem_xml)
-
- # Setting memory hard limit to max_memory + 1GiB
- memtune = root.find('memtune')
- if memtune is not None:
- hl = memtune.find('hard_limit')
- if hl is not None:
- memtune.remove(hl)
- memtune.insert(0, E.hard_limit(str(newMaxMem + 1048576),
- unit='Kib'))
-
+ # Does NUMA tag exist ?
+ numaTag = cpu.find('./numa')
+ if numaTag is None:
+ numa_element = get_numa_xml(0, newMem)
+ cpu.insert(0, ET.fromstring(numa_element))
+ else:
+ cellTag = cpu.find('./numa/cell')
+ cellTag.set('memory', str(newMem))
+
+ # Setting memory hard limit to max_memory + 1GiB
+ memtune = root.find('memtune')
+ if memtune is not None:
+ hl = memtune.find('hard_limit')
+ if hl is not None:
+ memtune.remove(hl)
+ memtune.insert(0, E.hard_limit(str(newMaxMem + 1048576),
+ unit='Kib'))
return ET.tostring(root, encoding="utf-8")
def _update_cpu_info(self, new_xml, dom, new_info):
@@ -943,7 +951,8 @@ class VMModel(object):
def _live_vm_update(self, dom, params):
self._vm_update_access_metadata(dom, params)
- if 'memory' in params and dom.isActive():
+ if (('memory' in params) and ('current' in
params['memory']) and
+ dom.isActive()):
self._update_memory_live(dom, params)
def _update_memory_live(self, dom, params):
@@ -960,13 +969,21 @@ class VMModel(object):
raise OperationFailed('KCHVM0042E', {'name': dom.name()})
# Memory live update must be done in chunks of 1024 Mib or 1Gib
- new_mem = params['memory']
+ new_mem = params['memory']['current']
old_mem = int(xpath_get_text(xml, XPATH_DOMAIN_MEMORY)[0]) >> 10
if new_mem < old_mem:
raise OperationFailed('KCHVM0043E')
if (new_mem - old_mem) % 1024 != 0:
raise OperationFailed('KCHVM0044E')
+ # make sure memory is alingned in 256MiB in PowerPC
+ distro, _, _ = platform.linux_distribution()
+ if (distro == "IBM_PowerKVM" and new_mem % PPC_MEM_ALIGN != 0):
+ raise InvalidParameter('KCHVM0071E',
+ {'param': "Memory",
+ 'mem': str(new_mem),
+ 'alignment': str(PPC_MEM_ALIGN)})
+
# Check slot spaces:
total_slots = int(xpath_get_text(xml, './maxMemory/@slots')[0])
needed_slots = (new_mem - old_mem) >> 10
@@ -1190,11 +1207,18 @@ class VMModel(object):
proc.join(1)
self._serial_procs.remove(proc)
+ # Get max memory, or return "memory" if not set
+ maxmemory = xpath_get_text(xml, XPATH_MAX_MEMORY)
+ if len(maxmemory) > 0:
+ maxmemory = convert_data_size(maxmemory[0], 'KiB', 'MiB')
+ else:
+ maxmemory = memory
+
return {'name': name,
'state': state,
'stats': res,
'uuid': dom.UUIDString(),
- 'memory': memory,
+ 'memory': {'current': memory, 'maxmemory':
maxmemory},
'cpu_info': cpu_info,
'screenshot': screenshot,
'icon': icon,
--
2.1.0