[Kimchi-devel] [PATCH] [Kimchi 1/4] Add maxvcpus attribute to templates

Lucio Correia luciojhc at linux.vnet.ibm.com
Thu Feb 4 16:36:46 UTC 2016


- 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 at 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




More information about the Kimchi-devel mailing list