Re: [Kimchi-devel] [PATCH] [Kimchi] Add support to Libvirt Events.
by Aline Manera
On 05/16/2016 11:32 PM, pvital(a)linux.vnet.ibm.com wrote:
> From: Paulo Vital <pvital(a)linux.vnet.ibm.com>
>
> This patch adds support to handle in Kimchi any Libvirt Event, just by adding
> a callback to process the event and register the event with the callback.
>
> None event is being registered by this patch and they need be added by developer
> by demand in the code. To see how register an event, the unit test
> test_libvirtevents.py register two events and then use them in the code.
>
> Signed-off-by: Paulo Vital <pvital(a)linux.vnet.ibm.com>
> ---
> i18n.py | 3 +
> model/libvirtevents.py | 59 ++++++++++
> model/model.py | 5 +-
> tests/test_model_libvirtevents.py | 219 ++++++++++++++++++++++++++++++++++++++
> 4 files changed, 285 insertions(+), 1 deletion(-)
> create mode 100644 model/libvirtevents.py
> create mode 100644 tests/test_model_libvirtevents.py
>
> diff --git a/i18n.py b/i18n.py
> index 2301e60..49b7f1c 100644
> --- a/i18n.py
> +++ b/i18n.py
> @@ -334,4 +334,7 @@ messages = {
> "KCHCONN0001E": _("Unable to establish connection with libvirt. Please check your libvirt URI which is often defined in /etc/libvirt/libvirt.conf"),
> "KCHCONN0002E": _("Libvirt service is not active. Please start the libvirt service in your host system."),
>
> + "KCHEVENT0001E": _("Failed to register the default event implementation."),
> + "KCHEVENT0002E": _("Failed to register timeout event."),
> + "KCHEVENT0003E": _("Failed to Run the default event implementation."),
> }
> diff --git a/model/libvirtevents.py b/model/libvirtevents.py
> new file mode 100644
> index 0000000..ab5c17d
> --- /dev/null
> +++ b/model/libvirtevents.py
> @@ -0,0 +1,59 @@
> +#
> +# Project Kimchi
> +#
> +# Copyright IBM Corp, 2016
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> +
> +import cherrypy
> +import libvirt
> +import time
> +
> +from wok.exception import OperationFailed
> +
> +
> +class LibvirtEvents(object):
> + def __init__(self):
> + # Register default implementation of event handlers
> + if libvirt.virEventRegisterDefaultImpl() < 0:
> + raise OperationFailed('KCHEVENT0001E')
> +
> + # Run a background thread with the event loop. Using cherrypy
> + # BackgroundTask class due to issues when using threading module with
> + # cherrypy.
> + self.event_loop_thread = cherrypy.process.plugins.BackgroundTask(
> + 2,
> + self._event_loop_run
> + )
> + self.event_loop_thread.setName('KimchiLibvirtEventLoop')
> + self.event_loop_thread.setDaemon(True)
> + self.event_loop_thread.start()
> +
> + # Set an event timeout to control the self._event_loop_run
> + if libvirt.virEventAddTimeout(0, self._kimchi_EventTimeout, None) < 0:
> + raise OperationFailed('KCHEVENT0002E')
> +
> + # Event loop method to be executed in background as thread
> + def _event_loop_run(self):
> + while True:
> + if libvirt.virEventRunDefaultImpl() < 0:
> + raise OperationFailed('KCHEVENT0003E')
> +
> + def is_event_loop_alive(self):
> + return self.event_loop_thread.isAlive()
> +
> + # Event loop handler used to limit length of waiting for any other event.
> + def _kimchi_EventTimeout(self, timer, opaque):
> + time.sleep(1)
> diff --git a/model/model.py b/model/model.py
> index e44f804..ed474d2 100644
> --- a/model/model.py
> +++ b/model/model.py
> @@ -26,6 +26,7 @@ from wok.plugins.kimchi import config
> from wok.utils import import_module, listPathModules
>
> from wok.plugins.kimchi.model.libvirtconnection import LibvirtConnection
> +from wok.plugins.kimchi.model.libvirtevents import LibvirtEvents
>
>
> class Model(BaseModel):
> @@ -43,8 +44,10 @@ class Model(BaseModel):
> return instances
>
> self.objstore = ObjectStore(objstore_loc or config.get_object_store())
> + self.events = LibvirtEvents()
> self.conn = LibvirtConnection(libvirt_uri)
> - kargs = {'objstore': self.objstore, 'conn': self.conn}
> + kargs = {'objstore': self.objstore, 'conn': self.conn,
> + 'eventsloop': self.events}
> models = []
>
> # Import task model from Wok
> diff --git a/tests/test_model_libvirtevents.py b/tests/test_model_libvirtevents.py
> new file mode 100644
> index 0000000..d7dc675
> --- /dev/null
> +++ b/tests/test_model_libvirtevents.py
> @@ -0,0 +1,219 @@
> +# -*- coding: utf-8 -*-
> +#
> +# Project Kimchi
> +#
> +# Copyright IBM Corp, 2016
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> +
> +import json
> +import libvirt
> +import os
> +import shutil
> +import tempfile
> +import time
> +import unittest
> +
> +import tests.utils as utils
> +
> +from wok.basemodel import Singleton
> +from wok.rollbackcontext import RollbackContext
> +
> +from wok.plugins.kimchi.model import model
> +
> +import iso_gen
> +
> +
> +TMP_DIR = '/var/lib/kimchi/tests/'
> +UBUNTU_ISO = TMP_DIR + 'ubuntu14.04.iso'
> +TMP_EVENT = None
> +EVENT_ID = 0
> +
> +
> +def setUpModule():
> + global TMP_DIR, TMP_EVENT
> +
> + if not os.path.exists(TMP_DIR):
> + os.makedirs(TMP_DIR)
> +
> + TMP_EVENT = tempfile.mktemp()
> +
> + iso_gen.construct_fake_iso(UBUNTU_ISO, True, '14.04', 'ubuntu')
> +
> + # Some FeatureTests functions depend on server to validate their result.
> + # As CapabilitiesModel is a Singleton class it will get the first result
> + # from FeatureTests which may be wrong when using the Model instance
> + # directly - the case of this test_model.py
> + # So clean Singleton instances to make sure to get the right result when
> + # running the following tests.
> + Singleton._instances = {}
> +
I don't think we need the above block with the patch you sent to
isolated the test cases.
> +
> +def tearDownModule():
> + global TMP_DIR, TMP_EVENT
> +
> + os.unlink(TMP_EVENT)
> + shutil.rmtree(TMP_DIR)
> +
> +
> +def _get_next_event_id():
> + global EVENT_ID
> + EVENT_ID += 1
> + return EVENT_ID
> +
> +
> +def _get_event_id():
> + global EVENT_ID
> + return EVENT_ID
> +
> +
> +def _store_event(data):
> + global TMP_EVENT
> + with open(TMP_EVENT, "a") as file:
> + file.write("%s\n" % data)
> +
> +
> +def _get_event(id):
> + global TMP_EVENT
> + with open(TMP_EVENT, "r") as file:
> + for event in [line.rstrip('\n') for line in file.readlines()]:
> + fields = event.split('|')
> + if fields[0] == id:
> + return fields[1]
> +
> +
> +class LibvirtEventsTests(unittest.TestCase):
> + def setUp(self):
> + self.tmp_store = tempfile.mktemp()
> +
> + def tearDown(self):
> + os.unlink(self.tmp_store)
> +
> + def domain_event_lifecycle_cb(self, conn, dom, event, detail, *args):
> + """
> + Callback to handle Domain (VMs) events - VM Livecycle.
> + """
> + evStrings = ("Defined", "Undefined", "Started", "Suspended", "Resumed",
> + "Stopped", "Shutdown", "PMSuspended", "Crashed")
> + evDetails = (("Added", "Updated"),
> + ("Removed", ),
> + ("Booted", "Migrated", "Restored", "Snapshot", "Wakeup"),
> + ("Paused", "Migrated", "IOError", "Watchdog", "Restored",
> + "Snapshot", "API error"),
> + ("Unpaused", "Migrated", "Snapshot"),
> + ("Shutdown", "Destroyed", "Crashed", "Migrated", "Saved",
> + "Failed", "Snapshot"),
> + ("Finished", ),
> + ("Memory", "Disk"),
> + ("Panicked"))
> +
> + data = {'domain': dom.name(), 'event': evStrings[event],
> + 'event_detail': evDetails[event][detail]}
> + _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
> +
> + def domain_event_reboot_cb(self, conn, dom, *args):
> + """
> + Callback to handle Domain (VMs) events - VM Reboot.
> + """
> + data = {'domain': dom.name(), 'event': 'Rebooted'}
> + _store_event('%s|%s' % (_get_next_event_id(), json.dumps(data)))
> +
> + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
> + def test_events_vm_lifecycle(self):
> + inst = model.Model(objstore_loc=self.tmp_store)
> + self.objstore = inst.objstore
> + conn = inst.conn.get()
> +
> + # Create a template and VM to test, and start lifecycle tests
> + with RollbackContext() as rollback:
> + # Register the most common Libvirt domain events to be handled.
> + event_map = [(libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
> + self.domain_event_lifecycle_cb),
> + (libvirt.VIR_DOMAIN_EVENT_ID_REBOOT,
> + self.domain_event_reboot_cb)]
> +
> + for event, event_cb in event_map:
> + ev_id = conn.domainEventRegisterAny(None, event, event_cb,
> + None)
> + rollback.prependDefer(conn.domainEventDeregisterAny, ev_id)
> +
> + # Create a template
> + template_params = {'name': 'ttest',
> + 'source_media': {'type': 'disk',
> + 'path': UBUNTU_ISO}}
> +
> + inst.templates_create(template_params)
> + rollback.prependDefer(inst.template_delete, 'ttest')
> +
> + # Create a VM (guest)
> + vm_params = {'name': 'kimchi-vm1',
> + 'template': '/plugins/kimchi/templates/ttest'}
> + task = inst.vms_create(vm_params)
> + inst.task_wait(task['id'], 10)
> + task = inst.task_lookup(task['id'])
> + self.assertEquals('finished', task['status'])
> + time.sleep(5)
> + # Check event of domain definition (addition)
> + res = json.loads(_get_event(str(_get_event_id())))
> + self.assertEquals('kimchi-vm1', res['domain'])
> + self.assertEquals('Defined', res['event'])
> + self.assertEquals('Added', res['event_detail'])
> +
> + # Start the VM and check the event
> + inst.vm_start('kimchi-vm1')
> + time.sleep(5)
> + res = json.loads(_get_event(str(_get_event_id())))
> + self.assertEquals('kimchi-vm1', res['domain'])
> + self.assertEquals('Started', res['event'])
> + self.assertEquals('Booted', res['event_detail'])
> +
> + # Suspend the VM and check the event
> + inst.vm_suspend('kimchi-vm1')
> + time.sleep(5)
> + res = json.loads(_get_event(str(_get_event_id())))
> + self.assertEquals('kimchi-vm1', res['domain'])
> + self.assertEquals('Suspended', res['event'])
> + self.assertEquals('Paused', res['event_detail'])
> +
> + # Resume the VM and check the event
> + inst.vm_resume('kimchi-vm1')
> + time.sleep(5)
> + res = json.loads(_get_event(str(_get_event_id())))
> + self.assertEquals('kimchi-vm1', res['domain'])
> + self.assertEquals('Resumed', res['event'])
> + self.assertEquals('Unpaused', res['event_detail'])
> +
> + # Reboot the VM and check the event
> + inst.vm_reset('kimchi-vm1')
> + time.sleep(5)
> + res = json.loads(_get_event(str(_get_event_id())))
> + self.assertEquals('kimchi-vm1', res['domain'])
> + self.assertEquals('Rebooted', res['event'])
> +
> + # PowerOff (hard stop) the VM and check the event
> + inst.vm_poweroff('kimchi-vm1')
> + time.sleep(5)
> + res = json.loads(_get_event(str(_get_event_id())))
> + self.assertEquals('kimchi-vm1', res['domain'])
> + self.assertEquals('Stopped', res['event'])
> + self.assertEquals('Destroyed', res['event_detail'])
> +
> + # Delete the VM and check the event
> + inst.vm_delete('kimchi-vm1')
> + time.sleep(5)
> + res = json.loads(_get_event(str(_get_event_id())))
> + self.assertEquals('kimchi-vm1', res['domain'])
> + self.assertEquals('Undefined', res['event'])
> + self.assertEquals('Removed', res['event_detail'])
> --
> 2.5.5
8 years, 7 months
[PATCH][Kimchi 2/2] Modify tests to support mem devs with different sizes
by Rodrigo Trujillo
This patch makes the changes and add tests to check new memory
behavior. It should add (hotplug) a memory device with the additional
memory device amount (instead of devs of 1GB as before).
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
tests/test_model.py | 42 ++++++++++++++++++++++++++++++++++++++----
1 file changed, 38 insertions(+), 4 deletions(-)
diff --git a/tests/test_model.py b/tests/test_model.py
index 737b39c..5f9f042 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -19,6 +19,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import grp
+import lxml.etree as ET
import os
import pwd
import re
@@ -34,7 +35,7 @@ from wok.config import config
from wok.exception import InvalidOperation
from wok.exception import InvalidParameter, NotFoundError, OperationFailed
from wok.rollbackcontext import RollbackContext
-from wok.utils import add_task, get_task_id
+from wok.utils import add_task, convert_data_size, get_task_id
from wok.xmlutils.utils import xpath_get_text
from wok.plugins.gingerbase import netinfo
@@ -58,7 +59,7 @@ NON_NUMA_XML = """
<domain type='kvm'>
<name>non-numa-kimchi-test</name>
<maxMemory slots='2' unit='GiB'>4</maxMemory>
- <memory unit='GiB'>2</memory>
+ <memory unit='GiB'>1</memory>
<os>
<type arch='ppc64'>hvm</type>
<boot dev='hd'/>
@@ -805,7 +806,7 @@ class ModelTests(unittest.TestCase):
config.set("authentication", "method", "pam")
inst = model.Model(None, objstore_loc=self.tmp_store)
orig_params = {'name': 'test',
- 'memory': {'current': 1024, 'maxmemory': 3072},
+ 'memory': {'current': 1024, 'maxmemory': 4096},
'source_media': {'type': 'disk', 'path': UBUNTU_ISO}}
inst.templates_create(orig_params)
@@ -827,9 +828,28 @@ class ModelTests(unittest.TestCase):
inst.vm_update('kimchi-vm1', params)
rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete,
'kimchi-vm1')
- params['memory']['maxmemory'] = 3072
+ params['memory']['maxmemory'] = 4096
self.assertEquals(params['memory'],
inst.vm_lookup('kimchi-vm1')['memory'])
+
+ params['memory']['current'] = 4096
+ del params['memory']['maxmemory']
+ inst.vm_update('kimchi-vm1', params)
+ vm = inst.vm_lookup('kimchi-vm1')
+ self.assertEquals(4096, vm['memory']['current'])
+
+ # Test memory devices
+ conn = inst.conn.get()
+ xml = conn.lookupByName('kimchi-vm1').XMLDesc()
+ root = ET.fromstring(xml)
+ devs = root.findall('./devices/memory/target/size')
+ self.assertEquals(2, len(devs))
+ totMemDevs = 0
+ for size in devs:
+ totMemDevs += convert_data_size(size.text,
+ size.get('unit'),
+ 'MiB')
+ self.assertEquals(3072, totMemDevs)
else:
self.assertRaises(InvalidOperation, inst.vm_update,
'kimchi-vm1', params)
@@ -856,11 +876,25 @@ class ModelTests(unittest.TestCase):
self.assertEquals(params['memory']['current'],
inst.vm_lookup(vm)['memory']['current'])
+ # Test number and size of memory device added
+ root = ET.fromstring(conn.lookupByName(vm).XMLDesc())
+ devs = root.findall('./devices/memory/target/size')
+ self.assertEquals(1, len(devs))
+ self.assertEquals(2048 << 10, int(devs[0].text))
+
params = {'memory': {'current': 4096}}
inst.vm_update(vm, params)
self.assertEquals(params['memory']['current'],
inst.vm_lookup(vm)['memory']['current'])
+ # Test number and size of memory device added
+ root = ET.fromstring(conn.lookupByName(vm).XMLDesc())
+ devs = root.findall('./devices/memory/target/size')
+ self.assertEquals(2, len(devs))
+ self.assertEquals(1024 << 10, int(devs[1].text))
+ self.assertEquals(3072 << 10,
+ int(devs[0].text) + int(devs[1].text))
+
# Stop vm and test persistence
inst.vm_poweroff(vm)
self.assertEquals(params['memory']['current'],
--
2.1.0
8 years, 7 months
[PATCH][Kimchi 1/2] Change memory hotplug to support more than 32GB
by Rodrigo Trujillo
This patch removes the restriction of only allow 32 memory devices
of 1GB. Now, Kimchi is going to add an unique memory device with the
amount of memory to be increased (per request).
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
i18n.py | 3 +--
model/vms.py | 81 ++++++++++++++++++++++++++----------------------------------
2 files changed, 36 insertions(+), 48 deletions(-)
diff --git a/i18n.py b/i18n.py
index 2301e60..9f40298 100644
--- a/i18n.py
+++ b/i18n.py
@@ -97,8 +97,7 @@ messages = {
"KCHVM0041E": _("Memory assigned is higher then the maximum allowed in the host: %(maxmem)sMib."),
"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."),
+ "KCHVM0045E": _("There are not enough free slots to add a new memory device."),
"KCHVM0046E": _("Host's libvirt or qemu version does not support memory devices and memory hotplug. Libvirt must be >= 1.2.14 and QEMU must be >= 2.1."),
"KCHVM0047E": _("Error attaching memory device. Details: %(error)s"),
"KCHVM0048E": _("Cannot start %(name)s. Virtual machine is already running."),
diff --git a/model/vms.py b/model/vms.py
index ef53e38..ae77d3d 100644
--- a/model/vms.py
+++ b/model/vms.py
@@ -971,6 +971,15 @@ class VMModel(object):
dom.isActive()):
self._update_memory_live(dom, params)
+ def _get_mem_dev_total_size(self, xml):
+ root = ET.fromstring(xml)
+ totMemDevs = 0
+ for size in root.findall('./devices/memory/target/size'):
+ totMemDevs += convert_data_size(size.text,
+ size.get('unit'),
+ 'MiB')
+ return totMemDevs
+
def _update_memory_live(self, dom, params):
# Check if host supports memory device
if not self.caps.mem_hotplug_support:
@@ -979,56 +988,42 @@ class VMModel(object):
xml = dom.XMLDesc(0)
max_mem = xpath_get_text(xml, './maxMemory')
if max_mem == []:
- raise OperationFailed('KCHVM0042E', {'name': dom.name()})
+ raise InvalidOperation('KCHVM0042E', {'name': dom.name()})
- # Memory live update must be done in chunks of 1024 Mib or 1Gib
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')
+ memory = new_mem - old_mem
+ flags = libvirt.VIR_DOMAIN_MEM_CONFIG | libvirt.VIR_DOMAIN_MEM_LIVE
- # 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
- used_slots = len(xpath_get_text(xml, './devices/memory'))
- if needed_slots > (total_slots - used_slots):
- raise OperationFailed('KCHVM0045E')
- elif needed_slots == 0:
- # New memory value is same that current memory set
+ if distro == "IBM_PowerKVM":
+ # make sure memory is alingned in 256MiB in PowerPC
+ if (new_mem % PPC_MEM_ALIGN != 0):
+ raise InvalidParameter('KCHVM0071E',
+ {'param': "Memory",
+ 'mem': str(new_mem),
+ 'alignment': str(PPC_MEM_ALIGN)})
+ # PPC suports only 32 memory slots
+ if len(xpath_get_text(xml, './devices/memory')) == 32:
+ raise InvalidOperation('KCHVM0045E')
+
+ if memory == 0:
+ # Nothing to do
return
+ if memory < 0:
+ raise InvalidOperation('KCHVM0043E')
- distro, _, _ = platform.linux_distribution()
- if distro == "IBM_PowerKVM" and needed_slots > 32:
- raise OperationFailed('KCHVM0045E')
-
- # Finally, we are ok to hot add the memory devices
+ # Finally HotPlug operation ( memory > 0 )
try:
- self._hot_add_memory_devices(dom, needed_slots)
+ # Create memory device xml
+ tmp_xml = E.memory(E.target(E.size(str(memory),
+ unit='MiB')), model='dimm')
+ if has_cpu_numa(dom):
+ tmp_xml.find('target').append(E.node('0'))
+ dom.attachDeviceFlags(etree.tostring(tmp_xml), flags)
except Exception as e:
raise OperationFailed("KCHVM0047E", {'error': e.message})
- def _hot_add_memory_devices(self, dom, amount):
- # Hot add given number of memory devices in the guest
- flags = libvirt.VIR_DOMAIN_MEM_CONFIG | libvirt.VIR_DOMAIN_MEM_LIVE
- # Create memory device xml
- tmp_xml = E.memory(E.target(E.size('1', unit='GiB')), model='dimm')
- if has_cpu_numa(dom):
- tmp_xml.find('target').append(E.node('0'))
- mem_dev_xml = etree.tostring(tmp_xml)
- # Add chunks of 1G of memory
- for i in range(amount):
- dom.attachDeviceFlags(mem_dev_xml, flags)
-
def _has_video(self, dom):
dom = ElementTree.fromstring(dom.XMLDesc(0))
return dom.find('devices/video') is not None
@@ -1217,13 +1212,7 @@ class VMModel(object):
memory = dom.maxMemory() >> 10
curr_mem = (info[2] >> 10)
if memory != curr_mem:
- root = ET.fromstring(xml)
- totMemDevs = 0
- for size in root.findall('./devices/memory/target/size'):
- totMemDevs += convert_data_size(size.text,
- size.get('unit'),
- 'MiB')
- memory = curr_mem + totMemDevs
+ memory = curr_mem + self._get_mem_dev_total_size(xml)
# assure there is no zombie process left
for proc in self._serial_procs[:]:
--
2.1.0
8 years, 7 months
[PATCH][Kimchi 2/2] Make static and live update functions independent
by Rodrigo Trujillo
Static and Live update functions were always called and VM
state was checked inside functions.
This patch makes functions be called separately and remove
unsed state checking. This separation is also important because
during live updates the static funtion is called and the guest
xml is updated. This might cause errors.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
i18n.py | 1 -
model/vms.py | 47 +++++++++++++++++++++++++----------------------
tests/test_model.py | 2 +-
3 files changed, 26 insertions(+), 24 deletions(-)
diff --git a/i18n.py b/i18n.py
index 0340303..b4f758e 100644
--- a/i18n.py
+++ b/i18n.py
@@ -59,7 +59,6 @@ messages = {
"KCHVM0001E": _("Virtual machine %(name)s already exists"),
"KCHVM0002E": _("Virtual machine %(name)s does not exist"),
- "KCHVM0003E": _("Unable to rename virtual machine %(name)s. The name %(new_name)s is already in use or the virtual machine is not powered off."),
"KCHVM0004E": _("Unable to retrieve screenshot for stopped virtual machine %(name)s"),
"KCHVM0005E": _("Remote ISO image is not supported by this server."),
"KCHVM0006E": _("Screenshot is not supported on virtual machine %(name)s"),
diff --git a/model/vms.py b/model/vms.py
index 48196dd..f5323ac 100644
--- a/model/vms.py
+++ b/model/vms.py
@@ -272,8 +272,20 @@ class VMModel(object):
raise InvalidParameter('KCHVM0074E',
{'params': ', '.join(ext_params)})
- self._live_vm_update(dom, params)
- vm_name, dom = self._static_vm_update(name, dom, params)
+ # METADATA can be updated offline or online
+ self._vm_update_access_metadata(dom, params)
+
+ # GRAPHICS can be updated offline or online
+ if 'graphics' in params:
+ dom = self._update_graphics(dom, params)
+
+ # Live updates
+ if dom.isActive():
+ self._live_vm_update(dom, params)
+
+ vm_name = name
+ if (DOM_STATE_MAP[dom.info()[0]] == 'shutoff'):
+ vm_name, dom = self._static_vm_update(name, dom, params)
return vm_name
def clone(self, name):
@@ -650,11 +662,11 @@ class VMModel(object):
os_elem = ET.fromstring(os_xml)
return (os_elem.attrib.get("version"), os_elem.attrib.get("distro"))
- def _update_graphics(self, dom, xml, params):
- root = objectify.fromstring(xml)
+ def _update_graphics(self, dom, params):
+ root = objectify.fromstring(dom.XMLDesc(0))
graphics = root.devices.find("graphics")
if graphics is None:
- return xml
+ return dom
password = params['graphics'].get("passwd")
if password is not None and len(password.strip()) == 0:
@@ -676,13 +688,14 @@ class VMModel(object):
valid_to = time.strftime('%Y-%m-%dT%H:%M:%S', expire_time)
graphics.attrib['passwdValidTo'] = valid_to
+ conn = self.conn.get()
if not dom.isActive():
- return ET.tostring(root, encoding="utf-8")
+ return conn.defineXML(ET.tostring(root, encoding="utf-8"))
xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
dom.updateDeviceFlags(etree.tostring(graphics),
libvirt.VIR_DOMAIN_AFFECT_LIVE)
- return xml
+ return conn.defineXML(xml)
def _backup_snapshots(self, snap, all_info):
""" Append "snap" and the children of "snap" to the list "all_info".
@@ -724,19 +737,14 @@ class VMModel(object):
remove_metadata_node(dom, 'name')
def _static_vm_update(self, vm_name, dom, params):
- old_xml = new_xml = dom.XMLDesc(0)
+ old_xml = new_xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
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:
- if state != 'shutoff':
- msg_args = {'name': vm_name, 'new_name': params['name']}
- raise InvalidParameter("KCHVM0003E", msg_args)
-
params['name'], nonascii_name = get_ascii_nonascii_name(name)
new_xml = xml_item_update(new_xml, XPATH_NAME, name, None)
@@ -770,14 +778,10 @@ class VMModel(object):
# topology is being undefined: remove it
new_xml = xml_item_remove(new_xml, XPATH_TOPOLOGY)
- # Updating memory if vm is offline
- if (not dom.isActive() and 'memory' in params and
- params['memory'] != {}):
+ # Updating memory
+ if ('memory' in params and params['memory'] != {}):
new_xml = self._update_memory_config(new_xml, params, dom)
- if 'graphics' in params:
- new_xml = self._update_graphics(dom, new_xml, params)
-
snapshots_info = []
conn = self.conn.get()
try:
@@ -954,9 +958,8 @@ class VMModel(object):
return cpu_info
def _live_vm_update(self, dom, params):
- self._vm_update_access_metadata(dom, params)
- if (('memory' in params) and ('current' in params['memory']) and
- dom.isActive()):
+ # Memory Hotplug/Unplug
+ if (('memory' in params) and ('current' in params['memory'])):
self._update_memory_live(dom, params)
def _get_mem_dev_total_size(self, xml):
diff --git a/tests/test_model.py b/tests/test_model.py
index 6b72bd0..f4a145f 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -957,7 +957,7 @@ class ModelTests(unittest.TestCase):
# template disk format must be qcow2 because vmsnapshot
# only supports this format
orig_params = {
- 'name': 'test', 'memory': {'current': 1024},
+ 'name': 'test', 'memory': {'current': 1024, 'maxmemory': 2048},
'cpu_info': {'vcpus': 1},
'source_media': {'type': 'disk', 'path': UBUNTU_ISO},
'disks': [{'size': 1, 'format': 'qcow2', 'pool': {
--
2.1.0
8 years, 7 months
[PATCH][Kimchi 1/2] Modify mockmodel to support memory devices
by Rodrigo Trujillo
Mock model functions were doubling memory devices, causing errors in
tests with latest code changes in memory hotplug.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
mockmodel.py | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/mockmodel.py b/mockmodel.py
index 38d08b5..4617668 100644
--- a/mockmodel.py
+++ b/mockmodel.py
@@ -28,7 +28,7 @@ from lxml.builder import E
from wok.exception import NotFoundError, OperationFailed
from wok.objectstore import ObjectStore
-from wok.utils import add_task
+from wok.utils import add_task, convert_data_size
from wok.xmlutils.utils import xml_item_update
from wok.plugins.kimchi import imageinfo
@@ -172,9 +172,18 @@ class MockModel(Model):
xml = MockModel._XMLDesc(dom, flags)
root = objectify.fromstring(xml)
+ mem_devs = root.findall('./devices/memory')
for dev_xml in MockModel._mock_vms.get(dom.name(), []):
dev = objectify.fromstring(dev_xml)
- root.devices.append(dev)
+ add = True
+ if dev.tag == 'memory':
+ for mem_dev in mem_devs:
+ if mem_dev.target.size == dev.target.size:
+ mem_devs.remove(mem_dev)
+ add = False
+ break
+ if add:
+ root.devices.append(dev)
return ET.tostring(root, encoding="utf-8")
@staticmethod
@@ -186,6 +195,15 @@ class MockModel(Model):
@staticmethod
def attachDeviceFlags(dom, xml, flags=0):
+ myxml = objectify.fromstring(xml)
+ if myxml.tag == 'memory':
+ unit = myxml.target.size.get('unit')
+ myxml.target.size.set('unit', 'KiB')
+ myxml.target.size._setText(str(int(convert_data_size(
+ myxml.target.size.text,
+ unit, 'KiB'))))
+ xml = ET.tostring(myxml)
+ dom.setMaxMemory(int(dom.maxMemory() + myxml.target.size))
MockModel._mock_vms[dom.name()].append(xml)
@staticmethod
--
2.1.0
8 years, 7 months
[PATCH][Kimchi 2/2] Add test case to test new slots value implementation
by Rodrigo Trujillo
Adds new test case to check if a new VM has the right slot value.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
tests/test_vmtemplate.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py
index 97c69ec..74816ef 100644
--- a/tests/test_vmtemplate.py
+++ b/tests/test_vmtemplate.py
@@ -25,7 +25,7 @@ import uuid
from wok.xmlutils.utils import xpath_get_text
-from wok.plugins.kimchi.osinfo import get_template_default
+from wok.plugins.kimchi.osinfo import get_template_default, MEM_DEV_SLOTS
from wok.plugins.kimchi.vmtemplate import VMTemplate
DISKS = [{'size': 10, 'format': 'raw', 'index': 0, 'pool': {'name':
@@ -87,6 +87,15 @@ class VMTemplateTests(unittest.TestCase):
self.assertEquals(graphics['type'], t.info['graphics']['type'])
self.assertEquals('127.0.0.1', t.info['graphics']['listen'])
+ def test_mem_dev_slots(self):
+ vm_uuid = str(uuid.uuid4()).replace('-', '')
+ t = VMTemplate({'name': 'test-template', 'cdrom': self.iso,
+ 'memory': {'current': 2048, 'maxmemory': 3072}})
+ xml = t.to_vm_xml('test-vm', vm_uuid)
+ expr = "/domain/maxMemory/@slots"
+ slots = str(MEM_DEV_SLOTS[os.uname()[4]])
+ self.assertEquals(slots, xpath_get_text(xml, expr)[0])
+
def test_to_xml(self):
graphics = {'type': 'spice', 'listen': '127.0.0.1'}
vm_uuid = str(uuid.uuid4()).replace('-', '')
--
2.1.0
8 years, 7 months
[PATCH][Kimchi 1/2] Modify max memory slots default numbers
by Rodrigo Trujillo
This patch sets the number of available memory device slots to the
number supported by each architecture in qemu/libvirt. Without the
restriction of 1GB per device, now kimchi Will allow users to hotplug
more devices than previous implementarion.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
model/vms.py | 23 ++---------------------
osinfo.py | 11 +++++++++++
vmtemplate.py | 20 +++++---------------
3 files changed, 18 insertions(+), 36 deletions(-)
diff --git a/model/vms.py b/model/vms.py
index 0108381..48196dd 100644
--- a/model/vms.py
+++ b/model/vms.py
@@ -58,6 +58,7 @@ 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.osinfo import defaults
from wok.plugins.kimchi.screenshot import VMScreenshot
from wok.plugins.kimchi.utils import get_next_clone_name
from wok.plugins.kimchi.utils import template_name_from_uri
@@ -858,23 +859,6 @@ class VMModel(object):
elif newMem == (oldMem << 10):
newMem = newMem - memDevsAmount
- 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
@@ -886,7 +870,7 @@ class VMModel(object):
max_mem_xml = E.maxMemory(
str(newMaxMem),
unit='Kib',
- slots=str(_get_slots(newMem, newMaxMem)))
+ slots=str(defaults['mem_dev_slots']))
root.insert(0, max_mem_xml)
elif (maxMemTag is None) and (newMem == newMaxMem):
# Nothing to do
@@ -894,7 +878,6 @@ class VMModel(object):
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):
if self._get_mem_dev_total_size(ET.tostring(root)) == 0:
# Remove the tag
@@ -928,8 +911,6 @@ class VMModel(object):
if (newMem == newMaxMem and
(self._get_mem_dev_total_size(ET.tostring(root)) == 0)):
root.remove(maxMemTag)
- else:
- maxMemTag.set('slots', str(_get_slots(newMem, newMaxMem)))
# Setting memory hard limit to max_memory + 1GiB
memtune = root.find('memtune')
diff --git a/osinfo.py b/osinfo.py
index 7b80f29..d3909ad 100644
--- a/osinfo.py
+++ b/osinfo.py
@@ -34,6 +34,14 @@ SUPPORTED_ARCHS = {'x86': ('i386', 'i686', 'x86_64'),
'ppc64le': ('ppc64le')}
+# Memory devices slot limits by architecture
+MEM_DEV_SLOTS = {'ppc64': 32,
+ 'ppc64le': 32,
+ 'x86_64': 256,
+ 'i686': 256,
+ 'i386': 256}
+
+
template_specs = {'x86': {'old': dict(disk_bus='ide',
nic_model='e1000', sound_model='ich6'),
'modern': dict(disk_bus='virtio',
@@ -175,6 +183,9 @@ def _get_tmpl_defaults():
# Update defaults values with graphics values
defaults['graphics'] = default_config.pop('graphics')
+ # Setting default memory device slots
+ defaults['mem_dev_slots'] = MEM_DEV_SLOTS.get(os.uname()[4], 32)
+
return defaults
diff --git a/vmtemplate.py b/vmtemplate.py
index 5e38275..ef92914 100644
--- a/vmtemplate.py
+++ b/vmtemplate.py
@@ -18,7 +18,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import os
-import platform
import stat
import time
import urlparse
@@ -369,25 +368,16 @@ class VMTemplate(object):
# TODO: need modify this when boot order edition feature came upstream.
params['boot_order'] = get_bootorder_xml()
- # 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.
+ # Setting maximum number of memory slots
+ slots = str(self.info['mem_dev_slots'])
+
+ # Rearrange memory parameters
memory = self.info['memory'].get('current')
maxmemory = self.info['memory'].get('maxmemory')
-
- slots = (maxmemory - memory) >> 10
- if slots < 0:
+ if maxmemory < memory:
raise OperationFailed("KCHVM0041E",
{'maxmem': str(maxmemory)})
- elif slots == 0:
- slots = 1
- elif slots > 32:
- distro, _, _ = platform.linux_distribution()
- if distro == "IBM_PowerKVM":
- slots = 32
- # Rearrange memory parameters
params['memory'] = self.info['memory'].get('current')
params['max_memory'] = ""
# if there is not support to memory hotplug in Libvirt or qemu, we
--
2.1.0
8 years, 7 months
[PATCH][Kimchi 2/2] Modify tests to support mem devs with different sizes
by Rodrigo Trujillo
This patch makes the changes and add tests to check new memory
behavior. It should add (hotplug) a memory device with the additional
memory device amount (instead of devs of 1GB as before).
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
tests/test_model.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 87 insertions(+), 4 deletions(-)
diff --git a/tests/test_model.py b/tests/test_model.py
index 737b39c..6b72bd0 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -19,6 +19,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import grp
+import lxml.etree as ET
import os
import pwd
import re
@@ -34,7 +35,7 @@ from wok.config import config
from wok.exception import InvalidOperation
from wok.exception import InvalidParameter, NotFoundError, OperationFailed
from wok.rollbackcontext import RollbackContext
-from wok.utils import add_task, get_task_id
+from wok.utils import add_task, convert_data_size, get_task_id
from wok.xmlutils.utils import xpath_get_text
from wok.plugins.gingerbase import netinfo
@@ -58,7 +59,7 @@ NON_NUMA_XML = """
<domain type='kvm'>
<name>non-numa-kimchi-test</name>
<maxMemory slots='2' unit='GiB'>4</maxMemory>
- <memory unit='GiB'>2</memory>
+ <memory unit='GiB'>1</memory>
<os>
<type arch='ppc64'>hvm</type>
<boot dev='hd'/>
@@ -805,7 +806,7 @@ class ModelTests(unittest.TestCase):
config.set("authentication", "method", "pam")
inst = model.Model(None, objstore_loc=self.tmp_store)
orig_params = {'name': 'test',
- 'memory': {'current': 1024, 'maxmemory': 3072},
+ 'memory': {'current': 1024, 'maxmemory': 4096},
'source_media': {'type': 'disk', 'path': UBUNTU_ISO}}
inst.templates_create(orig_params)
@@ -827,9 +828,77 @@ class ModelTests(unittest.TestCase):
inst.vm_update('kimchi-vm1', params)
rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete,
'kimchi-vm1')
- params['memory']['maxmemory'] = 3072
+ params['memory']['maxmemory'] = 4096
self.assertEquals(params['memory'],
inst.vm_lookup('kimchi-vm1')['memory'])
+
+ params['memory']['current'] = 4096
+ del params['memory']['maxmemory']
+ inst.vm_update('kimchi-vm1', params)
+ vm = inst.vm_lookup('kimchi-vm1')
+ self.assertEquals(4096, vm['memory']['current'])
+
+ # Test memory devices
+ conn = inst.conn.get()
+ xml = conn.lookupByName('kimchi-vm1').XMLDesc()
+ root = ET.fromstring(xml)
+ devs = root.findall('./devices/memory/target/size')
+ self.assertEquals(2, len(devs))
+ totMemDevs = 0
+ for size in devs:
+ totMemDevs += convert_data_size(size.text,
+ size.get('unit'),
+ 'MiB')
+ self.assertEquals(3072, totMemDevs)
+
+ inst.vm_poweroff('kimchi-vm1')
+ # Remove all devs:
+ params = {'memory': {'current': 1024}}
+ inst.vm_update('kimchi-vm1', params)
+ xml = conn.lookupByName('kimchi-vm1').XMLDesc()
+ root = ET.fromstring(xml)
+ devs = root.findall('./devices/memory')
+ self.assertEquals(0, len(devs))
+
+ # Hotplug 1G DIMM , 512M , 256M and 256M
+ inst.vm_start('kimchi-vm1')
+ params = {'memory': {'current': 2048}}
+ inst.vm_update('kimchi-vm1', params)
+ params = {'memory': {'current': 2560}}
+ inst.vm_update('kimchi-vm1', params)
+ params = {'memory': {'current': 2816}}
+ inst.vm_update('kimchi-vm1', params)
+ params = {'memory': {'current': 3072}}
+ inst.vm_update('kimchi-vm1', params)
+
+ vm = inst.vm_lookup('kimchi-vm1')
+ self.assertEquals(3072, vm['memory']['current'])
+
+ xml = conn.lookupByName('kimchi-vm1').XMLDesc()
+ root = ET.fromstring(xml)
+ devs = root.findall('./devices/memory/target/size')
+ self.assertEquals(4, len(devs))
+ totMemDevs = 0
+ for size in devs:
+ totMemDevs += convert_data_size(size.text,
+ size.get('unit'),
+ 'MiB')
+ self.assertEquals(2048, totMemDevs)
+
+ inst.vm_poweroff('kimchi-vm1')
+ # Remove 2x256M + 1x512M ... then sum 256M to virtual memory
+ params = {'memory': {'current': 2304}}
+ inst.vm_update('kimchi-vm1', params)
+ xml = conn.lookupByName('kimchi-vm1').XMLDesc()
+ root = ET.fromstring(xml)
+ devs = root.findall('./devices/memory/target/size')
+ self.assertEquals(1, len(devs))
+ totMemDevs = 0
+ for size in devs:
+ totMemDevs += convert_data_size(size.text,
+ size.get('unit'),
+ 'MiB')
+ self.assertEquals(1024, totMemDevs)
else:
self.assertRaises(InvalidOperation, inst.vm_update,
'kimchi-vm1', params)
@@ -856,11 +925,25 @@ class ModelTests(unittest.TestCase):
self.assertEquals(params['memory']['current'],
inst.vm_lookup(vm)['memory']['current'])
+ # Test number and size of memory device added
+ root = ET.fromstring(conn.lookupByName(vm).XMLDesc())
+ devs = root.findall('./devices/memory/target/size')
+ self.assertEquals(1, len(devs))
+ self.assertEquals(2048 << 10, int(devs[0].text))
+
params = {'memory': {'current': 4096}}
inst.vm_update(vm, params)
self.assertEquals(params['memory']['current'],
inst.vm_lookup(vm)['memory']['current'])
+ # Test number and size of memory device added
+ root = ET.fromstring(conn.lookupByName(vm).XMLDesc())
+ devs = root.findall('./devices/memory/target/size')
+ self.assertEquals(2, len(devs))
+ self.assertEquals(1024 << 10, int(devs[1].text))
+ self.assertEquals(3072 << 10,
+ int(devs[0].text) + int(devs[1].text))
+
# Stop vm and test persistence
inst.vm_poweroff(vm)
self.assertEquals(params['memory']['current'],
--
2.1.0
8 years, 7 months
[PATCH][Kimchi 1/2] Change memory hotplug to support more than 32GB
by Rodrigo Trujillo
This patch removes the restriction of only allow 32 memory devices
of 1GB. Now, Kimchi is going to add an unique memory device with the
amount of memory to be increased (per request).
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
i18n.py | 3 +-
model/vms.py | 120 +++++++++++++++++++++++++++++------------------------------
2 files changed, 59 insertions(+), 64 deletions(-)
diff --git a/i18n.py b/i18n.py
index cfbc81e..0340303 100644
--- a/i18n.py
+++ b/i18n.py
@@ -97,8 +97,7 @@ messages = {
"KCHVM0041E": _("Memory assigned is higher then the maximum allowed in the host: %(maxmem)sMib."),
"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."),
+ "KCHVM0045E": _("There are not enough free slots to add a new memory device."),
"KCHVM0046E": _("Host's libvirt or qemu version does not support memory devices and memory hotplug. Libvirt must be >= 1.2.14 and QEMU must be >= 2.1."),
"KCHVM0047E": _("Error attaching memory device. Details: %(error)s"),
"KCHVM0048E": _("Cannot start %(name)s. Virtual machine is already running."),
diff --git a/model/vms.py b/model/vms.py
index ef53e38..0108381 100644
--- a/model/vms.py
+++ b/model/vms.py
@@ -837,23 +837,26 @@ class VMModel(object):
# Adjust memory devices to new memory, if necessary
memDevs = root.findall('./devices/memory')
+ memDevsAmount = self._get_mem_dev_total_size(ET.tostring(root))
+
if len(memDevs) != 0 and hasMem:
- if newMem == newMaxMem:
+ if newMem > (oldMem << 10):
+ newMem = newMem - memDevsAmount
+ elif newMem < (oldMem << 10):
+ memDevs.reverse()
+ totRemoved = 0
for dev in memDevs:
+ size = dev.find('./target/size')
+ totRemoved += int(convert_data_size(size.text,
+ size.get('unit'),
+ 'KiB'))
root.find('./devices').remove(dev)
- elif newMem > (oldMem << 10):
- newMem = newMem - (len(memDevs) * (1024 << 10))
- elif newMem < (oldMem << 10):
- devsRemove = len(memDevs) - (oldMem - (newMem >> 10))/1024 - 1
- for dev in enumerate(memDevs):
- if dev[0] > devsRemove:
- root.find('./devices').remove(dev[1])
- newMem = \
- newMem - (
- len(root.findall('./devices/memory')) * 1024 << 10
- )
+ if ((oldMem << 10) - totRemoved) <= newMem:
+ newMem = newMem - self._get_mem_dev_total_size(
+ ET.tostring(root))
+ break
elif newMem == (oldMem << 10):
- newMem = newMem - ((len(memDevs) * 1024) << 10)
+ newMem = newMem - memDevsAmount
def _get_slots(mem, maxMem):
slots = (maxMem - mem) >> 10 >> 10
@@ -893,8 +896,11 @@ class VMModel(object):
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)
+ if self._get_mem_dev_total_size(ET.tostring(root)) == 0:
+ # Remove the tag
+ root.remove(maxMemTag)
+ else:
+ maxMemTag.text = str(newMaxMem)
# Update memory, if necessary
if hasMem:
@@ -919,7 +925,8 @@ class VMModel(object):
memory.text = str(newMem)
if (maxMemTag is not None) and (not hasMaxMem):
- if newMem == newMaxMem:
+ if (newMem == newMaxMem and
+ (self._get_mem_dev_total_size(ET.tostring(root)) == 0)):
root.remove(maxMemTag)
else:
maxMemTag.set('slots', str(_get_slots(newMem, newMaxMem)))
@@ -971,6 +978,15 @@ class VMModel(object):
dom.isActive()):
self._update_memory_live(dom, params)
+ def _get_mem_dev_total_size(self, xml):
+ root = ET.fromstring(xml)
+ totMemDevs = 0
+ for size in root.findall('./devices/memory/target/size'):
+ totMemDevs += convert_data_size(size.text,
+ size.get('unit'),
+ 'KiB')
+ return int(totMemDevs)
+
def _update_memory_live(self, dom, params):
# Check if host supports memory device
if not self.caps.mem_hotplug_support:
@@ -979,56 +995,42 @@ class VMModel(object):
xml = dom.XMLDesc(0)
max_mem = xpath_get_text(xml, './maxMemory')
if max_mem == []:
- raise OperationFailed('KCHVM0042E', {'name': dom.name()})
+ raise InvalidOperation('KCHVM0042E', {'name': dom.name()})
- # Memory live update must be done in chunks of 1024 Mib or 1Gib
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')
+ memory = new_mem - old_mem
+ flags = libvirt.VIR_DOMAIN_MEM_CONFIG | libvirt.VIR_DOMAIN_MEM_LIVE
- # 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
- used_slots = len(xpath_get_text(xml, './devices/memory'))
- if needed_slots > (total_slots - used_slots):
- raise OperationFailed('KCHVM0045E')
- elif needed_slots == 0:
- # New memory value is same that current memory set
+ if distro == "IBM_PowerKVM":
+ # make sure memory is alingned in 256MiB in PowerPC
+ if (new_mem % PPC_MEM_ALIGN != 0):
+ raise InvalidParameter('KCHVM0071E',
+ {'param': "Memory",
+ 'mem': str(new_mem),
+ 'alignment': str(PPC_MEM_ALIGN)})
+ # PPC suports only 32 memory slots
+ if len(xpath_get_text(xml, './devices/memory')) == 32:
+ raise InvalidOperation('KCHVM0045E')
+
+ if memory == 0:
+ # Nothing to do
return
+ if memory < 0:
+ raise InvalidOperation('KCHVM0043E')
- distro, _, _ = platform.linux_distribution()
- if distro == "IBM_PowerKVM" and needed_slots > 32:
- raise OperationFailed('KCHVM0045E')
-
- # Finally, we are ok to hot add the memory devices
+ # Finally HotPlug operation ( memory > 0 )
try:
- self._hot_add_memory_devices(dom, needed_slots)
+ # Create memory device xml
+ tmp_xml = E.memory(E.target(E.size(str(memory),
+ unit='MiB')), model='dimm')
+ if has_cpu_numa(dom):
+ tmp_xml.find('target').append(E.node('0'))
+ dom.attachDeviceFlags(etree.tostring(tmp_xml), flags)
except Exception as e:
raise OperationFailed("KCHVM0047E", {'error': e.message})
- def _hot_add_memory_devices(self, dom, amount):
- # Hot add given number of memory devices in the guest
- flags = libvirt.VIR_DOMAIN_MEM_CONFIG | libvirt.VIR_DOMAIN_MEM_LIVE
- # Create memory device xml
- tmp_xml = E.memory(E.target(E.size('1', unit='GiB')), model='dimm')
- if has_cpu_numa(dom):
- tmp_xml.find('target').append(E.node('0'))
- mem_dev_xml = etree.tostring(tmp_xml)
- # Add chunks of 1G of memory
- for i in range(amount):
- dom.attachDeviceFlags(mem_dev_xml, flags)
-
def _has_video(self, dom):
dom = ElementTree.fromstring(dom.XMLDesc(0))
return dom.find('devices/video') is not None
@@ -1217,13 +1219,7 @@ class VMModel(object):
memory = dom.maxMemory() >> 10
curr_mem = (info[2] >> 10)
if memory != curr_mem:
- root = ET.fromstring(xml)
- totMemDevs = 0
- for size in root.findall('./devices/memory/target/size'):
- totMemDevs += convert_data_size(size.text,
- size.get('unit'),
- 'MiB')
- memory = curr_mem + totMemDevs
+ memory = curr_mem + (self._get_mem_dev_total_size(xml) >> 10)
# assure there is no zombie process left
for proc in self._serial_procs[:]:
--
2.1.0
8 years, 7 months