components/openstack/cinder/files/zfssa/zfssarest.py
branchs11-update
changeset 3178 77584387a894
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openstack/cinder/files/zfssa/zfssarest.py	Fri Jun 13 09:10:23 2014 -0700
@@ -0,0 +1,614 @@
+# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+ZFS Storage Appliance Proxy
+"""
+import json
+import socket
+
+from cinder import exception
+from cinder.openstack.common import log
+
+from cinder.volume.drivers.zfssa import restclient
+
+LOG = log.getLogger(__name__)
+
+
+#pylint: disable=R0913
+#pylint: disable=R0904
+class ZFSSAApi(object):
+    """ZFSSA API proxy class"""
+    def __init__(self, host):
+        self.host = host
+        self.url = "https://" + self.host + ":215"
+        self.rclient = restclient.RestClientURL(self.url)
+
+    def __del__(self):
+        if self.rclient and self.rclient.islogin():
+            self.rclient.logout()
+
+    def _is_pool_owned(self, pdata):
+        """returns True if the pool's owner is the
+           same as the host.
+        """
+        svc = '/api/system/v1/version'
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error getting version: '
+                               'svc: %(svc)s.'
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'svc': svc,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+        vdata = json.loads(ret.data)
+        return vdata['version']['asn'] == pdata['pool']['asn'] and \
+            vdata['version']['nodename'] == pdata['pool']['owner']
+
+    def login(self, auth_str):
+        """Login to the appliance"""
+        if self.rclient:
+            self.rclient.login(auth_str)
+
+    def get_pool_stats(self, pool):
+        """Get space_available and used properties of a pool
+           returns (avail, used)
+        """
+        svc = '/api/storage/v1/pools/' + pool
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error Getting Pool Stats: '
+                               'Pool: %(pool)s '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'pool': pool,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.InvalidVolume(reason=exception_msg)
+
+        val = json.loads(ret.data)
+
+        if not self._is_pool_owned(val):
+            exception_msg = (_('Error Pool ownership: '
+                               'Pool %(pool)s is not owned '
+                               'by %(host)s.')
+                             % {'pool': pool,
+                                'host': self.host})
+            LOG.error(exception_msg)
+            raise exception.InstanceNotFound(instance_id=pool)
+
+        avail = val['pool']['usage']['available']
+        used = val['pool']['usage']['used']
+
+        return (avail, used)
+
+    def create_project(self, pool, project, compression=None, logbias=None):
+        """Create a project on a pool
+           Check first whether the pool exists.
+        """
+        self.verify_pool(pool)
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + project
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            svc = '/api/storage/v1/pools/' + pool + '/projects'
+            arg = {
+                'name': project
+            }
+            if compression and compression != '':
+                arg.update({'compression': compression})
+            if logbias and logbias != '':
+                arg.update({'logbias': logbias})
+
+            ret = self.rclient.post(svc, arg)
+            if ret.status != restclient.Status.CREATED:
+                exception_msg = (_('Error Creating Project: '
+                                   '%(project)s on '
+                                   'Pool: %(pool)s '
+                                   'Return code: %(ret.status)d '
+                                   'Message: %(ret.data)s .')
+                                 % {'project': project,
+                                    'pool': pool,
+                                    'ret.status': ret.status,
+                                    'ret.data': ret.data})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def create_initiator(self, initiator, alias, chapuser=None,
+                         chapsecret=None):
+        """Create an iSCSI initiator"""
+
+        svc = '/api/san/v1/iscsi/initiators/alias=' + alias
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            svc = '/api/san/v1/iscsi/initiators'
+            arg = {
+                'initiator': initiator,
+                'alias': alias
+            }
+            if chapuser and chapuser != '' and chapsecret and chapsecret != '':
+                arg.update({'chapuser': chapuser,
+                            'chapsecret': chapsecret})
+
+            ret = self.rclient.post(svc, arg)
+            if ret.status != restclient.Status.CREATED:
+                exception_msg = (_('Error Creating Initator: '
+                                   '%(initiator)s on '
+                                   'Alias: %(alias)s '
+                                   'Return code: %(ret.status)d '
+                                   'Message: %(ret.data)s .')
+                                 % {'initiator': initiator,
+                                    'alias': alias,
+                                    'ret.status': ret.status,
+                                    'ret.data': ret.data})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def add_to_initiatorgroup(self, initiator, initiatorgroup):
+        """Add an iSCSI initiator to initiatorgroup"""
+        svc = '/api/san/v1/iscsi/initiator-groups/' + initiatorgroup
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            svc = '/api/san/v1/iscsi/initiator-groups'
+            arg = {
+                'name': initiatorgroup,
+                'initiators': [initiator]
+            }
+            ret = self.rclient.post(svc, arg)
+            if ret.status != restclient.Status.CREATED:
+                exception_msg = (_('Error Adding Initator: '
+                                   '%(initiator)s on group'
+                                   'InitiatorGroup: %(initiatorgroup)s '
+                                   'Return code: %(ret.status)d '
+                                   'Message: %(ret.data)s .')
+                                 % {'initiator': initiator,
+                                    'initiatorgroup': initiatorgroup,
+                                    'ret.status': ret.status,
+                                    'ret.data': ret.data})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+        else:
+            svc = '/api/san/v1/iscsi/initiator-groups/' + initiatorgroup
+            arg = {
+                'initiators': [initiator]
+            }
+            ret = self.rclient.put(svc, arg)
+            if ret.status != restclient.Status.ACCEPTED:
+                exception_msg = (_('Error Adding Initator: '
+                                   '%(initiator)s on group'
+                                   'InitiatorGroup: %(initiatorgroup)s '
+                                   'Return code: %(ret.status)d '
+                                   'Message: %(ret.data)s .')
+                                 % {'initiator': initiator,
+                                    'initiatorgroup': initiatorgroup,
+                                    'ret.status': ret.status,
+                                    'ret.data': ret.data})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def create_target(self, alias, interfaces=None, tchapuser=None,
+                      tchapsecret=None):
+        """Create an iSCSI target
+           interfaces: an array with network interfaces
+           tchapuser, tchapsecret: target's chapuser and chapsecret
+           returns target iqn
+        """
+        svc = '/api/san/v1/iscsi/targets/alias=' + alias
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            svc = '/api/san/v1/iscsi/targets'
+            arg = {
+                'alias': alias
+            }
+
+            if tchapuser and tchapuser != '' and tchapsecret and \
+               tchapsecret != '':
+                arg.update({'targetchapuser': tchapuser,
+                            'targetchapsecret': tchapsecret,
+                            'auth': 'chap'})
+
+            if interfaces is not None and len(interfaces) > 0:
+                arg.update({'interfaces': interfaces})
+
+            ret = self.rclient.post(svc, arg)
+            if ret.status != restclient.Status.CREATED:
+                exception_msg = (_('Error Creating Target: '
+                                   '%(alias)s'
+                                   'Return code: %(ret.status)d '
+                                   'Message: %(ret.data)s .')
+                                 % {'alias': alias,
+                                    'ret.status': ret.status,
+                                    'ret.data': ret.data})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+        val = json.loads(ret.data)
+        return val['target']['iqn']
+
+    def get_target(self, alias):
+        """Get an iSCSI target iqn"""
+        svc = '/api/san/v1/iscsi/targets/alias=' + alias
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error Getting Target: '
+                               '%(alias)s'
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s .')
+                             % {'alias': alias,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+        val = json.loads(ret.data)
+        return val['target']['iqn']
+
+    def add_to_targetgroup(self, iqn, targetgroup):
+        """Add an iSCSI target to targetgroup"""
+        svc = '/api/san/v1/iscsi/target-groups/' + targetgroup
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            svccrt = '/api/san/v1/iscsi/target-groups'
+            arg = {
+                'name': targetgroup,
+                'targets': [iqn]
+            }
+
+            ret = self.rclient.post(svccrt, arg)
+            if ret.status != restclient.Status.CREATED:
+                exception_msg = (_('Error Creating TargetGroup: '
+                                   '%(targetgroup)s with'
+                                   'IQN: %(iqn)s'
+                                   'Return code: %(ret.status)d '
+                                   'Message: %(ret.data)s .')
+                                 % {'targetgroup': targetgroup,
+                                    'iqn': iqn,
+                                    'ret.status': ret.status,
+                                    'ret.data': ret.data})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+            return
+
+        arg = {
+            'targets': [iqn]
+        }
+
+        ret = self.rclient.put(svc, arg)
+        if ret.status != restclient.Status.ACCEPTED:
+            exception_msg = (_('Error Adding to TargetGroup: '
+                               '%(targetgroup)s with'
+                               'IQN: %(iqn)s'
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'targetgroup': targetgroup,
+                                'iqn': iqn,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def verify_pool(self, pool):
+        """Checks whether pool exists"""
+        svc = '/api/storage/v1/pools/' + pool
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error Verifying Pool: '
+                               '%(pool)s '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'pool': pool,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def verify_project(self, pool, project):
+        """Checks whether project exists"""
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + project
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error Verifying '
+                               'Project: %(project)s on '
+                               'Pool: %(pool)s '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'project': project,
+                                'pool': pool,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def verify_initiator(self, iqn):
+        """Check whether initiator iqn exists"""
+        svc = '/api/san/v1/iscsi/initiators/' + iqn
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error Verifying '
+                               'Initiator: %(iqn)s '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'initiator': iqn,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def verify_target(self, alias):
+        """Check whether target alias exists."""
+        svc = '/api/san/v1/iscsi/targets/alias=' + alias
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error Verifying '
+                               'Target: %(alias)s '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'alias': alias,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def create_lun(self, pool, project, lun, volsize, targetgroup,
+                   volblocksize='8k', sparse=False, compression=None,
+                   logbias=None):
+        """Create a LUN
+           required - pool, project, lun, volsize, targetgroup.
+           optional - volblocksize, sparse, compression, logbias
+        """
+
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + project + \
+              '/luns/' + lun
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+                  project + '/luns'
+            arg = {
+                'name': lun,
+                'volsize': volsize,
+                'targetgroup':  targetgroup,
+                'initiatorgroup': 'com.sun.ms.vss.hg.maskAll',
+                'volblocksize': volblocksize,
+                'sparse': sparse
+            }
+            if compression and compression != '':
+                arg.update({'compression': compression})
+            if logbias and logbias != '':
+                arg.update({'logbias': logbias})
+
+            ret = self.rclient.post(svc, arg)
+            if ret.status != restclient.Status.CREATED:
+                exception_msg = (_('Error Creating '
+                                   'Volume: %(lun)s '
+                                   'Size: %(size)s '
+                                   'Return code: %(ret.status)d '
+                                   'Message: %(ret.data)s.')
+                                 % {'lun': lun,
+                                    'size': volsize,
+                                    'ret.status': ret.status,
+                                    'ret.data': ret.data})
+                LOG.error(exception_msg)
+                raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def get_lun(self, pool, project, lun):
+        """return iscsi lun properties"""
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+            project + "/luns/" + lun
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error Getting '
+                               'Volume: %(lun)s on '
+                               'Pool: %(pool)s '
+                               'Project: %(project)s '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'lun': lun,
+                                'pool': pool,
+                                'project': project,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+        val = json.loads(ret.data)
+        ret = {
+            'guid': val['lun']['lunguid'],
+            'number': val['lun']['assignednumber'],
+            'initiatorgroup': val['lun']['initiatorgroup'],
+            'size': val['lun']['volsize']
+        }
+        if 'origin' in val['lun']:
+            ret.update({'origin': val['lun']['origin']})
+
+        return ret
+
+    def set_lun_initiatorgroup(self, pool, project, lun, initiatorgroup):
+        """Set the initiatorgroup property of a LUN"""
+        if initiatorgroup == '':
+            initiatorgroup = 'com.sun.ms.vss.hg.maskAll'
+
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+            project + '/luns/' + lun
+        arg = {
+            'initiatorgroup': initiatorgroup
+        }
+
+        ret = self.rclient.put(svc, arg)
+        if ret.status != restclient.Status.ACCEPTED:
+            exception_msg = (_('Error Setting '
+                               'Volume: %(lun)s to '
+                               'InitiatorGroup: %(initiatorgroup)s '
+                               'Pool: %(pool)s '
+                               'Project: %(project)s  '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'lun': lun,
+                                'initiatorgroup': initiatorgroup,
+                                'pool': pool,
+                                'project': project,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+
+    def delete_lun(self, pool, project, lun):
+        """delete iscsi lun"""
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+            project + '/luns/' + lun
+
+        ret = self.rclient.delete(svc)
+        if ret.status != restclient.Status.NO_CONTENT:
+            exception_msg = (_('Error Deleting '
+                               'Volume: %(lun)s to '
+                               'Pool: %(pool)s '
+                               'Project: %(project)s  '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'lun': lun,
+                                'pool': pool,
+                                'project': project,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+
+    def create_snapshot(self, pool, project, lun, snapshot):
+        """create snapshot"""
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+            project + '/luns/' + lun + '/snapshots'
+        arg = {
+            'name': snapshot
+        }
+
+        ret = self.rclient.post(svc, arg)
+        if ret.status != restclient.Status.CREATED:
+            exception_msg = (_('Error Creating '
+                               'Snapshot: %(snapshot)s on'
+                               'Volume: %(lun)s to '
+                               'Pool: %(pool)s '
+                               'Project: %(project)s  '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'snapshot': snapshot,
+                                'lun': lun,
+                                'pool': pool,
+                                'project': project,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def delete_snapshot(self, pool, project, lun, snapshot):
+        """delete snapshot"""
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+              project + '/luns/' + lun + '/snapshots/' + snapshot
+
+        ret = self.rclient.delete(svc)
+        if ret.status != restclient.Status.NO_CONTENT:
+            exception_msg = (_('Error Deleting '
+                               'Snapshot: %(snapshot)s on '
+                               'Volume: %(lun)s to '
+                               'Pool: %(pool)s '
+                               'Project: %(project)s  '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'snapshot': snapshot,
+                                'lun': lun,
+                                'pool': pool,
+                                'project': project,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def clone_snapshot(self, pool, project, lun, snapshot, clone):
+        """clone snapshot"""
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+            project + '/luns/' + lun + '/snapshots/' + snapshot + '/clone'
+        arg = {
+            'project': project,
+            'share': clone
+        }
+
+        ret = self.rclient.put(svc, arg)
+        if ret.status != restclient.Status.CREATED:
+            exception_msg = (_('Error Cloning '
+                               'Snapshot: %(snapshot)s on '
+                               'Volume: %(lun)s of '
+                               'Pool: %(pool)s '
+                               'Project: %(project)s  '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'snapshot': snapshot,
+                                'lun': lun,
+                                'pool': pool,
+                                'project': project,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def set_lun_size(self, pool, project, lun, size):
+        """increase lun size capacity"""
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+            project + '/luns/' + lun
+        arg = {
+            'volsize': size
+        }
+
+        ret = self.rclient.put(svc, arg)
+        if ret.status != restclient.Status.ACCEPTED:
+            exception_msg = (_('Error Setting size on '
+                               'Size: %(size)s on '
+                               'Volume: %(lun)s of '
+                               'Pool: %(pool)s '
+                               'Project: %(project)s  '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'size': size,
+                                'lun': lun,
+                                'pool': pool,
+                                'project': project,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+    def has_clones(self, pool, project, lun, snapshot):
+        """Checks whether snapshot has clones or not."""
+        svc = '/api/storage/v1/pools/' + pool + '/projects/' + \
+            project + '/luns/' + lun + '/snapshots/' + snapshot
+
+        ret = self.rclient.get(svc)
+        if ret.status != restclient.Status.OK:
+            exception_msg = (_('Error Getting '
+                               'Snapshot: %(snapshot)s on '
+                               'Volume: %(lun)s to '
+                               'Pool: %(pool)s '
+                               'Project: %(project)s  '
+                               'Return code: %(ret.status)d '
+                               'Message: %(ret.data)s.')
+                             % {'snapshot': snapshot,
+                                'lun': lun,
+                                'pool': pool,
+                                'project': project,
+                                'ret.status': ret.status,
+                                'ret.data': ret.data})
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+        val = json.loads(ret.data)
+        return (val['snapshot']['numclones'] != 0)