Commit 81d58495 authored by Alain Takoudjou's avatar Alain Takoudjou

qemu qmp: add support for cpu and memory hotplug in qemu

parent d8ef2aa7
......@@ -31,6 +31,7 @@ import os
import pprint
import socket
import time
from operator import itemgetter
def parseArgument():
"""
......@@ -45,11 +46,18 @@ def parseArgument():
parser.add_argument('--delete-internal-snapshot', action='store_const', dest='action', const='deleteInternalSnapshot')
parser.add_argument('--drive-backup', action='store_const', dest='action', const='driveBackup')
parser.add_argument('--query-commands', action='store_const', dest='action', const='queryCommands')
parser.add_argument('--query-item', dest='query', choices=[
"cpus", "hotpluggable-cpus", "memory-devices", "memdev", "balloon",
"pci", "status", "acpi-ospm-status"])
parser.add_argument('--update-device', action='store_const', dest='action', const='updateDevice')
parser.add_argument('--device-options', dest='device_options',
help="Option used for update-device:\n" \
'"device=cpu,amount=VALUE_INT[,model=MODEL]"\n OR '\
'"device=memory,mem=VALUE_MB[,slot=VALUE_MB],nslot=VALUE_INT"')
parser.add_argument('--socket', dest='unix_socket_location', required=True)
parser.add_argument('remainding_argument_list', nargs=argparse.REMAINDER)
args = parser.parse_args()
return args.unix_socket_location, args.action, args.remainding_argument_list
return parser.parse_args()
class QemuQMPWrapper(object):
......@@ -58,9 +66,10 @@ class QemuQMPWrapper(object):
See http://git.qemu.org/?p=qemu.git;a=blob;f=qmp-commands.hx for
QMP API definition.
"""
def __init__(self, unix_socket_location):
self.socket = self.connectToQemu(unix_socket_location)
self.capabilities()
def __init__(self, unix_socket_location, auto_connect=True):
if auto_connect:
self.socket = self.connectToQemu(unix_socket_location)
self.capabilities()
@staticmethod
def connectToQemu(unix_socket_location):
......@@ -86,11 +95,14 @@ class QemuQMPWrapper(object):
return so
def _send(self, message):
def _send(self, message, check_result=False):
self.socket.send(json.dumps(message))
data = self.socket.recv(65535)
try:
return json.loads(data)
result = json.loads(data)
if check_result and result.get('return', None) != {} and 'error' in data:
raise Exception('ERROR: %s' % data)
return result
except ValueError:
# if error the raise
if "error" in data:
......@@ -98,6 +110,15 @@ class QemuQMPWrapper(object):
else:
print 'Wrong data: %s' % data
def _sendRetry(self, message):
"""
Send Qmp message and retry once if the result is None
"""
result = self._send(message)
if result is None:
return self._send(message)
return result
def _getVMStatus(self):
response = self._send({'execute': 'query-status'})
if response:
......@@ -118,11 +139,29 @@ class QemuQMPWrapper(object):
except IOError:
print 'VM not ready, retrying...'
def capabilities(self):
print 'Asking for capabilities...'
self._send({'execute': 'qmp_capabilities'})
def setVNCPassword(self, password):
# Set VNC password
print 'Setting VNC password...'
result = self._send({
"execute": "change",
"arguments": {
"device": "vnc",
"target": "password",
"arg": password
}
})
if result and result.get('return', None) != {}:
raise ValueError(result)
print 'Done.'
def powerdown(self):
print 'Stopping the VM...'
self._send({'execute': 'system_powerdown'})
def suspend(self):
print 'Suspending VM...'
self._send({'execute': 'stop'})
......@@ -194,17 +233,297 @@ class QemuQMPWrapper(object):
}
})
def queryCommands(self):
pprint.pprint(self._send({'execute': 'query-commands'})['return'])
def getCPUInfo(self):
"""
return some info about VM CPUs
"""
cpu_info_dict = {'hotplugged': [], 'base': []}
cpu_list = self._sendRetry({
'execute': 'query-cpus'
})['return']
for cpu in cpu_list:
if 'unattached' in cpu['qom_path']:
index = 'base'
else:
index = 'hotplugged'
cpu_info_dict[index].append({
'props': cpu['props'],
'CPU': cpu['CPU'],
'qom_path': cpu['qom_path']
})
return cpu_info_dict
def getMemoryInfo(self):
"""
return some info about VM Memory. Can only say info about hotplugged RAM
"""
mem_info_dict = {'hotplugged': [], 'base': []}
memory_list = self._sendRetry({
'execute': 'query-memory-devices'
})['return']
for mem in memory_list:
if mem['data']['hotplugged'] == True:
mem_info_dict['hotplugged'].append(mem['data'])
return mem_info_dict
def _removeDevice(self, dev_id, command_dict):
max_retry = 3
result = None
while max_retry > 0 and result is None:
result = self._send(command_dict)
max_retry -= 1
if (not result or result.get('return', None) != {}) and \
max_retry > 0:
print "%s\nRetry remove %r in few seconds..." % (result, dev_id)
time.sleep(3)
result = None
if result is not None:
if result.get('return', None) != {}:
if result.get('error') and \
result['error'].get('class', '') == 'DeviceNotFound':
print 'Device %s was removed.' % dev_id
else:
raise ValueError("Error: Could not remove device %s... %s" % (
dev_id, result))
else:
raise ValueError("Cannot remove device %s" % dev_id)
def _updateCPU(self, amount, cpu_model):
"""
Add or remove CPU according current value
amount: number of CPU to update to
"""
cpu_amount = 0
empty_socket_list = []
used_socket_id_list = []
unremovable_cpu = 0
cpu_hotplugable_list = self._send({
'execute': 'query-hotpluggable-cpus'
})['return']
cpu_hotplugable_list.reverse()
for cpu in cpu_hotplugable_list:
if cpu.get('qom-path', '') == '':
if len(empty_socket_list) < amount:
cpu['props']['driver'] = cpu_model
cpu['props']['id'] = 'cpu%s' % (cpu['props']['socket-id'])
empty_socket_list.append(cpu['props'])
else:
# if this is an hotpluggable cpu
if '/machine/peripheral' in cpu.get('qom-path', ''):
used_socket_id_list.append('cpu%s' % (cpu['props']['socket-id']))
cpu_amount += 1
else:
unremovable_cpu += 1
hotplug_amount = amount - unremovable_cpu # get only hotpluggable CPU
if hotplug_amount < 0:
raise ValueError("Unattached CPU amount is %s, cannot update to %s" % (
unremovable_cpu, amount))
cpu_diff = hotplug_amount - cpu_amount
max_hotplug_cpu = len(empty_socket_list)
if cpu_amount == hotplug_amount:
# no chanches
print "Hotplug CPU is up to date."
return
if cpu_amount > hotplug_amount:
# we will remove CPU
cpu_diff = -1 * cpu_diff
if cpu_diff >= 1:
print "Request remove %s CPUs..." % cpu_diff
used_socket_id_list.reverse()
for i in range(0, cpu_diff):
self._removeDevice(used_socket_id_list[i], {
'execute': 'device_del',
'arguments': {'id': used_socket_id_list[i]}
})
elif cpu_amount < hotplug_amount:
if max_hotplug_cpu < cpu_diff:
# no hotplugable cpu socket found for Add
raise ValueError("Cannot Configure %s CPUs, the maximum amount of " \
"hotplugable CPU is %s!" % (hotplug_amount, max_hotplug_cpu))
print "Adding %s CPUs..." % cpu_diff
for i in range(0, cpu_diff):
self._send({
'execute': 'device_add',
'arguments': empty_socket_list[i]
}, check_result=True)
# check that hotplugged memery amount is consistent
cpu_info = self.getCPUInfo()
final_cpu_count = len(cpu_info['hotplugged'])
if hotplug_amount != final_cpu_count:
raise ValueError("Consistency error: Expected %s hotplugged CPU(s) but" \
" current CPU amount is %s" % (hotplug_amount, final_cpu_count))
print "Done."
def _removeMemory(self, id_dict):
print "Trying to remove devices %s, %s..." % (id_dict['id'], id_dict['memdev'])
self._removeDevice(id_dict['id'] ,{
'execute': 'device_del',
'arguments': {'id': id_dict['id']}
})
# when dimm is removed, remove memdev object
self._removeDevice(id_dict['memdev'], {
'execute': 'object-del',
'arguments': {
'id': id_dict['memdev']
}
})
def _updateMemory(self, mem_size, slot_size, slot_amount):
"""
Update memory size according to the current value. option_dict contains:
slot_amount: amount of slots available
mem_size: Size of memory to allocate
slot_size: size of the memory per slot (this value should not change).
default: 512 MB
ex: to add 2G of RAM, mem=2048,slot=512 => allocate 4 slots
to reduce to 1G of RAM, mem=1024,slot=512 => allocate 2 slots
"""
current_size = 0
num_slot_used = 0
memory_id_list = [] # current hotplugged memory
cleanup_memdev_id_dict = {}
current_dimm_list = self._send({ "execute": "query-memory-devices" })
current_memdev_list = self._send({ "execute": "query-memdev" })
for memdev in current_memdev_list['return']:
cleanup_memdev_id_dict[memdev['id']] = ''
for dimm in current_dimm_list['return']:
current_size += dimm['data']['size']
if dimm['data']['hotplugged']:
mem_dev = os.path.basename(dimm['data']['memdev'])
cleanup_memdev_id_dict.pop(mem_dev)
memory_id_list.append({
'memdev': mem_dev,
'id': dimm['data']['id'],
'size': dimm['data']['size']/(1024 * 1024),
})
memory_id_list = sorted(memory_id_list, key=itemgetter('id'))
# cleanup memdev that was not removed because of failure
for memdev in cleanup_memdev_id_dict.keys():
print "Cleaning up memdev %s..." % memdev
self._removeDevice(memdev, {
'execute': 'object-del',
'arguments': {
'id': memdev
}
})
num_slot_used = len(memory_id_list)
if num_slot_used > 0 and slot_size != memory_id_list[0]['size']:
# XXX - we won't change the defined size of RAM on slots on live,
# restart qemu will allow to change the value
self.powerdown()
raise ValueError("The Size of RAM Slot changed. Rebooting...")
if (mem_size % slot_size) != 0:
raise ValueError("Memory size %r is not a multiple of %r" % (mem_size,
slot_size))
if (mem_size / slot_size) > slot_amount:
raise ValueError("No enough slots available to add %sMB of RAM" % mem_size)
current_size = current_size/(1024 * 1024)
if current_size == mem_size:
print "Hotplug Memory size is up to date."
return
if mem_size < 0:
raise ValueError("Memory size is not valid: %s" % option_dict)
elif current_size > mem_size:
# Request to remove memory
slot_remove = (current_size - mem_size) / slot_size
print "Removing %s memory slots of %s MB..." % (slot_remove, slot_size)
for i in range(num_slot_used, (num_slot_used - slot_remove), -1):
# remove all slot that won't be used
self._removeMemory(memory_id_list[i - 1])
elif current_size < mem_size:
# ask for increase memory
slot_add = (mem_size - current_size) / slot_size
print "Adding %s memory slots of %s MB..." % (slot_add, slot_size)
for i in range(0, slot_add):
index = num_slot_used + i + 1
self._send({
'execute': 'object-add',
'arguments': {
'qom-type': 'memory-backend-ram',
'id': 'mem%s' % index,
'props': { 'size': slot_size * 1024 * 1024 }
}
})
self._send({
'execute': 'device_add',
'arguments': {
'driver': 'pc-dimm' ,
'id': 'dimm%s' % index,
'memdev': 'mem%s' % index
}
}, check_result=True)
# check that hotplugged memery amount is consistent
mem_info = self.getMemoryInfo()
final_mem_size = 0
for mem in mem_info['hotplugged']:
final_mem_size += mem['size']
final_mem_size = final_mem_size / (1024 * 1024) # get size in MB
if mem_size != final_mem_size:
raise ValueError("Consistency error: Expected %s MB of hotplugged RAM " \
"but current RAM size is %s MB" % (mem_size, final_mem_size))
print "Done."
def updateDevice(self, option_dict):
argument_dict = {}
if option_dict.has_key('device'):
if option_dict['device'] == 'cpu':
return self._updateCPU(
amount=int(option_dict['amount']),
cpu_model=option_dict.get('model', 'qemu64-x86_64-cpu')
)
elif option_dict['device'] == 'memory':
return self._updateMemory(
mem_size=int(option_dict['mem']),
slot_size=int(option_dict['slot']),
slot_amount=int(option_dict['nslot'])
)
else:
raise ValueError("Unknown device type: %s" % option_dict)
else:
raise ValueError("Options are unknown: %s" % option_dict)
def queryCommands(self, query=None):
if query is not None:
pprint.pprint(self._send({'execute': 'query-%s' % query})['return'])
else:
pprint.pprint(self._send({'execute': 'query-commands'})['return'])
def main():
unix_socket_location, action, remainding_argument_list = parseArgument()
qemu_wrapper = QemuQMPWrapper(unix_socket_location)
config = parseArgument()
qemu_wrapper = QemuQMPWrapper(config.unix_socket_location)
if remainding_argument_list:
getattr(qemu_wrapper, action)(*remainding_argument_list)
if config.remainding_argument_list:
getattr(qemu_wrapper, config.action)(*config.remainding_argument_list)
else:
getattr(qemu_wrapper, action)()
if config.query != None:
getattr(qemu_wrapper, config.action)(**{"query": config.query})
elif config.action =="updateDevice":
argument_dict = {}
for parameter in config.device_options.split(','):
parameter_list = parameter.split('=')
argument_dict[parameter_list[0].strip()] = parameter_list[1].strip()
getattr(qemu_wrapper, config.action)(argument_dict)
else:
getattr(qemu_wrapper, config.action)()
if __name__ == '__main__':
main()
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2018 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest
import os
import tempfile
import shutil
from slapos.qemuqmpclient import QemuQMPWrapper
class TestQemuQMPWrapper(unittest.TestCase):
def setUp(self):
self.base_dir = tempfile.mkdtemp()
self.socket_file = os.path.join(self.base_dir, 'qmp.socket')
self.call_stack_list = []
self.free_cpu_slot_amount = 4
self.hotplugged_memory_amount = 0
# slot of 1G
self.memory_slot_size = 1024
def tearDown(self):
if os.path.exists(self.base_dir):
shutil.rmtree(self.base_dir)
def setChange(self, device, value):
current = self.readChange(device)
with open(os.path.join(self.base_dir, device), 'w') as f:
f.write('%s' % (current + value, ))
def readChange(self, device):
if os.path.exists(os.path.join(self.base_dir, device)):
with open(os.path.join(self.base_dir, device)) as f:
return int(f.read())
return 0
def fake_send(self, message, check_result=False):
self.call_stack_list.append(message)
if message.get('execute', '').startswith('query-'):
return self.returnQueryResult(message)
elif message.get('execute', '') == 'device_add':
if message['arguments']['driver'] == 'pc-dimm':
self.setChange('dimm', self.memory_slot_size)
elif message['arguments']['driver'] == 'qemu64-x86_64-cpu':
self.setChange('cpu', 1)
elif message.get('execute', '') == 'device_del':
if message['arguments']['id'].startswith('dimm'):
self.setChange('dimm', -1 * self.memory_slot_size)
if message['arguments']['id'].startswith('cpu'):
self.setChange('cpu', -1)
return {"return": {}}
def returnQueryResult(self, message):
if message['execute'] == 'query-hotpluggable-cpus':
# return 4 hotpluggable cpu slots
hotpluggable_cpu_list = []
free_cpu_slot = self.free_cpu_slot_amount - self.readChange('cpu')
for i in range(4, 4 - free_cpu_slot, -1):
hotpluggable_cpu_list.append({
u'props': {u'core-id': 0, u'node-id': 0, u'socket-id': i, u'thread-id': 0},
u'type': u'qemu64-x86_64-cpu',
u'vcpus-count': 1
})
for i in range(4 - free_cpu_slot, 0, -1):
hotpluggable_cpu_list.append({
u'props': {u'core-id': 0, u'node-id': 0, u'socket-id': i, u'thread-id': 0},
u'qom-path': u'/machine/peripheral/cpu%s' % i,
u'type': u'qemu64-x86_64-cpu',
u'vcpus-count': 1
})
# first cpu
hotpluggable_cpu_list.append(
{u'props': {u'core-id': 0, u'node-id': 0, u'socket-id': 0, u'thread-id': 0},
u'qom-path': u'/machine/unattached/device[0]',
u'type': u'qemu64-x86_64-cpu',
u'vcpus-count': 1
}
)
return {"return": hotpluggable_cpu_list}
elif message['execute'] == 'query-cpus':
cpu_list = []
cpu_slot = 4 - self.free_cpu_slot_amount + self.readChange('cpu')
cpu_list.append({
u'CPU': 0,
u'arch': u'x86',
u'current': True,
u'halted': True,
u'pc': -1694470494,
u'props': {u'core-id': 0, u'node-id': 0, u'socket-id': 0, u'thread-id': 0},
u'qom_path': u'/machine/unattached/device[0]',
u'thread_id': 1181
})
for i in range(0, cpu_slot):
cpu_list.append({
u'CPU': i + 1,
u'arch': u'x86',
u'current': False,
u'halted': True,
u'pc': -1694470494,
u'props': {u'core-id': 0, u'node-id': 0, u'socket-id': i + 1, u'thread-id': 0},
u'qom_path': u'/machine/peripheral/cpu%s' % (i + 1),
u'thread_id': 1187
})
return {"return": cpu_list}
elif message['execute'] == 'query-memory-devices':
memory_list = []
added_mem = self.readChange('dimm') + self.hotplugged_memory_amount
slot_amount = added_mem / self.memory_slot_size
for i in range(slot_amount, 0, -1):
memory_list.append({
u'data': {
u'addr': 4563402752,
u'hotpluggable': True,
u'hotplugged': True,
u'id': u'dimm%s' % i,
u'memdev': u'/objects/mem%s' % i,
u'node': 0,
u'size': self.memory_slot_size * 1024 * 1024,
u'slot': 1
},
u'type': u'dimm'
})
return {"return": memory_list}
elif message['execute'] == 'query-memdev':
memory_list = []
added_mem = self.readChange('dimm') + self.hotplugged_memory_amount
slot_amount = added_mem / self.memory_slot_size
for i in range(slot_amount, 0, -1):
memory_list.append({
u'dump': True,
u'host-nodes': [],
u'id': u'mem%s' % i,
u'merge': True,
u'policy': u'default',
u'prealloc': False,
u'size': self.memory_slot_size * 1024 * 1024
})
return {"return": memory_list}
return {"return": {}}
def test_setVNCPassword(self):
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
expected_result = {
"execute": "change",
"arguments": {
"device": "vnc",
"target": "password",
"arg": "my password"
}
}
qmpwrapper.setVNCPassword("my password")
self.assertEquals(len(self.call_stack_list), 1)
self.assertEquals(self.call_stack_list[0], expected_result)
def test_updateDevice_cpu_add(self):
self.free_cpu_slot_amount = 4
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
# add 2 cpu
cpu_option = {
'device': 'cpu',
'amount': 2 + 1
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-hotpluggable-cpus'},
{
'execute': 'device_add',
'arguments': {
u'socket-id': 1,
u'thread-id': 0,
'driver': 'qemu64-x86_64-cpu',
u'core-id': 0,
u'node-id': 0,
'id': 'cpu1'
}
},
{
'execute': 'device_add',
'arguments': {
u'socket-id': 2,
u'thread-id': 0,
'driver': 'qemu64-x86_64-cpu',
u'core-id': 0,
u'node-id': 0,
'id': 'cpu2'
}
},
{'execute': 'query-cpus'}
]
self.assertEquals(len(self.call_stack_list), 4)
self.assertEquals(self.call_stack_list, expected_result)
def test_updateDevice_cpu_increase(self):
self.free_cpu_slot_amount = 2
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
# add 2 more cpu
cpu_option = {
'device': 'cpu',
'amount': 4 + 1
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-hotpluggable-cpus'},
{
'execute': 'device_add',
'arguments': {
u'socket-id': 3,
u'thread-id': 0,
'driver': 'qemu64-x86_64-cpu',
u'core-id': 0,
u'node-id': 0,
'id': 'cpu3'
}
},
{
'execute': 'device_add',
'arguments': {
u'socket-id': 4,
u'thread-id': 0,
'driver': 'qemu64-x86_64-cpu',
u'core-id': 0,
u'node-id': 0,
'id': 'cpu4'
}
},
{'execute': 'query-cpus'}
]
self.assertEquals(len(self.call_stack_list), 4)
self.assertEquals(self.call_stack_list, expected_result)
def test_updateDevice_cpu_remove(self):
self.free_cpu_slot_amount = 2
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
# add 2 more cpu
cpu_option = {
'device': 'cpu',
'amount': 1 + 1
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-hotpluggable-cpus'},
{
'execute': 'device_del',
'arguments': {
'id': 'cpu2'
}
},
{'execute': 'query-cpus'}
]
self.assertEquals(len(self.call_stack_list), 3)
self.assertEquals(self.call_stack_list, expected_result)
def test_updateDevice_cpu_no_update(self):
self.free_cpu_slot_amount = 2
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
# keep 2 cpu added
cpu_option = {
'device': 'cpu',
'amount': 2 + 1
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-hotpluggable-cpus'}
]
self.assertEquals(len(self.call_stack_list), 1)
self.assertEquals(self.call_stack_list, expected_result)
def test_updateDevice_memory_add(self):
self.hotplugged_memory_amount = 0
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
# slot of 1G
self.memory_slot_size = 1024
# add 2G of RAM = 2 slots to add
cpu_option = {
'device': 'memory',
'nslot': 4,
'mem': 2048,
'slot': self.memory_slot_size
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-memory-devices'},
{'execute': 'query-memdev'},
{
'execute': 'object-add',
'arguments': {
'id': 'mem1',
'qom-type': 'memory-backend-ram',
'props': {'size': self.memory_slot_size * 1024 * 1024}
}
},
{
'execute': 'device_add',
'arguments': {
'driver': 'pc-dimm',
'id': 'dimm1',
'memdev': 'mem1'
}
},
{
'execute': 'object-add',
'arguments': {
'id': 'mem2',
'qom-type': 'memory-backend-ram',
'props': {'size': 1073741824}
}
},
{
'execute': 'device_add',
'arguments': {
'driver': 'pc-dimm',
'id': 'dimm2',
'memdev': 'mem2'
}
},
{'execute': 'query-memory-devices'}
]
self.assertEquals(len(self.call_stack_list), 7)
self.assertEquals(self.call_stack_list, expected_result)
def test_updateDevice_memory_increase(self):
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
self.hotplugged_memory_amount = 2048
# slot of 1G
self.memory_slot_size = 1024
# increase to 3G, add one more slot of 1G
cpu_option = {
'device': 'memory',
'nslot': 4,
'mem': 3072,
'slot': self.memory_slot_size
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-memory-devices'},
{'execute': 'query-memdev'},
{
'execute': 'object-add',
'arguments': {
'id': 'mem3',
'qom-type': 'memory-backend-ram',
'props': {'size': self.memory_slot_size * 1024 * 1024}
}
},
{
'execute': 'device_add',
'arguments': {
'driver': 'pc-dimm',
'id': 'dimm3',
'memdev': 'mem3'
}
},
{'execute': 'query-memory-devices'}
]
self.assertEquals(len(self.call_stack_list), 5)
self.assertEquals(self.call_stack_list, expected_result)
def test_updateDevice_memory_delete(self):
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
self.hotplugged_memory_amount = 3072
# slot of 1G
self.memory_slot_size = 1024
# decrease memory to 1G, expext remove slot 3 and 2.
cpu_option = {
'device': 'memory',
'nslot': 4,
'mem': 1024,
'slot': self.memory_slot_size
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-memory-devices'},
{'execute': 'query-memdev'},
{
'execute': 'device_del',
'arguments': {'id': u'dimm3'}
},
{
'execute': 'object-del',
'arguments': {'id': u'mem3'}
},
{
'execute': 'device_del',
'arguments': {'id': u'dimm2'}
},
{
'execute': 'object-del',
'arguments': {'id': u'mem2'}
},
{'execute': 'query-memory-devices'}
]
self.assertEquals(len(self.call_stack_list), 7)
self.assertEquals(self.call_stack_list, expected_result)
def test_updateDevice_memory_delete_all(self):
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
self.hotplugged_memory_amount = 3072
# slot of 1G
self.memory_slot_size = 1024
# remove all hotplugged memory
cpu_option = {
'device': 'memory',
'nslot': 4,
'mem': 0,
'slot': self.memory_slot_size
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-memory-devices'},
{'execute': 'query-memdev'},
{
'execute': 'device_del',
'arguments': {'id': u'dimm3'}
},
{
'execute': 'object-del',
'arguments': {'id': u'mem3'}
},
{
'execute': 'device_del',
'arguments': {'id': u'dimm2'}
},
{
'execute': 'object-del',
'arguments': {'id': u'mem2'}
},
{
'execute': 'device_del',
'arguments': {'id': u'dimm1'}
},
{
'execute': 'object-del',
'arguments': {'id': u'mem1'}
},
{'execute': 'query-memory-devices'}
]
self.assertEquals(len(self.call_stack_list), 9)
self.assertEquals(self.call_stack_list, expected_result)
def test_updateDevice_memory_no_update(self):
qmpwrapper = QemuQMPWrapper(self.socket_file, auto_connect=False)
qmpwrapper._send = self.fake_send
self.hotplugged_memory_amount = 3072
# slot of 1G
self.memory_slot_size = 1024
# no changes
cpu_option = {
'device': 'memory',
'nslot': 4,
'mem': self.hotplugged_memory_amount,
'slot': self.memory_slot_size
}
qmpwrapper.updateDevice(cpu_option)
expected_result = [
{'execute': 'query-memory-devices'},
{'execute': 'query-memdev'}
]
self.assertEquals(len(self.call_stack_list), 2)
self.assertEquals(self.call_stack_list, expected_result)
if __name__ == '__main__':
unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment