[PATCH 0/2] Add support to update guest maxMemory {backend}

V1: In order to test, you can run something like this command: curl -k -u <U>:<PW> -H "Content-Type: application/json" -H "Accept: application/json" "<HOST>/plugins/kimchi/vms/<VM>" -X PUT -d '{"maxmemory": 3072}' Rodrigo Trujillo (2): Implement support to change guest maxMemory tag Add and fixes tests for maxMemory tag src/wok/plugins/kimchi/API.json | 6 ++ src/wok/plugins/kimchi/i18n.py | 6 +- src/wok/plugins/kimchi/model/vms.py | 119 +++++++++++++++++------------- src/wok/plugins/kimchi/tests/test_rest.py | 23 ++++++ 4 files changed, 101 insertions(+), 53 deletions(-) -- 2.1.0

This patch implements the backend part of the support to max memory (<maxMemory> tag) update. Max memory is the amount of memory that a VM can reach when memory devices are live attached (memory hotplug). Libvirt still does not expose this tag, so, it must be updated when the guest is offline. Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- src/wok/plugins/kimchi/API.json | 6 ++ src/wok/plugins/kimchi/i18n.py | 6 +- src/wok/plugins/kimchi/model/vms.py | 119 ++++++++++++++++++++---------------- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/src/wok/plugins/kimchi/API.json b/src/wok/plugins/kimchi/API.json index e236e51..a017c43 100644 --- a/src/wok/plugins/kimchi/API.json +++ b/src/wok/plugins/kimchi/API.json @@ -305,6 +305,12 @@ "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 diff --git a/src/wok/plugins/kimchi/i18n.py b/src/wok/plugins/kimchi/i18n.py index c69d072..4991dec 100644 --- a/src/wok/plugins/kimchi/i18n.py +++ b/src/wok/plugins/kimchi/i18n.py @@ -97,7 +97,7 @@ messages = { "KCHVM0038E": _("Unable to suspend VM '%(name)s'. Details: %(err)s"), "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."), + "KCHVM0041E": _("Memory assigned is higher then the maximum allowed."), "KCHVM0042E": _("VM '%(name)s' does not support live memory update. Update the memory with the machine offline 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"), @@ -125,7 +125,9 @@ messages = { "KCHVM0068E": _("Unable to setup password-less login at remote host %(host)s using user %(user)s. Error: %(error)s"), "KCHVM0069E": _("Password field must be a string."), "KCHVM0070E": _("Error creating local host ssh rsa key of user 'root'."), - "KCHVM0071E": _("Memory value %(mem)s must be aligned to %(alignment)sMiB."), + "KCHVM0071E": _("Memory or Max Memory value %(mem)s must be aligned to %(alignment)sMiB."), + "KCHVM0072E": _("Cannot update maximum memory when guest is running."), + "KCHVM0073E": _("Maximum memory requested is higher than amount supported by host: %(memHost)sMiB."), "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/src/wok/plugins/kimchi/model/vms.py b/src/wok/plugins/kimchi/model/vms.py index 7e12b91..cfbc05a 100644 --- a/src/wok/plugins/kimchi/model/vms.py +++ b/src/wok/plugins/kimchi/model/vms.py @@ -704,14 +704,19 @@ class VMModel(object): params = copy.deepcopy(params) 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) params['name'], nonascii_name = get_ascii_nonascii_name(name) + # You can only change <maxMemory> offline, updating guest XML + if 'maxmemory' in params and state != 'shutoff': + raise InvalidParameter("KCHVM0072E") + for key, val in params.items(): if key in VM_STATIC_UPDATE_PARAMS: if type(val) == int: @@ -721,6 +726,8 @@ class VMModel(object): # Updating memory and NUMA if necessary, if vm is offline if not dom.isActive(): + if self.caps.mem_hotplug_support: + new_xml = self._update_maxmemory_config(new_xml, params) if 'memory' in params: new_xml = self._update_memory_config(new_xml, params) elif 'cpus' in params and \ @@ -801,58 +808,68 @@ class VMModel(object): 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) - maxMem = root.find('.maxMemory') - host_mem = self.conn.get().getInfo()[1] - slots = (host_mem - params['memory']) >> 10 - # Libvirt does not accepts slots <= 1 - if slots < 0: - raise OperationFailed("KCHVM0041E") - elif slots == 0: - slots = 1 - - force_max_mem_update = False - distro, _, _ = platform.linux_distribution() - if distro == "IBM_PowerKVM": - # max memory 256MiB alignment - host_mem -= (host_mem % PPC_MEM_ALIGN) - # force max memory update if it exists but it's wrong. - if maxMem is not None and\ - int(maxMem.text) != (host_mem << 10): - force_max_mem_update = True - - # max 32 slots on Power - if slots > 32: - slots = 32 - - if maxMem is None: - max_mem_xml = E.maxMemory( - str(host_mem << 10), - unit='Kib', - slots=str(slots)) - root.insert(0, max_mem_xml) - new_xml = ET.tostring(root, encoding="utf-8") - else: - # Update slots only - new_xml = xml_item_update(ET.tostring(root, encoding="utf-8"), - './maxMemory', - str(slots), - attr='slots') - - if force_max_mem_update: - new_xml = xml_item_update(new_xml, - './maxMemory', - str(host_mem << 10)) - - return new_xml + if memory is not None: + memory.text = str(params['memory'] << 10) return ET.tostring(root, encoding="utf-8") + def _update_maxmemory_config(self, xml, params): + # Update maxMemory or slots if necessary + maxMemVal = params.get('maxmemory') + memory = params.get('memory') + if maxMemVal is None and memory is None: + return xml + root = ET.fromstring(xml) + maxMemTag = root.find('.maxMemory') + + distro, _, _ = platform.linux_distribution() + maxHostMem = self.conn.get().getInfo()[1] + # maxMem value was passed, or get it from xml, or use the total memory + # available in host + if maxMemVal is None: + if maxMemTag is not None: + maxMemVal = int(xpath_get_text(xml, './maxMemory')[0]) >> 10 + else: + maxMemVal = maxHostMem + # Align to 256Mib + if distro == "IBM_PowerKVM": + maxMemVal -= (maxMemVal % PPC_MEM_ALIGN) + # Check if the host supports the amount requested + elif maxMemVal > maxHostMem: + raise InvalidParameter('KCHVM0073E', {'memHost': str(maxHostMem)}) + + # Memory allocated value is passed or what is already configured + if memory is None: + memory = int(xpath_get_text(xml, XPATH_DOMAIN_MEMORY)[0]) >> 10 + + # Number of slots, cannot be <= 1 or 0 + slots = (maxMemVal - memory) >> 10 + if slots < 0: + raise OperationFailed("KCHVM0041E") + elif slots == 0: + slots = 1 + + if distro == "IBM_PowerKVM": + # max memory must be 256MiB alignment + if (maxMemVal % PPC_MEM_ALIGN) != 0: + raise InvalidParameter('KCHVM0071E', + {'mem': str(maxMemVal), + 'alignment': str(PPC_MEM_ALIGN)}) + # max 32 slots on Power + if slots > 32: + slots = 32 + + if maxMemTag is not None: + root.remove(maxMemTag) + + # Add max memory xml + max_mem_xml = E.maxMemory( + str(maxMemVal << 10), + unit='Kib', + slots=str(slots)) + root.insert(0, max_mem_xml) + new_xml = ET.tostring(root, encoding="utf-8") + return new_xml + def _live_vm_update(self, dom, params): self._vm_update_access_metadata(dom, params) if 'memory' in params and dom.isActive(): -- 2.1.0

This patch add tests to following cases: - maxMemory lesser than 512 - maxMemory not integer - maxMemory change - hotPlug more memory than supported Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- src/wok/plugins/kimchi/tests/test_rest.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/wok/plugins/kimchi/tests/test_rest.py b/src/wok/plugins/kimchi/tests/test_rest.py index 544f2e6..d4835bf 100644 --- a/src/wok/plugins/kimchi/tests/test_rest.py +++ b/src/wok/plugins/kimchi/tests/test_rest.py @@ -144,6 +144,19 @@ class RestTests(unittest.TestCase): vm = json.loads(self.request('/plugins/kimchi/vms/vm-1').read()) self.assertEquals('vm-1', vm['name']) + # Test max memory + req = json.dumps({'maxmemory': 23}) + resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + self.assertEquals(400, resp.status) + + req = json.dumps({'maxmemory': 'maxmem 80'}) + resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + self.assertEquals(400, resp.status) + + req = json.dumps({'maxmemory': 3072}) + resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + self.assertEquals(200, resp.status) + resp = self.request('/plugins/kimchi/vms/vm-1/start', '{}', 'POST') self.assertEquals(200, resp.status) @@ -216,11 +229,21 @@ class RestTests(unittest.TestCase): params = {'name': u'∨м-црdαtеd', 'cpus': 5, 'memory': 3072} req = json.dumps(params) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + # 1G hotpluged + 3G = 4G --> Higher than maxmemory = 3G + self.assertEquals(500, resp.status) + + params['memory'] = 2048 + req = json.dumps(params) + resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + # 1G hotpluged + 2G = 3G --> Equal maxmemory = 3G self.assertEquals(303, resp.status) vm = json.loads( self.request('/plugins/kimchi/vms/∨м-црdαtеd', req).read() ) for key in params.keys(): + if key == 'memory': + # 1G Hot Plugged + params[key] = params[key] + 1024 self.assertEquals(params[key], vm[key]) # change only VM users - groups are not changed (default is empty) -- 2.1.0

Hi Rodrigo, My suggestion is to have all the memory information in one single JSON object: {.., memory: {current:..., max:...}} That way the user can easily grab all the memory information. On 23/11/2015 21:18, Rodrigo Trujillo wrote:
V1: In order to test, you can run something like this command:
curl -k -u <U>:<PW> -H "Content-Type: application/json" -H "Accept: application/json" "<HOST>/plugins/kimchi/vms/<VM>" -X PUT -d '{"maxmemory": 3072}'
Rodrigo Trujillo (2): Implement support to change guest maxMemory tag Add and fixes tests for maxMemory tag
src/wok/plugins/kimchi/API.json | 6 ++ src/wok/plugins/kimchi/i18n.py | 6 +- src/wok/plugins/kimchi/model/vms.py | 119 +++++++++++++++++------------- src/wok/plugins/kimchi/tests/test_rest.py | 23 ++++++ 4 files changed, 101 insertions(+), 53 deletions(-)
participants (2)
-
Aline Manera
-
Rodrigo Trujillo