[Kimchi-devel] [PATCH v6 (BACKEND) 3/5] Storagepool SCSI/FC: Backend implementation

Aline Manera alinefm at linux.vnet.ibm.com
Thu Feb 13 22:22:43 UTC 2014


On 02/13/2014 04:32 PM, Rodrigo Trujillo wrote:
> This patch creates functions that allow kimchi users to create an libvirt
> SCSI storagepool using the rest API. This patch creates the feature test
> to check fc_host capability in libvirt. This patch implements basic
> routines to add a disk (scsi) to a new vm template, based on given
> volumes (LUN name) from UI or API directly.
>
> Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo at linux.vnet.ibm.com>
> ---
>   docs/API.md                            |  5 +++-
>   src/kimchi/API.json                    | 14 ++++++++--
>   src/kimchi/featuretests.py             | 29 ++++++++++++++++++++
>   src/kimchi/model/config.py             |  2 ++
>   src/kimchi/model/libvirtstoragepool.py | 48 ++++++++++++++++++++++++++++++++--
>   src/kimchi/model/storagepools.py       | 30 ++++++++++++++++++---
>   src/kimchi/model/templates.py          |  5 ++++
>   src/kimchi/model/vms.py                | 27 ++++++++++++++++---
>   src/kimchi/vmtemplate.py               | 31 +++++++++++++++++++++-
>   9 files changed, 178 insertions(+), 13 deletions(-)
>
> diff --git a/docs/API.md b/docs/API.md
> index d4a1716..ee89fe0 100644
> --- a/docs/API.md
> +++ b/docs/API.md
> @@ -55,6 +55,8 @@ the following general conventions:
>                        Independent Computing Environments
>               * null: Graphics is disabled or type not supported
>           * listen: The network which the vnc/spice server listens on.
> +    * volumes *(optional)*: List of Fibre channel LUN names to be assigned as
> +                            disk to VM. Required if pool is type SCSI.
>
>
>   ### Resource: Virtual Machine
> @@ -291,7 +293,7 @@ A interface represents available network interface on VM.
>   * **POST**: Create a new Storage Pool
>       * name: The name of the Storage Pool.
>       * type: The type of the defined Storage Pool.
> -            Supported types: 'dir', 'kimchi-iso', 'netfs', 'logical', 'iscsi'
> +            Supported types: 'dir', 'kimchi-iso', 'netfs', 'logical', 'iscsi', 'scsi'
>       * path: The path of the defined Storage Pool.
>               For 'kimchi-iso' pool refers to targeted deep scan path.
>               Pool types: 'dir', 'kimchi-iso'.
> @@ -310,6 +312,7 @@ A interface represents available network interface on VM.
>                                Pool types: 'iscsi'.
>               * username: Login username of the iSCSI target.
>               * password: Login password of the iSCSI target.
> +        * adapter_name *(optional)*: Scsi host name.
>
>   ### Resource: Storage Pool
>
> diff --git a/src/kimchi/API.json b/src/kimchi/API.json
> index 264ec57..84a9da7 100644
> --- a/src/kimchi/API.json
> +++ b/src/kimchi/API.json
> @@ -43,7 +43,7 @@
>                   "type": {
>                       "description": "The type of the defined Storage Pool",
>                       "type": "string",
> -                    "pattern": "^dir|netfs|logical|kimchi-iso$",
> +                    "pattern": "^dir|netfs|logical|kimchi-iso|iscsi|scsi$",
>                       "required": true,
>                       "error": "KCHPOOL0017E"
>                   },
> @@ -90,6 +90,10 @@
>                               "maximum": 65535,
>                               "error": "KCHPOOL0023E"
>                           },
> +                        "adapter_name": {
> +                            "description": "SCSI host name",
> +                            "type": "string"
> +                        },
>                           "auth": {
>                               "description": "Storage back-end authentication information",
>                               "type": "object",
> @@ -148,7 +152,13 @@
>                       "pattern": "^/storagepools/[^/]+/?$",
>                       "error": "KCHVM0013E"
>                   },
> -                "graphics": { "$ref": "#/kimchitype/graphics" }
> +                "graphics": { "$ref": "#/kimchitype/graphics" },
> +                "volumes": {
> +                    "description": "list of scsi volumes to be assigned to the new VM.",
> +                    "type": "array",
> +                    "items": { "type": "string" },
> +                    "uniqueItems": true
> +                }
>               }
>           },
>           "vm_update": {
> diff --git a/src/kimchi/featuretests.py b/src/kimchi/featuretests.py
> index d924050..e759b6d 100644
> --- a/src/kimchi/featuretests.py
> +++ b/src/kimchi/featuretests.py
> @@ -57,6 +57,18 @@ ISO_STREAM_XML = """
>     </devices>
>   </domain>"""
>
> +SCSI_FC_XML = """
> +<pool type='scsi'>
> +  <name>TEST_SCSI_FC_POOL</name>
> +  <source>
> +    <adapter type='fc_host' wwnn='1234567890abcdef' wwpn='abcdef1234567890'/>
> +  </source>
> +  <target>
> +    <path>/dev/disk/by-path</path>
> +  </target>
> +</pool>
> +"""
> +
>
>   class FeatureTests(object):
>
> @@ -150,3 +162,20 @@ class FeatureTests(object):
>               return False
>
>           return True
> +
> +    @staticmethod
> +    def libvirt_support_fc_host():
> +        try:
> +            FeatureTests.disable_screen_error_logging()
> +            conn = libvirt.open('qemu:///system')
> +            pool = None
> +            pool = conn.storagePoolDefineXML(SCSI_FC_XML, 0)
> +        except libvirt.libvirtError as e:
> +            if e.get_error_code() == 27:
> +                # Libvirt requires adapter name, not needed when supports to FC
> +                return False
> +        finally:
> +            FeatureTests.enable_screen_error_logging
> +            pool is None or pool.undefine()
> +            conn is None or conn.close()
> +        return True
> diff --git a/src/kimchi/model/config.py b/src/kimchi/model/config.py
> index 9b5814a..6641cce 100644
> --- a/src/kimchi/model/config.py
> +++ b/src/kimchi/model/config.py
> @@ -49,6 +49,7 @@ class CapabilitiesModel(object):
>           self.qemu_stream = False
>           self.qemu_stream_dns = False
>           self.libvirt_stream_protocols = []
> +        self.fc_host_support = False
>
>           # Subscribe function to set host capabilities to be run when cherrypy
>           # server is up
> @@ -60,6 +61,7 @@ class CapabilitiesModel(object):
>           self.qemu_stream = FeatureTests.qemu_supports_iso_stream()
>           self.qemu_stream_dns = FeatureTests.qemu_iso_stream_dns()
>           self.nfs_target_probe = FeatureTests.libvirt_support_nfs_probe()
> +        self.fc_host_support = FeatureTests.libvirt_support_fc_host()
>
>           self.libvirt_stream_protocols = []
>           for p in ['http', 'https', 'ftp', 'ftps', 'tftp']:
> diff --git a/src/kimchi/model/libvirtstoragepool.py b/src/kimchi/model/libvirtstoragepool.py
> index f045f1c..adffc55 100644
> --- a/src/kimchi/model/libvirtstoragepool.py
> +++ b/src/kimchi/model/libvirtstoragepool.py
> @@ -29,8 +29,7 @@ import libvirt
>   from kimchi.exception import InvalidParameter, OperationFailed, TimeoutExpired
>   from kimchi.iscsi import TargetClient
>   from kimchi.rollbackcontext import RollbackContext
> -from kimchi.utils import parse_cmd_output, run_command
> -
> +from kimchi.utils import kimchi_log, parse_cmd_output, run_command
>
>   class StoragePoolDef(object):
>       @classmethod
> @@ -173,6 +172,51 @@ class LogicalPoolDef(StoragePoolDef):
>           return xml
>
>
> +class ScsiPoolDef(StoragePoolDef):
> +    poolType = 'scsi'
> +
> +    def prepare(self, conn=None):
> +        tmp_name = self.poolArgs['source']['name']
> +        self.poolArgs['source']['name'] = tmp_name.replace('scsi_','')
> +        # fc_host adapters type are only available in libvirt >= 1.0.5
> +        if not self.poolArgs['fc_host_support']:
> +            self.poolArgs['source']['adapter_type'] = 'scsi_host'
> +            msg = "Libvirt version <= 1.0.5. Setting SCSI host name as '%s'; "\
> +                  "setting SCSI adapter type as 'scsi_host'; "\
> +                  "ignoring wwnn and wwpn." %tmp_name
> +            kimchi_log.info(msg)
> +        # Path for Fibre Channel scsi hosts
> +        self.poolArgs['path'] = '/dev/disk/by-path'
> +        if not self.poolArgs['source']['adapter_type']:
> +            self.poolArgs['source']['adapter_type'] = 'scsi_host'
> +
> +    @property
> +    def xml(self):
> +        # Required parameters
> +        # name:
> +        # source[adapter_type]:
> +        # source[name]:
> +        # source[wwnn]:
> +        # source[wwpn]:
> +        # path:
> +
> +        xml = """
> +        <pool type='scsi'>
> +          <name>{name}</name>
> +          <source>
> +            <adapter type='{source[adapter_type]}'\
> +                     name='{source[name]}'\
> +                     wwnn='{source[wwnn]}'\
> +                     wwpn='{source[wwpn]}'/>
> +          </source>
> +          <target>
> +            <path>{path}</path>
> +          </target>
> +        </pool>
> +        """.format(**self.poolArgs)
> +        return xml
> +
> +
>   class IscsiPoolDef(StoragePoolDef):
>       poolType = 'iscsi'
>
> diff --git a/src/kimchi/model/storagepools.py b/src/kimchi/model/storagepools.py
> index ff664c4..69113b2 100644
> --- a/src/kimchi/model/storagepools.py
> +++ b/src/kimchi/model/storagepools.py
> @@ -26,6 +26,8 @@ from kimchi import xmlutils
>   from kimchi.scan import Scanner
>   from kimchi.exception import InvalidOperation, MissingParameter
>   from kimchi.exception import NotFoundError, OperationFailed
> +from kimchi.model.config import CapabilitiesModel
> +from kimchi.model.host import DeviceModel
>   from kimchi.model.libvirtstoragepool import StoragePoolDef
>   from kimchi.utils import add_task, kimchi_log
>   from kimchi.utils import run_command
> @@ -39,7 +41,11 @@ POOL_STATE_MAP = {0: 'inactive',
>                     4: 'inaccessible'}
>
>   STORAGE_SOURCES = {'netfs': {'addr': '/pool/source/host/@name',
> -                             'path': '/pool/source/dir/@path'}}
> +                             'path': '/pool/source/dir/@path'},
> +                   'scsi': {'adapter_type': '/pool/source/adapter/@type',
> +                            'adapter_name': '/pool/source/adapter/@name',
> +                            'wwnn': '/pool/source/adapter/@wwnn',
> +                            'wwpn': '/pool/source/adapter/@wwpn'}}
>
>
>   class StoragePoolsModel(object):
> @@ -48,6 +54,8 @@ class StoragePoolsModel(object):
>           self.objstore = kargs['objstore']
>           self.scanner = Scanner(self._clean_scan)
>           self.scanner.delete()
> +        self.caps = CapabilitiesModel()
> +        self.device = DeviceModel(**kargs)
>
>       def get_list(self):
>           try:
> @@ -69,6 +77,14 @@ class StoragePoolsModel(object):
>
>               if params['type'] == 'kimchi-iso':
>                   task_id = self._do_deep_scan(params)
> +
> +            if params['type'] == 'scsi':
> +                extra_params = self.device.lookup(
> +                               params['source']['adapter_name'])
> +                # Adds name, adapter_type, wwpn and wwnn to source information
> +                params['source'].update(extra_params)
> +                params['fc_host_support'] = self.caps.fc_host_support
> +
>               poolDef = StoragePoolDef.create(params)
>               poolDef.prepare(conn)
>               xml = poolDef.xml.encode("utf-8")
> @@ -86,9 +102,10 @@ class StoragePoolsModel(object):
>                   return name
>
>               pool = conn.storagePoolDefineXML(xml, 0)
> -            if params['type'] in ['logical', 'dir', 'netfs']:
> +            if params['type'] in ['logical', 'dir', 'netfs', 'scsi']:
>                   pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW)
> -                # autostart dir and logical storage pool created from kimchi
> +                # autostart dir, logical, netfs and scsi storage pools created
> +                # from kimchi
>                   pool.setAutostart(1)
>               else:
>                   # disable autostart for others
> @@ -168,7 +185,12 @@ class StoragePoolModel(object):
>
>           for key, val in STORAGE_SOURCES[pool_type].items():
>               res = xmlutils.xpath_get_text(pool_xml, val)
> -            source[key] = res[0] if len(res) == 1 else res
> +            if len(res) == 1:
> +                source[key] = res[0]
> +            elif len(res) == 0:
> +                source[key] = ""
> +            else:
> +                souce[key] = res
>
>           return source
>
> diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py
> index fd93414..3d05e80 100644
> --- a/src/kimchi/model/templates.py
> +++ b/src/kimchi/model/templates.py
> @@ -161,6 +161,11 @@ class LibvirtVMTemplate(VMTemplate):
>           xml = pool.XMLDesc(0)
>           return xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
>
> +    def _get_storage_type(self):
> +        pool = self._storage_validate()
> +        xml = pool.XMLDesc(0)
> +        return xmlutils.xpath_get_text(xml, "/pool/@type")[0]
> +
>       def fork_vm_storage(self, vm_uuid):
>           # Provision storage:
>           # TODO: Rebase on the storage API once upstream
> diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
> index a5868c3..a215352 100644
> --- a/src/kimchi/model/vms.py
> +++ b/src/kimchi/model/vms.py
> @@ -35,7 +35,7 @@ from cherrypy.process.plugins import BackgroundTask
>   from kimchi import vnc
>   from kimchi import xmlutils
>   from kimchi.exception import InvalidOperation, InvalidParameter
> -from kimchi.exception import NotFoundError, OperationFailed
> +from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
>   from kimchi.model.config import CapabilitiesModel
>   from kimchi.model.templates import TemplateModel
>   from kimchi.model.utils import get_vm_name
> @@ -164,6 +164,11 @@ class VMsModel(object):
>                                  'diskRdKB': diskRdKB,
>                                  'diskWrKB': diskWrKB})
>
> +    def _get_volume_path(self, pool, vol):
> +         conn = self.conn.get()
> +         pool = conn.storagePoolLookupByName(pool)
> +         return pool.storageVolLookupByName(vol).path()
> +
>       def create(self, params):
>           conn = self.conn.get()
>           t_name = template_name_from_uri(params['template'])
> @@ -178,6 +183,7 @@ class VMsModel(object):
>           pool_uri = params.get('storagepool')
>           if pool_uri:
>               vm_overrides['storagepool'] = pool_uri
> +            vm_overrides['fc_host_support'] = self.caps.fc_host_support
>           t = TemplateModel.get_template(t_name, self.objstore, self.conn,
>                                          vm_overrides)
>
> @@ -185,7 +191,21 @@ class VMsModel(object):
>               raise InvalidOperation("KCHVM0005E")
>
>           t.validate()
> -        vol_list = t.fork_vm_storage(vm_uuid)
> +
> +        # If storagepool is SCSI, volumes will be LUNs and must be passed by
> +        # the user from UI or manually.
> +        vol_list = []
> +        if t._get_storage_type() == 'scsi':
> +            if not params.get('volumes'):

