[PATCH][Kimchi 0/3] Support to edit max memory

This patchset implements the support in backend to allow user to change maxMemory xml tag. It also changes the memory API from 'memory: XXX' to 'memory': {'current': XXX, 'maxmemory': YYY} maxMemory xml tag is not created by default anymore, when a guest is created. Tag will be created only when the user changes maxmemory manually, and if the value is different (higher) of 'current' memory. So in order to performe memory hotplug, an user has to stop guest, modify maxmemory and then start the guest. Tests were fixed and new ones were included. Rodrigo Trujillo (3): Add support to edit max memory in Templates Add support to edit max memory in Guest Changes and add tests to support new memory API (templates/guest) API.json | 66 +++++++++--- docs/API.md | 29 ++++-- i18n.py | 11 +- model/templates.py | 58 ++++++++++- model/vms.py | 245 ++++++++++++++++++++++++++------------------ osinfo.py | 16 ++- template.conf | 10 +- tests/test_livemigration.py | 6 +- tests/test_model.py | 22 ++-- tests/test_rest.py | 23 ++++- tests/test_template.py | 38 +++++-- tests/test_vmtemplate.py | 26 +++-- vmtemplate.py | 74 ++++++------- 13 files changed, 411 insertions(+), 213 deletions(-) -- 2.1.0

This patch changes the 'memory' parameter in API of Templates to: memory: {current: XXX, maxmemory: YYY} Other changes include: * enable maxmemory edition * remove max memory tests and limits * keeps max memory limit to 1TiB * changes templates.conf to suport max memory * set default memory and maxmemory to 1024 Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- API.json | 44 ++++++++++++++++++++++++++------ docs/API.md | 18 ++++++++++--- i18n.py | 11 ++++++-- model/templates.py | 58 +++++++++++++++++++++++++++++++++++++++--- model/vms.py | 10 ++++++-- osinfo.py | 16 ++++++++---- template.conf | 10 +++++--- vmtemplate.py | 74 ++++++++++++++++++++++-------------------------------- 8 files changed, 169 insertions(+), 72 deletions(-) diff --git a/API.json b/API.json index 294be64..fcde123 100644 --- a/API.json +++ b/API.json @@ -453,10 +453,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", @@ -630,10 +644,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", diff --git a/docs/API.md b/docs/API.md index a46e80e..33d51ac 100644 --- a/docs/API.md +++ b/docs/API.md @@ -292,8 +292,11 @@ Represents a snapshot of the Virtual Machine's primary monitor. * name: The name of the Template. Used to identify the Template in this API * os_distro *(optional)*: The operating system distribution * os_version *(optional)*: The version of the operating system distribution - * memory *(optional)*: The amount of memory assigned to the VM. - Default is 1024M. + * memory *(optional)*: The memory parameters of the template, specify one + or both. Default values are 1024MiB: + * current: The amount of memory that will be 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 * cdrom *(optional)*: A volume name or URI to an ISO image. * networks *(optional)*: list of networks will be assigned to the new VM. Default is '[default]' @@ -396,7 +399,11 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM in the unit of MB + * memory: The memory parameters of the template, that will be assigned to + the VM in the unit of MiB. + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * storagepool: URI of the storagepool where template allocates vm storage. * networks *(optional)*: list of networks will be assigned to the new VM. @@ -439,7 +446,10 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM + * memory: The memory parameters of the template, specify one or both of: + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * networks *(optional)*: list of networks will be assigned to the new VM. * disks: An array of requested disks with the following optional fields diff --git a/i18n.py b/i18n.py index 59e75f7..5395879 100644 --- a/i18n.py +++ b/i18n.py @@ -127,9 +127,14 @@ 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": _("%(param)s value (%(mem)sMiB) 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": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0076E": _("Maximum memory requested is higher than maximum amount recommended: 1TiB"), + "KCHVM0077E": _("Cannot update maximum memory when guest is running."), + "KCHVM0078E": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0079E": _("Memory requested is higher than maximum amount recommended: 1TiB"), "KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"), "KCHVM0077E": _("Impossible to get the serial console of %(name)s"), @@ -165,7 +170,7 @@ messages = { "KCHTMPL0010E": _("Template distribution must be a string"), "KCHTMPL0011E": _("Template distribution version must be a string"), "KCHTMPL0012E": _("The number of CPUs must be an integer greater than 0"), - "KCHTMPL0013E": _("Amount of memory (MB) must be an integer greater than 512"), + "KCHTMPL0013E": _("Amount of memory and maximum memory (MB) must be an integer greater than 512"), "KCHTMPL0014E": _("Template CDROM must be a local or remote ISO file"), "KCHTMPL0015E": _("Invalid storage pool URI %(value)s specified for template"), "KCHTMPL0016E": _("Specify an ISO image as CDROM or a base image to create a template"), @@ -181,6 +186,8 @@ messages = { "KCHTMPL0027E": _("Invalid disk image format. Valid formats: bochs, cloop, cow, dmg, qcow, qcow2, qed, raw, vmdk, vpc."), "KCHTMPL0028E": _("When setting template disks, following parameters are required: 'index', 'pool name', 'format', 'size' or 'volume' (for scsi/iscsi pools)"), "KCHTMPL0029E": _("Disk format must be 'raw', for logical, iscsi, and scsi pools."), + "KCHTMPL0030E": _("Memory expects an object with one or both parameters: 'current' and 'maxmemory'"), + "KCHTMPL0031E": _("Memory value (%(mem)sMiB) must be equal or lesser than maximum memory value (%(maxmem)sMiB)"), "KCHPOOL0001E": _("Storage pool %(name)s already exists"), "KCHPOOL0002E": _("Storage pool %(name)s does not exist"), diff --git a/model/templates.py b/model/templates.py index 8a29e02..6138592 100644 --- a/model/templates.py +++ b/model/templates.py @@ -20,6 +20,8 @@ import copy import libvirt import os +import platform +import psutil import stat from wok.exception import InvalidOperation, InvalidParameter @@ -34,6 +36,12 @@ from wok.plugins.kimchi.utils import pool_name_from_uri from wok.plugins.kimchi.vmtemplate import VMTemplate +# In PowerPC, memories must be aligned to 256 MiB +PPC_MEM_ALIGN = 256 +# Max memory 1TB, in KiB +MAX_MEM_LIM = 1073741824 + + class TemplatesModel(object): def __init__(self, **kargs): self.objstore = kargs['objstore'] @@ -69,10 +77,8 @@ class TemplatesModel(object): # Validate cpu info t.cpuinfo_validate() - # Validate max memory - maxMem = (t._get_max_memory(t.info.get('memory')) >> 10) - if t.info.get('memory') > maxMem: - raise OperationFailed("KCHVM0041E", {'maxmem': str(maxMem)}) + # Validate memory + self._validate_memory(t.info.get('memory')) # Validate volumes for disk in t.info.get('disks'): @@ -115,6 +121,44 @@ class TemplatesModel(object): raise InvalidParameter("KCHTMPL0019E", {'pool': pool_name, 'volume': volume}) + def _validate_memory(self, memory): + current = memory.get('current') + maxmem = memory.get('maxmemory') + + # Memories must be lesser than 1TB and the Host memory limit + if maxmem > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0076E") + if current > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0079E") + + if hasattr(psutil, 'virtual_memory'): + host_memory = psutil.virtual_memory().total >> 10 >> 10 + else: + host_memory = psutil.TOTAL_PHYMEM >> 10 >> 10 + if maxmem > host_memory: + raise InvalidParameter("KCHVM0075E", {'memHost': host_memory}) + if current > host_memory: + raise InvalidParameter("KCHVM0078E", {'memHost': host_memory}) + + if current > maxmem: + raise InvalidParameter("KCHTMPL0031E", + {'mem': str(current), + 'maxmem': str(maxmem)}) + + # make sure memory and Maxmemory are alingned in 256MiB in PowerPC + distro, _, _ = platform.linux_distribution() + if distro == "IBM_PowerKVM": + if current % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Memory", + 'mem': str(current), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), + 'alignment': str(PPC_MEM_ALIGN)}) + class TemplateModel(object): def __init__(self, **kargs): @@ -180,6 +224,12 @@ class TemplateModel(object): cpu_info.update(new_cpu_info) params['cpu_info'] = cpu_info + # Fix memory values, because method update does not work recursively + new_mem = params.get('memory') + if new_mem: + params['memory'] = copy.copy(old_t.get('memory')) + params['memory'].update(new_mem) + new_t.update(params) for net_name in params.get(u'networks', []): diff --git a/model/vms.py b/model/vms.py index 23e0df9..400ef46 100644 --- a/model/vms.py +++ b/model/vms.py @@ -53,13 +53,13 @@ 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.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 from wok.plugins.kimchi.model.utils import set_metadata_node from wok.plugins.kimchi.screenshot import VMScreenshot from wok.plugins.kimchi.utils import template_name_from_uri -from wok.plugins.kimchi.vmtemplate import MAX_MEM_LIM, PPC_MEM_ALIGN from wok.plugins.kimchi.xmlutils.cpu import get_cpu_xml, get_numa_xml from wok.plugins.kimchi.xmlutils.cpu import get_topology_xml from wok.plugins.kimchi.xmlutils.disk import get_vm_disk_info, get_vm_disks @@ -256,7 +256,13 @@ class VMModel(object): if 'memory' in params and distro == "IBM_PowerKVM": if params['memory'] % PPC_MEM_ALIGN != 0: raise InvalidParameter('KCHVM0071E', - {'mem': str(params['memory']), + {'param': "Memory", + 'mem': str(memory), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), 'alignment': str(PPC_MEM_ALIGN)}) dom = self.get_vm(name, self.conn) diff --git a/osinfo.py b/osinfo.py index 2ec5c3e..8102548 100644 --- a/osinfo.py +++ b/osinfo.py @@ -110,11 +110,13 @@ def _get_tmpl_defaults(): ConfigObj returns a dict like below when no changes were made in the template configuration file (template.conf) - {'main': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} + {'main': {}, 'memory': {}, 'storage': {'disk.0': {}}, 'processor': {}, + 'graphics': {}} The default values should be like below: - {'main': {'networks': ['default'], 'memory': '1024'}, + {'main': {'networks': ['default']}, + 'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, @@ -123,7 +125,8 @@ def _get_tmpl_defaults(): # Create dict with default values tmpl_defaults = defaultdict(dict) tmpl_defaults['main']['networks'] = ['default'] - tmpl_defaults['main']['memory'] = _get_default_template_mem() + tmpl_defaults['memory'] = {'current': _get_default_template_mem(), + 'maxmemory': _get_default_template_mem()} tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2', 'pool': 'default'} tmpl_defaults['processor']['vcpus'] = 1 @@ -145,8 +148,11 @@ def _get_tmpl_defaults(): 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'} # Parse main section to get networks and memory values - main_section = default_config.pop('main') - defaults.update(main_section) + defaults.update(default_config.pop('main')) + defaults['memory'] = default_config.pop('memory') + + defaults['memory']['current'] = int(defaults['memory']['current']) + defaults['memory']['maxmemory'] = int(defaults['memory']['maxmemory']) # Parse storage section to get disks values storage_section = default_config.pop('storage') diff --git a/template.conf b/template.conf index 3839be4..c4598f1 100644 --- a/template.conf +++ b/template.conf @@ -3,13 +3,17 @@ # [main] -# Memory in MB -#memory = 1024 - # List of networks separated by comma # Represents the virtual network interfaces to be assigned to guest #networks = default, +[memory] +# Memory in MB +# current = 1024 + +# Maximum value of memory to be assigned to guest in MB +# maxmemory = 1024 + [storage] # Specify multiple [[disk.X]] sub-sections to add multiples disks to guest diff --git a/vmtemplate.py b/vmtemplate.py index ef17ff6..ab6d580 100644 --- a/vmtemplate.py +++ b/vmtemplate.py @@ -19,7 +19,6 @@ import os import platform -import psutil import stat import time import urlparse @@ -42,12 +41,6 @@ from wok.plugins.kimchi.xmlutils.qemucmdline import get_qemucmdline_xml from wok.plugins.kimchi.xmlutils.serial import get_serial_xml -# In PowerPC, memories must be aligned to 256 MiB -PPC_MEM_ALIGN = 256 -# Max memory 1TB, in KiB -MAX_MEM_LIM = 1073741824 - - class VMTemplate(object): def __init__(self, args, scan=False): """ @@ -84,6 +77,14 @@ class VMTemplate(object): args['graphics'] = graphics default_disk = self.info['disks'][0] + + # Complete memory args, because dict method update is not recursive + if 'memory' in args: + if 'current' not in args['memory']: + args['memory']['current'] = self.info['memory']['current'] + if 'maxmemory' not in args['memory']: + args['memory']['maxmemory'] = self.info['memory']['maxmemory'] + # Override template values according to 'args' self.info.update(args) disks = self.info.get('disks') @@ -325,29 +326,8 @@ class VMTemplate(object): def _get_cpu_xml(self): # Include CPU topology, if provided cpu_topo = self.info.get('cpu_info', {}).get('topology', {}) - - 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: - # 1TB, (Template Memory * 4), Host Physical Memory. - max_memory = MAX_MEM_LIM - if hasattr(psutil, 'virtual_memory'): - host_memory = psutil.virtual_memory().total >> 10 - else: - host_memory = psutil.TOTAL_PHYMEM >> 10 - if host_memory < max_memory: - max_memory = host_memory - if (((guest_memory * 4) << 10) < max_memory): - max_memory = (guest_memory * 4) << 10 - - # set up arch to ppc64 instead of ppc64le due to libvirt compatibility - if self.info["arch"] == "ppc64": - # in Power, memory must be aligned in 256MiB - if (max_memory >> 10) % PPC_MEM_ALIGN != 0: - alignment = max_memory % (PPC_MEM_ALIGN << 10) - max_memory -= alignment - return max_memory + return get_cpu_xml(0, (self.info.get('memory').get('current')) << 10, + cpu_topo) def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) @@ -375,27 +355,33 @@ class VMTemplate(object): else: params['cdroms'] = cdrom_xml - # max memory - params['max_memory'] = self._get_max_memory(params['memory']) - # Setting maximum number of slots to avoid errors when hotplug memory # Number of slots are the numbers of chunks of 1GB that fit inside # the max_memory of the host minus memory assigned to the VM. It # cannot have more than 32 slots in Power. - params['slots'] = ((params['max_memory'] >> 10) - - params['memory']) >> 10 - if params['slots'] < 0: + memory = self.info['memory'].get('current') + maxmemory = self.info['memory'].get('maxmemory') + + slots = (maxmemory - memory) >> 10 + if slots < 0: raise OperationFailed("KCHVM0041E", - {'maxmem': str(params['max_memory'] >> 10)}) - elif params['slots'] == 0: - params['slots'] = 1 - elif params['slots'] > 32: + {'maxmem': str(maxmemory)}) + elif slots == 0: + slots = 1 + elif slots > 32: distro, _, _ = platform.linux_distribution() if distro == "IBM_PowerKVM": - params['slots'] = 32 + slots = 32 + + # Rearrange memory parameters + params['memory'] = self.info['memory'].get('current') + params['max_memory'] = "" + if memory != maxmemory: + maxmem_xml = "<maxMemory slots='%s' unit='MiB'>%s</maxMemory>" + params['max_memory'] = maxmem_xml % (slots, maxmemory) # set a hard limit using max_memory + 1GiB - params['hard_limit'] = params['max_memory'] + (1024 << 10) + params['hard_limit'] = maxmemory + 1024 # vcpu element cpus = params['cpu_info']['vcpus'] @@ -411,9 +397,9 @@ class VMTemplate(object): <name>%(name)s</name> <uuid>%(uuid)s</uuid> <memtune> - <hard_limit unit='KiB'>%(hard_limit)s</hard_limit> + <hard_limit unit='MiB'>%(hard_limit)s</hard_limit> </memtune> - <maxMemory slots='%(slots)s' unit='KiB'>%(max_memory)s</maxMemory> + %(max_memory)s <memory unit='MiB'>%(memory)s</memory> %(vcpus_xml)s %(cpu_info_xml)s -- 2.1.0

