On 02/16/2016 01:56 PM, Lucio Correia wrote:
On 16-02-2016 00:32, Rodrigo Trujillo wrote:
> 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 | 22 +++++-
> docs/API.md | 11 ++-
> model/vms.py | 235
> ++++++++++++++++++++++++++++++++++-------------------------
> 3 files changed, 161 insertions(+), 107 deletions(-)
>
> diff --git a/API.json b/API.json
> index fcde123..9f478e3 100644
> --- a/API.json
> +++ b/API.json
> @@ -310,10 +310,24 @@
> },
> "cpu_info": { "$ref":
"#/kimchitype/cpu_info" },
> "memory": {
> - "description": "The new amount (MB) of memory
> for the VM",
> - "type": "integer",
> - "minimum": 512,
> - "error": "KCHTMPL0013E"
> + "description": "New guest memory and maximum
> memory values",
> + "type": "object",
> + "properties": {
> + "current": {
> + "description": "The new amount (MB) of
> current memory for the VM",
> + "type": "integer",
> + "minimum": 512,
> + "error": "KCHTMPL0013E"
> + },
> + "maxmemory": {
> + "description": "Maximum amount (MB) of
> memory that a VM can reach when memory devices are live attached
> (memory hotplug)",
> + "type": "integer",
> + "minimum": 512,
> + "error": "KCHTMPL0013E"
> + }
> + },
> + "additionalProperties": false,
> + "error": "KCHTMPL0030E"
Same as patch 1. Use object.
ACK
> }
> },
> "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/model/vms.py b/model/vms.py
> index 400ef46..6b3578f 100644
> --- a/model/vms.py
> +++ b/model/vms.py
> @@ -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,10 +252,12 @@ class VMModel(object):
> vm_locks[name] = lock
>
> with lock:
> - # make sure memory is alingned in 256MiB in PowerPC
> + # make sure memory and Maxmemory are alingned in 256MiB
> in PowerPC
> distro, _, _ = platform.linux_distribution()
> if 'memory' in params and distro == "IBM_PowerKVM":
> - if params['memory'] % PPC_MEM_ALIGN != 0:
> + memory = params['memory'].get('current')
> + maxmem = params['memory'].get('maxmemory')
> + if memory % PPC_MEM_ALIGN != 0:
> raise InvalidParameter('KCHVM0071E',
> {'param': "Memory",
> 'mem': str(memory),
> @@ -736,8 +739,9 @@ class VMModel(object):
> # 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)
> @@ -775,8 +779,13 @@ class VMModel(object):
> # topology is being undefined: remove it
> new_xml = xml_item_remove(new_xml, XPATH_TOPOLOGY)
>
> + # You can only change <maxMemory> offline, updating guest XML
> + if ("memory" in params) and ('maxmemory' in
> params['memory']) and\
> + (state != 'shutoff'):
> + raise InvalidParameter("KCHVM0077E")
> +
> # 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:
> new_xml = self._update_memory_config(new_xml, params)
>
> if 'graphics' in params:
> @@ -816,103 +825,122 @@ 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))
> + # MiB to KiB
> + hostMem = self.conn.get().getInfo()[1] << 10
> + newMem = (params['memory'].get('current', 0)) << 10
> + newMaxMem = (params['memory'].get('maxmemory', 0)) <<
10
> +
> + if (not newMem):
> + newMem = int(xpath_get_text(xml, XPATH_DOMAIN_MEMORY)[0])
> +
> + # Check if the host supports max memory amount requested
> + if newMaxMem > MAX_MEM_LIM:
> + raise InvalidParameter('KCHVM0076E')
> + elif newMaxMem > hostMem:
> + raise InvalidParameter('KCHVM0075E',
> + {'memHost': str(hostMem)})
> +
> + def _get_slots(mem, maxMem):
> + slots = (maxMem - mem) >> 10 >> 10
> + # Libvirt does not accepts slots <= 1
> + if slots < 0:
> + raise OperationFailed("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
> + maxMemTag = root.find('.maxMemory')
> + if newMaxMem:
> + # Conditions:
> + if (maxMemTag is None) and (newMem != newMaxMem):
> + # Creates the maxMemory tag
> + slots = _get_slots(newMem, newMaxMem)
> + max_mem_xml = E.maxMemory(
> + str(newMaxMem),
> + unit='Kib',
> + slots=str(slots))
> + 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 'current' in params['memory']:
> + # 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:
> - numa_element = get_numa_xml(0, params['memory'] <<
10)
> - cpu.insert(0, ET.fromstring(numa_element))
> + 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:
> + if ((not newMaxMem) and (newMem ==
> int(maxMemTag.text))):
> + root.remove(maxMemTag)
> + elif ((not newMaxMem) and (newMem !=
> int(maxMemTag.text))):
> + maxMemTag.set('slots',
> + str(_get_slots(newMem,
> int(maxMemTag.text))))
> +
> + # Set NUMA parameterers and create it if does not exist
> + # CPU tag DO NOT exist? Create it
> + cpu = root.find('./cpu')
Use XPATH_CPU here
Here I am interested in the cpu tag only and not in the value/text of
cpu, then the ET object is better, because I reuse it below.
> + if cpu is None:
> + cpu = get_cpu_xml(0, newMem)
> + root.insert(0, ET.fromstring(cpu))
> 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)
> - 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)
> + memLimit = newMaxMem
> + if (not memLimit):
> + try:
> + memLimit = int(maxMemTag.text)
> + except:
> + memLimit = newMem
> + memtune.insert(0, E.hard_limit(str(memLimit + 1048576),
> + unit='Kib'))
> return ET.tostring(root, encoding="utf-8")
>
> def _update_cpu_info(self, new_xml, dom, new_info):
> @@ -965,7 +993,7 @@ 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')
> @@ -1195,11 +1223,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,
>