From 7f875697e062c69b31cc8bfc31f38c2d79abbdf4 Mon Sep 17 00:00:00 2001
From: Nikesh Mahalka <Nikesh.Mahalka.ctr@kaminario.com>
Date: Wed, 8 Jun 2016 14:57:38 -0400
Subject: [PATCH] Provide Kaminario K2 all-flash array iSCSI driver

The purpose is to provide cinder driver for
Kaminario K2 all-flash iSCSI array. It will include the minimum
set of features required by the Newton(N) release.

DocImpact
Implements: blueprint kaminario-iscsi-cinder-driver
Co-Authored-By: Lakshmi Narayana<Lakshmi.Narayana.ctr@kaminario.com>
Co-Authored-By: Sreedhar Varma<Sreedhar.Varma.ctr@kaminario.com>
Co-Authored-By: Ido Benda<Ido.Benda@kaminario.com>

Change-Id: Iad05e85ae512b0a97da5420cb66a08e23aa457db
---
 cinder/exception.py                           |   5 +
 cinder/opts.py                                |   4 +
 .../unit/volume/drivers/test_kaminario.py     | 235 ++++++++++
 cinder/volume/drivers/kaminario/__init__.py   |   0
 .../drivers/kaminario/kaminario_common.py     | 433 ++++++++++++++++++
 .../drivers/kaminario/kaminario_iscsi.py      | 153 +++++++
 ...-iscsi-cinder-driver-c34fadf63cd253de.yaml |   3 +
 7 files changed, 833 insertions(+)
 create mode 100644 cinder/tests/unit/volume/drivers/test_kaminario.py
 create mode 100644 cinder/volume/drivers/kaminario/__init__.py
 create mode 100644 cinder/volume/drivers/kaminario/kaminario_common.py
 create mode 100644 cinder/volume/drivers/kaminario/kaminario_iscsi.py
 create mode 100644 releasenotes/notes/kaminario-iscsi-cinder-driver-c34fadf63cd253de.yaml

diff --git a/cinder/exception.py b/cinder/exception.py
index 35e4b9008ef..b8a2c6fc42a 100644
--- a/cinder/exception.py
+++ b/cinder/exception.py
@@ -1146,3 +1146,8 @@ class GCSApiFailure(BackupDriverException):
 
 class GCSOAuth2Failure(BackupDriverException):
     message = _("Google Cloud Storage oauth2 failure: %(reason)s")
+
+
+# Kaminario K2
+class KaminarioCinderDriverException(VolumeDriverException):
+    message = _("KaminarioCinderDriver failure: %(reason)s")
diff --git a/cinder/opts.py b/cinder/opts.py
index 9962bd28050..64c6ca5f2db 100644
--- a/cinder/opts.py
+++ b/cinder/opts.py
@@ -126,6 +126,8 @@ from cinder.volume.drivers.ibm import xiv_ds8k as \
     cinder_volume_drivers_ibm_xivds8k
 from cinder.volume.drivers.infortrend.eonstor_ds_cli import common_cli as \
     cinder_volume_drivers_infortrend_eonstor_ds_cli_commoncli
+from cinder.volume.drivers.kaminario import kaminario_common as \
+    cinder_volume_drivers_kaminario_kaminariocommon
 from cinder.volume.drivers.lenovo import lenovo_common as \
     cinder_volume_drivers_lenovo_lenovocommon
 from cinder.volume.drivers import lvm as cinder_volume_drivers_lvm
@@ -310,6 +312,8 @@ def list_opts():
                 [cinder_scheduler_scheduleroptions.
                     scheduler_json_config_location_opt],
                 cinder_volume_drivers_zfssa_zfssanfs.ZFSSA_OPTS,
+                cinder_volume_drivers_kaminario_kaminariocommon.
+                kaminario1_opts,
                 cinder_volume_drivers_disco_disco.disco_opts,
                 cinder_volume_drivers_hgst.hgst_opts,
                 cinder_message_api.messages_opts,