> +                raise MissingParameter("Volume list (LUNs names) not given.")

Needs to do it according to refactor exception patches

> +            else:
> +                # Get system path of the LUNs
> +                pool = t.info['storagepool'].split('/')[-1]
> +                for vol in params.get('volumes'):
> +                    path = self._get_volume_path(pool, vol)
> +                    vol_list.append((vol, path))
> +        else:
> +            vol_list = t.fork_vm_storage(vm_uuid)
>
>           # Store the icon for displaying later
>           icon = t.info.get('icon')
> @@ -201,7 +221,8 @@ class VMsModel(object):
>           xml = t.to_vm_xml(name, vm_uuid,
>                             libvirt_stream=libvirt_stream,
>                             qemu_stream_dns=self.caps.qemu_stream_dns,
> -                          graphics=graphics)
> +                          graphics=graphics,
> +                          volumes=vol_list)
>
>           try:
>               conn.defineXML(xml.encode('utf-8'))
> diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py
> index 3545de4..37994c2 100644
> --- a/src/kimchi/vmtemplate.py
> +++ b/src/kimchi/vmtemplate.py
> @@ -49,6 +49,7 @@ class VMTemplate(object):
>           """
>           self.name = args['name']
>           self.info = {}
> +        self.fc_host_support = args.get('fc_host_support')
>
>           # Identify the cdrom if present
>           iso_distro = iso_version = 'unknown'
> @@ -180,6 +181,25 @@ class VMTemplate(object):
>               graphics_xml = graphics_xml + spicevmc_xml
>           return graphics_xml
>
> +    def _get_scsi_disks_xml(self, luns):
> +        ret = ""
> +        # Passthrough configuration
> +        disk_xml = """
> +            <disk type='volume' device='lun'>
> +              <driver name='qemu' type='raw'/>
> +              <source dev='%(src)s'/>
> +              <target dev='%(dev)s' bus='scsi'/>
> +            </disk>"""
> +        if not self.fc_host_support:
> +            disk_xml = disk_xml.replace('volume','block')
> +
> +        # Creating disk xml for each lun passed
> +        for index,(lun, path) in enumerate(luns):
> +            dev = "sd%s" % string.lowercase[index]
> +            params = {'src': path, 'dev': dev}
> +            ret = ret + disk_xml % params
> +        return ret
> +
>       def to_volume_list(self, vm_uuid):
>           storage_path = self._get_storage_path()
>           ret = []
> @@ -225,7 +245,6 @@ class VMTemplate(object):
>           params = dict(self.info)
>           params['name'] = vm_name
>           params['uuid'] = vm_uuid
> -        params['disks'] = self._get_disks_xml(vm_uuid)
>           params['networks'] = self._get_networks_xml()
>           params['qemu-namespace'] = ''
>           params['cdroms'] = ''
> @@ -233,6 +252,13 @@ class VMTemplate(object):
>           graphics = kwargs.get('graphics')
>           params['graphics'] = self._get_graphics_xml(graphics)
>
> +        # Current implementation just allows to create disk in one single
> +        # storage pool, so we cannot mix the types (scsi volumes vs img file)
> +        if self._get_storage_type() == 'scsi':
> +            params['disks'] = self._get_scsi_disks_xml(kwargs.get('volumes'))
> +        else:
> +            params['disks'] = self._get_disks_xml(vm_uuid)
> +
>           qemu_stream_dns = kwargs.get('qemu_stream_dns', False)
>           libvirt_stream = kwargs.get('libvirt_stream', False)
>           cdrom_xml = self._get_cdrom_xml(libvirt_stream, qemu_stream_dns)
> @@ -292,3 +318,6 @@ class VMTemplate(object):
>
>       def _get_storage_path(self):
>           return ''
> +
> +    def _get_storage_type(self):
> +        return ''




More information about the Kimchi-devel mailing list