A couple comments below. On 16-02-2016 00:32, Rodrigo Trujillo wrote:
This patch changes the 'memory' parameter in API of Templates to: memory: {current: XXX, maxmemory: YYY} Other changes include: * enable maxmemory edition * remove max memory tests and limits * keeps max memory limit to 1TiB * changes templates.conf to suport max memory * set default memory and maxmemory to 1024
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- API.json | 44 ++++++++++++++++++++++++++------ docs/API.md | 18 ++++++++++--- i18n.py | 11 ++++++-- model/templates.py | 58 +++++++++++++++++++++++++++++++++++++++--- model/vms.py | 10 ++++++-- osinfo.py | 16 ++++++++---- template.conf | 10 +++++--- vmtemplate.py | 74 ++++++++++++++++++++++-------------------------------- 8 files changed, 169 insertions(+), 72 deletions(-)
diff --git a/API.json b/API.json index 294be64..fcde123 100644 --- a/API.json +++ b/API.json @@ -453,10 +453,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", @@ -630,10 +644,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, What about define a single memory object to avoid duplication here? See cpu_info definition for an example.
+ "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", diff --git a/docs/API.md b/docs/API.md index a46e80e..33d51ac 100644 --- a/docs/API.md +++ b/docs/API.md @@ -292,8 +292,11 @@ Represents a snapshot of the Virtual Machine's primary monitor. * name: The name of the Template. Used to identify the Template in this API * os_distro *(optional)*: The operating system distribution * os_version *(optional)*: The version of the operating system distribution - * memory *(optional)*: The amount of memory assigned to the VM. - Default is 1024M. + * memory *(optional)*: The memory parameters of the template, specify one + or both. Default values are 1024MiB: + * current: The amount of memory that will be 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 * cdrom *(optional)*: A volume name or URI to an ISO image. * networks *(optional)*: list of networks will be assigned to the new VM. Default is '[default]' @@ -396,7 +399,11 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM in the unit of MB + * memory: The memory parameters of the template, that will be assigned to + the VM in the unit of MiB. + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * storagepool: URI of the storagepool where template allocates vm storage. * networks *(optional)*: list of networks will be assigned to the new VM. @@ -439,7 +446,10 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM + * memory: The memory parameters of the template, specify one or both of: + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * networks *(optional)*: list of networks will be assigned to the new VM. * disks: An array of requested disks with the following optional fields diff --git a/i18n.py b/i18n.py index 59e75f7..5395879 100644 --- a/i18n.py +++ b/i18n.py @@ -127,9 +127,14 @@ 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": _("%(param)s value (%(mem)sMiB) 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": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0076E": _("Maximum memory requested is higher than maximum amount recommended: 1TiB"), + "KCHVM0077E": _("Cannot update maximum memory when guest is running."), + "KCHVM0078E": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0079E": _("Memory requested is higher than maximum amount recommended: 1TiB"),
"KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"), "KCHVM0077E": _("Impossible to get the serial console of %(name)s"), @@ -165,7 +170,7 @@ messages = { "KCHTMPL0010E": _("Template distribution must be a string"), "KCHTMPL0011E": _("Template distribution version must be a string"), "KCHTMPL0012E": _("The number of CPUs must be an integer greater than 0"), - "KCHTMPL0013E": _("Amount of memory (MB) must be an integer greater than 512"), + "KCHTMPL0013E": _("Amount of memory and maximum memory (MB) must be an integer greater than 512"), "KCHTMPL0014E": _("Template CDROM must be a local or remote ISO file"), "KCHTMPL0015E": _("Invalid storage pool URI %(value)s specified for template"), "KCHTMPL0016E": _("Specify an ISO image as CDROM or a base image to create a template"), @@ -181,6 +186,8 @@ messages = { "KCHTMPL0027E": _("Invalid disk image format. Valid formats: bochs, cloop, cow, dmg, qcow, qcow2, qed, raw, vmdk, vpc."), "KCHTMPL0028E": _("When setting template disks, following parameters are required: 'index', 'pool name', 'format', 'size' or 'volume' (for scsi/iscsi pools)"), "KCHTMPL0029E": _("Disk format must be 'raw', for logical, iscsi, and scsi pools."), + "KCHTMPL0030E": _("Memory expects an object with one or both parameters: 'current' and 'maxmemory'"), + "KCHTMPL0031E": _("Memory value (%(mem)sMiB) must be equal or lesser than maximum memory value (%(maxmem)sMiB)"),
"KCHPOOL0001E": _("Storage pool %(name)s already exists"), "KCHPOOL0002E": _("Storage pool %(name)s does not exist"), diff --git a/model/templates.py b/model/templates.py index 8a29e02..6138592 100644 --- a/model/templates.py +++ b/model/templates.py @@ -20,6 +20,8 @@ import copy import libvirt import os +import platform +import psutil import stat
from wok.exception import InvalidOperation, InvalidParameter @@ -34,6 +36,12 @@ from wok.plugins.kimchi.utils import pool_name_from_uri from wok.plugins.kimchi.vmtemplate import VMTemplate
+# In PowerPC, memories must be aligned to 256 MiB +PPC_MEM_ALIGN = 256 +# Max memory 1TB, in KiB +MAX_MEM_LIM = 1073741824 + + class TemplatesModel(object): def __init__(self, **kargs): self.objstore = kargs['objstore'] @@ -69,10 +77,8 @@ class TemplatesModel(object): # Validate cpu info t.cpuinfo_validate()
- # Validate max memory - maxMem = (t._get_max_memory(t.info.get('memory')) >> 10) - if t.info.get('memory') > maxMem: - raise OperationFailed("KCHVM0041E", {'maxmem': str(maxMem)}) + # Validate memory + self._validate_memory(t.info.get('memory'))
# Validate volumes for disk in t.info.get('disks'): @@ -115,6 +121,44 @@ class TemplatesModel(object): raise InvalidParameter("KCHTMPL0019E", {'pool': pool_name, 'volume': volume})
+ def _validate_memory(self, memory): + current = memory.get('current') + maxmem = memory.get('maxmemory') + + # Memories must be lesser than 1TB and the Host memory limit + if maxmem > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0076E") + if current > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0079E") + + if hasattr(psutil, 'virtual_memory'): + host_memory = psutil.virtual_memory().total >> 10 >> 10 + else: + host_memory = psutil.TOTAL_PHYMEM >> 10 >> 10 + if maxmem > host_memory: + raise InvalidParameter("KCHVM0075E", {'memHost': host_memory}) + if current > host_memory: + raise InvalidParameter("KCHVM0078E", {'memHost': host_memory}) + + if current > maxmem: + raise InvalidParameter("KCHTMPL0031E", + {'mem': str(current), + 'maxmem': str(maxmem)}) + + # make sure memory and Maxmemory are alingned in 256MiB in PowerPC + distro, _, _ = platform.linux_distribution() + if distro == "IBM_PowerKVM": + if current % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Memory", + 'mem': str(current), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), + 'alignment': str(PPC_MEM_ALIGN)}) +
class TemplateModel(object): def __init__(self, **kargs): @@ -180,6 +224,12 @@ class TemplateModel(object): cpu_info.update(new_cpu_info) params['cpu_info'] = cpu_info
+ # Fix memory values, because method update does not work recursively + new_mem = params.get('memory') + if new_mem: + params['memory'] = copy.copy(old_t.get('memory')) + params['memory'].update(new_mem) + new_t.update(params)
for net_name in params.get(u'networks', []): diff --git a/model/vms.py b/model/vms.py index 23e0df9..400ef46 100644 --- a/model/vms.py +++ b/model/vms.py @@ -53,13 +53,13 @@ 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.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 from wok.plugins.kimchi.model.utils import set_metadata_node from wok.plugins.kimchi.screenshot import VMScreenshot from wok.plugins.kimchi.utils import template_name_from_uri -from wok.plugins.kimchi.vmtemplate import MAX_MEM_LIM, PPC_MEM_ALIGN from wok.plugins.kimchi.xmlutils.cpu import get_cpu_xml, get_numa_xml from wok.plugins.kimchi.xmlutils.cpu import get_topology_xml from wok.plugins.kimchi.xmlutils.disk import get_vm_disk_info, get_vm_disks @@ -256,7 +256,13 @@ class VMModel(object): if 'memory' in params and distro == "IBM_PowerKVM": if params['memory'] % PPC_MEM_ALIGN != 0: raise InvalidParameter('KCHVM0071E', - {'mem': str(params['memory']), + {'param': "Memory", + 'mem': str(memory), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: local var maxmem is not defined
+ raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), 'alignment': str(PPC_MEM_ALIGN)})
dom = self.get_vm(name, self.conn) diff --git a/osinfo.py b/osinfo.py index 2ec5c3e..8102548 100644 --- a/osinfo.py +++ b/osinfo.py @@ -110,11 +110,13 @@ def _get_tmpl_defaults(): ConfigObj returns a dict like below when no changes were made in the template configuration file (template.conf)
- {'main': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} + {'main': {}, 'memory': {}, 'storage': {'disk.0': {}}, 'processor': {}, + 'graphics': {}}
The default values should be like below:
- {'main': {'networks': ['default'], 'memory': '1024'}, + {'main': {'networks': ['default']}, + 'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, @@ -123,7 +125,8 @@ def _get_tmpl_defaults(): # Create dict with default values tmpl_defaults = defaultdict(dict) tmpl_defaults['main']['networks'] = ['default'] - tmpl_defaults['main']['memory'] = _get_default_template_mem() + tmpl_defaults['memory'] = {'current': _get_default_template_mem(), + 'maxmemory': _get_default_template_mem()} tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2', 'pool': 'default'} tmpl_defaults['processor']['vcpus'] = 1 @@ -145,8 +148,11 @@ def _get_tmpl_defaults(): 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'}
# Parse main section to get networks and memory values - main_section = default_config.pop('main') - defaults.update(main_section) + defaults.update(default_config.pop('main')) + defaults['memory'] = default_config.pop('memory') + + defaults['memory']['current'] = int(defaults['memory']['current']) + defaults['memory']['maxmemory'] = int(defaults['memory']['maxmemory'])
# Parse storage section to get disks values storage_section = default_config.pop('storage') diff --git a/template.conf b/template.conf index 3839be4..c4598f1 100644 --- a/template.conf +++ b/template.conf @@ -3,13 +3,17 @@ #
[main] -# Memory in MB -#memory = 1024 - # List of networks separated by comma # Represents the virtual network interfaces to be assigned to guest #networks = default,
+[memory] +# Memory in MB +# current = 1024 + +# Maximum value of memory to be assigned to guest in MB +# maxmemory = 1024 + [storage]
# Specify multiple [[disk.X]] sub-sections to add multiples disks to guest diff --git a/vmtemplate.py b/vmtemplate.py index ef17ff6..ab6d580 100644 --- a/vmtemplate.py +++ b/vmtemplate.py @@ -19,7 +19,6 @@
import os import platform -import psutil import stat import time import urlparse @@ -42,12 +41,6 @@ from wok.plugins.kimchi.xmlutils.qemucmdline import get_qemucmdline_xml from wok.plugins.kimchi.xmlutils.serial import get_serial_xml
-# In PowerPC, memories must be aligned to 256 MiB -PPC_MEM_ALIGN = 256 -# Max memory 1TB, in KiB -MAX_MEM_LIM = 1073741824 - - class VMTemplate(object): def __init__(self, args, scan=False): """ @@ -84,6 +77,14 @@ class VMTemplate(object): args['graphics'] = graphics
default_disk = self.info['disks'][0] + + # Complete memory args, because dict method update is not recursive + if 'memory' in args: + if 'current' not in args['memory']: + args['memory']['current'] = self.info['memory']['current'] + if 'maxmemory' not in args['memory']: + args['memory']['maxmemory'] = self.info['memory']['maxmemory'] + # Override template values according to 'args' self.info.update(args) disks = self.info.get('disks') @@ -325,29 +326,8 @@ class VMTemplate(object): def _get_cpu_xml(self): # Include CPU topology, if provided cpu_topo = self.info.get('cpu_info', {}).get('topology', {}) - - 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: - # 1TB, (Template Memory * 4), Host Physical Memory. - max_memory = MAX_MEM_LIM - if hasattr(psutil, 'virtual_memory'): - host_memory = psutil.virtual_memory().total >> 10 - else: - host_memory = psutil.TOTAL_PHYMEM >> 10 - if host_memory < max_memory: - max_memory = host_memory - if (((guest_memory * 4) << 10) < max_memory): - max_memory = (guest_memory * 4) << 10 - - # set up arch to ppc64 instead of ppc64le due to libvirt compatibility - if self.info["arch"] == "ppc64": - # in Power, memory must be aligned in 256MiB - if (max_memory >> 10) % PPC_MEM_ALIGN != 0: - alignment = max_memory % (PPC_MEM_ALIGN << 10) - max_memory -= alignment - return max_memory + return get_cpu_xml(0, (self.info.get('memory').get('current')) << 10, + cpu_topo)
def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) @@ -375,27 +355,33 @@ class VMTemplate(object): else: params['cdroms'] = cdrom_xml
- # max memory - params['max_memory'] = self._get_max_memory(params['memory']) - # Setting maximum number of slots to avoid errors when hotplug memory # Number of slots are the numbers of chunks of 1GB that fit inside # the max_memory of the host minus memory assigned to the VM. It # cannot have more than 32 slots in Power. - params['slots'] = ((params['max_memory'] >> 10) - - params['memory']) >> 10 - if params['slots'] < 0: + memory = self.info['memory'].get('current') + maxmemory = self.info['memory'].get('maxmemory') + + slots = (maxmemory - memory) >> 10 + if slots < 0: raise OperationFailed("KCHVM0041E", - {'maxmem': str(params['max_memory'] >> 10)}) - elif params['slots'] == 0: - params['slots'] = 1 - elif params['slots'] > 32: + {'maxmem': str(maxmemory)}) + elif slots == 0: + slots = 1 + elif slots > 32: distro, _, _ = platform.linux_distribution() if distro == "IBM_PowerKVM": - params['slots'] = 32 + slots = 32 + + # Rearrange memory parameters + params['memory'] = self.info['memory'].get('current') + params['max_memory'] = "" + if memory != maxmemory: + maxmem_xml = "<maxMemory slots='%s' unit='MiB'>%s</maxMemory>" + params['max_memory'] = maxmem_xml % (slots, maxmemory)
# set a hard limit using max_memory + 1GiB - params['hard_limit'] = params['max_memory'] + (1024 << 10) + params['hard_limit'] = maxmemory + 1024
# vcpu element cpus = params['cpu_info']['vcpus'] @@ -411,9 +397,9 @@ class VMTemplate(object): <name>%(name)s</name> <uuid>%(uuid)s</uuid> <memtune> - <hard_limit unit='KiB'>%(hard_limit)s</hard_limit> + <hard_limit unit='MiB'>%(hard_limit)s</hard_limit> </memtune> - <maxMemory slots='%(slots)s' unit='KiB'>%(max_memory)s</maxMemory> + %(max_memory)s <memory unit='MiB'>%(memory)s</memory> %(vcpus_xml)s %(cpu_info_xml)s
-- Lucio Correia Software Engineer IBM LTC Brazil

See below On 02/16/2016 10:29 AM, Lucio Correia wrote:
A couple comments below.
On 16-02-2016 00:32, Rodrigo Trujillo wrote:
This patch changes the 'memory' parameter in API of Templates to: memory: {current: XXX, maxmemory: YYY} Other changes include: * enable maxmemory edition * remove max memory tests and limits * keeps max memory limit to 1TiB * changes templates.conf to suport max memory * set default memory and maxmemory to 1024
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- API.json | 44 ++++++++++++++++++++++++++------ docs/API.md | 18 ++++++++++--- i18n.py | 11 ++++++-- model/templates.py | 58 +++++++++++++++++++++++++++++++++++++++--- model/vms.py | 10 ++++++-- osinfo.py | 16 ++++++++---- template.conf | 10 +++++--- vmtemplate.py | 74 ++++++++++++++++++++++-------------------------------- 8 files changed, 169 insertions(+), 72 deletions(-)
diff --git a/API.json b/API.json index 294be64..fcde123 100644 --- a/API.json +++ b/API.json @@ -453,10 +453,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", @@ -630,10 +644,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, What about define a single memory object to avoid duplication here? See cpu_info definition for an example.
Yes, definitively an object is better here, and I can reuse. If I recall correctly, in the past we did not use objects because there were distros that had a older version of JSON libraries, which did not support latest JSON standard definition. Today I think we can use.
+ "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", diff --git a/docs/API.md b/docs/API.md index a46e80e..33d51ac 100644 --- a/docs/API.md +++ b/docs/API.md @@ -292,8 +292,11 @@ Represents a snapshot of the Virtual Machine's primary monitor. * name: The name of the Template. Used to identify the Template in this API * os_distro *(optional)*: The operating system distribution * os_version *(optional)*: The version of the operating system distribution - * memory *(optional)*: The amount of memory assigned to the VM. - Default is 1024M. + * memory *(optional)*: The memory parameters of the template, specify one + or both. Default values are 1024MiB: + * current: The amount of memory that will be 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 * cdrom *(optional)*: A volume name or URI to an ISO image. * networks *(optional)*: list of networks will be assigned to the new VM. Default is '[default]' @@ -396,7 +399,11 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM in the unit of MB + * memory: The memory parameters of the template, that will be assigned to + the VM in the unit of MiB. + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * storagepool: URI of the storagepool where template allocates vm storage. * networks *(optional)*: list of networks will be assigned to the new VM. @@ -439,7 +446,10 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM + * memory: The memory parameters of the template, specify one or both of: + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * networks *(optional)*: list of networks will be assigned to the new VM. * disks: An array of requested disks with the following optional fields diff --git a/i18n.py b/i18n.py index 59e75f7..5395879 100644 --- a/i18n.py +++ b/i18n.py @@ -127,9 +127,14 @@ 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": _("%(param)s value (%(mem)sMiB) 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": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0076E": _("Maximum memory requested is higher than maximum amount recommended: 1TiB"), + "KCHVM0077E": _("Cannot update maximum memory when guest is running."), + "KCHVM0078E": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0079E": _("Memory requested is higher than maximum amount recommended: 1TiB"),
"KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"), "KCHVM0077E": _("Impossible to get the serial console of %(name)s"), @@ -165,7 +170,7 @@ messages = { "KCHTMPL0010E": _("Template distribution must be a string"), "KCHTMPL0011E": _("Template distribution version must be a string"), "KCHTMPL0012E": _("The number of CPUs must be an integer greater than 0"), - "KCHTMPL0013E": _("Amount of memory (MB) must be an integer greater than 512"), + "KCHTMPL0013E": _("Amount of memory and maximum memory (MB) must be an integer greater than 512"), "KCHTMPL0014E": _("Template CDROM must be a local or remote ISO file"), "KCHTMPL0015E": _("Invalid storage pool URI %(value)s specified for template"), "KCHTMPL0016E": _("Specify an ISO image as CDROM or a base image to create a template"), @@ -181,6 +186,8 @@ messages = { "KCHTMPL0027E": _("Invalid disk image format. Valid formats: bochs, cloop, cow, dmg, qcow, qcow2, qed, raw, vmdk, vpc."), "KCHTMPL0028E": _("When setting template disks, following parameters are required: 'index', 'pool name', 'format', 'size' or 'volume' (for scsi/iscsi pools)"), "KCHTMPL0029E": _("Disk format must be 'raw', for logical, iscsi, and scsi pools."), + "KCHTMPL0030E": _("Memory expects an object with one or both parameters: 'current' and 'maxmemory'"), + "KCHTMPL0031E": _("Memory value (%(mem)sMiB) must be equal or lesser than maximum memory value (%(maxmem)sMiB)"),
"KCHPOOL0001E": _("Storage pool %(name)s already exists"), "KCHPOOL0002E": _("Storage pool %(name)s does not exist"), diff --git a/model/templates.py b/model/templates.py index 8a29e02..6138592 100644 --- a/model/templates.py +++ b/model/templates.py @@ -20,6 +20,8 @@ import copy import libvirt import os +import platform +import psutil import stat
from wok.exception import InvalidOperation, InvalidParameter @@ -34,6 +36,12 @@ from wok.plugins.kimchi.utils import pool_name_from_uri from wok.plugins.kimchi.vmtemplate import VMTemplate
+# In PowerPC, memories must be aligned to 256 MiB +PPC_MEM_ALIGN = 256 +# Max memory 1TB, in KiB +MAX_MEM_LIM = 1073741824 + + class TemplatesModel(object): def __init__(self, **kargs): self.objstore = kargs['objstore'] @@ -69,10 +77,8 @@ class TemplatesModel(object): # Validate cpu info t.cpuinfo_validate()
- # Validate max memory - maxMem = (t._get_max_memory(t.info.get('memory')) >> 10) - if t.info.get('memory') > maxMem: - raise OperationFailed("KCHVM0041E", {'maxmem': str(maxMem)}) + # Validate memory + self._validate_memory(t.info.get('memory'))
# Validate volumes for disk in t.info.get('disks'): @@ -115,6 +121,44 @@ class TemplatesModel(object): raise InvalidParameter("KCHTMPL0019E", {'pool': pool_name, 'volume': volume})
+ def _validate_memory(self, memory): + current = memory.get('current') + maxmem = memory.get('maxmemory') + + # Memories must be lesser than 1TB and the Host memory limit + if maxmem > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0076E") + if current > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0079E") + + if hasattr(psutil, 'virtual_memory'): + host_memory = psutil.virtual_memory().total >> 10 >> 10 + else: + host_memory = psutil.TOTAL_PHYMEM >> 10 >> 10 + if maxmem > host_memory: + raise InvalidParameter("KCHVM0075E", {'memHost': host_memory}) + if current > host_memory: + raise InvalidParameter("KCHVM0078E", {'memHost': host_memory}) + + if current > maxmem: + raise InvalidParameter("KCHTMPL0031E", + {'mem': str(current), + 'maxmem': str(maxmem)}) + + # make sure memory and Maxmemory are alingned in 256MiB in PowerPC + distro, _, _ = platform.linux_distribution() + if distro == "IBM_PowerKVM": + if current % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Memory", + 'mem': str(current), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), + 'alignment': str(PPC_MEM_ALIGN)}) +
class TemplateModel(object): def __init__(self, **kargs): @@ -180,6 +224,12 @@ class TemplateModel(object): cpu_info.update(new_cpu_info) params['cpu_info'] = cpu_info
+ # Fix memory values, because method update does not work recursively + new_mem = params.get('memory') + if new_mem: + params['memory'] = copy.copy(old_t.get('memory')) + params['memory'].update(new_mem) + new_t.update(params)
for net_name in params.get(u'networks', []): diff --git a/model/vms.py b/model/vms.py index 23e0df9..400ef46 100644 --- a/model/vms.py +++ b/model/vms.py @@ -53,13 +53,13 @@ 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.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 from wok.plugins.kimchi.model.utils import set_metadata_node from wok.plugins.kimchi.screenshot import VMScreenshot from wok.plugins.kimchi.utils import template_name_from_uri -from wok.plugins.kimchi.vmtemplate import MAX_MEM_LIM, PPC_MEM_ALIGN from wok.plugins.kimchi.xmlutils.cpu import get_cpu_xml, get_numa_xml from wok.plugins.kimchi.xmlutils.cpu import get_topology_xml from wok.plugins.kimchi.xmlutils.disk import get_vm_disk_info, get_vm_disks @@ -256,7 +256,13 @@ class VMModel(object): if 'memory' in params and distro == "IBM_PowerKVM": if params['memory'] % PPC_MEM_ALIGN != 0: raise InvalidParameter('KCHVM0071E', - {'mem': str(params['memory']), + {'param': "Memory", + 'mem': str(memory), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: local var maxmem is not defined
Humm, I think I mixed part of the patches. thanks
+ raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), 'alignment': str(PPC_MEM_ALIGN)})
dom = self.get_vm(name, self.conn) diff --git a/osinfo.py b/osinfo.py index 2ec5c3e..8102548 100644 --- a/osinfo.py +++ b/osinfo.py @@ -110,11 +110,13 @@ def _get_tmpl_defaults(): ConfigObj returns a dict like below when no changes were made in the template configuration file (template.conf)
- {'main': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} + {'main': {}, 'memory': {}, 'storage': {'disk.0': {}}, 'processor': {}, + 'graphics': {}}
The default values should be like below:
- {'main': {'networks': ['default'], 'memory': '1024'}, + {'main': {'networks': ['default']}, + 'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, @@ -123,7 +125,8 @@ def _get_tmpl_defaults(): # Create dict with default values tmpl_defaults = defaultdict(dict) tmpl_defaults['main']['networks'] = ['default'] - tmpl_defaults['main']['memory'] = _get_default_template_mem() + tmpl_defaults['memory'] = {'current': _get_default_template_mem(), + 'maxmemory': _get_default_template_mem()} tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2', 'pool': 'default'} tmpl_defaults['processor']['vcpus'] = 1 @@ -145,8 +148,11 @@ def _get_tmpl_defaults(): 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'}
# Parse main section to get networks and memory values - main_section = default_config.pop('main') - defaults.update(main_section) + defaults.update(default_config.pop('main')) + defaults['memory'] = default_config.pop('memory') + + defaults['memory']['current'] = int(defaults['memory']['current']) + defaults['memory']['maxmemory'] = int(defaults['memory']['maxmemory'])
# Parse storage section to get disks values storage_section = default_config.pop('storage') diff --git a/template.conf b/template.conf index 3839be4..c4598f1 100644 --- a/template.conf +++ b/template.conf @@ -3,13 +3,17 @@ #
[main] -# Memory in MB -#memory = 1024 - # List of networks separated by comma # Represents the virtual network interfaces to be assigned to guest #networks = default,
+[memory] +# Memory in MB +# current = 1024 + +# Maximum value of memory to be assigned to guest in MB +# maxmemory = 1024 + [storage]
# Specify multiple [[disk.X]] sub-sections to add multiples disks to guest diff --git a/vmtemplate.py b/vmtemplate.py index ef17ff6..ab6d580 100644 --- a/vmtemplate.py +++ b/vmtemplate.py @@ -19,7 +19,6 @@
import os import platform -import psutil import stat import time import urlparse @@ -42,12 +41,6 @@ from wok.plugins.kimchi.xmlutils.qemucmdline import get_qemucmdline_xml from wok.plugins.kimchi.xmlutils.serial import get_serial_xml
-# In PowerPC, memories must be aligned to 256 MiB -PPC_MEM_ALIGN = 256 -# Max memory 1TB, in KiB -MAX_MEM_LIM = 1073741824 - - class VMTemplate(object): def __init__(self, args, scan=False): """ @@ -84,6 +77,14 @@ class VMTemplate(object): args['graphics'] = graphics
default_disk = self.info['disks'][0] + + # Complete memory args, because dict method update is not recursive + if 'memory' in args: + if 'current' not in args['memory']: + args['memory']['current'] = self.info['memory']['current'] + if 'maxmemory' not in args['memory']: + args['memory']['maxmemory'] = self.info['memory']['maxmemory'] + # Override template values according to 'args' self.info.update(args) disks = self.info.get('disks') @@ -325,29 +326,8 @@ class VMTemplate(object): def _get_cpu_xml(self): # Include CPU topology, if provided cpu_topo = self.info.get('cpu_info', {}).get('topology', {}) - - 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: - # 1TB, (Template Memory * 4), Host Physical Memory. - max_memory = MAX_MEM_LIM - if hasattr(psutil, 'virtual_memory'): - host_memory = psutil.virtual_memory().total >> 10 - else: - host_memory = psutil.TOTAL_PHYMEM >> 10 - if host_memory < max_memory: - max_memory = host_memory - if (((guest_memory * 4) << 10) < max_memory): - max_memory = (guest_memory * 4) << 10 - - # set up arch to ppc64 instead of ppc64le due to libvirt compatibility - if self.info["arch"] == "ppc64": - # in Power, memory must be aligned in 256MiB - if (max_memory >> 10) % PPC_MEM_ALIGN != 0: - alignment = max_memory % (PPC_MEM_ALIGN << 10) - max_memory -= alignment - return max_memory + return get_cpu_xml(0, (self.info.get('memory').get('current')) << 10, + cpu_topo)
def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) @@ -375,27 +355,33 @@ class VMTemplate(object): else: params['cdroms'] = cdrom_xml
- # max memory - params['max_memory'] = self._get_max_memory(params['memory']) - # Setting maximum number of slots to avoid errors when hotplug memory # Number of slots are the numbers of chunks of 1GB that fit inside # the max_memory of the host minus memory assigned to the VM. It # cannot have more than 32 slots in Power. - params['slots'] = ((params['max_memory'] >> 10) - - params['memory']) >> 10 - if params['slots'] < 0: + memory = self.info['memory'].get('current') + maxmemory = self.info['memory'].get('maxmemory') + + slots = (maxmemory - memory) >> 10 + if slots < 0: raise OperationFailed("KCHVM0041E", - {'maxmem': str(params['max_memory'] >> 10)}) - elif params['slots'] == 0: - params['slots'] = 1 - elif params['slots'] > 32: + {'maxmem': str(maxmemory)}) + elif slots == 0: + slots = 1 definitively + elif slots > 32: distro, _, _ = platform.linux_distribution() if distro == "IBM_PowerKVM": - params['slots'] = 32 + slots = 32 + + # Rearrange memory parameters + params['memory'] = self.info['memory'].get('current') + params['max_memory'] = "" + if memory != maxmemory: + maxmem_xml = "<maxMemory slots='%s' unit='MiB'>%s</maxMemory>" + params['max_memory'] = maxmem_xml % (slots, maxmemory)
# set a hard limit using max_memory + 1GiB - params['hard_limit'] = params['max_memory'] + (1024 << 10) + params['hard_limit'] = maxmemory + 1024
# vcpu element cpus = params['cpu_info']['vcpus'] @@ -411,9 +397,9 @@ class VMTemplate(object): <name>%(name)s</name> <uuid>%(uuid)s</uuid> <memtune> - <hard_limit unit='KiB'>%(hard_limit)s</hard_limit> + <hard_limit unit='MiB'>%(hard_limit)s</hard_limit> </memtune> - <maxMemory slots='%(slots)s' unit='KiB'>%(max_memory)s</maxMemory> + %(max_memory)s <memory unit='MiB'>%(memory)s</memory> %(vcpus_xml)s %(cpu_info_xml)s

On 16-02-2016 00:32, Rodrigo Trujillo wrote:
@@ -127,9 +127,14 @@ 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": _("%(param)s value (%(mem)sMiB) 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": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0076E": _("Maximum memory requested is higher than maximum amount recommended: 1TiB"), + "KCHVM0077E": _("Cannot update maximum memory when guest is running."), + "KCHVM0078E": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0079E": _("Memory requested is higher than maximum amount recommended: 1TiB"),
"KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"), "KCHVM0077E": _("Impossible to get the serial console of %(name)s"),
Just noticed there are some ids duplicated here. -- Lucio Correia Software Engineer IBM LTC Brazil

ACK On 02/16/2016 10:36 AM, Lucio Correia wrote:
On 16-02-2016 00:32, Rodrigo Trujillo wrote:
@@ -127,9 +127,14 @@ 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": _("%(param)s value (%(mem)sMiB) 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": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0076E": _("Maximum memory requested is higher than maximum amount recommended: 1TiB"), + "KCHVM0077E": _("Cannot update maximum memory when guest is running."), + "KCHVM0078E": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0079E": _("Memory requested is higher than maximum amount recommended: 1TiB"),
"KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"), "KCHVM0077E": _("Impossible to get the serial console of %(name)s"),
Just noticed there are some ids duplicated here.

On 02/16/2016 12:32 AM, Rodrigo Trujillo wrote:
This patch changes the 'memory' parameter in API of Templates to: memory: {current: XXX, maxmemory: YYY} Other changes include: * enable maxmemory edition * remove max memory tests and limits * keeps max memory limit to 1TiB * changes templates.conf to suport max memory * set default memory and maxmemory to 1024
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- API.json | 44 ++++++++++++++++++++++++++------ docs/API.md | 18 ++++++++++--- i18n.py | 11 ++++++-- model/templates.py | 58 +++++++++++++++++++++++++++++++++++++++--- model/vms.py | 10 ++++++-- osinfo.py | 16 ++++++++---- template.conf | 10 +++++--- vmtemplate.py | 74 ++++++++++++++++++++++-------------------------------- 8 files changed, 169 insertions(+), 72 deletions(-)
diff --git a/API.json b/API.json index 294be64..fcde123 100644 --- a/API.json +++ b/API.json @@ -453,10 +453,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", @@ -630,10 +644,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", diff --git a/docs/API.md b/docs/API.md index a46e80e..33d51ac 100644 --- a/docs/API.md +++ b/docs/API.md @@ -292,8 +292,11 @@ Represents a snapshot of the Virtual Machine's primary monitor. * name: The name of the Template. Used to identify the Template in this API * os_distro *(optional)*: The operating system distribution * os_version *(optional)*: The version of the operating system distribution - * memory *(optional)*: The amount of memory assigned to the VM. - Default is 1024M. + * memory *(optional)*: The memory parameters of the template, specify one + or both. Default values are 1024MiB: + * current: The amount of memory that will be 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 * cdrom *(optional)*: A volume name or URI to an ISO image. * networks *(optional)*: list of networks will be assigned to the new VM. Default is '[default]' @@ -396,7 +399,11 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM in the unit of MB + * memory: The memory parameters of the template, that will be assigned to + the VM in the unit of MiB. + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * storagepool: URI of the storagepool where template allocates vm storage. * networks *(optional)*: list of networks will be assigned to the new VM. @@ -439,7 +446,10 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM + * memory: The memory parameters of the template, specify one or both of: + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * networks *(optional)*: list of networks will be assigned to the new VM. * disks: An array of requested disks with the following optional fields diff --git a/i18n.py b/i18n.py index 59e75f7..5395879 100644 --- a/i18n.py +++ b/i18n.py @@ -127,9 +127,14 @@ 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": _("%(param)s value (%(mem)sMiB) 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": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0076E": _("Maximum memory requested is higher than maximum amount recommended: 1TiB"), + "KCHVM0077E": _("Cannot update maximum memory when guest is running."),
KCHVM0076E and KCHVM0077E already exist and were not modified. See below
+ "KCHVM0078E": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0079E": _("Memory requested is higher than maximum amount recommended: 1TiB"),
"KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"), "KCHVM0077E": _("Impossible to get the serial console of %(name)s"),
They are here ^^^
@@ -165,7 +170,7 @@ messages = { "KCHTMPL0010E": _("Template distribution must be a string"), "KCHTMPL0011E": _("Template distribution version must be a string"), "KCHTMPL0012E": _("The number of CPUs must be an integer greater than 0"), - "KCHTMPL0013E": _("Amount of memory (MB) must be an integer greater than 512"), + "KCHTMPL0013E": _("Amount of memory and maximum memory (MB) must be an integer greater than 512"), "KCHTMPL0014E": _("Template CDROM must be a local or remote ISO file"), "KCHTMPL0015E": _("Invalid storage pool URI %(value)s specified for template"), "KCHTMPL0016E": _("Specify an ISO image as CDROM or a base image to create a template"), @@ -181,6 +186,8 @@ messages = { "KCHTMPL0027E": _("Invalid disk image format. Valid formats: bochs, cloop, cow, dmg, qcow, qcow2, qed, raw, vmdk, vpc."), "KCHTMPL0028E": _("When setting template disks, following parameters are required: 'index', 'pool name', 'format', 'size' or 'volume' (for scsi/iscsi pools)"), "KCHTMPL0029E": _("Disk format must be 'raw', for logical, iscsi, and scsi pools."), + "KCHTMPL0030E": _("Memory expects an object with one or both parameters: 'current' and 'maxmemory'"), + "KCHTMPL0031E": _("Memory value (%(mem)sMiB) must be equal or lesser than maximum memory value (%(maxmem)sMiB)"),
"KCHPOOL0001E": _("Storage pool %(name)s already exists"), "KCHPOOL0002E": _("Storage pool %(name)s does not exist"), diff --git a/model/templates.py b/model/templates.py index 8a29e02..6138592 100644 --- a/model/templates.py +++ b/model/templates.py @@ -20,6 +20,8 @@ import copy import libvirt import os +import platform +import psutil import stat
from wok.exception import InvalidOperation, InvalidParameter @@ -34,6 +36,12 @@ from wok.plugins.kimchi.utils import pool_name_from_uri from wok.plugins.kimchi.vmtemplate import VMTemplate
+# In PowerPC, memories must be aligned to 256 MiB +PPC_MEM_ALIGN = 256 +# Max memory 1TB, in KiB +MAX_MEM_LIM = 1073741824 + + class TemplatesModel(object): def __init__(self, **kargs): self.objstore = kargs['objstore'] @@ -69,10 +77,8 @@ class TemplatesModel(object): # Validate cpu info t.cpuinfo_validate()
- # Validate max memory - maxMem = (t._get_max_memory(t.info.get('memory')) >> 10) - if t.info.get('memory') > maxMem: - raise OperationFailed("KCHVM0041E", {'maxmem': str(maxMem)}) + # Validate memory + self._validate_memory(t.info.get('memory'))
# Validate volumes for disk in t.info.get('disks'): @@ -115,6 +121,44 @@ class TemplatesModel(object): raise InvalidParameter("KCHTMPL0019E", {'pool': pool_name, 'volume': volume})
+ def _validate_memory(self, memory): + current = memory.get('current') + maxmem = memory.get('maxmemory') + + # Memories must be lesser than 1TB and the Host memory limit + if maxmem > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0076E") + if current > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0079E") + + if hasattr(psutil, 'virtual_memory'): + host_memory = psutil.virtual_memory().total >> 10 >> 10 + else: + host_memory = psutil.TOTAL_PHYMEM >> 10 >> 10 + if maxmem > host_memory: + raise InvalidParameter("KCHVM0075E", {'memHost': host_memory}) + if current > host_memory: + raise InvalidParameter("KCHVM0078E", {'memHost': host_memory}) + + if current > maxmem: + raise InvalidParameter("KCHTMPL0031E", + {'mem': str(current), + 'maxmem': str(maxmem)}) + + # make sure memory and Maxmemory are alingned in 256MiB in PowerPC + distro, _, _ = platform.linux_distribution() + if distro == "IBM_PowerKVM": + if current % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Memory", + 'mem': str(current), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), + 'alignment': str(PPC_MEM_ALIGN)}) +
class TemplateModel(object): def __init__(self, **kargs): @@ -180,6 +224,12 @@ class TemplateModel(object): cpu_info.update(new_cpu_info) params['cpu_info'] = cpu_info
+ # Fix memory values, because method update does not work recursively + new_mem = params.get('memory') + if new_mem: + params['memory'] = copy.copy(old_t.get('memory')) + params['memory'].update(new_mem) + new_t.update(params)
for net_name in params.get(u'networks', []): diff --git a/model/vms.py b/model/vms.py index 23e0df9..400ef46 100644 --- a/model/vms.py +++ b/model/vms.py @@ -53,13 +53,13 @@ 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.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 from wok.plugins.kimchi.model.utils import set_metadata_node from wok.plugins.kimchi.screenshot import VMScreenshot from wok.plugins.kimchi.utils import template_name_from_uri -from wok.plugins.kimchi.vmtemplate import MAX_MEM_LIM, PPC_MEM_ALIGN from wok.plugins.kimchi.xmlutils.cpu import get_cpu_xml, get_numa_xml from wok.plugins.kimchi.xmlutils.cpu import get_topology_xml from wok.plugins.kimchi.xmlutils.disk import get_vm_disk_info, get_vm_disks @@ -256,7 +256,13 @@ class VMModel(object): if 'memory' in params and distro == "IBM_PowerKVM": if params['memory'] % PPC_MEM_ALIGN != 0: raise InvalidParameter('KCHVM0071E', - {'mem': str(params['memory']), + {'param': "Memory", + 'mem': str(memory), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), 'alignment': str(PPC_MEM_ALIGN)})
dom = self.get_vm(name, self.conn) diff --git a/osinfo.py b/osinfo.py index 2ec5c3e..8102548 100644 --- a/osinfo.py +++ b/osinfo.py @@ -110,11 +110,13 @@ def _get_tmpl_defaults(): ConfigObj returns a dict like below when no changes were made in the template configuration file (template.conf)
- {'main': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} + {'main': {}, 'memory': {}, 'storage': {'disk.0': {}}, 'processor': {}, + 'graphics': {}}
The default values should be like below:
- {'main': {'networks': ['default'], 'memory': '1024'}, + {'main': {'networks': ['default']}, + 'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, @@ -123,7 +125,8 @@ def _get_tmpl_defaults(): # Create dict with default values tmpl_defaults = defaultdict(dict) tmpl_defaults['main']['networks'] = ['default'] - tmpl_defaults['main']['memory'] = _get_default_template_mem() + tmpl_defaults['memory'] = {'current': _get_default_template_mem(), + 'maxmemory': _get_default_template_mem()} tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2', 'pool': 'default'} tmpl_defaults['processor']['vcpus'] = 1 @@ -145,8 +148,11 @@ def _get_tmpl_defaults(): 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'}
# Parse main section to get networks and memory values - main_section = default_config.pop('main') - defaults.update(main_section) + defaults.update(default_config.pop('main')) + defaults['memory'] = default_config.pop('memory') + + defaults['memory']['current'] = int(defaults['memory']['current']) + defaults['memory']['maxmemory'] = int(defaults['memory']['maxmemory'])
# Parse storage section to get disks values storage_section = default_config.pop('storage') diff --git a/template.conf b/template.conf index 3839be4..c4598f1 100644 --- a/template.conf +++ b/template.conf @@ -3,13 +3,17 @@ #
[main] -# Memory in MB -#memory = 1024 - # List of networks separated by comma # Represents the virtual network interfaces to be assigned to guest #networks = default,
+[memory] +# Memory in MB +# current = 1024 + +# Maximum value of memory to be assigned to guest in MB +# maxmemory = 1024 + [storage]
# Specify multiple [[disk.X]] sub-sections to add multiples disks to guest diff --git a/vmtemplate.py b/vmtemplate.py index ef17ff6..ab6d580 100644 --- a/vmtemplate.py +++ b/vmtemplate.py @@ -19,7 +19,6 @@
import os import platform -import psutil import stat import time import urlparse @@ -42,12 +41,6 @@ from wok.plugins.kimchi.xmlutils.qemucmdline import get_qemucmdline_xml from wok.plugins.kimchi.xmlutils.serial import get_serial_xml
-# In PowerPC, memories must be aligned to 256 MiB -PPC_MEM_ALIGN = 256 -# Max memory 1TB, in KiB -MAX_MEM_LIM = 1073741824 - - class VMTemplate(object): def __init__(self, args, scan=False): """ @@ -84,6 +77,14 @@ class VMTemplate(object): args['graphics'] = graphics
default_disk = self.info['disks'][0] + + # Complete memory args, because dict method update is not recursive + if 'memory' in args: + if 'current' not in args['memory']:
After apply your patch and execute a GET on templates, to list my current templates, I got the following error: File "/home/pvital/Projects/tmp/wok_test/src/wok/plugins/kimchi/vmtemplate.py", line 83, in __init__ if 'current' not in args['memory']: TypeError: argument of type 'int' is not iterable the line executed was: $ curl -k -u test -H "Content-Type: application/json" -H "Accept: application/json" 'https://localhost:8001/plugins/kimchi/templates' -X GET So, what happens if I already have templates in the old model (the current one today) after I apply your patches? I don't have the new structure of memory.
+ args['memory']['current'] = self.info['memory']['current'] + if 'maxmemory' not in args['memory']: + args['memory']['maxmemory'] = self.info['memory']['maxmemory'] + # Override template values according to 'args' self.info.update(args) disks = self.info.get('disks') @@ -325,29 +326,8 @@ class VMTemplate(object): def _get_cpu_xml(self): # Include CPU topology, if provided cpu_topo = self.info.get('cpu_info', {}).get('topology', {}) - - 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: - # 1TB, (Template Memory * 4), Host Physical Memory. - max_memory = MAX_MEM_LIM - if hasattr(psutil, 'virtual_memory'): - host_memory = psutil.virtual_memory().total >> 10 - else: - host_memory = psutil.TOTAL_PHYMEM >> 10 - if host_memory < max_memory: - max_memory = host_memory - if (((guest_memory * 4) << 10) < max_memory): - max_memory = (guest_memory * 4) << 10 - - # set up arch to ppc64 instead of ppc64le due to libvirt compatibility - if self.info["arch"] == "ppc64": - # in Power, memory must be aligned in 256MiB - if (max_memory >> 10) % PPC_MEM_ALIGN != 0: - alignment = max_memory % (PPC_MEM_ALIGN << 10) - max_memory -= alignment - return max_memory + return get_cpu_xml(0, (self.info.get('memory').get('current')) << 10, + cpu_topo)
def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) @@ -375,27 +355,33 @@ class VMTemplate(object): else: params['cdroms'] = cdrom_xml
- # max memory - params['max_memory'] = self._get_max_memory(params['memory']) - # Setting maximum number of slots to avoid errors when hotplug memory # Number of slots are the numbers of chunks of 1GB that fit inside # the max_memory of the host minus memory assigned to the VM. It # cannot have more than 32 slots in Power. - params['slots'] = ((params['max_memory'] >> 10) - - params['memory']) >> 10 - if params['slots'] < 0: + memory = self.info['memory'].get('current') + maxmemory = self.info['memory'].get('maxmemory') + + slots = (maxmemory - memory) >> 10 + if slots < 0: raise OperationFailed("KCHVM0041E", - {'maxmem': str(params['max_memory'] >> 10)}) - elif params['slots'] == 0: - params['slots'] = 1 - elif params['slots'] > 32: + {'maxmem': str(maxmemory)}) + elif slots == 0: + slots = 1 + elif slots > 32: distro, _, _ = platform.linux_distribution() if distro == "IBM_PowerKVM": - params['slots'] = 32 + slots = 32 + + # Rearrange memory parameters + params['memory'] = self.info['memory'].get('current') + params['max_memory'] = "" + if memory != maxmemory: + maxmem_xml = "<maxMemory slots='%s' unit='MiB'>%s</maxMemory>" + params['max_memory'] = maxmem_xml % (slots, maxmemory)
# set a hard limit using max_memory + 1GiB - params['hard_limit'] = params['max_memory'] + (1024 << 10) + params['hard_limit'] = maxmemory + 1024
# vcpu element cpus = params['cpu_info']['vcpus'] @@ -411,9 +397,9 @@ class VMTemplate(object): <name>%(name)s</name> <uuid>%(uuid)s</uuid> <memtune> - <hard_limit unit='KiB'>%(hard_limit)s</hard_limit> + <hard_limit unit='MiB'>%(hard_limit)s</hard_limit> </memtune> - <maxMemory slots='%(slots)s' unit='KiB'>%(max_memory)s</maxMemory> + %(max_memory)s <memory unit='MiB'>%(memory)s</memory> %(vcpus_xml)s %(cpu_info_xml)s

On 02/16/2016 10:43 AM, Paulo Ricardo Paz Vital wrote:
On 02/16/2016 12:32 AM, Rodrigo Trujillo wrote:
This patch changes the 'memory' parameter in API of Templates to: memory: {current: XXX, maxmemory: YYY} Other changes include: * enable maxmemory edition * remove max memory tests and limits * keeps max memory limit to 1TiB * changes templates.conf to suport max memory * set default memory and maxmemory to 1024
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- API.json | 44 ++++++++++++++++++++++++++------ docs/API.md | 18 ++++++++++--- i18n.py | 11 ++++++-- model/templates.py | 58 +++++++++++++++++++++++++++++++++++++++--- model/vms.py | 10 ++++++-- osinfo.py | 16 ++++++++---- template.conf | 10 +++++--- vmtemplate.py | 74 ++++++++++++++++++++++-------------------------------- 8 files changed, 169 insertions(+), 72 deletions(-)
diff --git a/API.json b/API.json index 294be64..fcde123 100644 --- a/API.json +++ b/API.json @@ -453,10 +453,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", @@ -630,10 +644,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", diff --git a/docs/API.md b/docs/API.md index a46e80e..33d51ac 100644 --- a/docs/API.md +++ b/docs/API.md @@ -292,8 +292,11 @@ Represents a snapshot of the Virtual Machine's primary monitor. * name: The name of the Template. Used to identify the Template in this API * os_distro *(optional)*: The operating system distribution * os_version *(optional)*: The version of the operating system distribution - * memory *(optional)*: The amount of memory assigned to the VM. - Default is 1024M. + * memory *(optional)*: The memory parameters of the template, specify one + or both. Default values are 1024MiB: + * current: The amount of memory that will be 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 * cdrom *(optional)*: A volume name or URI to an ISO image. * networks *(optional)*: list of networks will be assigned to the new VM. Default is '[default]' @@ -396,7 +399,11 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM in the unit of MB + * memory: The memory parameters of the template, that will be assigned to + the VM in the unit of MiB. + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * storagepool: URI of the storagepool where template allocates vm storage. * networks *(optional)*: list of networks will be assigned to the new VM. @@ -439,7 +446,10 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM + * memory: The memory parameters of the template, specify one or both of: + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * networks *(optional)*: list of networks will be assigned to the new VM. * disks: An array of requested disks with the following optional fields diff --git a/i18n.py b/i18n.py index 59e75f7..5395879 100644 --- a/i18n.py +++ b/i18n.py @@ -127,9 +127,14 @@ 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": _("%(param)s value (%(mem)sMiB) 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": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0076E": _("Maximum memory requested is higher than maximum amount recommended: 1TiB"), + "KCHVM0077E": _("Cannot update maximum memory when guest is running."), KCHVM0076E and KCHVM0077E already exist and were not modified. See below
+ "KCHVM0078E": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0079E": _("Memory requested is higher than maximum amount recommended: 1TiB"),
"KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"), "KCHVM0077E": _("Impossible to get the serial console of %(name)s"), They are here ^^^
ACK
@@ -165,7 +170,7 @@ messages = { "KCHTMPL0010E": _("Template distribution must be a string"), "KCHTMPL0011E": _("Template distribution version must be a string"), "KCHTMPL0012E": _("The number of CPUs must be an integer greater than 0"), - "KCHTMPL0013E": _("Amount of memory (MB) must be an integer greater than 512"), + "KCHTMPL0013E": _("Amount of memory and maximum memory (MB) must be an integer greater than 512"), "KCHTMPL0014E": _("Template CDROM must be a local or remote ISO file"), "KCHTMPL0015E": _("Invalid storage pool URI %(value)s specified for template"), "KCHTMPL0016E": _("Specify an ISO image as CDROM or a base image to create a template"), @@ -181,6 +186,8 @@ messages = { "KCHTMPL0027E": _("Invalid disk image format. Valid formats: bochs, cloop, cow, dmg, qcow, qcow2, qed, raw, vmdk, vpc."), "KCHTMPL0028E": _("When setting template disks, following parameters are required: 'index', 'pool name', 'format', 'size' or 'volume' (for scsi/iscsi pools)"), "KCHTMPL0029E": _("Disk format must be 'raw', for logical, iscsi, and scsi pools."), + "KCHTMPL0030E": _("Memory expects an object with one or both parameters: 'current' and 'maxmemory'"), + "KCHTMPL0031E": _("Memory value (%(mem)sMiB) must be equal or lesser than maximum memory value (%(maxmem)sMiB)"),
"KCHPOOL0001E": _("Storage pool %(name)s already exists"), "KCHPOOL0002E": _("Storage pool %(name)s does not exist"), diff --git a/model/templates.py b/model/templates.py index 8a29e02..6138592 100644 --- a/model/templates.py +++ b/model/templates.py @@ -20,6 +20,8 @@ import copy import libvirt import os +import platform +import psutil import stat
from wok.exception import InvalidOperation, InvalidParameter @@ -34,6 +36,12 @@ from wok.plugins.kimchi.utils import pool_name_from_uri from wok.plugins.kimchi.vmtemplate import VMTemplate
+# In PowerPC, memories must be aligned to 256 MiB +PPC_MEM_ALIGN = 256 +# Max memory 1TB, in KiB +MAX_MEM_LIM = 1073741824 + + class TemplatesModel(object): def __init__(self, **kargs): self.objstore = kargs['objstore'] @@ -69,10 +77,8 @@ class TemplatesModel(object): # Validate cpu info t.cpuinfo_validate()
- # Validate max memory - maxMem = (t._get_max_memory(t.info.get('memory')) >> 10) - if t.info.get('memory') > maxMem: - raise OperationFailed("KCHVM0041E", {'maxmem': str(maxMem)}) + # Validate memory + self._validate_memory(t.info.get('memory'))
# Validate volumes for disk in t.info.get('disks'): @@ -115,6 +121,44 @@ class TemplatesModel(object): raise InvalidParameter("KCHTMPL0019E", {'pool': pool_name, 'volume': volume})
+ def _validate_memory(self, memory): + current = memory.get('current') + maxmem = memory.get('maxmemory') + + # Memories must be lesser than 1TB and the Host memory limit + if maxmem > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0076E") + if current > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0079E") + + if hasattr(psutil, 'virtual_memory'): + host_memory = psutil.virtual_memory().total >> 10 >> 10 + else: + host_memory = psutil.TOTAL_PHYMEM >> 10 >> 10 + if maxmem > host_memory: + raise InvalidParameter("KCHVM0075E", {'memHost': host_memory}) + if current > host_memory: + raise InvalidParameter("KCHVM0078E", {'memHost': host_memory}) + + if current > maxmem: + raise InvalidParameter("KCHTMPL0031E", + {'mem': str(current), + 'maxmem': str(maxmem)}) + + # make sure memory and Maxmemory are alingned in 256MiB in PowerPC + distro, _, _ = platform.linux_distribution() + if distro == "IBM_PowerKVM": + if current % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Memory", + 'mem': str(current), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), + 'alignment': str(PPC_MEM_ALIGN)}) +
class TemplateModel(object): def __init__(self, **kargs): @@ -180,6 +224,12 @@ class TemplateModel(object): cpu_info.update(new_cpu_info) params['cpu_info'] = cpu_info
+ # Fix memory values, because method update does not work recursively + new_mem = params.get('memory') + if new_mem: + params['memory'] = copy.copy(old_t.get('memory')) + params['memory'].update(new_mem) + new_t.update(params)
for net_name in params.get(u'networks', []): diff --git a/model/vms.py b/model/vms.py index 23e0df9..400ef46 100644 --- a/model/vms.py +++ b/model/vms.py @@ -53,13 +53,13 @@ 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.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 from wok.plugins.kimchi.model.utils import set_metadata_node from wok.plugins.kimchi.screenshot import VMScreenshot from wok.plugins.kimchi.utils import template_name_from_uri -from wok.plugins.kimchi.vmtemplate import MAX_MEM_LIM, PPC_MEM_ALIGN from wok.plugins.kimchi.xmlutils.cpu import get_cpu_xml, get_numa_xml from wok.plugins.kimchi.xmlutils.cpu import get_topology_xml from wok.plugins.kimchi.xmlutils.disk import get_vm_disk_info, get_vm_disks @@ -256,7 +256,13 @@ class VMModel(object): if 'memory' in params and distro == "IBM_PowerKVM": if params['memory'] % PPC_MEM_ALIGN != 0: raise InvalidParameter('KCHVM0071E', - {'mem': str(params['memory']), + {'param': "Memory", + 'mem': str(memory), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), 'alignment': str(PPC_MEM_ALIGN)})
dom = self.get_vm(name, self.conn) diff --git a/osinfo.py b/osinfo.py index 2ec5c3e..8102548 100644 --- a/osinfo.py +++ b/osinfo.py @@ -110,11 +110,13 @@ def _get_tmpl_defaults(): ConfigObj returns a dict like below when no changes were made in the template configuration file (template.conf)
- {'main': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} + {'main': {}, 'memory': {}, 'storage': {'disk.0': {}}, 'processor': {}, + 'graphics': {}}
The default values should be like below:
- {'main': {'networks': ['default'], 'memory': '1024'}, + {'main': {'networks': ['default']}, + 'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, @@ -123,7 +125,8 @@ def _get_tmpl_defaults(): # Create dict with default values tmpl_defaults = defaultdict(dict) tmpl_defaults['main']['networks'] = ['default'] - tmpl_defaults['main']['memory'] = _get_default_template_mem() + tmpl_defaults['memory'] = {'current': _get_default_template_mem(), + 'maxmemory': _get_default_template_mem()} tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2', 'pool': 'default'} tmpl_defaults['processor']['vcpus'] = 1 @@ -145,8 +148,11 @@ def _get_tmpl_defaults(): 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'}
# Parse main section to get networks and memory values - main_section = default_config.pop('main') - defaults.update(main_section) + defaults.update(default_config.pop('main')) + defaults['memory'] = default_config.pop('memory') + + defaults['memory']['current'] = int(defaults['memory']['current']) + defaults['memory']['maxmemory'] = int(defaults['memory']['maxmemory'])
# Parse storage section to get disks values storage_section = default_config.pop('storage') diff --git a/template.conf b/template.conf index 3839be4..c4598f1 100644 --- a/template.conf +++ b/template.conf @@ -3,13 +3,17 @@ #
[main] -# Memory in MB -#memory = 1024 - # List of networks separated by comma # Represents the virtual network interfaces to be assigned to guest #networks = default,
+[memory] +# Memory in MB +# current = 1024 + +# Maximum value of memory to be assigned to guest in MB +# maxmemory = 1024 + [storage]
# Specify multiple [[disk.X]] sub-sections to add multiples disks to guest diff --git a/vmtemplate.py b/vmtemplate.py index ef17ff6..ab6d580 100644 --- a/vmtemplate.py +++ b/vmtemplate.py @@ -19,7 +19,6 @@
import os import platform -import psutil import stat import time import urlparse @@ -42,12 +41,6 @@ from wok.plugins.kimchi.xmlutils.qemucmdline import get_qemucmdline_xml from wok.plugins.kimchi.xmlutils.serial import get_serial_xml
-# In PowerPC, memories must be aligned to 256 MiB -PPC_MEM_ALIGN = 256 -# Max memory 1TB, in KiB -MAX_MEM_LIM = 1073741824 - - class VMTemplate(object): def __init__(self, args, scan=False): """ @@ -84,6 +77,14 @@ class VMTemplate(object): args['graphics'] = graphics
default_disk = self.info['disks'][0] + + # Complete memory args, because dict method update is not recursive + if 'memory' in args: + if 'current' not in args['memory']: After apply your patch and execute a GET on templates, to list my current templates, I got the following error:
File "/home/pvital/Projects/tmp/wok_test/src/wok/plugins/kimchi/vmtemplate.py", line 83, in __init__ if 'current' not in args['memory']: TypeError: argument of type 'int' is not iterable
the line executed was:
$ curl -k -u test -H "Content-Type: application/json" -H "Accept: application/json" 'https://localhost:8001/plugins/kimchi/templates' -X GET
So, what happens if I already have templates in the old model (the current one today) after I apply your patches? I don't have the new structure of memory.
Yes, templates must be updated. I will add a script to do this. Thanks
+ args['memory']['current'] = self.info['memory']['current'] + if 'maxmemory' not in args['memory']: + args['memory']['maxmemory'] = self.info['memory']['maxmemory'] + # Override template values according to 'args' self.info.update(args) disks = self.info.get('disks') @@ -325,29 +326,8 @@ class VMTemplate(object): def _get_cpu_xml(self): # Include CPU topology, if provided cpu_topo = self.info.get('cpu_info', {}).get('topology', {}) - - 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: - # 1TB, (Template Memory * 4), Host Physical Memory. - max_memory = MAX_MEM_LIM - if hasattr(psutil, 'virtual_memory'): - host_memory = psutil.virtual_memory().total >> 10 - else: - host_memory = psutil.TOTAL_PHYMEM >> 10 - if host_memory < max_memory: - max_memory = host_memory - if (((guest_memory * 4) << 10) < max_memory): - max_memory = (guest_memory * 4) << 10 - - # set up arch to ppc64 instead of ppc64le due to libvirt compatibility - if self.info["arch"] == "ppc64": - # in Power, memory must be aligned in 256MiB - if (max_memory >> 10) % PPC_MEM_ALIGN != 0: - alignment = max_memory % (PPC_MEM_ALIGN << 10) - max_memory -= alignment - return max_memory + return get_cpu_xml(0, (self.info.get('memory').get('current')) << 10, + cpu_topo)
def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) @@ -375,27 +355,33 @@ class VMTemplate(object): else: params['cdroms'] = cdrom_xml
- # max memory - params['max_memory'] = self._get_max_memory(params['memory']) - # Setting maximum number of slots to avoid errors when hotplug memory # Number of slots are the numbers of chunks of 1GB that fit inside # the max_memory of the host minus memory assigned to the VM. It # cannot have more than 32 slots in Power. - params['slots'] = ((params['max_memory'] >> 10) - - params['memory']) >> 10 - if params['slots'] < 0: + memory = self.info['memory'].get('current') + maxmemory = self.info['memory'].get('maxmemory') + + slots = (maxmemory - memory) >> 10 + if slots < 0: raise OperationFailed("KCHVM0041E", - {'maxmem': str(params['max_memory'] >> 10)}) - elif params['slots'] == 0: - params['slots'] = 1 - elif params['slots'] > 32: + {'maxmem': str(maxmemory)}) + elif slots == 0: + slots = 1 + elif slots > 32: distro, _, _ = platform.linux_distribution() if distro == "IBM_PowerKVM": - params['slots'] = 32 + slots = 32 + + # Rearrange memory parameters + params['memory'] = self.info['memory'].get('current') + params['max_memory'] = "" + if memory != maxmemory: + maxmem_xml = "<maxMemory slots='%s' unit='MiB'>%s</maxMemory>" + params['max_memory'] = maxmem_xml % (slots, maxmemory)
# set a hard limit using max_memory + 1GiB - params['hard_limit'] = params['max_memory'] + (1024 << 10) + params['hard_limit'] = maxmemory + 1024
# vcpu element cpus = params['cpu_info']['vcpus'] @@ -411,9 +397,9 @@ class VMTemplate(object): <name>%(name)s</name> <uuid>%(uuid)s</uuid> <memtune> - <hard_limit unit='KiB'>%(hard_limit)s</hard_limit> + <hard_limit unit='MiB'>%(hard_limit)s</hard_limit> </memtune> - <maxMemory slots='%(slots)s' unit='KiB'>%(max_memory)s</maxMemory> + %(max_memory)s <memory unit='MiB'>%(memory)s</memory> %(vcpus_xml)s %(cpu_info_xml)s
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel

On 02/17/2016 10:39 AM, Rodrigo Trujillo wrote:
On 02/16/2016 10:43 AM, Paulo Ricardo Paz Vital wrote:
On 02/16/2016 12:32 AM, Rodrigo Trujillo wrote:
This patch changes the 'memory' parameter in API of Templates to: memory: {current: XXX, maxmemory: YYY} Other changes include: * enable maxmemory edition * remove max memory tests and limits * keeps max memory limit to 1TiB * changes templates.conf to suport max memory * set default memory and maxmemory to 1024
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- API.json | 44 ++++++++++++++++++++++++++------ docs/API.md | 18 ++++++++++--- i18n.py | 11 ++++++-- model/templates.py | 58 +++++++++++++++++++++++++++++++++++++++--- model/vms.py | 10 ++++++-- osinfo.py | 16 ++++++++---- template.conf | 10 +++++--- vmtemplate.py | 74 ++++++++++++++++++++++-------------------------------- 8 files changed, 169 insertions(+), 72 deletions(-)
diff --git a/API.json b/API.json index 294be64..fcde123 100644 --- a/API.json +++ b/API.json @@ -453,10 +453,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", @@ -630,10 +644,24 @@ "error": "KCHTMPL0011E" }, "memory": { - "description": "Memory (MB) for the template", - "type": "integer", - "minimum": 512, - "error": "KCHTMPL0013E" + "description": "Current memory and maximum memory values", + "type": "object", + "properties": { + "current": { + "description": "Memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + }, + "maxmemory": { + "description": "Maximum memory (MB) for the template", + "type": "integer", + "minimum": 512, + "error": "KCHTMPL0013E" + } + }, + "additionalProperties": false, + "error": "KCHTMPL0030E" }, "cdrom": { "description": "Path for cdrom", diff --git a/docs/API.md b/docs/API.md index a46e80e..33d51ac 100644 --- a/docs/API.md +++ b/docs/API.md @@ -292,8 +292,11 @@ Represents a snapshot of the Virtual Machine's primary monitor. * name: The name of the Template. Used to identify the Template in this API * os_distro *(optional)*: The operating system distribution * os_version *(optional)*: The version of the operating system distribution - * memory *(optional)*: The amount of memory assigned to the VM. - Default is 1024M. + * memory *(optional)*: The memory parameters of the template, specify one + or both. Default values are 1024MiB: + * current: The amount of memory that will be 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 * cdrom *(optional)*: A volume name or URI to an ISO image. * networks *(optional)*: list of networks will be assigned to the new VM. Default is '[default]' @@ -396,7 +399,11 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM in the unit of MB + * memory: The memory parameters of the template, that will be assigned to + the VM in the unit of MiB. + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * storagepool: URI of the storagepool where template allocates vm storage. * networks *(optional)*: list of networks will be assigned to the new VM. @@ -439,7 +446,10 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * memory: The amount of memory assigned to the VM + * memory: The memory parameters of the template, specify one or both of: + * current: The amount of memory that will be 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 * cdrom: A volume name or URI to an ISO image * networks *(optional)*: list of networks will be assigned to the new VM. * disks: An array of requested disks with the following optional fields diff --git a/i18n.py b/i18n.py index 59e75f7..5395879 100644 --- a/i18n.py +++ b/i18n.py @@ -127,9 +127,14 @@ 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": _("%(param)s value (%(mem)sMiB) 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": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0076E": _("Maximum memory requested is higher than maximum amount recommended: 1TiB"), + "KCHVM0077E": _("Cannot update maximum memory when guest is running."), KCHVM0076E and KCHVM0077E already exist and were not modified. See below
+ "KCHVM0078E": _("Maximum memory requested is higher than amount supported by the host: %(memHost)sMiB."), + "KCHVM0079E": _("Memory requested is higher than maximum amount recommended: 1TiB"),
"KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"), "KCHVM0077E": _("Impossible to get the serial console of %(name)s"), They are here ^^^
ACK
@@ -165,7 +170,7 @@ messages = { "KCHTMPL0010E": _("Template distribution must be a string"), "KCHTMPL0011E": _("Template distribution version must be a string"), "KCHTMPL0012E": _("The number of CPUs must be an integer greater than 0"), - "KCHTMPL0013E": _("Amount of memory (MB) must be an integer greater than 512"), + "KCHTMPL0013E": _("Amount of memory and maximum memory (MB) must be an integer greater than 512"), "KCHTMPL0014E": _("Template CDROM must be a local or remote ISO file"), "KCHTMPL0015E": _("Invalid storage pool URI %(value)s specified for template"), "KCHTMPL0016E": _("Specify an ISO image as CDROM or a base image to create a template"), @@ -181,6 +186,8 @@ messages = { "KCHTMPL0027E": _("Invalid disk image format. Valid formats: bochs, cloop, cow, dmg, qcow, qcow2, qed, raw, vmdk, vpc."), "KCHTMPL0028E": _("When setting template disks, following parameters are required: 'index', 'pool name', 'format', 'size' or 'volume' (for scsi/iscsi pools)"), "KCHTMPL0029E": _("Disk format must be 'raw', for logical, iscsi, and scsi pools."), + "KCHTMPL0030E": _("Memory expects an object with one or both parameters: 'current' and 'maxmemory'"), + "KCHTMPL0031E": _("Memory value (%(mem)sMiB) must be equal or lesser than maximum memory value (%(maxmem)sMiB)"),
"KCHPOOL0001E": _("Storage pool %(name)s already exists"), "KCHPOOL0002E": _("Storage pool %(name)s does not exist"), diff --git a/model/templates.py b/model/templates.py index 8a29e02..6138592 100644 --- a/model/templates.py +++ b/model/templates.py @@ -20,6 +20,8 @@ import copy import libvirt import os +import platform +import psutil import stat
from wok.exception import InvalidOperation, InvalidParameter @@ -34,6 +36,12 @@ from wok.plugins.kimchi.utils import pool_name_from_uri from wok.plugins.kimchi.vmtemplate import VMTemplate
+# In PowerPC, memories must be aligned to 256 MiB +PPC_MEM_ALIGN = 256 +# Max memory 1TB, in KiB +MAX_MEM_LIM = 1073741824 + + class TemplatesModel(object): def __init__(self, **kargs): self.objstore = kargs['objstore'] @@ -69,10 +77,8 @@ class TemplatesModel(object): # Validate cpu info t.cpuinfo_validate()
- # Validate max memory - maxMem = (t._get_max_memory(t.info.get('memory')) >> 10) - if t.info.get('memory') > maxMem: - raise OperationFailed("KCHVM0041E", {'maxmem': str(maxMem)}) + # Validate memory + self._validate_memory(t.info.get('memory'))
# Validate volumes for disk in t.info.get('disks'): @@ -115,6 +121,44 @@ class TemplatesModel(object): raise InvalidParameter("KCHTMPL0019E", {'pool': pool_name, 'volume': volume})
+ def _validate_memory(self, memory): + current = memory.get('current') + maxmem = memory.get('maxmemory') + + # Memories must be lesser than 1TB and the Host memory limit + if maxmem > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0076E") + if current > (MAX_MEM_LIM >> 10): + raise InvalidParameter("KCHVM0079E") + + if hasattr(psutil, 'virtual_memory'): + host_memory = psutil.virtual_memory().total >> 10 >> 10 + else: + host_memory = psutil.TOTAL_PHYMEM >> 10 >> 10 + if maxmem > host_memory: + raise InvalidParameter("KCHVM0075E", {'memHost': host_memory}) + if current > host_memory: + raise InvalidParameter("KCHVM0078E", {'memHost': host_memory}) + + if current > maxmem: + raise InvalidParameter("KCHTMPL0031E", + {'mem': str(current), + 'maxmem': str(maxmem)}) + + # make sure memory and Maxmemory are alingned in 256MiB in PowerPC + distro, _, _ = platform.linux_distribution() + if distro == "IBM_PowerKVM": + if current % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Memory", + 'mem': str(current), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), + 'alignment': str(PPC_MEM_ALIGN)}) +
class TemplateModel(object): def __init__(self, **kargs): @@ -180,6 +224,12 @@ class TemplateModel(object): cpu_info.update(new_cpu_info) params['cpu_info'] = cpu_info
+ # Fix memory values, because method update does not work recursively + new_mem = params.get('memory') + if new_mem: + params['memory'] = copy.copy(old_t.get('memory')) + params['memory'].update(new_mem) + new_t.update(params)
for net_name in params.get(u'networks', []): diff --git a/model/vms.py b/model/vms.py index 23e0df9..400ef46 100644 --- a/model/vms.py +++ b/model/vms.py @@ -53,13 +53,13 @@ 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.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 from wok.plugins.kimchi.model.utils import set_metadata_node from wok.plugins.kimchi.screenshot import VMScreenshot from wok.plugins.kimchi.utils import template_name_from_uri -from wok.plugins.kimchi.vmtemplate import MAX_MEM_LIM, PPC_MEM_ALIGN from wok.plugins.kimchi.xmlutils.cpu import get_cpu_xml, get_numa_xml from wok.plugins.kimchi.xmlutils.cpu import get_topology_xml from wok.plugins.kimchi.xmlutils.disk import get_vm_disk_info, get_vm_disks @@ -256,7 +256,13 @@ class VMModel(object): if 'memory' in params and distro == "IBM_PowerKVM": if params['memory'] % PPC_MEM_ALIGN != 0: raise InvalidParameter('KCHVM0071E', - {'mem': str(params['memory']), + {'param': "Memory", + 'mem': str(memory), + 'alignment': str(PPC_MEM_ALIGN)}) + elif maxmem % PPC_MEM_ALIGN != 0: + raise InvalidParameter('KCHVM0071E', + {'param': "Maximum Memory", + 'mem': str(maxmem), 'alignment': str(PPC_MEM_ALIGN)})
dom = self.get_vm(name, self.conn) diff --git a/osinfo.py b/osinfo.py index 2ec5c3e..8102548 100644 --- a/osinfo.py +++ b/osinfo.py @@ -110,11 +110,13 @@ def _get_tmpl_defaults(): ConfigObj returns a dict like below when no changes were made in the template configuration file (template.conf)
- {'main': {}, 'storage': {'disk.0': {}}, 'processor': {}, 'graphics': {}} + {'main': {}, 'memory': {}, 'storage': {'disk.0': {}}, 'processor': {}, + 'graphics': {}}
The default values should be like below:
- {'main': {'networks': ['default'], 'memory': '1024'}, + {'main': {'networks': ['default']}, + 'memory': {'current': 1024, 'maxmemory': 1024}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, 'processor': {'vcpus': '1', 'maxvcpus': 1}, @@ -123,7 +125,8 @@ def _get_tmpl_defaults(): # Create dict with default values tmpl_defaults = defaultdict(dict) tmpl_defaults['main']['networks'] = ['default'] - tmpl_defaults['main']['memory'] = _get_default_template_mem() + tmpl_defaults['memory'] = {'current': _get_default_template_mem(), + 'maxmemory': _get_default_template_mem()} tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2', 'pool': 'default'} tmpl_defaults['processor']['vcpus'] = 1 @@ -145,8 +148,11 @@ def _get_tmpl_defaults(): 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'}
# Parse main section to get networks and memory values - main_section = default_config.pop('main') - defaults.update(main_section) + defaults.update(default_config.pop('main')) + defaults['memory'] = default_config.pop('memory') + + defaults['memory']['current'] = int(defaults['memory']['current']) + defaults['memory']['maxmemory'] = int(defaults['memory']['maxmemory'])
# Parse storage section to get disks values storage_section = default_config.pop('storage') diff --git a/template.conf b/template.conf index 3839be4..c4598f1 100644 --- a/template.conf +++ b/template.conf @@ -3,13 +3,17 @@ #
[main] -# Memory in MB -#memory = 1024 - # List of networks separated by comma # Represents the virtual network interfaces to be assigned to guest #networks = default,
+[memory] +# Memory in MB +# current = 1024 + +# Maximum value of memory to be assigned to guest in MB +# maxmemory = 1024 + [storage]
# Specify multiple [[disk.X]] sub-sections to add multiples disks to guest diff --git a/vmtemplate.py b/vmtemplate.py index ef17ff6..ab6d580 100644 --- a/vmtemplate.py +++ b/vmtemplate.py @@ -19,7 +19,6 @@
import os import platform -import psutil import stat import time import urlparse @@ -42,12 +41,6 @@ from wok.plugins.kimchi.xmlutils.qemucmdline import get_qemucmdline_xml from wok.plugins.kimchi.xmlutils.serial import get_serial_xml
-# In PowerPC, memories must be aligned to 256 MiB -PPC_MEM_ALIGN = 256 -# Max memory 1TB, in KiB -MAX_MEM_LIM = 1073741824 - - class VMTemplate(object): def __init__(self, args, scan=False): """ @@ -84,6 +77,14 @@ class VMTemplate(object): args['graphics'] = graphics
default_disk = self.info['disks'][0] + + # Complete memory args, because dict method update is not recursive + if 'memory' in args: + if 'current' not in args['memory']: After apply your patch and execute a GET on templates, to list my current templates, I got the following error:
File "/home/pvital/Projects/tmp/wok_test/src/wok/plugins/kimchi/vmtemplate.py",
line 83, in __init__ if 'current' not in args['memory']: TypeError: argument of type 'int' is not iterable
the line executed was:
$ curl -k -u test -H "Content-Type: application/json" -H "Accept: application/json" 'https://localhost:8001/plugins/kimchi/templates' -X GET
So, what happens if I already have templates in the old model (the current one today) after I apply your patches? I don't have the new structure of memory.
Yes, templates must be updated. I will add a script to do this. Thanks
We also have to update the cpu struture as it was changed too.
+ args['memory']['current'] = self.info['memory']['current'] + if 'maxmemory' not in args['memory']: + args['memory']['maxmemory'] = self.info['memory']['maxmemory'] + # Override template values according to 'args' self.info.update(args) disks = self.info.get('disks') @@ -325,29 +326,8 @@ class VMTemplate(object): def _get_cpu_xml(self): # Include CPU topology, if provided cpu_topo = self.info.get('cpu_info', {}).get('topology', {}) - - 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: - # 1TB, (Template Memory * 4), Host Physical Memory. - max_memory = MAX_MEM_LIM - if hasattr(psutil, 'virtual_memory'): - host_memory = psutil.virtual_memory().total >> 10 - else: - host_memory = psutil.TOTAL_PHYMEM >> 10 - if host_memory < max_memory: - max_memory = host_memory - if (((guest_memory * 4) << 10) < max_memory): - max_memory = (guest_memory * 4) << 10 - - # set up arch to ppc64 instead of ppc64le due to libvirt compatibility - if self.info["arch"] == "ppc64": - # in Power, memory must be aligned in 256MiB - if (max_memory >> 10) % PPC_MEM_ALIGN != 0: - alignment = max_memory % (PPC_MEM_ALIGN << 10) - max_memory -= alignment - return max_memory + return get_cpu_xml(0, (self.info.get('memory').get('current')) << 10, + cpu_topo)
def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) @@ -375,27 +355,33 @@ class VMTemplate(object): else: params['cdroms'] = cdrom_xml
- # max memory - params['max_memory'] = self._get_max_memory(params['memory']) - # Setting maximum number of slots to avoid errors when hotplug memory # Number of slots are the numbers of chunks of 1GB that fit inside # the max_memory of the host minus memory assigned to the VM. It # cannot have more than 32 slots in Power. - params['slots'] = ((params['max_memory'] >> 10) - - params['memory']) >> 10 - if params['slots'] < 0: + memory = self.info['memory'].get('current') + maxmemory = self.info['memory'].get('maxmemory') + + slots = (maxmemory - memory) >> 10 + if slots < 0: raise OperationFailed("KCHVM0041E", - {'maxmem': str(params['max_memory'] >> 10)}) - elif params['slots'] == 0: - params['slots'] = 1 - elif params['slots'] > 32: + {'maxmem': str(maxmemory)}) + elif slots == 0: + slots = 1 + elif slots > 32: distro, _, _ = platform.linux_distribution() if distro == "IBM_PowerKVM": - params['slots'] = 32 + slots = 32 + + # Rearrange memory parameters + params['memory'] = self.info['memory'].get('current') + params['max_memory'] = "" + if memory != maxmemory: + maxmem_xml = "<maxMemory slots='%s' unit='MiB'>%s</maxMemory>" + params['max_memory'] = maxmem_xml % (slots, maxmemory)
# set a hard limit using max_memory + 1GiB - params['hard_limit'] = params['max_memory'] + (1024 << 10) + params['hard_limit'] = maxmemory + 1024
# vcpu element cpus = params['cpu_info']['vcpus'] @@ -411,9 +397,9 @@ class VMTemplate(object): <name>%(name)s</name> <uuid>%(uuid)s</uuid> <memtune> - <hard_limit unit='KiB'>%(hard_limit)s</hard_limit> + <hard_limit unit='MiB'>%(hard_limit)s</hard_limit> </memtune> - <maxMemory slots='%(slots)s' unit='KiB'>%(max_memory)s</maxMemory> + %(max_memory)s <memory unit='MiB'>%(memory)s</memory> %(vcpus_xml)s %(cpu_info_xml)s
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel

On 17-02-2016 17:32, Aline Manera wrote:
After apply your patch and execute a GET on templates, to list my current templates, I got the following error:
File "/home/pvital/Projects/tmp/wok_test/src/wok/plugins/kimchi/vmtemplate.py",
line 83, in __init__ if 'current' not in args['memory']: TypeError: argument of type 'int' is not iterable
the line executed was:
$ curl -k -u test -H "Content-Type: application/json" -H "Accept: application/json" 'https://localhost:8001/plugins/kimchi/templates' -X GET
So, what happens if I already have templates in the old model (the current one today) after I apply your patches? I don't have the new structure of memory.
Yes, templates must be updated. I will add a script to do this. Thanks
We also have to update the cpu struture as it was changed too.
That problem does not happen with cpu structure. No need to update it. -- Lucio Correia Software Engineer IBM LTC Brazil

def __init__(self, args, scan=False): """ @@ -84,6 +77,14 @@ class VMTemplate(object): args['graphics'] = graphics
default_disk = self.info['disks'][0] + + # Complete memory args, because dict method update is not recursive + if 'memory' in args: + if 'current' not in args['memory']:
After apply your patch and execute a GET on templates, to list my current templates, I got the following error:
File "/home/pvital/Projects/tmp/wok_test/src/wok/plugins/kimchi/vmtemplate.py",
line 83, in __init__ if 'current' not in args['memory']: TypeError: argument of type 'int' is not iterable
the line executed was:
$ curl -k -u test -H "Content-Type: application/json" -H "Accept: application/json" 'https://localhost:8001/plugins/kimchi/templates' -X GET
So, what happens if I already have templates in the old model (the current one today) after I apply your patches? I don't have the new structure of memory.
Yes, templates must be updated. I will add a script to do this. Thanks
We also have to update the cpu struture as it was changed too.
Humm, I have not noticed any problem in Templates related to latest CPU changes. I have just sent V2 and did not included anything about this. However I think we need to take a deeper look and analyze if we need an update script to CPUs as well. Rodrigo Trujillo

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@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" } }, "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') + 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, -- 2.1.0

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@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.
} }, "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
+ 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,
-- Lucio Correia Software Engineer IBM LTC Brazil

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@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,

This patch fixes all tests, changing memory API, to support memory: {'current', 'maxmemory'}. It also include some new tests to test changing maxmemory parameter. Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- tests/test_livemigration.py | 6 ++---- tests/test_model.py | 22 ++++++++++++++-------- tests/test_rest.py | 23 ++++++++++++++++++----- tests/test_template.py | 38 ++++++++++++++++++++++++++++++++------ tests/test_vmtemplate.py | 26 +++++++++++++++----------- 5 files changed, 81 insertions(+), 34 deletions(-) diff --git a/tests/test_livemigration.py b/tests/test_livemigration.py index 7119688..35d2175 100644 --- a/tests/test_livemigration.py +++ b/tests/test_livemigration.py @@ -98,14 +98,12 @@ class LiveMigrationTests(unittest.TestCase): params = {'name': u'template_test_vm_migrate', 'disks': [], 'cdrom': UBUNTU_ISO, - 'memory': 2048, - 'max_memory': 4096 << 10} + 'memory': {'current': 2048, 'maxmemory': 4096 << 10}} self.inst.templates_create(params) params = {'name': u'template_test_vm_migrate_nonshared', 'disks': [{'name': 'test_vm_migrate.img', 'size': 1}], 'cdrom': UBUNTU_ISO, - 'memory': 2048, - 'max_memory': 4096*1024} + 'memory': {'current': 2048, 'maxmemory': 4096*1024}} self.inst.templates_create(params) def tearDown(self): diff --git a/tests/test_model.py b/tests/test_model.py index 097c2d6..fcd1bbd 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -113,7 +113,7 @@ class ModelTests(unittest.TestCase): self.assertEquals(keys, set(info.keys())) self.assertEquals('running', info['state']) self.assertEquals('test', info['name']) - self.assertEquals(2048, info['memory']) + self.assertEquals(2048, info['memory']['current']) self.assertEquals(2, info['cpu_info']['vcpus']) self.assertEquals(2, info['cpu_info']['maxvcpus']) self.assertEquals(None, info['icon']) @@ -270,9 +270,10 @@ class ModelTests(unittest.TestCase): pool_uri = "/plugins/kimchi/storagepools/default" tmpl_info = {"cpu_info": {"vcpus": 1}, "name": tmpl_name, "graphics": {"type": "vnc", "listen": "127.0.0.1"}, - "networks": ["default"], "memory": 1024, "folder": [], - "icon": "images/icon-vm.png", "cdrom": "", - "os_distro": "unknown", "os_version": "unknown", + "networks": ["default"], "memory": {'current': 1024}, + "folder": [], "icon": "images/icon-vm.png", + "cdrom": "", "os_distro": "unknown", + "os_version": "unknown", "disks": [{"base": vol_path, "size": 10, "format": "qcow2", "pool": {"name": pool_uri}}]} @@ -755,7 +756,9 @@ class ModelTests(unittest.TestCase): def test_vm_memory_hotplug(self): config.set("authentication", "method", "pam") inst = model.Model(None, objstore_loc=self.tmp_store) - orig_params = {'name': 'test', 'memory': 1024, 'cdrom': UBUNTU_ISO} + orig_params = {'name': 'test', + 'memory': {'current': 1024, 'maxmemory': 3072}, + 'cdrom': UBUNTU_ISO} inst.templates_create(orig_params) with RollbackContext() as rollback: @@ -771,11 +774,12 @@ class ModelTests(unittest.TestCase): 'kimchi-vm1') # Hotplug memory, only available in Libvirt >= 1.2.14 - params = {'memory': 2048} + params = {'memory': {'current': 2048}} if inst.capabilities_lookup()['mem_hotplug_support']: inst.vm_update('kimchi-vm1', params) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, 'kimchi-vm1') + params['memory']['maxmemory'] = 3072 self.assertEquals(params['memory'], inst.vm_lookup('kimchi-vm1')['memory']) else: @@ -789,7 +793,7 @@ class ModelTests(unittest.TestCase): # template disk format must be qcow2 because vmsnapshot # only supports this format - orig_params = {'name': 'test', 'memory': 1024, + orig_params = {'name': 'test', 'memory': {'current': 1024}, 'cpu_info': {'vcpus': 1}, 'cdrom': UBUNTU_ISO, 'disks': [{'size': 1, 'format': 'qcow2', 'pool': { @@ -907,7 +911,9 @@ class ModelTests(unittest.TestCase): self.assertEquals(4, vm_info['cpu_info']['maxvcpus']) # rename and increase memory when vm is not running - params = {'name': u'пeω-∨м', 'memory': 2048} + params = {'name': u'пeω-∨м', + 'memory': {'current': 2048, + 'maxmemory': 2048}} inst.vm_update('kimchi-vm1', params) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, u'пeω-∨м') diff --git a/tests/test_rest.py b/tests/test_rest.py index 1fc0f71..8da7853 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -152,6 +152,19 @@ class RestTests(unittest.TestCase): resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(200, resp.status) + # Test max memory + req = json.dumps({'memory': {'maxmemory': 23}}) + resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + self.assertEquals(400, resp.status) + + req = json.dumps({'memory': {'maxmemory': 'maxmem 80'}}) + resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + self.assertEquals(400, resp.status) + + req = json.dumps({'memory': {'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) @@ -171,7 +184,7 @@ class RestTests(unittest.TestCase): # Check if there is support to memory hotplug, once vm is running resp = self.request('/plugins/kimchi/config/capabilities').read() conf = json.loads(resp) - req = json.dumps({'memory': 2048}) + req = json.dumps({'memory': {'current': 2048}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') if conf['mem_hotplug_support']: self.assertEquals(200, resp.status) @@ -211,11 +224,11 @@ class RestTests(unittest.TestCase): resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) - req = json.dumps({'memory': 100}) + req = json.dumps({'memory': {'current': 100}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) - req = json.dumps({'memory': 'ten gigas'}) + req = json.dumps({'memory': {'current': 'ten gigas'}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) @@ -225,7 +238,7 @@ class RestTests(unittest.TestCase): self.assertEquals(400, resp.status) params = {'name': u'∨м-црdαtеd', 'cpu_info': {'vcpus': 5}, - 'memory': 3072} + 'memory': {'current': 3072}} req = json.dumps(params) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(303, resp.status) @@ -233,7 +246,7 @@ class RestTests(unittest.TestCase): self.request('/plugins/kimchi/vms/∨м-црdαtеd', req).read() ) # Memory was hot plugged - params['memory'] += 1024 + params['memory']['current'] += 1024 for key in params.keys(): self.assertEquals(params[key], vm[key]) diff --git a/tests/test_template.py b/tests/test_template.py index da0037e..a145fa6 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -70,7 +70,8 @@ class TemplateTests(unittest.TestCase): # Create a template without cdrom and disk specified fails with 400 t = {'name': 'test', 'os_distro': 'ImagineOS', - 'os_version': '1.0', 'memory': 1024, 'cpu_info': {'vcpus': 1}} + 'os_version': '1.0', 'memory': {'current': 1024}, + 'cpu_info': {'vcpus': 1}} req = json.dumps(t) resp = self.request('/plugins/kimchi/templates', req, 'POST') self.assertEquals(400, resp.status) @@ -146,10 +147,11 @@ class TemplateTests(unittest.TestCase): else: max_mem = (psutil.TOTAL_PHYMEM >> 10 >> 10) memory = max_mem + 1024 - t = {'name': 'test-maxmem', 'cdrom': '/tmp/mock.iso', 'memory': memory} + t = {'name': 'test-maxmem', 'cdrom': '/tmp/mock.iso', + 'memory': {'current': memory}} req = json.dumps(t) resp = self.request('/plugins/kimchi/templates', req, 'POST') - self.assertEquals(500, resp.status) + self.assertEquals(400, resp.status) self.assertTrue(str(max_mem) in resp.read()) def test_customized_tmpl(self): @@ -219,12 +221,36 @@ class TemplateTests(unittest.TestCase): update_tmpl = json.loads(resp.read()) self.assertEquals(update_tmpl['cpu_info'], cpu_info_data['cpu_info']) - # Update memory - req = json.dumps({'memory': 2048}) + # Test memory and max memory + # - memory greated than max memory (1024 default) + req = json.dumps({'memory': {'current': 2048}}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(400, resp.status) + # - max memory greater than 1TiB limit + req = json.dumps({'memory': {'maxmemory': 1073741824 + 1024}}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(400, resp.status) + self.assertTrue('KCHVM0076E' in resp.read()) + # - change only max memory + req = json.dumps({'memory': {'maxmemory': 3072}}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(200, resp.status) + update_tmpl = json.loads(resp.read()) + self.assertEquals(3072, update_tmpl['memory']['maxmemory']) + # - change only memory + req = json.dumps({'memory': {'current': 2048}}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(200, resp.status) + update_tmpl = json.loads(resp.read()) + self.assertEquals(2048, update_tmpl['memory']['current']) + self.assertEquals(3072, update_tmpl['memory']['maxmemory']) + # - change both values + req = json.dumps({'memory': {'current': 1024, 'maxmemory': 1024}}) resp = self.request(new_tmpl_uri, req, 'PUT') self.assertEquals(200, resp.status) update_tmpl = json.loads(resp.read()) - self.assertEquals(2048, update_tmpl['memory']) + self.assertEquals(1024, update_tmpl['memory']['current']) + self.assertEquals(1024, update_tmpl['memory']['maxmemory']) # Update cdrom cdrom_data = {'cdrom': '/tmp/mock2.iso'} diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py index e157198..f5c44bd 100644 --- a/tests/test_vmtemplate.py +++ b/tests/test_vmtemplate.py @@ -98,21 +98,23 @@ class VMTemplateTests(unittest.TestCase): expr = "/domain/devices/graphics/@listen" self.assertEquals(graphics['listen'], xpath_get_text(xml, expr)[0]) expr = "/domain/maxMemory/@slots" - self.assertEquals('3', xpath_get_text(xml, expr)[0]) - expr = "/domain/maxMemory" - self.assertEquals(str((1024 * 4) << 10), xpath_get_text(xml, expr)[0]) + # The default is memory and maxmemory have the same value, so + # max memory tag is not set + self.assertEquals(0, len(xpath_get_text(xml, expr))) + expr = "/domain/memory" + self.assertEquals(str(1024), xpath_get_text(xml, expr)[0]) if hasattr(psutil, 'virtual_memory'): host_memory = psutil.virtual_memory().total >> 10 else: host_memory = psutil.TOTAL_PHYMEM >> 10 t = VMTemplate({'name': 'test-template', 'cdrom': self.iso, - 'memory': (host_memory >> 10) - 512}) - xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) - expr = "/domain/maxMemory" - self.assertEquals(str(host_memory), xpath_get_text(xml, expr)[0]) - expr = "/domain/maxMemory/@slots" - self.assertEquals('1', xpath_get_text(xml, expr)[0]) + 'memory': {'current': (host_memory >> 10) - 512}}) + try: + xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) + except Exception as e: + # Test current memory greater than maxmemory (1024/default) + self.assertTrue('KCHVM0041E' in e.message) def test_arg_merging(self): """ @@ -121,12 +123,14 @@ class VMTemplateTests(unittest.TestCase): """ graphics = {'type': 'vnc', 'listen': '127.0.0.1'} args = {'name': 'test', 'os_distro': 'opensuse', 'os_version': '12.3', - 'cpu_info': {'vcpus': 2, 'maxvcpus': 4}, 'memory': 2048, + 'cpu_info': {'vcpus': 2, 'maxvcpus': 4}, + 'memory': {'current': 2048, 'maxmemory': 3072}, 'networks': ['foo'], 'cdrom': self.iso, 'graphics': graphics} t = VMTemplate(args) self.assertEquals(2, t.info.get('cpu_info', {}).get('vcpus')) self.assertEquals(4, t.info.get('cpu_info', {}).get('maxvcpus')) - self.assertEquals(2048, t.info.get('memory')) + self.assertEquals(2048, t.info.get('memory').get('current')) + self.assertEquals(3072, t.info.get('memory').get('maxmemory')) self.assertEquals(['foo'], t.info.get('networks')) self.assertEquals(self.iso, t.info.get('cdrom')) self.assertEquals(graphics, t.info.get('graphics')) -- 2.1.0

Reviewed-By: Lucio Correia <luciojhc@linux.vnet.ibm.com> On 16-02-2016 00:32, Rodrigo Trujillo wrote:
This patch fixes all tests, changing memory API, to support memory: {'current', 'maxmemory'}. It also include some new tests to test changing maxmemory parameter.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- tests/test_livemigration.py | 6 ++---- tests/test_model.py | 22 ++++++++++++++-------- tests/test_rest.py | 23 ++++++++++++++++++----- tests/test_template.py | 38 ++++++++++++++++++++++++++++++++------ tests/test_vmtemplate.py | 26 +++++++++++++++----------- 5 files changed, 81 insertions(+), 34 deletions(-)
diff --git a/tests/test_livemigration.py b/tests/test_livemigration.py index 7119688..35d2175 100644 --- a/tests/test_livemigration.py +++ b/tests/test_livemigration.py @@ -98,14 +98,12 @@ class LiveMigrationTests(unittest.TestCase): params = {'name': u'template_test_vm_migrate', 'disks': [], 'cdrom': UBUNTU_ISO, - 'memory': 2048, - 'max_memory': 4096 << 10} + 'memory': {'current': 2048, 'maxmemory': 4096 << 10}} self.inst.templates_create(params) params = {'name': u'template_test_vm_migrate_nonshared', 'disks': [{'name': 'test_vm_migrate.img', 'size': 1}], 'cdrom': UBUNTU_ISO, - 'memory': 2048, - 'max_memory': 4096*1024} + 'memory': {'current': 2048, 'maxmemory': 4096*1024}} self.inst.templates_create(params)
def tearDown(self): diff --git a/tests/test_model.py b/tests/test_model.py index 097c2d6..fcd1bbd 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -113,7 +113,7 @@ class ModelTests(unittest.TestCase): self.assertEquals(keys, set(info.keys())) self.assertEquals('running', info['state']) self.assertEquals('test', info['name']) - self.assertEquals(2048, info['memory']) + self.assertEquals(2048, info['memory']['current']) self.assertEquals(2, info['cpu_info']['vcpus']) self.assertEquals(2, info['cpu_info']['maxvcpus']) self.assertEquals(None, info['icon']) @@ -270,9 +270,10 @@ class ModelTests(unittest.TestCase): pool_uri = "/plugins/kimchi/storagepools/default" tmpl_info = {"cpu_info": {"vcpus": 1}, "name": tmpl_name, "graphics": {"type": "vnc", "listen": "127.0.0.1"}, - "networks": ["default"], "memory": 1024, "folder": [], - "icon": "images/icon-vm.png", "cdrom": "", - "os_distro": "unknown", "os_version": "unknown", + "networks": ["default"], "memory": {'current': 1024}, + "folder": [], "icon": "images/icon-vm.png", + "cdrom": "", "os_distro": "unknown", + "os_version": "unknown", "disks": [{"base": vol_path, "size": 10, "format": "qcow2", "pool": {"name": pool_uri}}]} @@ -755,7 +756,9 @@ class ModelTests(unittest.TestCase): def test_vm_memory_hotplug(self): config.set("authentication", "method", "pam") inst = model.Model(None, objstore_loc=self.tmp_store) - orig_params = {'name': 'test', 'memory': 1024, 'cdrom': UBUNTU_ISO} + orig_params = {'name': 'test', + 'memory': {'current': 1024, 'maxmemory': 3072}, + 'cdrom': UBUNTU_ISO} inst.templates_create(orig_params)
with RollbackContext() as rollback: @@ -771,11 +774,12 @@ class ModelTests(unittest.TestCase): 'kimchi-vm1')
# Hotplug memory, only available in Libvirt >= 1.2.14 - params = {'memory': 2048} + params = {'memory': {'current': 2048}} if inst.capabilities_lookup()['mem_hotplug_support']: inst.vm_update('kimchi-vm1', params) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, 'kimchi-vm1') + params['memory']['maxmemory'] = 3072 self.assertEquals(params['memory'], inst.vm_lookup('kimchi-vm1')['memory']) else: @@ -789,7 +793,7 @@ class ModelTests(unittest.TestCase):
# template disk format must be qcow2 because vmsnapshot # only supports this format - orig_params = {'name': 'test', 'memory': 1024, + orig_params = {'name': 'test', 'memory': {'current': 1024}, 'cpu_info': {'vcpus': 1}, 'cdrom': UBUNTU_ISO, 'disks': [{'size': 1, 'format': 'qcow2', 'pool': { @@ -907,7 +911,9 @@ class ModelTests(unittest.TestCase): self.assertEquals(4, vm_info['cpu_info']['maxvcpus'])
# rename and increase memory when vm is not running - params = {'name': u'пeω-∨м', 'memory': 2048} + params = {'name': u'пeω-∨м', + 'memory': {'current': 2048, + 'maxmemory': 2048}} inst.vm_update('kimchi-vm1', params) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, u'пeω-∨м') diff --git a/tests/test_rest.py b/tests/test_rest.py index 1fc0f71..8da7853 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -152,6 +152,19 @@ class RestTests(unittest.TestCase): resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(200, resp.status)
+ # Test max memory + req = json.dumps({'memory': {'maxmemory': 23}}) + resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + self.assertEquals(400, resp.status) + + req = json.dumps({'memory': {'maxmemory': 'maxmem 80'}}) + resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') + self.assertEquals(400, resp.status) + + req = json.dumps({'memory': {'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)
@@ -171,7 +184,7 @@ class RestTests(unittest.TestCase): # Check if there is support to memory hotplug, once vm is running resp = self.request('/plugins/kimchi/config/capabilities').read() conf = json.loads(resp) - req = json.dumps({'memory': 2048}) + req = json.dumps({'memory': {'current': 2048}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') if conf['mem_hotplug_support']: self.assertEquals(200, resp.status) @@ -211,11 +224,11 @@ class RestTests(unittest.TestCase): resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
- req = json.dumps({'memory': 100}) + req = json.dumps({'memory': {'current': 100}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
- req = json.dumps({'memory': 'ten gigas'}) + req = json.dumps({'memory': {'current': 'ten gigas'}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
@@ -225,7 +238,7 @@ class RestTests(unittest.TestCase): self.assertEquals(400, resp.status)
params = {'name': u'∨м-црdαtеd', 'cpu_info': {'vcpus': 5}, - 'memory': 3072} + 'memory': {'current': 3072}} req = json.dumps(params) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(303, resp.status) @@ -233,7 +246,7 @@ class RestTests(unittest.TestCase): self.request('/plugins/kimchi/vms/∨м-црdαtеd', req).read() ) # Memory was hot plugged - params['memory'] += 1024 + params['memory']['current'] += 1024 for key in params.keys(): self.assertEquals(params[key], vm[key])
diff --git a/tests/test_template.py b/tests/test_template.py index da0037e..a145fa6 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -70,7 +70,8 @@ class TemplateTests(unittest.TestCase):
# Create a template without cdrom and disk specified fails with 400 t = {'name': 'test', 'os_distro': 'ImagineOS', - 'os_version': '1.0', 'memory': 1024, 'cpu_info': {'vcpus': 1}} + 'os_version': '1.0', 'memory': {'current': 1024}, + 'cpu_info': {'vcpus': 1}} req = json.dumps(t) resp = self.request('/plugins/kimchi/templates', req, 'POST') self.assertEquals(400, resp.status) @@ -146,10 +147,11 @@ class TemplateTests(unittest.TestCase): else: max_mem = (psutil.TOTAL_PHYMEM >> 10 >> 10) memory = max_mem + 1024 - t = {'name': 'test-maxmem', 'cdrom': '/tmp/mock.iso', 'memory': memory} + t = {'name': 'test-maxmem', 'cdrom': '/tmp/mock.iso', + 'memory': {'current': memory}} req = json.dumps(t) resp = self.request('/plugins/kimchi/templates', req, 'POST') - self.assertEquals(500, resp.status) + self.assertEquals(400, resp.status) self.assertTrue(str(max_mem) in resp.read())
def test_customized_tmpl(self): @@ -219,12 +221,36 @@ class TemplateTests(unittest.TestCase): update_tmpl = json.loads(resp.read()) self.assertEquals(update_tmpl['cpu_info'], cpu_info_data['cpu_info'])
- # Update memory - req = json.dumps({'memory': 2048}) + # Test memory and max memory + # - memory greated than max memory (1024 default) + req = json.dumps({'memory': {'current': 2048}}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(400, resp.status) + # - max memory greater than 1TiB limit + req = json.dumps({'memory': {'maxmemory': 1073741824 + 1024}}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(400, resp.status) + self.assertTrue('KCHVM0076E' in resp.read()) + # - change only max memory + req = json.dumps({'memory': {'maxmemory': 3072}}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(200, resp.status) + update_tmpl = json.loads(resp.read()) + self.assertEquals(3072, update_tmpl['memory']['maxmemory']) + # - change only memory + req = json.dumps({'memory': {'current': 2048}}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(200, resp.status) + update_tmpl = json.loads(resp.read()) + self.assertEquals(2048, update_tmpl['memory']['current']) + self.assertEquals(3072, update_tmpl['memory']['maxmemory']) + # - change both values + req = json.dumps({'memory': {'current': 1024, 'maxmemory': 1024}}) resp = self.request(new_tmpl_uri, req, 'PUT') self.assertEquals(200, resp.status) update_tmpl = json.loads(resp.read()) - self.assertEquals(2048, update_tmpl['memory']) + self.assertEquals(1024, update_tmpl['memory']['current']) + self.assertEquals(1024, update_tmpl['memory']['maxmemory'])
# Update cdrom cdrom_data = {'cdrom': '/tmp/mock2.iso'} diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py index e157198..f5c44bd 100644 --- a/tests/test_vmtemplate.py +++ b/tests/test_vmtemplate.py @@ -98,21 +98,23 @@ class VMTemplateTests(unittest.TestCase): expr = "/domain/devices/graphics/@listen" self.assertEquals(graphics['listen'], xpath_get_text(xml, expr)[0]) expr = "/domain/maxMemory/@slots" - self.assertEquals('3', xpath_get_text(xml, expr)[0]) - expr = "/domain/maxMemory" - self.assertEquals(str((1024 * 4) << 10), xpath_get_text(xml, expr)[0]) + # The default is memory and maxmemory have the same value, so + # max memory tag is not set + self.assertEquals(0, len(xpath_get_text(xml, expr))) + expr = "/domain/memory" + self.assertEquals(str(1024), xpath_get_text(xml, expr)[0])
if hasattr(psutil, 'virtual_memory'): host_memory = psutil.virtual_memory().total >> 10 else: host_memory = psutil.TOTAL_PHYMEM >> 10 t = VMTemplate({'name': 'test-template', 'cdrom': self.iso, - 'memory': (host_memory >> 10) - 512}) - xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) - expr = "/domain/maxMemory" - self.assertEquals(str(host_memory), xpath_get_text(xml, expr)[0]) - expr = "/domain/maxMemory/@slots" - self.assertEquals('1', xpath_get_text(xml, expr)[0]) + 'memory': {'current': (host_memory >> 10) - 512}}) + try: + xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) + except Exception as e: + # Test current memory greater than maxmemory (1024/default) + self.assertTrue('KCHVM0041E' in e.message)
def test_arg_merging(self): """ @@ -121,12 +123,14 @@ class VMTemplateTests(unittest.TestCase): """ graphics = {'type': 'vnc', 'listen': '127.0.0.1'} args = {'name': 'test', 'os_distro': 'opensuse', 'os_version': '12.3', - 'cpu_info': {'vcpus': 2, 'maxvcpus': 4}, 'memory': 2048, + 'cpu_info': {'vcpus': 2, 'maxvcpus': 4}, + 'memory': {'current': 2048, 'maxmemory': 3072}, 'networks': ['foo'], 'cdrom': self.iso, 'graphics': graphics} t = VMTemplate(args) self.assertEquals(2, t.info.get('cpu_info', {}).get('vcpus')) self.assertEquals(4, t.info.get('cpu_info', {}).get('maxvcpus')) - self.assertEquals(2048, t.info.get('memory')) + self.assertEquals(2048, t.info.get('memory').get('current')) + self.assertEquals(3072, t.info.get('memory').get('maxmemory')) self.assertEquals(['foo'], t.info.get('networks')) self.assertEquals(self.iso, t.info.get('cdrom')) self.assertEquals(graphics, t.info.get('graphics'))
-- Lucio Correia Software Engineer IBM LTC Brazil
participants (4)
-
Aline Manera
-
Lucio Correia
-
Paulo Ricardo Paz Vital
-
Rodrigo Trujillo