[PATCH] [Kimchi 0/4] Add backend support to set maxvcpus

Lucio Correia (4): Add maxvcpus attribute to templates Add maxvcpus attribute to guests Update tests Do not break web UI API.json | 34 +++--- control/templates.py | 3 +- docs/API.md | 66 ++++++++--- i18n.py | 10 +- model/cpuinfo.py | 63 ++++++++--- model/templates.py | 79 ++++++++------ model/vms.py | 194 +++++++++++++++++---------------- osinfo.py | 13 ++- template.conf | 7 +- tests/test_mockmodel.py | 7 +- tests/test_model.py | 17 +-- tests/test_rest.py | 14 ++- tests/test_template.py | 43 ++++---- tests/test_vmtemplate.py | 17 +-- ui/js/src/kimchi.guest_edit_main.js | 8 +- ui/js/src/kimchi.template_edit_main.js | 15 ++- ui/pages/guest-edit.html.tmpl | 2 +- ui/pages/template-edit.html.tmpl | 4 +- vmtemplate.py | 43 +++----- xmlutils/cpu.py | 6 +- 20 files changed, 369 insertions(+), 276 deletions(-) -- 1.9.1

- Move cpus as vcpus into cpu_info structure - Update defalut values for maxvcpus and vcpus - Rename check_topology() to check_cpu_info() to also verify vcpus and maxvcpus and use it for simplification of all CPU parameter validations - Unduplicate and move _get_host_maxcpu() to CPUInfoModel as get_host_max_vcpus() - Add merging code for cpu_info settings in template update - Update docs and APIs with maxvcpus attribute Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- API.json | 27 ++++++++---------- control/templates.py | 3 +- docs/API.md | 41 +++++++++++++++++++-------- i18n.py | 9 ++++-- model/cpuinfo.py | 63 ++++++++++++++++++++++++++++++----------- model/templates.py | 79 +++++++++++++++++++++++++++++++--------------------- model/vms.py | 25 +---------------- osinfo.py | 13 +++++---- template.conf | 7 +++-- vmtemplate.py | 6 +++- 10 files changed, 161 insertions(+), 112 deletions(-) diff --git a/API.json b/API.json index 2b64d07..d97a810 100644 --- a/API.json +++ b/API.json @@ -31,25 +31,34 @@ "description": "Configure CPU specifics for a VM.", "type": "object", "properties": { + "vcpus": { + "description": "The new number of virtual CPUs for the VM", + "type": "integer", + "minimum": 1, + "error": "KCHTMPL0012E" + }, + "maxvcpus": { + "description": "The maximum number of virtual CPUs that can be assigned to the VM", + "type": "integer", + "minimum": 1, + "error": "KCHTMPL0012E" + }, "topology": { "description": "Configure the guest CPU topology.", "type": "object", "properties": { "sockets": { "type": "integer", - "required": true, "minimum": 1, "error": "KCHTMPL0026E" }, "cores": { "type": "integer", - "required": true, "minimum": 1, "error": "KCHTMPL0026E" }, "threads": { "type": "integer", - "required": true, "minimum": 1, "error": "KCHTMPL0026E" } @@ -448,12 +457,6 @@ "minLength": 1, "error": "KCHTMPL0011E" }, - "cpus": { - "description": "Number of CPUs for the template", - "type": "integer", - "minimum": 1, - "error": "KCHTMPL0012E" - }, "memory": { "description": "Memory (MB) for the template", "type": "integer", @@ -631,12 +634,6 @@ "minLength": 1, "error": "KCHTMPL0011E" }, - "cpus": { - "description": "Number of CPUs for the template", - "type": "integer", - "minimum": 1, - "error": "KCHTMPL0012E" - }, "memory": { "description": "Memory (MB) for the template", "type": "integer", diff --git a/control/templates.py b/control/templates.py index 4cd70c2..b91196c 100644 --- a/control/templates.py +++ b/control/templates.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -46,7 +46,6 @@ class Template(Resource): 'invalid': self.info['invalid'], 'os_distro': self.info['os_distro'], 'os_version': self.info['os_version'], - 'cpus': self.info['cpus'], 'memory': self.info['memory'], 'cdrom': self.info.get('cdrom', None), 'disks': self.info['disks'], diff --git a/docs/API.md b/docs/API.md index 5122a0c..070f938 100644 --- a/docs/API.md +++ b/docs/API.md @@ -275,9 +275,6 @@ Represents a snapshot of the Virtual Machine's primary monitor. * name: The name of the Template. Used to identify the Template in this API * os_distro *(optional)*: The operating system distribution * os_version *(optional)*: The version of the operating system distribution - * cpus *(optional)*: The number of CPUs assigned to the VM. - Default is 1, unlees specifying a cpu topology. In that case, cpus - will default to a product of the topology values (see cpu_info). * memory *(optional)*: The amount of memory assigned to the VM. Default is 1024M. * cdrom *(optional)*: A volume name or URI to an ISO image. @@ -301,14 +298,18 @@ Represents a snapshot of the Virtual Machine's primary monitor. * null: Graphics is disabled or type not supported * listen: The network which the vnc/spice server listens on. * cpu_info *(optional)*: CPU-specific information. - * topology: Specify sockets, threads, and cores to run the virtual CPU - threads on. - All three are required in order to specify cpu topology. - * sockets - The number of sockets to use. + * maxvcpus *(optional)*: The maximum number of vCPUs that can be + assigned to the VM. If topology is specified, maxvcpus must be a + product of sockets, cores and threads. + * vcpus *(optional)*: The number of vCPUs assigned to the VM. Default + is 1, unless a CPU topology is specified. In that case, vcpus + must be a multiple of a product of cores and threads, and will + default to maxvcpus value. + * topology *(optional)*: Specify sockets, threads, and cores to run the + virtual CPU threads on. All three are required. + * sockets - The maximum number of sockets to use. * cores - The number of cores per socket. * threads - The number of threads per core. - If specifying both cpus and CPU topology, make sure cpus is - equal to the product of sockets, cores, and threads. ### Sub-Collection: Virtual Machine Network Interfaces @@ -378,7 +379,6 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * cpus: The number of CPUs assigned to the VM * memory: The amount of memory assigned to the VM in the unit of MB * cdrom: A volume name or URI to an ISO image * storagepool: URI of the storagepool where template allocates vm storage. @@ -405,6 +405,13 @@ A interface represents available network interface on VM. * cdrom *(optional)*: An array of invalid cdrom names. * disks *(optional)*: An array of invalid volume names. * storagepools *(optional)*: An array of invalid storagepool names. + * 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 + * topology: Processor topology, includes: + * sockets - The maximum number of sockets to use. + * cores - The number of cores per socket. + * threads - The number of threads per core. * **DELETE**: Remove the Template * **POST**: *See Template Actions* @@ -415,7 +422,6 @@ A interface represents available network interface on VM. * icon: A URI to a PNG image representing this template * os_distro: The operating system distribution * os_version: The version of the operating system distribution - * cpus: The number of CPUs assigned to the VM * memory: The amount of memory assigned to the VM * cdrom: A volume name or URI to an ISO image * networks *(optional)*: list of networks will be assigned to the new VM. @@ -435,6 +441,19 @@ A interface represents available network interface on VM. Independent Computing Environments * null: Graphics is disabled or type not supported * listen: The network which the vnc/spice server listens on. + * cpu_info *(optional)*: CPU-specific information. + * maxvcpus *(optional)*: The maximum number of vCPUs that can be + assigned to the VM. If topology is specified, maxvcpus must be a + product of sockets, cores and threads. + * vcpus *(optional)*: The number of vCPUs assigned to the VM. Default + is 1, unless a CPU topology is specified. In that case, vcpus + must be a multiple of a product of cores and threads, and will + default to maxvcpus value. + * topology *(optional)*: Specify sockets, threads, and cores to run the + virtual CPU threads on. All three are required. + * sockets - The maximum number of sockets to use. + * cores - The number of cores per socket. + * threads - The number of threads per core. **Actions (POST):** diff --git a/i18n.py b/i18n.py index 83d99b9..bbe68b6 100644 --- a/i18n.py +++ b/i18n.py @@ -175,7 +175,6 @@ messages = { "KCHTMPL0022E": _("Disk size must be an integer greater than 1GB."), "KCHTMPL0023E": _("Template base image must be a valid local image file"), "KCHTMPL0024E": _("Cannot identify base image %(path)s format"), - "KCHTMPL0025E": _("When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."), "KCHTMPL0026E": _("When specifying CPU topology, each element must be an integer greater than zero."), "KCHTMPL0027E": _("Invalid disk image format. Valid formats: bochs, cloop, cow, dmg, qcow, qcow2, qed, raw, vmdk, vpc."), "KCHTMPL0028E": _("When setting template disks, following parameters are required: 'index', 'pool name', 'format', 'size' or 'volume' (for scsi/iscsi pools)"), @@ -311,9 +310,13 @@ messages = { "KCHSNAP0009E": _("Unable to revert virtual machine '%(vm)s' to snapshot '%(name)s'. Details: %(err)s"), "KCHSNAP0010E": _("Unable to create snapshot of virtual machine '%(vm)s' because it contains a disk with format '%(format)s'; only 'qcow2' is supported."), - "KCHCPUINF0001E": _("The number of vCPUs is too large for this system."), - "KCHCPUINF0002E": _("Invalid vCPU/topology combination."), + "KCHCPUINF0001E": _("The number of vCPUs must be less than or equal the maximum number of vCPUs specified."), + "KCHCPUINF0002E": _("When CPU topology is defined, maximum number of vCPUs must be a product of sockets, cores, and threads."), "KCHCPUINF0003E": _("This host (or current configuration) does not allow CPU topology."), + "KCHCPUINF0004E": _("The maximum number of vCPUs is too large for this system."), + "KCHCPUINF0005E": _("When CPU topology is defined, vCPUs must be a multiple of a product of cores and threads."), + "KCHCPUINF0006E": _("The number of threads is too large for this system."), + "KCHCPUINF0007E": _("When CPU topology is specified, sockets, cores and threads are required paramaters."), "KCHLVMS0001E": _("Invalid volume group name parameter: %(name)s."), diff --git a/model/cpuinfo.py b/model/cpuinfo.py index 299e445..6bade85 100644 --- a/model/cpuinfo.py +++ b/model/cpuinfo.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2014-2015 +# Copyright IBM, Corp. 2014-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -25,6 +25,7 @@ from wok.utils import run_command, wok_log ARCH = 'power' if platform.machine().startswith('ppc') else 'x86' +MAX_PPC_VCPUS = 255 def get_topo_capabilities(connect): @@ -106,21 +107,51 @@ class CPUInfoModel(object): 'threads_per_core': self.threads_per_core, } - def check_topology(self, vcpus, topology): + def check_cpu_info(self, cpu_info): """ - param vcpus: should be an integer - param iso_path: the path of the guest ISO - param topology: {'sockets': x, 'cores': x, 'threads': x} + param cpu_info: topology definition dict: { + 'maxvcpus': integer + 'vcpus': integer + 'topology': { + 'sockets': integer, + 'cores': integer, + 'threads': integer + } + } """ - sockets = topology['sockets'] - cores = topology['cores'] - threads = topology['threads'] - - if not self.guest_threads_enabled: - raise InvalidOperation("KCHCPUINF0003E") - if vcpus != sockets * cores * threads: - raise InvalidParameter("KCHCPUINF0002E") - if vcpus > self.cores_available * self.threads_per_core: + maxvcpus = cpu_info.get('maxvcpus') + vcpus = cpu_info.get('vcpus') + topology = cpu_info.get('topology') + if topology: + # sockets, cores and threads are required when topology is defined + if 'sockets' not in topology or 'cores' not in topology or \ + 'threads' not in topology: + raise InvalidOperation("KCHCPUINF0007E") + + sockets = topology['sockets'] + cores = topology['cores'] + threads = topology['threads'] + + if not self.guest_threads_enabled: + raise InvalidOperation("KCHCPUINF0003E") + if threads > self.threads_per_core: + raise InvalidParameter("KCHCPUINF0006E") + if maxvcpus != sockets * cores * threads: + raise InvalidParameter("KCHCPUINF0002E") + if vcpus % (cores * threads) != 0: + raise InvalidParameter("KCHCPUINF0005E") + + if maxvcpus > self.get_host_max_vcpus(): + raise InvalidParameter("KCHCPUINF0004E") + if vcpus > maxvcpus: raise InvalidParameter("KCHCPUINF0001E") - if threads > self.threads_per_core: - raise InvalidParameter("KCHCPUINF0002E") + + def get_host_max_vcpus(self): + if ARCH == 'power': + max_vcpus = self.cores_available * self.threads_per_core + if max_vcpus > MAX_PPC_VCPUS: + max_vcpus = MAX_PPC_VCPUS + else: + max_vcpus = self.conn.get().getMaxVcpus('kvm') + + return max_vcpus diff --git a/model/templates.py b/model/templates.py index c9b11c3..053942a 100644 --- a/model/templates.py +++ b/model/templates.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2014-2015 +# Copyright IBM, Corp. 2014-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -54,22 +54,6 @@ class TemplatesModel(object): {'filename': iso, 'user': user, 'err': excp}) - cpu_info = params.get('cpu_info') - if cpu_info: - topology = cpu_info.get('topology') - # Check, even though currently only topology - # is supported. - if topology: - sockets = topology['sockets'] - cores = topology['cores'] - threads = topology['threads'] - if params.get('cpus') is None: - params['cpus'] = sockets * cores * threads - # check_topoology will raise the appropriate - # exception if a topology is invalid. - CPUInfoModel(conn=self.conn).\ - check_topology(params['cpus'], topology) - conn = self.conn.get() for net_name in params.get(u'networks', []): try: @@ -82,6 +66,9 @@ class TemplatesModel(object): # will be raised here t = LibvirtVMTemplate(params, scan=True, conn=self.conn) + # Validate cpu info + t.cpuinfo_validate() + # Validate max memory maxMem = (t._get_max_memory(t.info.get('memory')) >> 10) if t.info.get('memory') > maxMem: @@ -178,10 +165,15 @@ class TemplateModel(object): def update(self, name, params): old_t = self.lookup(name) new_t = copy.copy(old_t) - new_t.update(params) - if not self._validate_updated_cpu_params(new_t): - raise InvalidParameter('KCHTMPL0025E') + # Merge cpu_info settings + new_cpu_info = params.get('cpu_info') + if new_cpu_info: + cpu_info = dict(new_t['cpu_info']) + cpu_info.update(new_cpu_info) + params['cpu_info'] = cpu_info + + new_t.update(params) for net_name in params.get(u'networks', []): try: @@ -199,22 +191,18 @@ class TemplateModel(object): raise return ident - def _validate_updated_cpu_params(self, info): - # Note: cpu_info is the parent of topology. cpus is vcpus - vcpus = info['cpus'] - cpu_info = info.get('cpu_info') - # cpu_info will always be at least an empty dict - topology = cpu_info.get('topology') - if topology is None: - return True - return vcpus == topology['sockets'] * topology['cores'] * \ - topology['threads'] - class LibvirtVMTemplate(VMTemplate): def __init__(self, args, scan=False, conn=None): self.conn = conn VMTemplate.__init__(self, args, scan) + self.set_cpu_info() + + def cpuinfo_validate(self): + cpu_model = CPUInfoModel(conn=self.conn) + + # validate CPU info values - will raise appropriate exceptions + cpu_model.check_cpu_info(self.info['cpu_info']) def _storage_validate(self, pool_uri): pool_name = pool_name_from_uri(pool_uri) @@ -283,3 +271,32 @@ class LibvirtVMTemplate(VMTemplate): except libvirt.libvirtError as e: raise OperationFailed("KCHVMSTOR0008E", {'error': e.message}) return vol_list + + def set_cpu_info(self): + # undefined topology: consider these values to calculate maxvcpus + sockets = 1 + cores = 1 + threads = 1 + + # get topology values + cpu_info = self.info.get('cpu_info', {}) + topology = cpu_info.get('topology', {}) + if topology: + sockets = topology['sockets'] + cores = topology['cores'] + threads = topology['threads'] + + # maxvcpus not specified: use defaults + if 'maxvcpus' not in cpu_info: + vcpus = cpu_info.get('vcpus') + if vcpus and not topology: + cpu_info['maxvcpus'] = vcpus + else: + cpu_info['maxvcpus'] = sockets * cores * threads + + # current vcpus not specified: defaults is maxvcpus + if 'vcpus' not in cpu_info: + cpu_info['vcpus'] = cpu_info['maxvcpus'] + + # update cpu_info + self.info['cpu_info'] = cpu_info diff --git a/model/vms.py b/model/vms.py index 1203c71..9aaf530 100644 --- a/model/vms.py +++ b/model/vms.py @@ -107,17 +107,6 @@ class VMsModel(object): self.caps = CapabilitiesModel(**kargs) self.task = TaskModel(**kargs) - def _get_host_maxcpu(self): - if os.uname()[4] in ['ppc', 'ppc64', 'ppc64le']: - cpu_model = CPUInfoModel(conn=self.conn) - max_vcpu_val = (cpu_model.cores_available * - cpu_model.threads_per_core) - if max_vcpu_val > 255: - max_vcpu_val = 255 - else: - max_vcpu_val = self.conn.get().getMaxVcpus('kvm') - return max_vcpu_val - def create(self, params): t_name = template_name_from_uri(params['template']) vm_list = self.get_list() @@ -178,8 +167,7 @@ class VMsModel(object): stream_protocols = self.caps.libvirt_stream_protocols xml = t.to_vm_xml(name, vm_uuid, libvirt_stream_protocols=stream_protocols, - graphics=graphics, - max_vcpus=self._get_host_maxcpu()) + graphics=graphics) cb('Defining new VM') try: @@ -940,17 +928,6 @@ class VMModel(object): return ET.tostring(root, encoding="utf-8") - def _get_host_maxcpu(self): - if os.uname()[4] in ['ppc', 'ppc64', 'ppc64le']: - cpu_model = CPUInfoModel(conn=self.conn) - max_vcpu_val = (cpu_model.cores_available * - cpu_model.threads_per_core) - if max_vcpu_val > 255: - max_vcpu_val = 255 - else: - max_vcpu_val = self.conn.get().getMaxVcpus('kvm') - return max_vcpu_val - def _live_vm_update(self, dom, params): self._vm_update_access_metadata(dom, params) if 'memory' in params and dom.isActive(): diff --git a/osinfo.py b/osinfo.py index e6553a0..2ec5c3e 100644 --- a/osinfo.py +++ b/osinfo.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -117,7 +117,7 @@ def _get_tmpl_defaults(): {'main': {'networks': ['default'], 'memory': '1024'}, 'storage': { 'disk.0': {'format': 'qcow2', 'size': '10', 'pool': '/plugins/kimchi/storagepools/default'}}, - 'processor': {'cpus': '1'}, + 'processor': {'vcpus': '1', 'maxvcpus': 1}, 'graphics': {'type': 'spice', 'listen': '127.0.0.1'}} """ # Create dict with default values @@ -126,7 +126,8 @@ def _get_tmpl_defaults(): tmpl_defaults['main']['memory'] = _get_default_template_mem() tmpl_defaults['storage']['disk.0'] = {'size': 10, 'format': 'qcow2', 'pool': 'default'} - tmpl_defaults['processor']['cpus'] = 1 + tmpl_defaults['processor']['vcpus'] = 1 + tmpl_defaults['processor']['maxvcpus'] = 1 tmpl_defaults['graphics'] = {'type': 'vnc', 'listen': '127.0.0.1'} default_config = ConfigObj(tmpl_defaults) @@ -157,10 +158,10 @@ def _get_tmpl_defaults(): storage_section[disk].pop('pool')} defaults['disks'].append(data) - # Parse processor section to get cpus and cpu_topology values + # Parse processor section to get vcpus and cpu_topology values processor_section = default_config.pop('processor') - defaults['cpus'] = processor_section.pop('cpus') - defaults['cpu_info'] = {} + defaults['cpu_info'] = {'vcpus': processor_section.pop('vcpus'), + 'maxvcpus': processor_section.pop('maxvcpus')} if len(processor_section.keys()) > 0: defaults['cpu_info']['topology'] = processor_section diff --git a/template.conf b/template.conf index 37fadb8..3839be4 100644 --- a/template.conf +++ b/template.conf @@ -34,9 +34,10 @@ [processor] # Number of vcpus -# When specifying CPU topology, make sure cpus value is equal to the product -# of sockets, cores, and threads. -#cpus = 1 +# When specifying CPU topology, make sure maxvcpus value is equal to the +# product of sockets, cores, and threads. +#vcpus = 1 +#maxvcpus = 1 # Number of sockets (not set by default) #sockets = diff --git a/vmtemplate.py b/vmtemplate.py index d629226..2f524a7 100644 --- a/vmtemplate.py +++ b/vmtemplate.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -466,6 +466,10 @@ class VMTemplate(object): self._storage_validate(pool_uri) self._network_validate() self._iso_validate() + self.cpuinfo_validate() + + def cpuinfo_validate(self): + pass def _iso_validate(self): pass -- 1.9.1

- Enable update of maxvcpus, vcpus and CPU topology - Enable vCPU cold plug when topology is defined - Add cpu_info validation to guest update - Simplify _get_cpu_xml() code - Remove change_numa verification - Update docs and APIs with maxvcpus attribute Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- API.json | 7 +-- docs/API.md | 25 +++++++-- i18n.py | 1 - model/vms.py | 171 +++++++++++++++++++++++++++++++++----------------------- vmtemplate.py | 37 ++++-------- xmlutils/cpu.py | 6 +- 6 files changed, 136 insertions(+), 111 deletions(-) diff --git a/API.json b/API.json index d97a810..294be64 100644 --- a/API.json +++ b/API.json @@ -308,12 +308,7 @@ } } }, - "cpus": { - "description": "The new number of virtual CPUs for the VM", - "type": "integer", - "minimum": 1, - "error": "KCHTMPL0012E" - }, + "cpu_info": { "$ref": "#/kimchitype/cpu_info" }, "memory": { "description": "The new amount (MB) of memory for the VM", "type": "integer", diff --git a/docs/API.md b/docs/API.md index 070f938..a46e80e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -117,7 +117,13 @@ server. * 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) - * cpus: The number of CPUs assigned to the VM + * 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 + * topology: Processor topology, includes: + * sockets - The maximum number of sockets to use. + * cores - The number of cores per socket. + * threads - The number of threads per core. * screenshot: A link to a recent capture of the screen in PNG format * icon: A link to an icon that represents the VM * graphics: A dict to show detail of VM graphics. @@ -138,12 +144,10 @@ server. * groups: A list of system groups whose users have permission to access the VM. Default is: empty (i.e. no groups given access). * **DELETE**: Remove the Virtual Machine -* **PUT**: update the parameters of existed VM +* **PUT**: update the parameters of existing VM * name: New name for this VM (only applied for shutoff VM) * users: New list of system users. * groups: New list of system groups. - * cpus: New number of virtual cpus for this VM (if VM is running, new value - will take effect in next reboot) * memory: New amount of memory (MB) for this VM (if VM is running, new value will take effect in next reboot) * graphics: A dict to show detail of VM graphics. @@ -152,6 +156,19 @@ server. * passwdValidTo *(optional)*: lifetime for the console password. When omitted the password will be valid just for 30 seconds. + * cpu_info *(optional)*: CPU-specific information. + * maxvcpus *(optional)*: The maximum number of vCPUs that can be + assigned to the VM. If topology is specified, maxvcpus must be a + product of sockets, cores and threads. + * vcpus *(optional)*: The number of vCPUs assigned to the VM. Default + is 1, unless a CPU topology is specified. In that case, vcpus + must be a multiple of a product of cores and threads, and will + default to maxvcpus value. + * topology *(optional)*: Specify sockets, threads, and cores to run the + virtual CPU threads on. All three are required. + * sockets - The maximum number of sockets to use. + * cores - The number of cores per socket. + * threads - The number of threads per core. * **POST**: *See Virtual Machine Actions* diff --git a/i18n.py b/i18n.py index bbe68b6..0b9fa39 100644 --- a/i18n.py +++ b/i18n.py @@ -130,7 +130,6 @@ messages = { "KCHVM0071E": _("Memory value %(mem)s must be aligned to %(alignment)sMiB."), "KCHVM0073E": _("Unable to update the following parameters while the VM is offline: %(params)s"), "KCHVM0074E": _("Unable to update the following parameters while the VM is online: %(params)s"), - "KCHVM0075E": _("Cannot change VCPU value because '%(vm)s' has a topology defined - sockets: %(sockets)s, cores: %(cores)s, threads: %(threads)s."), "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 9aaf530..593d73b 100644 --- a/model/vms.py +++ b/model/vms.py @@ -41,8 +41,8 @@ from wok.model.tasks import TaskModel from wok.rollbackcontext import RollbackContext from wok.utils import add_task, convert_data_size, get_next_clone_name from wok.utils import import_class, run_setfacl_set_attr, run_command, wok_log -from wok.xmlutils.utils import xpath_get_text, xml_item_update -from wok.xmlutils.utils import dictize +from wok.xmlutils.utils import dictize, xpath_get_text, xml_item_insert +from wok.xmlutils.utils import xml_item_remove, xml_item_update from wok.plugins.kimchi import model from wok.plugins.kimchi import vnc @@ -60,6 +60,7 @@ from wok.plugins.kimchi.screenshot import VMScreenshot from wok.plugins.kimchi.utils import template_name_from_uri from wok.plugins.kimchi.vmtemplate import MAX_MEM_LIM, PPC_MEM_ALIGN from wok.plugins.kimchi.xmlutils.cpu import get_cpu_xml, get_numa_xml +from wok.plugins.kimchi.xmlutils.cpu import get_topology_xml from wok.plugins.kimchi.xmlutils.disk import get_vm_disk_info, get_vm_disks @@ -72,15 +73,12 @@ DOM_STATE_MAP = {0: 'nostate', 6: 'crashed', 7: 'pmsuspended'} -VM_STATIC_UPDATE_PARAMS = {'name': './name', 'cpus': './vcpu'} - -VM_LIVE_UPDATE_PARAMS = {} - # update parameters which are updatable when the VM is online VM_ONLINE_UPDATE_PARAMS = ['graphics', 'groups', 'memory', 'users'] + # update parameters which are updatable when the VM is offline -VM_OFFLINE_UPDATE_PARAMS = ['cpus', 'graphics', 'groups', 'memory', 'name', - 'users'] +VM_OFFLINE_UPDATE_PARAMS = ['cpu_info', 'graphics', 'groups', 'memory', + 'name', 'users'] XPATH_DOMAIN_DISK = "/domain/devices/disk[@device='disk']/source/@file" XPATH_DOMAIN_DISK_BY_FILE = "./devices/disk[@device='disk']/source[@file='%s']" @@ -93,8 +91,11 @@ XPATH_DOMAIN_MEMORY_UNIT = '/domain/memory/@unit' XPATH_DOMAIN_UUID = '/domain/uuid' XPATH_DOMAIN_DEV_CPU_ID = '/domain/devices/spapr-cpu-socket/@id' +XPATH_CPU = './cpu' +XPATH_NAME = './name' XPATH_NUMA_CELL = './cpu/numa/cell' XPATH_TOPOLOGY = './cpu/topology' +XPATH_VCPU = './vcpu' # key: VM name; value: lock object vm_locks = {} @@ -241,23 +242,6 @@ class VMModel(object): threads = xpath_get_text(xml, XPATH_TOPOLOGY + '/@threads') return sockets and cores and threads - def get_vm_max_sockets(self, dom): - return int(xpath_get_text(dom.XMLDesc(0), - XPATH_TOPOLOGY + '/@sockets')[0]) - - def get_vm_sockets(self, dom): - current_vcpu = dom.vcpusFlags(libvirt.VIR_DOMAIN_AFFECT_CURRENT) - return (current_vcpu / self.get_vm_cores(dom) / - self.get_vm_threads(dom)) - - def get_vm_cores(self, dom): - return int(xpath_get_text(dom.XMLDesc(0), - XPATH_TOPOLOGY + '/@cores')[0]) - - def get_vm_threads(self, dom): - return int(xpath_get_text(dom.XMLDesc(0), - XPATH_TOPOLOGY + '/@threads')[0]) - def update(self, name, params): lock = vm_locks.get(name) if lock is None: @@ -285,17 +269,6 @@ class VMModel(object): raise InvalidParameter('KCHVM0074E', {'params': ', '.join(ext_params)}) - if 'cpus' in params and DOM_STATE_MAP[dom.info()[0]] == 'shutoff': - # user cannot change vcpu if topology is defined. - curr_vcpu = dom.vcpusFlags(libvirt.VIR_DOMAIN_AFFECT_CURRENT) - if self.has_topology(dom) and curr_vcpu != params['cpus']: - raise InvalidOperation( - 'KCHVM0075E', - {'vm': dom.name(), - 'sockets': self.get_vm_sockets(dom), - 'cores': self.get_vm_cores(dom), - 'threads': self.get_vm_threads(dom)}) - self._live_vm_update(dom, params) vm_name, dom = self._static_vm_update(name, dom, params) return vm_name @@ -751,6 +724,8 @@ class VMModel(object): old_xml = new_xml = dom.XMLDesc(0) params = copy.deepcopy(params) + + # Update name name = params.get('name') nonascii_name = None if name is not None: @@ -760,37 +735,41 @@ class VMModel(object): raise InvalidParameter("KCHVM0003E", msg_args) params['name'], nonascii_name = get_ascii_nonascii_name(name) - - for key, val in params.items(): - change_numa = True - if key in VM_STATIC_UPDATE_PARAMS: - if type(val) == int: - val = str(val) - xpath = VM_STATIC_UPDATE_PARAMS[key] - attrib = None - if key == 'cpus': - if self.has_topology(dom) or dom.isActive(): - change_numa = False - continue - # Update maxvcpu firstly - new_xml = xml_item_update(new_xml, xpath, - str(self._get_host_maxcpu()), - attrib) - # Update current vcpu - attrib = 'current' - new_xml = xml_item_update(new_xml, xpath, val, attrib) + new_xml = xml_item_update(new_xml, XPATH_NAME, name, None) + + # Update CPU info + cpu_info = params.get('cpu_info', {}) + cpu_info = self._update_cpu_info(new_xml, dom, cpu_info) + + vcpus = str(cpu_info['vcpus']) + new_xml = xml_item_update(new_xml, XPATH_VCPU, vcpus, 'current') + + maxvcpus = str(cpu_info['maxvcpus']) + new_xml = xml_item_update(new_xml, XPATH_VCPU, maxvcpus, None) + + topology = cpu_info['topology'] + if topology: + sockets = str(topology['sockets']) + cores = str(topology['cores']) + threads = str(topology['threads']) + + if self.has_topology(dom): + # topology is being updated + xpath = XPATH_TOPOLOGY + new_xml = xml_item_update(new_xml, xpath, sockets, 'sockets') + new_xml = xml_item_update(new_xml, xpath, cores, 'cores') + new_xml = xml_item_update(new_xml, xpath, threads, 'threads') + else: + # topology is being added + new_xml = xml_item_insert(new_xml, XPATH_CPU, + get_topology_xml(topology)) + elif self.has_topology(dom): + # topology is being undefined: remove it + new_xml = xml_item_remove(new_xml, XPATH_TOPOLOGY) # Updating memory and NUMA if necessary, if vm is offline - if not dom.isActive(): - if 'memory' in params: - new_xml = self._update_memory_config(new_xml, params) - elif 'cpus' in params and change_numa and \ - (xpath_get_text(new_xml, XPATH_NUMA_CELL + '/@memory') != []): - new_xml = xml_item_update( - new_xml, - XPATH_NUMA_CELL, - value='0', - attr='cpus') + if 'memory' in params and not dom.isActive(): + new_xml = self._update_memory_config(new_xml, params) if 'graphics' in params: new_xml = self._update_graphics(dom, new_xml, params) @@ -834,10 +813,10 @@ class VMModel(object): # apropriated root = ET.fromstring(xml) numa_mem = xpath_get_text(xml, XPATH_NUMA_CELL + '/@memory') - vcpus = params.get('cpus') + maxvcpus = params.get('cpu_info', {}).get('maxvcpus') if numa_mem == []: - if vcpus is None: - vcpus = int(xpath_get_text(xml, 'vcpu')[0]) + 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) @@ -846,7 +825,7 @@ class VMModel(object): numa_element = get_numa_xml(0, params['memory'] << 10) cpu.insert(0, ET.fromstring(numa_element)) else: - if vcpus is not None: + if maxvcpus is not None: xml = xml_item_update( xml, XPATH_NUMA_CELL, @@ -928,6 +907,37 @@ class VMModel(object): return ET.tostring(root, encoding="utf-8") + def _update_cpu_info(self, new_xml, dom, new_info): + topology = {} + if self.has_topology(dom): + sockets = xpath_get_text(new_xml, XPATH_TOPOLOGY + '/@sockets')[0] + cores = xpath_get_text(new_xml, XPATH_TOPOLOGY + '/@cores')[0] + threads = xpath_get_text(new_xml, XPATH_TOPOLOGY + '/@threads')[0] + topology = { + 'sockets': int(sockets), + 'cores': int(cores), + 'threads': int(threads), + } + + # if current is not defined in vcpu, vcpus is equal to maxvcpus + xml_maxvcpus = xpath_get_text(new_xml, 'vcpu') + maxvcpus = int(xml_maxvcpus[0]) + xml_vcpus = xpath_get_text(new_xml, './vcpu/@current') + vcpus = int(xml_vcpus[0]) if xml_vcpus else maxvcpus + + cpu_info = { + 'maxvcpus': maxvcpus, + 'vcpus': vcpus, + 'topology': topology, + } + cpu_info.update(new_info) + + # Revalidate cpu info - may raise CPUInfo exceptions + cpu_model = CPUInfoModel(conn=self.conn) + cpu_model.check_cpu_info(cpu_info) + + return cpu_info + def _live_vm_update(self, dom, params): self._vm_update_access_metadata(dom, params) if 'memory' in params and dom.isActive(): @@ -1140,8 +1150,27 @@ class VMModel(object): res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100) users, groups = self._get_access_info(dom) + xml = dom.XMLDesc(0) + maxvcpus = int(xpath_get_text(xml, XPATH_VCPU)[0]) + + cpu_info = { + 'vcpus': info[3], + 'maxvcpus': maxvcpus, + 'topology': {}, + } + + if self.has_topology(dom): + sockets = int(xpath_get_text(xml, XPATH_TOPOLOGY + '/@sockets')[0]) + cores = int(xpath_get_text(xml, XPATH_TOPOLOGY + '/@cores')[0]) + threads = int(xpath_get_text(xml, XPATH_TOPOLOGY + '/@threads')[0]) + + cpu_info['topology'] = { + 'sockets': sockets, + 'cores': cores, + 'threads': threads, + } + if state == 'shutoff': - xml = dom.XMLDesc(0) val = xpath_get_text(xml, XPATH_DOMAIN_MEMORY)[0] unit_list = xpath_get_text(xml, XPATH_DOMAIN_MEMORY_UNIT) if len(unit_list) > 0: @@ -1157,7 +1186,7 @@ class VMModel(object): 'stats': res, 'uuid': dom.UUIDString(), 'memory': memory, - 'cpus': info[3], + 'cpu_info': cpu_info, 'screenshot': screenshot, 'icon': icon, # (type, listen, port, passwd, passwdValidTo) diff --git a/vmtemplate.py b/vmtemplate.py index 2f524a7..ef17ff6 100644 --- a/vmtemplate.py +++ b/vmtemplate.py @@ -324,12 +324,9 @@ class VMTemplate(object): def _get_cpu_xml(self): # Include CPU topology, if provided - cpu_info = self.info.get('cpu_info') - if cpu_info is not None: - cpu_topo = cpu_info.get('topology') - return get_cpu_xml(0, - self.info.get('memory') << 10, - cpu_topo) + cpu_topo = self.info.get('cpu_info', {}).get('topology', {}) + + return get_cpu_xml(0, self.info.get('memory') << 10, cpu_topo) def _get_max_memory(self, guest_memory): # Setting maxMemory of the VM, which will be lesser value between: @@ -371,8 +368,6 @@ class VMTemplate(object): libvirt_stream_protocols = kwargs.get('libvirt_stream_protocols', []) cdrom_xml = self._get_cdrom_xml(libvirt_stream_protocols) - max_vcpus = kwargs.get('max_vcpus', 1) - if not urlparse.urlparse(self.info.get('cdrom', "")).scheme in \ libvirt_stream_protocols and \ params.get('iso_stream', False): @@ -402,23 +397,13 @@ class VMTemplate(object): # set a hard limit using max_memory + 1GiB params['hard_limit'] = params['max_memory'] + (1024 << 10) - cpu_topo = self.info.get('cpu_info').get('topology') - if (cpu_topo is not None): - sockets = int(max_vcpus / (cpu_topo['cores'] * - cpu_topo['threads'])) - self.info['cpu_info']['topology']['sockets'] = sockets + # vcpu element + cpus = params['cpu_info']['vcpus'] + maxvcpus = params['cpu_info']['maxvcpus'] + params['vcpus_xml'] = "<vcpu current='%d'>%d</vcpu>" % (cpus, maxvcpus) - # Reduce maxvcpu to fit number of sockets if necessary - total_max_vcpu = sockets * cpu_topo['cores'] * cpu_topo['threads'] - if total_max_vcpu != max_vcpus: - max_vcpus = total_max_vcpu - - params['vcpus'] = "<vcpu current='%s'>%d</vcpu>" % \ - (params['cpus'], max_vcpus) - else: - params['vcpus'] = "<vcpu current='%s'>%d</vcpu>" % \ - (params['cpus'], max_vcpus) - params['cpu_info'] = self._get_cpu_xml() + # cpu_info element + params['cpu_info_xml'] = self._get_cpu_xml() xml = """ <domain type='%(domain)s'> @@ -430,8 +415,8 @@ class VMTemplate(object): </memtune> <maxMemory slots='%(slots)s' unit='KiB'>%(max_memory)s</maxMemory> <memory unit='MiB'>%(memory)s</memory> - %(vcpus)s - %(cpu_info)s + %(vcpus_xml)s + %(cpu_info_xml)s <os> <type arch='%(arch)s'>hvm</type> <boot dev='hd'/> diff --git a/xmlutils/cpu.py b/xmlutils/cpu.py index 32c01a4..b3eaaae 100644 --- a/xmlutils/cpu.py +++ b/xmlutils/cpu.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2015 +# Copyright IBM, Corp. 2015-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -45,7 +45,7 @@ def get_topology_xml(cpu_topo): return ET.tostring(xml) -def get_cpu_xml(cpus, memory, cpu_topo=None): +def get_cpu_xml(cpus, memory, cpu_topo={}): # Returns the libvirt CPU element based on given numa and topology # CPU element will always have numa element # <cpu> @@ -55,6 +55,6 @@ def get_cpu_xml(cpus, memory, cpu_topo=None): # <topology sockets='1' cores='2' threads='1'/> # </cpu> xml = E.cpu(ET.fromstring(get_numa_xml(cpus, memory))) - if cpu_topo is not None: + if cpu_topo: xml.insert(0, ET.fromstring(get_topology_xml(cpu_topo))) return ET.tostring(xml) -- 1.9.1

Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- tests/test_mockmodel.py | 7 ++++--- tests/test_model.py | 17 ++++++++++------- tests/test_rest.py | 14 ++++++++------ tests/test_template.py | 43 ++++++++++++++++++++++++------------------- tests/test_vmtemplate.py | 17 +++++++++-------- 5 files changed, 55 insertions(+), 43 deletions(-) diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index ce6e837..b7f8f23 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -122,7 +122,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(2, len(vms)) self.assertIn(u'test-vm', vms) - keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus', + keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpu_info', 'screenshot', 'icon', 'graphics', 'users', 'groups', 'access', 'persistent')) @@ -136,7 +136,8 @@ class MockModelTests(unittest.TestCase): self.assertEquals('test-vm', info['name']) self.assertEquals(get_template_default('old', 'memory'), info['memory']) - self.assertEquals(1, info['cpus']) + self.assertEquals(1, info['cpu_info']['vcpus']) + self.assertEquals(1, info['cpu_info']['maxvcpus']) self.assertEquals('plugins/kimchi/images/icon-vm.png', info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertEquals('vnc', info['graphics']['type']) diff --git a/tests/test_model.py b/tests/test_model.py index 762f0f8..5d4b99e 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -2,7 +2,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -102,7 +102,7 @@ class ModelTests(unittest.TestCase): self.assertEquals(1, len(vms)) self.assertEquals('test', vms[0]) - keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus', + keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpu_info', 'screenshot', 'icon', 'graphics', 'users', 'groups', 'access', 'persistent')) @@ -114,7 +114,8 @@ class ModelTests(unittest.TestCase): self.assertEquals('running', info['state']) self.assertEquals('test', info['name']) self.assertEquals(2048, info['memory']) - self.assertEquals(2, info['cpus']) + self.assertEquals(2, info['cpu_info']['vcpus']) + self.assertEquals(2, info['cpu_info']['maxvcpus']) self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') @@ -267,10 +268,10 @@ class ModelTests(unittest.TestCase): # Create template based on IMG file tmpl_name = "img-tmpl" pool_uri = "/plugins/kimchi/storagepools/default" - tmpl_info = {"cpus": 1, "cdrom": "", "name": tmpl_name, + tmpl_info = {"cpu_info": {"vcpus": 1}, "name": tmpl_name, "graphics": {"type": "vnc", "listen": "127.0.0.1"}, "networks": ["default"], "memory": 1024, "folder": [], - "icon": "images/icon-vm.png", + "icon": "images/icon-vm.png", "cdrom": "", "os_distro": "unknown", "os_version": "unknown", "disks": [{"base": vol_path, "size": 10, "format": "qcow2", @@ -768,7 +769,8 @@ class ModelTests(unittest.TestCase): # template disk format must be qcow2 because vmsnapshot # only supports this format - orig_params = {'name': 'test', 'memory': 1024, 'cpus': 1, + orig_params = {'name': 'test', 'memory': 1024, + 'cpu_info': {'vcpus': 1}, 'cdrom': UBUNTU_ISO, 'disks': [{'size': 1, 'format': 'qcow2', 'pool': { 'name': '/plugins/kimchi/storagepools/default'}}]} @@ -838,7 +840,8 @@ class ModelTests(unittest.TestCase): self.assertRaises(OperationFailed, inst.vm_update, 'kimchi-vm1', {'name': 'kimchi-vm2'}) - params = {'name': u'пeω-∨м', 'cpus': 4, 'memory': 2048} + params = {'name': u'пeω-∨м', 'cpu_info': {'vcpus': 4}, + 'memory': 2048} inst.vm_update('kimchi-vm1', params) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, u'пeω-∨м') diff --git a/tests/test_rest.py b/tests/test_rest.py index f4c4f4e..1fc0f71 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -148,7 +148,7 @@ class RestTests(unittest.TestCase): vm = json.loads(self.request('/plugins/kimchi/vms/vm-1').read()) self.assertEquals('vm-1', vm['name']) - req = json.dumps({'cpus': 3}) + req = json.dumps({'cpu_info': {'maxvcpus': 5, 'vcpus': 3}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(200, resp.status) @@ -164,7 +164,7 @@ class RestTests(unittest.TestCase): self.assertEquals(400, resp.status) # Unable to do CPU hotplug - req = json.dumps({'cpus': 5}) + req = json.dumps({'cpu_info': {'vcpus': 5}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) @@ -203,11 +203,11 @@ class RestTests(unittest.TestCase): resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) - req = json.dumps({'cpus': -2}) + req = json.dumps({'cpu_info': {'vcpus': -2}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) - req = json.dumps({'cpus': 'four'}) + req = json.dumps({'cpu_info': {'vcpus': 'four'}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) @@ -219,11 +219,13 @@ class RestTests(unittest.TestCase): resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) - req = json.dumps({'name': 'new-name', 'cpus': 5, 'UUID': 'notallowed'}) + req = json.dumps({'name': 'new-name', 'cpu_info': {'vcpus': 5}, + 'UUID': 'notallowed'}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status) - params = {'name': u'∨м-црdαtеd', 'cpus': 5, 'memory': 3072} + params = {'name': u'∨м-црdαtеd', 'cpu_info': {'vcpus': 5}, + 'memory': 3072} req = json.dumps(params) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(303, resp.status) diff --git a/tests/test_template.py b/tests/test_template.py index 0b3dd98..3b9f26a 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -2,7 +2,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2015 +# Copyright IBM, Corp. 2015-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -70,7 +70,7 @@ class TemplateTests(unittest.TestCase): # Create a template without cdrom and disk specified fails with 400 t = {'name': 'test', 'os_distro': 'ImagineOS', - 'os_version': '1.0', 'memory': 1024, 'cpus': 1} + 'os_version': '1.0', 'memory': 1024, 'cpu_info': {'vcpus': 1}} req = json.dumps(t) resp = self.request('/plugins/kimchi/templates', req, 'POST') self.assertEquals(400, resp.status) @@ -82,9 +82,8 @@ class TemplateTests(unittest.TestCase): self.assertEquals(201, resp.status) # Verify the template - keys = ['name', 'icon', 'invalid', 'os_distro', 'os_version', 'cpus', - 'memory', 'cdrom', 'disks', 'networks', - 'folder', 'graphics', 'cpu_info'] + keys = ['name', 'icon', 'invalid', 'os_distro', 'os_version', 'memory', + 'cdrom', 'disks', 'networks', 'folder', 'graphics', 'cpu_info'] tmpl = json.loads( self.request('/plugins/kimchi/templates/test').read() ) @@ -193,34 +192,40 @@ class TemplateTests(unittest.TestCase): self.assertEquals('fedora', update_tmpl['os_distro']) self.assertEquals('21', update_tmpl['os_version']) - # Update cpus - req = json.dumps({'cpus': 2}) + # Update maxvcpus only + req = json.dumps({'cpu_info': {'maxvcpus': 2}}) resp = self.request(new_tmpl_uri, req, 'PUT') self.assertEquals(200, resp.status) update_tmpl = json.loads(resp.read()) - self.assertEquals(2, update_tmpl['cpus']) + self.assertEquals(2, update_tmpl['cpu_info']['maxvcpus']) - # Update memory - req = json.dumps({'memory': 2048}) + # Update vcpus only + req = json.dumps({'cpu_info': {'vcpus': 2}}) resp = self.request(new_tmpl_uri, req, 'PUT') self.assertEquals(200, resp.status) update_tmpl = json.loads(resp.read()) - self.assertEquals(2048, update_tmpl['memory']) + self.assertEquals(2, update_tmpl['cpu_info']['vcpus']) # Update cpu_info - resp = self.request(new_tmpl_uri) - cpu_info = json.loads(resp.read())['cpu_info'] - self.assertEquals(cpu_info, {}) - self.assertEquals(cpu_info.get('topology'), None) - - cpu_info_data = {'cpu_info': {'topology': {'sockets': 1, - 'cores': 2, - 'threads': 1}}} + cpu_info_data = { + 'cpu_info': { + 'maxvcpus': 2, + 'vcpus': 2, + 'topology': {'sockets': 1, 'cores': 2, 'threads': 1} + } + } resp = self.request(new_tmpl_uri, json.dumps(cpu_info_data), 'PUT') self.assertEquals(200, resp.status) update_tmpl = json.loads(resp.read()) self.assertEquals(update_tmpl['cpu_info'], cpu_info_data['cpu_info']) + # Update memory + req = json.dumps({'memory': 2048}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(200, resp.status) + update_tmpl = json.loads(resp.read()) + self.assertEquals(2048, update_tmpl['memory']) + # Update cdrom cdrom_data = {'cdrom': '/tmp/mock2.iso'} resp = self.request(new_tmpl_uri, json.dumps(cdrom_data), 'PUT') diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py index de2d542..e157198 100644 --- a/tests/test_vmtemplate.py +++ b/tests/test_vmtemplate.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -45,12 +45,12 @@ class VMTemplateTests(unittest.TestCase): disk_bus = get_template_default('old', 'disk_bus') memory = get_template_default('old', 'memory') nic_model = get_template_default('old', 'nic_model') - fields = (('name', 'test'), ('os_distro', 'unknown'), - ('os_version', 'unknown'), ('cpus', 1), + fields = (('name', 'test'), ('cdrom', self.iso), + ('os_distro', 'unknown'), ('os_version', 'unknown'), + ('cpu_info', {'vcpus': 1, 'maxvcpus': 1}), ('memory', memory), ('networks', ['default']), ('disk_bus', disk_bus), ('nic_model', nic_model), - ('graphics', {'type': 'vnc', 'listen': '127.0.0.1'}), - ('cdrom', self.iso)) + ('graphics', {'type': 'vnc', 'listen': '127.0.0.1'})) args = {'name': 'test', 'cdrom': self.iso} t = VMTemplate(args) @@ -121,10 +121,11 @@ class VMTemplateTests(unittest.TestCase): """ graphics = {'type': 'vnc', 'listen': '127.0.0.1'} args = {'name': 'test', 'os_distro': 'opensuse', 'os_version': '12.3', - 'cpus': 2, 'memory': 2048, 'networks': ['foo'], - 'cdrom': self.iso, 'graphics': graphics} + 'cpu_info': {'vcpus': 2, 'maxvcpus': 4}, 'memory': 2048, + 'networks': ['foo'], 'cdrom': self.iso, 'graphics': graphics} t = VMTemplate(args) - self.assertEquals(2, t.info.get('cpus')) + self.assertEquals(2, t.info.get('cpu_info', {}).get('vcpus')) + self.assertEquals(4, t.info.get('cpu_info', {}).get('maxvcpus')) self.assertEquals(2048, t.info.get('memory')) self.assertEquals(['foo'], t.info.get('networks')) self.assertEquals(self.iso, t.info.get('cdrom')) -- 1.9.1

On 02/04/2016 02:36 PM, Lucio Correia wrote:
Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- tests/test_mockmodel.py | 7 ++++--- tests/test_model.py | 17 ++++++++++------- tests/test_rest.py | 14 ++++++++------ tests/test_template.py | 43 ++++++++++++++++++++++++------------------- tests/test_vmtemplate.py | 17 +++++++++-------- 5 files changed, 55 insertions(+), 43 deletions(-)
diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index ce6e837..b7f8f23 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -122,7 +122,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(2, len(vms)) self.assertIn(u'test-vm', vms)
- keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus', + keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpu_info', 'screenshot', 'icon', 'graphics', 'users', 'groups', 'access', 'persistent'))
@@ -136,7 +136,8 @@ class MockModelTests(unittest.TestCase): self.assertEquals('test-vm', info['name']) self.assertEquals(get_template_default('old', 'memory'), info['memory']) - self.assertEquals(1, info['cpus']) + self.assertEquals(1, info['cpu_info']['vcpus']) + self.assertEquals(1, info['cpu_info']['maxvcpus']) self.assertEquals('plugins/kimchi/images/icon-vm.png', info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertEquals('vnc', info['graphics']['type']) diff --git a/tests/test_model.py b/tests/test_model.py index 762f0f8..5d4b99e 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -2,7 +2,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -102,7 +102,7 @@ class ModelTests(unittest.TestCase): self.assertEquals(1, len(vms)) self.assertEquals('test', vms[0])
- keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus', + keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpu_info', 'screenshot', 'icon', 'graphics', 'users', 'groups', 'access', 'persistent'))
@@ -114,7 +114,8 @@ class ModelTests(unittest.TestCase): self.assertEquals('running', info['state']) self.assertEquals('test', info['name']) self.assertEquals(2048, info['memory']) - self.assertEquals(2, info['cpus']) + self.assertEquals(2, info['cpu_info']['vcpus']) + self.assertEquals(2, info['cpu_info']['maxvcpus']) self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') @@ -267,10 +268,10 @@ class ModelTests(unittest.TestCase): # Create template based on IMG file tmpl_name = "img-tmpl" pool_uri = "/plugins/kimchi/storagepools/default" - tmpl_info = {"cpus": 1, "cdrom": "", "name": tmpl_name, + tmpl_info = {"cpu_info": {"vcpus": 1}, "name": tmpl_name, "graphics": {"type": "vnc", "listen": "127.0.0.1"}, "networks": ["default"], "memory": 1024, "folder": [],
- "icon": "images/icon-vm.png", + "icon": "images/icon-vm.png", "cdrom": "",
Why did you add 'cdrom'?
"os_distro": "unknown", "os_version": "unknown", "disks": [{"base": vol_path, "size": 10, "format": "qcow2", @@ -768,7 +769,8 @@ class ModelTests(unittest.TestCase):
# template disk format must be qcow2 because vmsnapshot # only supports this format - orig_params = {'name': 'test', 'memory': 1024, 'cpus': 1, + orig_params = {'name': 'test', 'memory': 1024, + 'cpu_info': {'vcpus': 1}, 'cdrom': UBUNTU_ISO, 'disks': [{'size': 1, 'format': 'qcow2', 'pool': { 'name': '/plugins/kimchi/storagepools/default'}}]} @@ -838,7 +840,8 @@ class ModelTests(unittest.TestCase): self.assertRaises(OperationFailed, inst.vm_update, 'kimchi-vm1', {'name': 'kimchi-vm2'})
- params = {'name': u'пeω-∨м', 'cpus': 4, 'memory': 2048} + params = {'name': u'пeω-∨м', 'cpu_info': {'vcpus': 4}, + 'memory': 2048} inst.vm_update('kimchi-vm1', params) rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, u'пeω-∨м') diff --git a/tests/test_rest.py b/tests/test_rest.py index f4c4f4e..1fc0f71 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -148,7 +148,7 @@ class RestTests(unittest.TestCase): vm = json.loads(self.request('/plugins/kimchi/vms/vm-1').read()) self.assertEquals('vm-1', vm['name'])
- req = json.dumps({'cpus': 3}) + req = json.dumps({'cpu_info': {'maxvcpus': 5, 'vcpus': 3}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(200, resp.status)
@@ -164,7 +164,7 @@ class RestTests(unittest.TestCase): self.assertEquals(400, resp.status)
# Unable to do CPU hotplug - req = json.dumps({'cpus': 5}) + req = json.dumps({'cpu_info': {'vcpus': 5}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
@@ -203,11 +203,11 @@ class RestTests(unittest.TestCase): resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
- req = json.dumps({'cpus': -2}) + req = json.dumps({'cpu_info': {'vcpus': -2}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
- req = json.dumps({'cpus': 'four'}) + req = json.dumps({'cpu_info': {'vcpus': 'four'}}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
@@ -219,11 +219,13 @@ class RestTests(unittest.TestCase): resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
- req = json.dumps({'name': 'new-name', 'cpus': 5, 'UUID': 'notallowed'}) + req = json.dumps({'name': 'new-name', 'cpu_info': {'vcpus': 5}, + 'UUID': 'notallowed'}) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(400, resp.status)
- params = {'name': u'∨м-црdαtеd', 'cpus': 5, 'memory': 3072} + params = {'name': u'∨м-црdαtеd', 'cpu_info': {'vcpus': 5}, + 'memory': 3072} req = json.dumps(params) resp = self.request('/plugins/kimchi/vms/vm-1', req, 'PUT') self.assertEquals(303, resp.status) diff --git a/tests/test_template.py b/tests/test_template.py index 0b3dd98..3b9f26a 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -2,7 +2,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2015 +# Copyright IBM, Corp. 2015-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -70,7 +70,7 @@ class TemplateTests(unittest.TestCase):
# Create a template without cdrom and disk specified fails with 400 t = {'name': 'test', 'os_distro': 'ImagineOS', - 'os_version': '1.0', 'memory': 1024, 'cpus': 1} + 'os_version': '1.0', 'memory': 1024, 'cpu_info': {'vcpus': 1}} req = json.dumps(t) resp = self.request('/plugins/kimchi/templates', req, 'POST') self.assertEquals(400, resp.status) @@ -82,9 +82,8 @@ class TemplateTests(unittest.TestCase): self.assertEquals(201, resp.status)
# Verify the template - keys = ['name', 'icon', 'invalid', 'os_distro', 'os_version', 'cpus', - 'memory', 'cdrom', 'disks', 'networks', - 'folder', 'graphics', 'cpu_info'] + keys = ['name', 'icon', 'invalid', 'os_distro', 'os_version', 'memory', + 'cdrom', 'disks', 'networks', 'folder', 'graphics', 'cpu_info'] tmpl = json.loads( self.request('/plugins/kimchi/templates/test').read() ) @@ -193,34 +192,40 @@ class TemplateTests(unittest.TestCase): self.assertEquals('fedora', update_tmpl['os_distro']) self.assertEquals('21', update_tmpl['os_version'])
- # Update cpus - req = json.dumps({'cpus': 2}) + # Update maxvcpus only + req = json.dumps({'cpu_info': {'maxvcpus': 2}}) resp = self.request(new_tmpl_uri, req, 'PUT') self.assertEquals(200, resp.status) update_tmpl = json.loads(resp.read()) - self.assertEquals(2, update_tmpl['cpus']) + self.assertEquals(2, update_tmpl['cpu_info']['maxvcpus'])
- # Update memory - req = json.dumps({'memory': 2048}) + # Update vcpus only + req = json.dumps({'cpu_info': {'vcpus': 2}}) resp = self.request(new_tmpl_uri, req, 'PUT') self.assertEquals(200, resp.status) update_tmpl = json.loads(resp.read()) - self.assertEquals(2048, update_tmpl['memory']) + self.assertEquals(2, update_tmpl['cpu_info']['vcpus'])
# Update cpu_info - resp = self.request(new_tmpl_uri) - cpu_info = json.loads(resp.read())['cpu_info'] - self.assertEquals(cpu_info, {}) - self.assertEquals(cpu_info.get('topology'), None) - - cpu_info_data = {'cpu_info': {'topology': {'sockets': 1, - 'cores': 2, - 'threads': 1}}} + cpu_info_data = { + 'cpu_info': { + 'maxvcpus': 2, + 'vcpus': 2, + 'topology': {'sockets': 1, 'cores': 2, 'threads': 1} + } + } resp = self.request(new_tmpl_uri, json.dumps(cpu_info_data), 'PUT') self.assertEquals(200, resp.status) update_tmpl = json.loads(resp.read()) self.assertEquals(update_tmpl['cpu_info'], cpu_info_data['cpu_info'])
+ # Update memory + req = json.dumps({'memory': 2048}) + resp = self.request(new_tmpl_uri, req, 'PUT') + self.assertEquals(200, resp.status) + update_tmpl = json.loads(resp.read()) + self.assertEquals(2048, update_tmpl['memory']) + # Update cdrom cdrom_data = {'cdrom': '/tmp/mock2.iso'} resp = self.request(new_tmpl_uri, json.dumps(cdrom_data), 'PUT') diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py index de2d542..e157198 100644 --- a/tests/test_vmtemplate.py +++ b/tests/test_vmtemplate.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2015 +# Copyright IBM, Corp. 2013-2016 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -45,12 +45,12 @@ class VMTemplateTests(unittest.TestCase): disk_bus = get_template_default('old', 'disk_bus') memory = get_template_default('old', 'memory') nic_model = get_template_default('old', 'nic_model') - fields = (('name', 'test'), ('os_distro', 'unknown'), - ('os_version', 'unknown'), ('cpus', 1), + fields = (('name', 'test'), ('cdrom', self.iso), + ('os_distro', 'unknown'), ('os_version', 'unknown'), + ('cpu_info', {'vcpus': 1, 'maxvcpus': 1}), ('memory', memory), ('networks', ['default']), ('disk_bus', disk_bus), ('nic_model', nic_model), - ('graphics', {'type': 'vnc', 'listen': '127.0.0.1'}), - ('cdrom', self.iso)) + ('graphics', {'type': 'vnc', 'listen': '127.0.0.1'}))
args = {'name': 'test', 'cdrom': self.iso} t = VMTemplate(args) @@ -121,10 +121,11 @@ class VMTemplateTests(unittest.TestCase): """ graphics = {'type': 'vnc', 'listen': '127.0.0.1'} args = {'name': 'test', 'os_distro': 'opensuse', 'os_version': '12.3', - 'cpus': 2, 'memory': 2048, 'networks': ['foo'], - 'cdrom': self.iso, 'graphics': graphics} + 'cpu_info': {'vcpus': 2, 'maxvcpus': 4}, 'memory': 2048, + 'networks': ['foo'], 'cdrom': self.iso, 'graphics': graphics} t = VMTemplate(args) - self.assertEquals(2, t.info.get('cpus')) + self.assertEquals(2, t.info.get('cpu_info', {}).get('vcpus')) + self.assertEquals(4, t.info.get('cpu_info', {}).get('maxvcpus')) self.assertEquals(2048, t.info.get('memory')) self.assertEquals(['foo'], t.info.get('networks')) self.assertEquals(self.iso, t.info.get('cdrom'))

Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- ui/js/src/kimchi.guest_edit_main.js | 8 ++++++-- ui/js/src/kimchi.template_edit_main.js | 15 +++++++++------ ui/pages/guest-edit.html.tmpl | 2 +- ui/pages/template-edit.html.tmpl | 4 ++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js index 136469b..94b521e 100644 --- a/ui/js/src/kimchi.guest_edit_main.js +++ b/ui/js/src/kimchi.guest_edit_main.js @@ -660,6 +660,7 @@ kimchi.guest_edit_main = function() { }; var initContent = function(guest) { + guest['vcpus'] = guest['cpu_info']['vcpus'] guest['icon'] = guest['icon'] || 'plugins/kimchi/images/icon-vm.png'; $('#form-guest-edit-general').fillWithObject(guest); kimchi.thisVMState = guest['state']; @@ -707,8 +708,11 @@ kimchi.guest_edit_main = function() { if (data['memory'] !== undefined) { data['memory'] = Number(data['memory']); } - if (data['cpus'] !== undefined) { - data['cpus'] = Number(data['cpus']); + if (data['vcpus'] !== undefined) { + data['cpu_info'] = { + vcpus: Number(data['vcpus']) + }; + delete data['vcpus']; } kimchi.updateVM(kimchi.selectedGuest, data, function() { diff --git a/ui/js/src/kimchi.template_edit_main.js b/ui/js/src/kimchi.template_edit_main.js index f120e91..53f74ba 100644 --- a/ui/js/src/kimchi.template_edit_main.js +++ b/ui/js/src/kimchi.template_edit_main.js @@ -241,9 +241,9 @@ kimchi.template_edit_main = function() { var initProcessor = function(){ var setCPUValue = function(){ if(!$('#cores').hasClass("invalid-field")&&$('#cores').val()!=""){ - $("#cpus").val(parseInt($("#cores").val())*parseInt($("#threads").val())); + $("#vcpus").val(parseInt($("#cores").val())*parseInt($("#threads").val())); }else{ - $("#cpus").val(''); + $("#vcpus").val(''); } }; $("input:text", "#form-template-processor").on('keyup', function(){ @@ -252,7 +252,7 @@ kimchi.template_edit_main = function() { }); $("input:checkbox", "#form-template-processor").click(function(){ $(".topology", "#form-template-processor").toggleClass("hide", !$(this).prop("checked")); - $("#cpus").attr("disabled", $(this).prop("checked")); + $("#vcpus").attr("disabled", $(this).prop("checked")); setCPUValue(); }); $('select', '#form-template-processor').change(function(){ @@ -266,7 +266,7 @@ kimchi.template_edit_main = function() { } $('select', '#form-template-processor').append(options); $('select', '#form-template-processor').selectpicker(); - if(template.cpus) $("#cpus").val(template.cpus); + if(template.cpu_info.vcpus) $("#vcpus").val(template.cpu_info.vcpus); var topo = template.cpu_info.topology; if(topo&&topo.cores) $("#cores").val(topo.cores); if(topo&&topo.threads){ @@ -320,9 +320,10 @@ kimchi.template_edit_main = function() { } }); data['memory'] = Number(data['memory']); - data['cpus'] = parseInt($('#cpus').val()); if($("input:checkbox", "#form-template-processor").prop("checked")){ data['cpu_info'] = { + vcpus: parseInt($('#vcpus').val()), + maxvcpus: parseInt($('#vcpus').val()), topology: { sockets: 1, cores: parseInt($("#cores").val()), @@ -330,7 +331,9 @@ kimchi.template_edit_main = function() { } }; }else{ - data['cpu_info'] = {}; + data['cpu_info'] = { + vcpus: parseInt($('#vcpus').val()) + }; } var networks = $('.template-tab-body .item', '#form-template-interface'); var networkForUpdate = new Array(); diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl index 0ad635c..32576e9 100644 --- a/ui/pages/guest-edit.html.tmpl +++ b/ui/pages/guest-edit.html.tmpl @@ -46,7 +46,7 @@ </div> <div class="form-group"> <label for="guest-edit-cores-textbox">$_("CPUs")</label> - <input id="guest-edit-cores-textbox" class="form-control" name="cpus" type="text" /> + <input id="guest-edit-cores-textbox" class="form-control" name="vcpus" type="text" /> </div> <div class="form-group"> <label for="guest-edit-memory-textbox">$_("Memory (MB)")</label> diff --git a/ui/pages/template-edit.html.tmpl b/ui/pages/template-edit.html.tmpl index 478ccd5..36d0751 100644 --- a/ui/pages/template-edit.html.tmpl +++ b/ui/pages/template-edit.html.tmpl @@ -123,8 +123,8 @@ <form id="form-template-processor"> <div class="form-inline"> <div class="form-group"> - <label for="cpus">$_("CPU Number"):</label> - <input type="text" class="form-control" value="1" id="cpus" /> + <label for="vcpus">$_("CPU Number"):</label> + <input type="text" class="form-control" value="1" id="vcpus" /> </div> </div> <div class="manual form-group"> -- 1.9.1

This is V2 -- Lucio Correia Software Engineer IBM LTC Brazil

Reviewed-by: Paulo Vital <pvital@linux.vnet.ibm.com> On 02/04/2016 02:36 PM, Lucio Correia wrote:
Lucio Correia (4): Add maxvcpus attribute to templates Add maxvcpus attribute to guests Update tests Do not break web UI
API.json | 34 +++--- control/templates.py | 3 +- docs/API.md | 66 ++++++++--- i18n.py | 10 +- model/cpuinfo.py | 63 ++++++++--- model/templates.py | 79 ++++++++------ model/vms.py | 194 +++++++++++++++++---------------- osinfo.py | 13 ++- template.conf | 7 +- tests/test_mockmodel.py | 7 +- tests/test_model.py | 17 +-- tests/test_rest.py | 14 ++- tests/test_template.py | 43 ++++---- tests/test_vmtemplate.py | 17 +-- ui/js/src/kimchi.guest_edit_main.js | 8 +- ui/js/src/kimchi.template_edit_main.js | 15 ++- ui/pages/guest-edit.html.tmpl | 2 +- ui/pages/template-edit.html.tmpl | 4 +- vmtemplate.py | 43 +++----- xmlutils/cpu.py | 6 +- 20 files changed, 369 insertions(+), 276 deletions(-)
participants (3)
-
Aline Manera
-
Lucio Correia
-
Paulo Vital