[Kimchi-devel] [PATCH v2][Kimchi 3/4] Add support to edit max memory in Guest

Rodrigo Trujillo rodrigo.trujillo at linux.vnet.ibm.com
Sun Feb 21 04:14:38 UTC 2016


This patch changes the backend in order to allow user to edit max memory
tag. It changes the memory API to return and receive:
 'memory': {'current': XXX, 'maxmemory': YYY}.

Other changes include:
- maxMemory xml tag is not created by default anymore;
- maxMemory xml tag is only created if user passes maxmemory;
- if memory and maxmemory are equal, than maxMemory tag is removed;
- keeps the limit to max memory and memory to 1TiB;
- adds tests to json and modify documentation;

Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo at linux.vnet.ibm.com>
---
 API.json     |   7 +-
 docs/API.md  |  11 ++-
 i18n.py      |   3 +-
 model/vms.py | 246 ++++++++++++++++++++++++++++++++---------------------------
 4 files changed, 146 insertions(+), 121 deletions(-)

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




More information about the Kimchi-devel mailing list