[Kimchi-devel] [PATCH][Kimchi 2/3] Add support to edit max memory in Guest
Rodrigo Trujillo
rodrigo.trujillo at linux.vnet.ibm.com
Wed Feb 17 12:46:43 UTC 2016
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 at 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,
>>
>
>
More information about the Kimchi-devel
mailing list