[Kimchi-devel] [PATCH v3 4/5] storagepool: Support Creating iSCSI storagepool in model.py

Royce Lv lvroyce at linux.vnet.ibm.com
Mon Dec 30 08:49:05 UTC 2013


On 2013年12月27日 18:20, Zhou Zheng Sheng wrote:
> This patch implements creating iSCSI storagepool for libvirt.
> Each LUN in iSCSI storagepool can be used as a volume, but iSCSI
> storagepool does not provide ability to create volume. For now in kimchi
> we create volume for each newly created VM. Next is to implement
> attaching existing volume to a new VM.
>
> This patch also adds validation for the iSCSI back-end. It tries to
> login the target using the auth before actually creating the pool.
> On RedHat family distributions, libiscsi comes with a Python binding,
> but not on other distributions. So in this patch it invokes iscsiadm to
> check iSCSI target.
>
> As regard to iSCSI CHAP authentication, it's somewhat broken before
> libvirt 1.1.1. It was reworked in libvirt commit eb0d79, and this patch
> goes with libvirt 1.1.1, but it doesn't require libvirt in the spec
> file. This is because libvirt 1.1.1 is not available on Ubuntu 12.04
> LTS. The user can just ignore this feature.
>
> How to test it manually
>
> Prerequisite
> An running iSCSI target on the network.
>
> Create:
> curl -u root -H 'Content-type: application/json' \
>    -H 'Accept: application/json' \
>    -d '{"source": {
>            "target": "iqn.YouriSCSITargetIQN.XXX",
>            "host": "10.0.0.X"},
>         "type": "iscsi",
>         "name": "iscsipool"}' \
>    http://127.0.0.1:8000/storagepools
>
> Show Info:
> curl -u root -H 'Accept: application/json' \
>    http://127.0.0.1:8000/storagepools/iscsipool
>
> Activate:
> curl -u root -H 'Content-type: application/json' \
>    -H 'Accept: application/json' \
>    -d '' http://127.0.0.1:8000/storagepools/iscsipool/activate
>
> Examine:
> iscsiadm -m session
>
> Deactivate:
> curl -u root -H 'Content-type: application/json' \
>    -H 'Accept: application/json' \
>    -d '' http://127.0.0.1:8000/storagepools/iscsipool/deactivate
>
> Delete:
> curl -u root -X DELETE -H 'Accept: application/json' \
>    http://127.0.0.1:8000/storagepools/iscsipool
>
> v2 -> v3
>    1) Support CHAP authentication.
>    2) Validate iSCSI target before creating the pool.
>    3) Add XML generation unit test with "port" and "auth".
>
> Signed-off-by: Zhou Zheng Sheng <zhshzhou at linux.vnet.ibm.com>
> ---
>   Makefile.am               |  1 +
>   docs/API.md               | 12 +++++--
>   src/kimchi/Makefile.am    |  1 +
>   src/kimchi/iscsi.py       | 89 +++++++++++++++++++++++++++++++++++++++++++++++
>   src/kimchi/model.py       | 88 ++++++++++++++++++++++++++++++++++++++++++++--
>   tests/test_storagepool.py | 63 +++++++++++++++++++++++++++++++++
>   6 files changed, 249 insertions(+), 5 deletions(-)
>   create mode 100644 src/kimchi/iscsi.py
>
> diff --git a/Makefile.am b/Makefile.am
> index 33059a7..b87f5d0 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -43,6 +43,7 @@ PEP8_WHITELIST = \
>   	src/kimchi/cachebust.py \
>   	src/kimchi/config.py.in \
>   	src/kimchi/disks.py \
> +	src/kimchi/iscsi.py \
>   	src/kimchi/root.py \
>   	src/kimchi/server.py \
>   	plugins/__init__.py \
> diff --git a/docs/API.md b/docs/API.md
> index a8b9ea0..863f6ad 100644
> --- a/docs/API.md
> +++ b/docs/API.md
> @@ -182,17 +182,25 @@ Represents a snapshot of the Virtual Machine's primary monitor.
>   * **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'
> +            Supported types: 'dir', 'kimchi-iso', 'netfs', 'logical', 'iscsi'
>       * path: The path of the defined Storage Pool.
>               For 'kimchi-iso' pool refers to targeted deep scan path.
>               Pool types: 'dir', 'kimchi-iso'.
>       * source: Dictionary containing source information of the pool.
>           * host: IP or hostname of server for a pool backed from a remote host.
> -                Pool types: 'netfs'.
> +                Pool types: 'netfs', 'iscsi'.
>           * path: Export path on NFS server for NFS pool.
>                   Pool types: 'netfs'.
>           * devices: Array of devices to be used in the Storage Pool
>                      Pool types: 'logical'.
> +        * target: Target IQN of an iSCSI pooVl.
Typo?
> +                  Pool types: 'iscsi'.
> +        * port *(optional)*: Listening port of a remote storage server.
> +                             Pool types: 'iscsi'.
> +        * auth *(optional)*: Storage back-end authentication information.
> +                             Pool types: 'iscsi'.
> +            * username: Login username of the iSCSI target.
> +            * password: Login password of the iSCSI target.
>
>   ### Resource: Storage Pool
>
> diff --git a/src/kimchi/Makefile.am b/src/kimchi/Makefile.am
> index 47a3bd2..02b6fee 100644
> --- a/src/kimchi/Makefile.am
> +++ b/src/kimchi/Makefile.am
> @@ -28,6 +28,7 @@ kimchi_PYTHON = \
>   	distroloader.py   \
>   	exception.py      \
>   	__init__.py       \
> +	iscsi.py          \
>   	isoinfo.py        \
>   	netinfo.py        \
>   	network.py        \
> diff --git a/src/kimchi/iscsi.py b/src/kimchi/iscsi.py
> new file mode 100644
> index 0000000..51cffbb
> --- /dev/null
> +++ b/src/kimchi/iscsi.py
> @@ -0,0 +1,89 @@
> +#
> +# Project Kimchi
> +#
> +# Copyright IBM, Corp. 2013
> +#
> +# Authors:
> +#  Zhou Zheng Sheng <zhshzhou at linux.vnet.ibm.com>
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301USA
> +
> +import subprocess
> +
> +
> +from kimchi.exception import OperationFailed
> +
> +
> +class IscsiTarget(object):
What about change its name to IscsiCli?or sth like initiator? Target 
sounds like a server
> +    def __init__(self, target, host, port=None, auth=None):
> +        self.portal = host + ("" if port is None else ":%s" % port)
> +        self.target = target
> +        self.auth = auth
> +        self.targetCmd = ['iscsiadm', '--mode', 'node', '--targetname',
> +                          self.target, '--portal', self.portal]
The cmd iscsiadm is at least not installed by default in my Ubuntu 
13.04, I think we need to update the spec file.
> +
> +    def _update_db(self, Name, Value):
> +        self._run_cmd(['--op=update', '--name', Name, '--value', Value])
> +
> +    def _update_auth(self):
> +        if self.auth is None:
> +            items = (('node.session.auth.authmethod', 'None'),
> +                     ('node.session.auth.username', ''),
> +                     ('node.session.auth.password', ''))
> +        else:
> +            items = (('node.session.auth.authmethod', 'CHAP'),
> +                     ('node.session.auth.username', self.auth['username']),
> +                     ('node.session.auth.password', self.auth['password']))
> +        for name, value in items:
> +            self._update_db(name, value)
> +
> +    def _run_cmd(self, cmd):
> +        iscsiadm = subprocess.Popen(
> +            self.targetCmd + cmd,
> +            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
> +        out, err = iscsiadm.communicate()
> +        if iscsiadm.returncode != 0:
> +            raise OperationFailed('Error executing iscsiadm: %s' % err)
> +        return out
> +
> +    def _discover(self):
> +        iscsiadm = subprocess.Popen(
> +            ['iscsiadm', '--mode', 'discovery', '--type', 'sendtargets',
> +             '--portal', self.portal],
> +            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
> +        out, err = iscsiadm.communicate()
> +        if iscsiadm.returncode != 0:
> +            raise OperationFailed('Error executing iscsiadm: %s' % err)
> +        return out
> +
> +    def _run_op(self, op):
> +        self._run_cmd(['--' + op])
> +
> +    def login(self):
> +        self._discover()
> +        self._update_auth()
> +        self._run_op('login')
> +
> +    def logout(self):
> +        self._run_op('logout')
> +
> +    def validate(self):
> +        try:
> +            self.login()
> +        except OperationFailed:
> +            return False
> +
> +        self.logout()
> +        return True
> diff --git a/src/kimchi/model.py b/src/kimchi/model.py
> index b6213e9..39795c9 100644
> --- a/src/kimchi/model.py
> +++ b/src/kimchi/model.py
> @@ -65,6 +65,7 @@ from kimchi.distroloader import DistroLoader
>   from kimchi.exception import InvalidOperation, InvalidParameter, MissingParameter
>   from kimchi.exception import NotFoundError, OperationFailed
>   from kimchi.featuretests import FeatureTests
> +from kimchi.iscsi import IscsiTarget
>   from kimchi.networkxml import to_network_xml
>   from kimchi.objectstore import ObjectStore
>   from kimchi.scan import Scanner
> @@ -995,7 +996,7 @@ class Model(object):
>               if params['type'] == 'kimchi-iso':
>                   task_id = self._do_deep_scan(params)
>               poolDef = StoragePoolDef.create(params)
> -            poolDef.prepare()
> +            poolDef.prepare(conn)
>               xml = poolDef.get_xml()
>           except KeyError, key:
>               raise MissingParameter(key)
> @@ -1444,7 +1445,7 @@ class StoragePoolDef(object):
>       def __init__(self, poolArgs):
>           self.poolArgs = poolArgs
>
> -    def prepare(self):
> +    def prepare(self, conn):
>           ''' Validate pool arguments and perform preparations. Operation which
>           would cause side effect should be put here. Subclasses can optionally
>           override this method, or it always succeeds by default. '''
> @@ -1489,7 +1490,7 @@ class NetfsPoolDef(StoragePoolDef):
>           super(NetfsPoolDef, self).__init__(poolArgs)
>           self.path = '/var/lib/kimchi/nfs_mount/' + self.poolArgs['name']
>
> -    def prepare(self):
> +    def prepare(self, conn):
>           # TODO Verify the NFS export can be actually mounted.
>           pass
>
> @@ -1548,6 +1549,87 @@ class LogicalPoolDef(StoragePoolDef):
>           return xml
>
>
> +class IscsiPoolDef(StoragePoolDef):
> +    poolType = 'iscsi'
> +
> +    def prepare(self, conn):
> +        source = self.poolArgs['source']
> +        if not IscsiTarget(**source).validate():
> +            raise OperationFailed("Can not login to iSCSI host %s target %s" %
> +                                  (source['host'], source['target']))
> +        self._prepare_auth(conn)
> +
> +    def _prepare_auth(self, conn):
> +        try:
> +            auth = self.poolArgs['source']['auth']
> +        except KeyError:
> +            return
> +
> +        try:
> +            virSecret = conn.secretLookupByUsage(
> +                libvirt.VIR_SECRET_USAGE_TYPE_ISCSI, self.poolArgs['name'])
> +        except libvirt.libvirtError:
> +            xml = '''
> +            <secret ephemeral='no' private='yes'>
> +              <description>Secret for iSCSI storage pool {name}</description>
> +              <auth type='chap' username='{username}'/>
> +              <usage type='iscsi'>
> +                <target>{name}</target>
> +              </usage>
> +            </secret>'''.format(name=self.poolArgs['name'],
> +                                username=auth['username'])
> +            virSecret = conn.secretDefineXML(xml)
> +
> +        virSecret.setValue(auth['password'])
> +
> +    def _format_port(self, poolArgs):
> +        try:
> +            port = poolArgs['source']['port']
> +        except KeyError:
> +            return ""
> +        return "port='%s'" % port
> +
> +    def _format_auth(self, poolArgs):
> +        try:
> +            auth = poolArgs['source']['auth']
> +        except KeyError:
> +            return ""
> +
> +        return '''
> +        <auth type='chap' username='{username}'>
> +          <secret type='iscsi' usage='{name}'/>
> +        </auth>'''.format(name=poolArgs['name'], username=auth['username'])
I think these are suitable usecase for @property.
> +
> +    def _get_xml_imp(self, poolArgs):
> +        # Required parameters
> +        # name:
> +        # type:
> +        # source[host]:
> +        # source[target]:
> +        #
> +        # Optional parameters
> +        # source[port]:
> +
> +        poolArgs['source'].update({'port': self._format_port(poolArgs),
> +                                   'auth': self._format_auth(poolArgs)})
> +        poolArgs['path'] = '/dev/disk/by-id'
> +
> +        xml = """
> +        <pool type='iscsi'>
> +          <name>{name}</name>
> +          <source>
> +            <host name='{source[host]}' {source[port]}/>
> +            <device path='{source[target]}'/>
> +            {source[auth]}
> +          </source>
> +          <target>
> +            <path>{path}</path>
> +          </target>
> +        </pool>
> +        """.format(**poolArgs)
> +        return xml
> +
> +
>   def _get_volume_xml(**kwargs):
>       # Required parameters
>       # name:
> diff --git a/tests/test_storagepool.py b/tests/test_storagepool.py
> index beecdb6..c3f08ad 100644
> --- a/tests/test_storagepool.py
> +++ b/tests/test_storagepool.py
> @@ -78,6 +78,69 @@ class storagepoolTests(unittest.TestCase):
>                    <path>/var/lib/kimchi/logical_mount/unitTestLogicalPool</path>
>                </target>
>                </pool>
> +             """},
> +            {'def':
> +                {'type': 'iscsi',
> +                 'name': 'unitTestISCSIPool',
> +                 'source': {
> +                     'host': '127.0.0.1',
> +                     'target': 'iqn.2003-01.org.linux-iscsi.localhost'}},
> +             'xml':
> +             """
> +             <pool type='iscsi'>
> +               <name>unitTestISCSIPool</name>
> +               <source>
> +                 <host name='127.0.0.1' />
> +                 <device path='iqn.2003-01.org.linux-iscsi.localhost'/>
> +               </source>
> +               <target>
> +                 <path>/dev/disk/by-id</path>
> +               </target>
> +             </pool>
> +             """},
> +            {'def':
> +                {'type': 'iscsi',
> +                 'name': 'unitTestISCSIPoolPort',
> +                 'source': {
> +                     'host': '127.0.0.1',
> +                     'port': 3266,
> +                     'target': 'iqn.2003-01.org.linux-iscsi.localhost'}},
> +             'xml':
> +             """
> +             <pool type='iscsi'>
> +               <name>unitTestISCSIPoolPort</name>
> +               <source>
> +                 <host name='127.0.0.1' port='3266' />
> +                 <device path='iqn.2003-01.org.linux-iscsi.localhost'/>
> +               </source>
> +               <target>
> +                 <path>/dev/disk/by-id</path>
> +               </target>
> +             </pool>
> +             """},
> +            {'def':
> +                {'type': 'iscsi',
> +                 'name': 'unitTestISCSIPoolAuth',
> +                 'source': {
> +                     'host': '127.0.0.1',
> +                     'target': 'iqn.2003-01.org.linux-iscsi.localhost',
> +                     'auth': {'username': 'testUser',
> +                              'password': 'ActuallyNotUsedInPoolXML'}}},
> +             'xml':
> +             """
> +             <pool type='iscsi'>
> +               <name>unitTestISCSIPoolAuth</name>
> +               <source>
> +                 <host name='127.0.0.1' />
> +                 <device path='iqn.2003-01.org.linux-iscsi.localhost'/>
> +                 <auth type='chap' username='testUser'>
> +                   <secret type='iscsi' usage='unitTestISCSIPoolAuth'/>
> +                 </auth>
> +               </source>
> +               <target>
> +                 <path>/dev/disk/by-id</path>
> +               </target>
> +             </pool>
>                """}]
>
>           for poolDef in poolDefs:




More information about the Kimchi-devel mailing list