diff --git a/cinder/tests/unit/volume/drivers/test_kaminario.py b/cinder/tests/unit/volume/drivers/test_kaminario.py
new file mode 100644
index 00000000000..19c49d32a89
--- /dev/null
+++ b/cinder/tests/unit/volume/drivers/test_kaminario.py
@@ -0,0 +1,235 @@
+# Copyright (c) 2016 by Kaminario Technologies, Ltd.
+# 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.
+"""Unit tests for kaminario driver."""
+import mock
+
+from cinder import context
+from cinder import exception
+from cinder import test
+from cinder.tests.unit import fake_snapshot
+from cinder.tests.unit import fake_volume
+from cinder import utils
+from cinder.volume import configuration
+from cinder.volume.drivers.kaminario import kaminario_iscsi
+from cinder.volume import utils as vol_utils
+
+
+CONNECTOR = {'initiator': 'iqn.1993-08.org.debian:01:84cd7e88bb5a',
+             'ip': '192.168.6.5', 'platform': 'x86_64', 'host': 'il-ksm1-055',
+             'os_type': 'linux2', 'multipath': False}
+
+
+class FakeK2Obj(object):
+    id = 548
+    lun = 548
+
+
+class FakeSaveObject(FakeK2Obj):
+    def __init__(self, *args, **kwargs):
+        self.ntype = kwargs.get('ntype')
+        self.ip_address = '10.0.0.1'
+        self.iscsi_qualified_target_name = "xyztlnxyz"
+        self.snapshot = FakeK2Obj()
+        self.name = 'test'
+
+    def save(self):
+        return FakeSaveObject()
+
+    def delete(self):
+        return None
+
+
+class FakeSaveObjectExp(FakeSaveObject):
+    def save(self):
+        raise exception.KaminarioCinderDriverException("test")
+
+    def delete(self):
+        raise exception.KaminarioCinderDriverException("test")
+
+
+class FakeSearchObject(object):
+    hits = [FakeSaveObject()]
+    total = 1
+
+    def __init__(self, *args):
+        if args and "mappings" in args[0]:
+            self.total = 0
+
+
+class FakeSearchObjectExp(object):
+    hits = [FakeSaveObjectExp()]
+    total = 1
+
+
+class FakeKrest(object):
+    def search(self, *args, **argv):
+        return FakeSearchObject(*args)
+
+    def new(self, *args, **argv):
+        return FakeSaveObject()
+
+
+class FakeKrestException(object):
+    def search(self, *args, **argv):
+        return FakeSearchObjectExp()
+
+    def new(self, *args, **argv):
+        return FakeSaveObjectExp()
+
+
+class TestKaminarioISCSI(test.TestCase):
+    driver = None
+    conf = None
+
+    def setUp(self):
+        self._setup_config()
+        self._setup_driver()
+        super(TestKaminarioISCSI, self).setUp()
+        self.context = context.get_admin_context()
+        self.vol = fake_volume.fake_volume_obj(self.context)
+        self.vol.volume_type = fake_volume.fake_volume_type_obj(self.context)
+        self.snap = fake_snapshot.fake_snapshot_obj(self.context)
+        self.snap.volume = self.vol
+
+    def _setup_config(self):
+        self.conf = mock.Mock(spec=configuration.Configuration)
+        self.conf.kaminario_dedup_type_name = "dedup"
+        self.conf.volume_dd_blocksize = 2
+
+    def _setup_driver(self):
+        self.driver = (kaminario_iscsi.
+                       KaminarioISCSIDriver(configuration=self.conf))
+        device = mock.Mock(return_value={'device': {'path': '/dev'}})
+        self.driver._connect_device = device
+        self.driver._protocol = False
+        self.driver.client = FakeKrest()
+
+    def test_create_volume(self):
+        """Test create_volume."""
+        result = self.driver.create_volume(self.vol)
+        self.assertIsNone(result)
+
+    def test_create_volume_with_exception(self):
+        """Test create_volume_with_exception."""
+        self.driver.client = FakeKrestException()
+        self.assertRaises(exception.KaminarioCinderDriverException,
+                          self.driver.create_volume, self.vol)
+
+    def test_delete_volume(self):
+        """Test delete_volume."""
+        result = self.driver.delete_volume(self.vol)
+        self.assertIsNone(result)
+
+    def test_delete_volume_with_exception(self):
+        """Test delete_volume_with_exception."""
+        self.driver.client = FakeKrestException()
+        self.assertRaises(exception.KaminarioCinderDriverException,
+                          self.driver.delete_volume, self.vol)
+
+    def test_create_snapshot(self):
+        """Test create_snapshot."""
+        result = self.driver.create_snapshot(self.snap)
+        self.assertIsNone(result)
+
+    def test_create_snapshot_with_exception(self):
+        """Test create_snapshot_with_exception."""
+        self.driver.client = FakeKrestException()
+        self.assertRaises(exception.KaminarioCinderDriverException,
+                          self.driver.create_snapshot, self.snap)
+
+    def test_delete_snapshot(self):
+        """Test delete_snapshot."""
+        result = self.driver.delete_snapshot(self.snap)
+        self.assertIsNone(result)
+
+    def test_delete_snapshot_with_exception(self):
+        """Test delete_snapshot_with_exception."""
+        self.driver.client = FakeKrestException()
+        self.assertRaises(exception.KaminarioCinderDriverException,
+                          self.driver.delete_snapshot, self.snap)
+
+    @mock.patch.object(utils, 'brick_get_connector_properties')
+    @mock.patch.object(vol_utils, 'copy_volume')
+    def test_create_volume_from_snapshot(self, mock_copy_volume,
+                                         mock_brick_get):
+        """Test create_volume_from_snapshot."""
+        mock_brick_get.return_value = CONNECTOR
+        mock_copy_volume.return_value = None
+        result = self.driver.create_volume_from_snapshot(self.vol, self.snap)
+        self.assertIsNone(result)
+
+    @mock.patch.object(utils, 'brick_get_connector_properties')
+    @mock.patch.object(vol_utils, 'copy_volume')
+    def test_create_volume_from_snapshot_with_exception(self, mock_copy_volume,
+                                                        mock_brick_get):
+        """Test create_volume_from_snapshot_with_exception."""
+        mock_brick_get.return_value = CONNECTOR
+        mock_copy_volume.return_value = None
+        self.driver.client = FakeKrestException()
+        self.assertRaises(exception.KaminarioCinderDriverException,
+                          self.driver.create_volume_from_snapshot, self.vol,
+                          self.snap)
+
+    @mock.patch.object(utils, 'brick_get_connector_properties')
+    @mock.patch.object(vol_utils, 'copy_volume')
+    def test_create_cloned_volume(self, mock_copy_volume, mock_brick_get):
+        """Test create_cloned_volume."""
+        mock_brick_get.return_value = CONNECTOR
+        mock_copy_volume.return_value = None
+        result = self.driver.create_cloned_volume(self.vol, self.vol)
+        self.assertIsNone(result)
+
+    @mock.patch.object(utils, 'brick_get_connector_properties')
+    @mock.patch.object(vol_utils, 'copy_volume')
+    def test_create_cloned_volume_with_exception(self, mock_copy_volume,
+                                                 mock_brick_get):
+        """Test create_cloned_volume_with_exception."""
+        mock_brick_get.return_value = CONNECTOR
+        mock_copy_volume.return_value = None
+        self.driver.terminate_connection = mock.Mock()
+        self.driver.client = FakeKrestException()
+        self.assertRaises(exception.KaminarioCinderDriverException,
+                          self.driver.create_cloned_volume, self.vol, self.vol)
+
+    def test_extend_volume(self):
+        """Test extend_volume."""
+        new_size = 256
+        result = self.driver.extend_volume(self.vol, new_size)
+        self.assertIsNone(result)
+
+    def test_extend_volume_with_exception(self):
+        """Test extend_volume_with_exception."""
+        self.driver.client = FakeKrestException()
+        new_size = 256
+        self.assertRaises(exception.KaminarioCinderDriverException,
+                          self.driver.extend_volume, self.vol, new_size)
+
+    def test_initialize_connection(self):
+        """Test initialize_connection."""
+        conn_info = self.driver.initialize_connection(self.vol, CONNECTOR)
+        self.assertIn('data', conn_info)
+        self.assertIn('target_iqn', conn_info['data'])
+
+    def test_initialize_connection_with_exception(self):
+        """Test initialize_connection_with_exception."""
+        self.driver.client = FakeKrestException()
+        self.assertRaises(exception.KaminarioCinderDriverException,
+                          self.driver.initialize_connection, self.vol,
+                          CONNECTOR)
+
+    def test_terminate_connection(self):
+        """Test terminate_connection."""
+        result = self.driver.terminate_connection(self.vol, CONNECTOR)
+        self.assertIsNone(result)
diff --git a/cinder/volume/drivers/kaminario/__init__.py b/cinder/volume/drivers/kaminario/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cinder/volume/drivers/kaminario/kaminario_common.py b/cinder/volume/drivers/kaminario/kaminario_common.py
new file mode 100644
index 00000000000..d123b77b0e7
--- /dev/null
+++ b/cinder/volume/drivers/kaminario/kaminario_common.py
@@ -0,0 +1,433 @@
+# Copyright (c) 2016 by Kaminario Technologies, Ltd.
+# 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.
+"""Volume driver for Kaminario K2 all-flash arrays."""
+
+import re
+import six
+
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import importutils
+from oslo_utils import units
+from oslo_utils import versionutils
+
+import cinder
+from cinder import exception
+from cinder.i18n import _, _LE
+from cinder import utils
+from cinder.volume.drivers.san import san
+from cinder.volume import utils as vol_utils
+
+K2_MIN_VERSION = '2.2.0'
+LOG = logging.getLogger(__name__)
+
+kaminario1_opts = [
+    cfg.StrOpt('kaminario_nodedup_substring',
+               default='K2-nodedup',
+               help="If volume-type name contains this substring "
+                    "nodedup volume will be created, otherwise "
+                    "dedup volume wil be created.")]
+kaminario2_opts = [
+    cfg.BoolOpt('auto_calc_max_oversubscription_ratio',
+                default=False,
+                help="K2 driver will calculate max_oversubscription_ratio "
+                     "on setting this option as True.")]
+
+CONF = cfg.CONF
+CONF.register_opts(kaminario1_opts)
+
+
+def kaminario_logger(func):
+    """Return a function wrapper.
+
+    The wrapper adds log for entry and exit to the function.
+    """
+    def func_wrapper(*args, **kwargs):
+        LOG.debug('Entering %(function)s of %(class)s with arguments: '
+                  ' %(args)s, %(kwargs)s',
+                  {'class': args[0].__class__.__name__,
+                   'function': func.__name__,
+                   'args': args[1:],
+                   'kwargs': kwargs})
+        ret = func(*args, **kwargs)
+        LOG.debug('Exiting %(function)s of %(class)s '
+                  'having return value: %(ret)s',
+                  {'class': args[0].__class__.__name__,
+                   'function': func.__name__,
+                   'ret': ret})
+        return ret
+    return func_wrapper
+
+
+class KaminarioCinderDriver(cinder.volume.driver.ISCSIDriver):
+    VENDOR = "Kaminario"
+    VERSION = "1.0"
+    stats = {}
+
+    def __init__(self, *args, **kwargs):
+        super(KaminarioCinderDriver, self).__init__(*args, **kwargs)
+        self.configuration.append_config_values(san.san_opts)
+        self.configuration.append_config_values(kaminario2_opts)
+
+    def check_for_setup_error(self):
+        if self.krest is None:
+            msg = _("Unable to import 'krest' python module.")
+            LOG.error(msg)
+            raise exception.KaminarioCinderDriverException(reason=msg)
+        else:
+            conf = self.configuration
+            self.client = self.krest.EndPoint(conf.san_ip,
+                                              conf.san_login,
+                                              conf.san_password,
+                                              ssl_validate=False)
+            v_rs = self.client.search("system/state")
+            if hasattr(v_rs, 'hits') and v_rs.total != 0:
+                ver = v_rs.hits[0].rest_api_version
+                ver_exist = versionutils.convert_version_to_int(ver)
+                ver_min = versionutils.convert_version_to_int(K2_MIN_VERSION)
+                if ver_exist < ver_min:
+                    msg = _("K2 rest api version should be "
+                            ">= %s.") % K2_MIN_VERSION
+                    LOG.error(msg)
+                    raise exception.KaminarioCinderDriverException(reason=msg)
+
+            else:
+                msg = _("K2 rest api version search failed.")
+                LOG.error(msg)
+                raise exception.KaminarioCinderDriverException(reason=msg)
+
+    @kaminario_logger
+    def _check_ops(self):
+        """Ensure that the options we care about are set."""
+        required_ops = ['san_ip', 'san_login', 'san_password']
+        for attr in required_ops:
+            if not getattr(self.configuration, attr, None):
+                raise exception.InvalidInput(reason=_('%s is not set.') % attr)
+
+    @kaminario_logger
+    def do_setup(self, context):
+        super(KaminarioCinderDriver, self).do_setup(context)
+        self._check_ops()
+        self.krest = importutils.try_import("krest")
+
+    @kaminario_logger
+    def create_volume(self, volume):
+        """Volume creation in K2 needs a volume group.
+
+        - create a volume group
+        - create a volume in the volume group
+        """
+        vg_name = self.get_volume_group_name(volume.id)
+        vol_name = self.get_volume_name(volume.id)
+        if CONF.kaminario_nodedup_substring in volume.volume_type.name:
+            prov_type = False
+        else:
+            prov_type = True
+        try:
+            LOG.debug("Creating volume group with name: %(name)s, "
+                      "quota: unlimited and dedup_support: %(dedup)s",
+                      {'name': vg_name, 'dedup': prov_type})
+
+            vg = self.client.new("volume_groups", name=vg_name, quota=0,
+                                 is_dedup=prov_type).save()
+            LOG.debug("Creating volume with name: %(name)s, size: %(size)s "
+                      "GB, volume_group: %(vg)s",
+                      {'name': vol_name, 'size': volume.size, 'vg': vg_name})
+            self.client.new("volumes", name=vol_name,
+                            size=volume.size * units.Mi,
+                            volume_group=vg).save()
+        except Exception as ex:
+            vg_rs = self.client.search("volume_groups", name=vg_name)
+            if vg_rs.total != 0:
+                LOG.debug("Deleting vg: %s for failed volume in K2.", vg_name)
+                vg_rs.hits[0].delete()
+            LOG.exception(_LE("Creation of volume %s failed."), vol_name)
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+
+    @kaminario_logger
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Create volume from snapshot.
+
+        - search for snapshot and retention_policy
+        - create a view from snapshot and attach view
+        - create a volume and attach volume
+        - copy data from attached view to attached volume
+        - detach volume and view and finally delete view
+        """
+        snap_name = self.get_snap_name(snapshot.id)
+        view_name = self.get_view_name(volume.id)
+        vol_name = self.get_volume_name(volume.id)
+        cview = src_attach_info = dest_attach_info = None
+        rpolicy = self.get_policy()
+        properties = utils.brick_get_connector_properties()
+        LOG.debug("Searching for snapshot: %s in K2.", snap_name)
+        snap_rs = self.client.search("snapshots", short_name=snap_name)
+        if hasattr(snap_rs, 'hits') and snap_rs.total != 0:
+            snap = snap_rs.hits[0]
+            LOG.debug("Creating a view: %(view)s from snapshot: %(snap)s",
+                      {'view': view_name, 'snap': snap_name})
+            try:
+                cview = self.client.new("snapshots",
+                                        short_name=view_name,
+                                        source=snap, retention_policy=rpolicy,
+                                        is_exposable=True).save()
+            except Exception as ex:
+                LOG.exception(_LE("Creating a view: %(view)s from snapshot: "
+                                  "%(snap)s failed"), {"view": view_name,
+                                                       "snap": snap_name})
+                raise exception.KaminarioCinderDriverException(
+                    reason=six.text_type(ex.message))
+
+        else:
+            msg = _("Snapshot: %s search failed in K2.") % snap_name
+            LOG.error(msg)
+            raise exception.KaminarioCinderDriverException(reason=msg)
+
+        try:
+            conn = self.initialize_connection(cview, properties)
+            src_attach_info = self._connect_device(conn)
+            self.create_volume(volume)
+            conn = self.initialize_connection(volume, properties)
+            dest_attach_info = self._connect_device(conn)
+            vol_utils.copy_volume(src_attach_info['device']['path'],
+                                  dest_attach_info['device']['path'],
+                                  snapshot.volume.size * units.Ki,
+                                  self.configuration.volume_dd_blocksize,
+                                  sparse=True)
+            self.terminate_connection(volume, properties)
+            self.terminate_connection(cview, properties)
+        except Exception as ex:
+            self.terminate_connection(cview, properties)
+            self.terminate_connection(volume, properties)
+            cview.delete()
+            self.delete_volume(volume)
+            LOG.exception(_LE("Copy to volume: %(vol)s from view: %(view)s "
+                              "failed"), {"vol": vol_name, "view": view_name})
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+
+    @kaminario_logger
+    def create_cloned_volume(self, volume, src_vref):
+        """Create a clone from source volume.
+
+        - attach source volume
+        - create and attach new volume
+        - copy data from attached source volume to attached new volume
+        - detach both volumes
+        """
+        clone_name = self.get_volume_name(volume.id)
+        src_name = self.get_volume_name(src_vref.id)
+        src_vol = self.client.search("volumes", name=src_name)
+        src_map = self.client.search("mappings", volume=src_vol)
+        if src_map.total != 0:
+            msg = _("K2 driver does not support clone of a attached volume. "
+                    "To get this done, create a snapshot from the attached "
+                    "volume and then create a volume from the snapshot.")
+            LOG.error(msg)
+            raise exception.KaminarioCinderDriverException(reason=msg)
+        try:
+            properties = utils.brick_get_connector_properties()
+            conn = self.initialize_connection(src_vref, properties)
+            src_attach_info = self._connect_device(conn)
+            self.create_volume(volume)
+            conn = self.initialize_connection(volume, properties)
+            dest_attach_info = self._connect_device(conn)
+            vol_utils.copy_volume(src_attach_info['device']['path'],
+                                  dest_attach_info['device']['path'],
+                                  src_vref.size * units.Ki,
+                                  self.configuration.volume_dd_blocksize,
+                                  sparse=True)
+
+            self.terminate_connection(volume, properties)
+            self.terminate_connection(src_vref, properties)
+        except Exception as ex:
+            self.terminate_connection(src_vref, properties)
+            self.terminate_connection(volume, properties)
+            self.delete_volume(volume)
+            LOG.exception(_LE("Create a clone: %s failed."), clone_name)
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+
+    @kaminario_logger
+    def delete_volume(self, volume):
+        """Volume in K2 exists in a volume group.
+
+        - delete the volume
+        - delete the corresponding volume group
+        """
+        vg_name = self.get_volume_group_name(volume.id)
+        vol_name = self.get_volume_name(volume.id)
+        try:
+            LOG.debug("Searching and deleting volume: %s in K2.", vol_name)
+            vol_rs = self.client.search("volumes", name=vol_name)
+            if vol_rs.total != 0:
+                vol_rs.hits[0].delete()
+            LOG.debug("Searching and deleting vg: %s in K2.", vg_name)
+            vg_rs = self.client.search("volume_groups", name=vg_name)
+            if vg_rs.total != 0:
+                vg_rs.hits[0].delete()
+        except Exception as ex:
+            LOG.exception(_LE("Deletion of volume %s failed."), vol_name)
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+
+    @kaminario_logger
+    def get_volume_stats(self, refresh=False):
+        if refresh:
+            self.update_volume_stats()
+        stats = self.stats
+        stats['storage_protocol'] = self._protocol
+        stats['driver_version'] = self.VERSION
+        stats['vendor_name'] = self.VENDOR
+        backend_name = self.configuration.safe_get('volume_backend_name')
+        stats['volume_backend_name'] = (backend_name or
+                                        self.__class__.__name__)
+        return stats
+
+    def create_export(self, context, volume, connector):
+        pass
+
+    def ensure_export(self, context, volume):
+        pass
+
+    def remove_export(self, context, volume):
+        pass
+
+    @kaminario_logger
+    def create_snapshot(self, snapshot):
+        """Create a snapshot from a volume_group."""
+        vg_name = self.get_volume_group_name(snapshot.volume_id)
+        snap_name = self.get_snap_name(snapshot.id)
+        rpolicy = self.get_policy()
+        try:
+            LOG.debug("Searching volume_group: %s in K2.", vg_name)
+            vg = self.client.search("volume_groups", name=vg_name).hits[0]
+            LOG.debug("Creating a snapshot: %(snap)s from vg: %(vg)s",
+                      {'snap': snap_name, 'vg': vg_name})
+            self.client.new("snapshots", short_name=snap_name,
+                            source=vg, retention_policy=rpolicy).save()
+        except Exception as ex:
+            LOG.exception(_LE("Creation of snapshot: %s failed."), snap_name)
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+
+    @kaminario_logger
+    def delete_snapshot(self, snapshot):
+        """Delete a snapshot."""
+        snap_name = self.get_snap_name(snapshot.id)
+        try:
+            LOG.debug("Searching and deleting snapshot: %s in K2.", snap_name)
+            snap_rs = self.client.search("snapshots", short_name=snap_name)
+            if snap_rs.total != 0:
+                snap_rs.hits[0].delete()
+        except Exception as ex:
+            LOG.exception(_LE("Deletion of snapshot: %s failed."), snap_name)
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+
+    @kaminario_logger
+    def extend_volume(self, volume, new_size):
+        """Extend volume."""
+        vol_name = self.get_volume_name(volume.id)
+        try:
+            LOG.debug("Searching volume: %s in K2.", vol_name)
+            vol = self.client.search("volumes", name=vol_name).hits[0]
+            vol.size = new_size * units.Mi
+            LOG.debug("Extending volume: %s in K2.", vol_name)
+            vol.save()
+        except Exception as ex:
+            LOG.exception(_LE("Extending volume: %s failed."), vol_name)
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+
+    @kaminario_logger
+    def update_volume_stats(self):
+        conf = self.configuration
+        LOG.debug("Searching system capacity in K2.")
+        cap = self.client.search("system/capacity").hits[0]
+        LOG.debug("Searching total volumes in K2 for updating stats.")
+        total_volumes = self.client.search("volumes").total - 1
+        provisioned_vol = cap.provisioned_volumes
+        if (conf.auto_calc_max_oversubscription_ratio and cap.provisioned
+                and (cap.total - cap.free) != 0):
+            ratio = provisioned_vol / float(cap.total - cap.free)
+        else:
+            ratio = conf.max_over_subscription_ratio
+        self.stats = {'QoS_support': False,
+                      'free_capacity_gb': cap.free / units.Mi,
+                      'total_capacity_gb': cap.total / units.Mi,
+                      'thin_provisioning_support': True,
+                      'sparse_copy_volume': True,
+                      'total_volumes': total_volumes,
+                      'thick_provisioning_support': False,
+                      'provisioned_capacity_gb': provisioned_vol / units.Mi,
+                      'max_oversubscription_ratio': ratio}
+
+    @kaminario_logger
+    def get_initiator_host_name(self, connector):
+        """Return the initiator host name.
+
+        Valid characters: 0-9, a-z, A-Z, '-', '_'
+        All other characters are replaced with '_'.
+        Total characters in initiator host name: 32
+        """
+        return re.sub('[^0-9a-zA-Z-_]', '_', connector['host'])[:32]
+
+    @kaminario_logger
+    def get_volume_group_name(self, vid):
+        """Return the volume group name."""
+        return "cvg-{0}".format(vid)
+
+    @kaminario_logger
+    def get_volume_name(self, vid):
+        """Return the volume name."""
+        return "cv-{0}".format(vid)
+
+    @kaminario_logger
+    def get_snap_name(self, sid):
+        """Return the snapshot name."""
+        return "cs-{0}".format(sid)
+
+    @kaminario_logger
+    def get_view_name(self, vid):
+        """Return the view name."""
+        return "cview-{0}".format(vid)
+
+    @kaminario_logger
+    def delete_host_by_name(self, name):
+        """Deleting host by name."""
+        host_rs = self.client.search("hosts", name=name)
+        if hasattr(host_rs, "hits") and host_rs.total != 0:
+            host = host_rs.hits[0]
+            host.delete()
+
+    @kaminario_logger
+    def get_policy(self):
+        """Return the retention policy."""
+        try:
+            LOG.debug("Searching for retention_policy in K2.")
+            return self.client.search("retention_policies",
+                                      name="Best_Effort_Retention").hits[0]
+        except Exception as ex:
+            LOG.exception(_LE("Retention policy search failed in K2."))
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+
+    def initialize_connection(self, volume, connector):
+        pass
+
+    def terminate_connection(self, volume, connector, **kwargs):
+        pass
diff --git a/cinder/volume/drivers/kaminario/kaminario_iscsi.py b/cinder/volume/drivers/kaminario/kaminario_iscsi.py
new file mode 100644
index 00000000000..23aac2a9507
--- /dev/null
+++ b/cinder/volume/drivers/kaminario/kaminario_iscsi.py
@@ -0,0 +1,153 @@
+# Copyright (c) 2016 by Kaminario Technologies, Ltd.
+# 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.
+"""Volume driver for Kaminario K2 all-flash arrays."""
+import six
+
+from cinder import exception
+from cinder.i18n import _, _LW
+from cinder import interface
+from cinder.volume.drivers.kaminario import kaminario_common as common
+from oslo_log import log as logging
+
+ISCSI_TCP_PORT = "3260"
+LOG = logging.getLogger(__name__)
+kaminario_logger = common.kaminario_logger
+
+
+@interface.volumedriver
+class KaminarioISCSIDriver(common.KaminarioCinderDriver):
+    """Kaminario K2 iSCSI Volume Driver."""
+
+    @kaminario_logger
+    def __init__(self, *args, **kwargs):
+        super(KaminarioISCSIDriver, self).__init__(*args, **kwargs)
+        self._protocol = 'iSCSI'
+
+    @kaminario_logger
+    def initialize_connection(self, volume, connector):
+        """Get volume object and map to initiator host."""
+        if type(volume).__name__ != 'RestObject':
+            vol_name = self.get_volume_name(volume.id)
+            LOG.debug("Searching volume : %s in K2.", vol_name)
+            vol_rs = self.client.search("volumes", name=vol_name)
+            if not hasattr(vol_rs, 'hits') or vol_rs.total == 0:
+                msg = _("Unable to find volume: %s from K2.") % vol_name
+                LOG.error(msg)
+                raise exception.KaminarioCinderDriverException(reason=msg)
+            vol = vol_rs.hits[0]
+        else:
+            vol = volume
+        """Get target_portal"""
+        LOG.debug("Searching first iscsi port ip  without wan in K2.")
+        iscsi_ip_rs = self.client.search("system/net_ips", wan_port="")
+        iscsi_ip = target_iqn = None
+        if hasattr(iscsi_ip_rs, 'hits') and iscsi_ip_rs.total != 0:
+            iscsi_ip = iscsi_ip_rs.hits[0].ip_address
+        if not iscsi_ip:
+            msg = _("Unable to get ISCSI IP addres from K2.")
+            LOG.error(msg)
+            raise exception.KaminarioCinderDriverException(reason=msg)
+        iscsi_portal = "{0}:{1}".format(iscsi_ip, ISCSI_TCP_PORT)
+        LOG.debug("Searching system state for target iqn in K2.")
+        sys_state_rs = self.client.search("system/state")
+
+        if hasattr(sys_state_rs, 'hits') and sys_state_rs.total != 0:
+            target_iqn = sys_state_rs.hits[0].iscsi_qualified_target_name
+
+        if not target_iqn:
+            msg = _("Unable to get target iqn from K2.")
+            LOG.error(msg)
+            raise exception.KaminarioCinderDriverException(reason=msg)
+        host_name = self.get_initiator_host_name(connector)
+        LOG.debug("Searching initiator hostname: %s in K2.", host_name)
+        host_rs = self.client.search("hosts", name=host_name)
+        """Create a host if not exists."""
+        if host_rs.total == 0:
+            try:
+                LOG.debug("Creating initiator hostname: %s in K2.", host_name)
+                host = self.client.new("hosts", name=host_name,
+                                       type="Linux").save()
+                LOG.debug("Adding iqn: %(iqn)s to host: %(host)s in K2.",
+                          {'iqn': connector['initiator'], 'host': host_name})
+                iqn = self.client.new("host_iqns", iqn=connector['initiator'],
+                                      host=host)
+                iqn.save()
+            except Exception as ex:
+                LOG.debug("Unable to create host : %s in K2.", host_name)
+                self.delete_host_by_name(host_name)
+                raise exception.KaminarioCinderDriverException(
+                    reason=six.text_type(ex.message))
+        else:
+            LOG.debug("Use existing initiator hostname: %s in K2.", host_name)
+            host = host_rs.hits[0]
+        try:
+            LOG.debug("Mapping volume: %(vol)s to host: %(host)s",
+                      {'host': host_name, 'vol': vol.name})
+            mapping = self.client.new("mappings", volume=vol, host=host).save()
+        except Exception as ex:
+            if host_rs.total == 0:
+                LOG.debug("Unable to mapping volume:%(vol)s to host: %(host)s",
+                          {'host': host_name, 'vol': vol.name})
+                self.delete_host_by_name(host_name)
+            raise exception.KaminarioCinderDriverException(
+                reason=six.text_type(ex.message))
+        if type(volume).__name__ == 'RestObject':
+            volsnap = None
+            LOG.debug("Searching volsnaps in K2.")
+            volsnaps = self.client.search("volsnaps")
+            for v in volsnaps.hits:
+                if v.snapshot.id == vol.id:
+                    volsnap = v
+                    break
+            LOG.debug("Searching mapping of volsnap in K2.")
+            rv = self.client.search("mappings", volume=volsnap)
+            lun = rv.hits[0].lun
+
+        else:
+            lun = mapping.lun
+        return {"driver_volume_type": "iscsi",
+                "data": {"target_iqn": target_iqn,
+                         "target_portal": iscsi_portal,
+                         "target_lun": lun,
+                         "target_discovered": True}}
+
+    @kaminario_logger
+    def terminate_connection(self, volume, connector, **kwargs):
+        """Terminate connection of volume from host."""
+        # Get volume object
+        if type(volume).__name__ != 'RestObject':
+            vol_name = self.get_volume_name(volume.id)
+            LOG.debug("Searching volume: %s in K2.", vol_name)
+            volume_rs = self.client.search("volumes", name=vol_name)
+            if hasattr(volume_rs, "hits") and volume_rs.total != 0:
+                volume = volume_rs.hits[0]
+        else:
+            vol_name = volume.name
+
+        # Get host object.
+        host_name = self.get_initiator_host_name(connector)
+        host_rs = self.client.search("hosts", name=host_name)
+        if hasattr(host_rs, "hits") and host_rs.total != 0 and volume:
+            host = host_rs.hits[0]
+            LOG.debug("Searching and deleting mapping of volume: %(name)s to "
+                      "host: %(host)s", {'host': host_name, 'name': vol_name})
+            map_rs = self.client.search("mappings", volume=volume, host=host)
+            if hasattr(map_rs, "hits") and map_rs.total != 0:
+                map_rs.hits[0].delete()
+            if self.client.search("mappings", host=host).total == 0:
+                LOG.debug("Deleting initiator hostname: %s in K2.", host_name)
+                host.delete()
+        else:
+            LOG.warning(_LW("Host: %s not found on K2."), host_name)
diff --git a/releasenotes/notes/kaminario-iscsi-cinder-driver-c34fadf63cd253de.yaml b/releasenotes/notes/kaminario-iscsi-cinder-driver-c34fadf63cd253de.yaml
new file mode 100644
index 00000000000..c931f365d6b
--- /dev/null
+++ b/releasenotes/notes/kaminario-iscsi-cinder-driver-c34fadf63cd253de.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Add iSCSI cinder volume driver for Kaminario K2 all-flash arrays.