PSARC 2016/361 os-brick - managing local volume attaches in OpenStack Cinder
authorLaszlo Peter <laszlo.peter@oracle.com>
Wed, 07 Sep 2016 14:48:24 -0700
changeset 6761 f2bb9c5b1768
parent 6760 59cfe036953d
child 6762 d311620a9c15
PSARC 2016/361 os-brick - managing local volume attaches in OpenStack Cinder 23538847 Python module os-brick should be added to Userland
components/python/os-brick/Makefile
components/python/os-brick/files/solaris/solarisfc.py
components/python/os-brick/files/solaris/solarisiscsi.py
components/python/os-brick/os-brick-PYVER.p5m
components/python/os-brick/patches/01-volume-backup.patch
components/python/os-brick/patches/02-remotefs.patch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/os-brick/Makefile	Wed Sep 07 14:48:24 2016 -0700
@@ -0,0 +1,63 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+#
+
+include ../../../make-rules/shared-macros.mk
+
+COMPONENT_NAME=		os-brick
+COMPONENT_VERSION=	1.2.0
+COMPONENT_SRC=		$(COMPONENT_NAME)-$(COMPONENT_VERSION)
+COMPONENT_ARCHIVE=	$(COMPONENT_SRC).tar.gz
+COMPONENT_ARCHIVE_HASH=	\
+    sha256:e8c5931d2a5115a7c484276add6be6b5e3ba13402c8a71e32b2313448010be30
+COMPONENT_ARCHIVE_URL=	$(call pypi_url)
+COMPONENT_PROJECT_URL=	http://docs.openstack.org/developer/os-brick/
+COMPONENT_BUGDB=	python-mod/os-brick
+
+TPNO=			29698
+
+# Depends on oslo.serialization which is not Python 3 ready.
+PYTHON_VERSIONS =	$(PYTHON2_VERSIONS)
+
+include $(WS_MAKE_RULES)/prep.mk
+include $(WS_MAKE_RULES)/setup.py.mk
+include $(WS_MAKE_RULES)/ips.mk
+
+ASLR_MODE = $(ASLR_NOT_APPLICABLE)
+
+COMPONENT_POST_INSTALL_ACTION += \
+    $(CP) \
+	files/solaris/solarisfc.py \
+	files/solaris/solarisiscsi.py \
+	$(PROTO_DIR)$(PYTHON_LIB)/os_brick/initiator; \
+    $(PYTHON) -m compileall $(PROTO_DIR)/$(PYTHON_VENDOR_PACKAGES)
+
+# common targets
+build:		$(BUILD_NO_ARCH)
+
+install:	$(INSTALL_NO_ARCH)
+
+# See $(COMPONENT_SRC)/test-requirements.txt for the Python modules
+# required to execute unittests.
+test:		$(NO_TESTS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/os-brick/files/solaris/solarisfc.py	Wed Sep 07 14:48:24 2016 -0700
@@ -0,0 +1,198 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2015, 2016, 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.
+
+"""Generic Solaris Fibre Channel utilities."""
+
+import os
+import platform
+import time
+
+from oslo_concurrency import processutils as putils
+from oslo_log import log as logging
+
+from os_brick import exception
+import oslo_i18n
+
+LOG = logging.getLogger(__name__)
+
+
+class SolarisFibreChannel(object):
+    def __init__(self, *args, **kwargs):
+        self.execute = putils.execute
+
+    def _get_fc_hbas(self):
+        """Get Fibre Channel HBA information."""
+        out = None
+        try:
+            out, err = self.execute('/usr/sbin/fcinfo', 'hba-port')
+        except putils.ProcessExecutionError as err:
+            return None
+
+        if out is None:
+            LOG.info(_("Cannot find any Fibre Channel HBAs"))
+            return None
+
+        hbas = []
+        hba = {}
+        for line in out.splitlines():
+            line = line.strip()
+            # Collect the following hba-port data:
+            # 1: Port WWN
+            # 2: State (online|offline)
+            # 3: Node WWN
+            if line.startswith("HBA Port WWN:"):
+                # New HBA port entry
+                hba = {}
+                wwpn = line.split()[-1]
+                hba['port_name'] = wwpn
+                continue
+            elif line.startswith("Port Mode:"):
+                mode = line.split()[-1]
+                # Skip Target mode ports
+                if mode != 'Initiator':
+                    break
+            elif line.startswith("State:"):
+                state = line.split()[-1]
+                hba['port_state'] = state
+                continue
+            elif line.startswith("Node WWN:"):
+                wwnn = line.split()[-1]
+                hba['node_name'] = wwnn
+                continue
+            if len(hba) == 3:
+                hbas.append(hba)
+                hba = {}
+        return hbas
+
+    def get_fc_wwnns(self):
+        """Get Fibre Channel WWNNs from the system, if any."""
+        hbas = self._get_fc_hbas()
+        if hbas is None:
+            return None
+
+        wwnns = []
+        for hba in hbas:
+            if hba['port_state'] == 'online':
+                wwnn = hba['node_name']
+                wwnns.append(wwnn)
+        return wwnns
+
+    def get_fc_wwpns(self):
+        """Get Fibre Channel WWPNs from the system, if any."""
+        hbas = self._get_fc_hbas()
+        if hbas is None:
+            return None
+
+        wwpns = []
+        for hba in hbas:
+            if hba['port_state'] == 'online':
+                wwpn = hba['port_name']
+                wwpns.append(wwpn)
+        return wwpns
+
+    def _refresh_connection(self):
+        """Force the link reinitialization to make the LUN present."""
+        wwpns = self.get_fc_wwpns()
+        for wwpn in wwpns:
+            self.execute('/usr/sbin/fcadm', 'force-lip', wwpn)
+
+    def _get_device_path(self, wwn, target_lun):
+        """Get the Device path for the specified LUN.
+
+        The output of CMD below is like this:
+
+        OS Device Name: /dev/rdsk/c0t600C0FF0000000000036223AE73EB705d0s2
+               HBA Port WWN: 210100e08b27a8a1
+                       Remote Port WWN: 256000c0ffc03622
+                               LUN: 0
+                       Remote Port WWN: 216000c0ff803622
+                               LUN: 0
+               HBA Port WWN: 210000e08b07a8a1
+                       Remote Port WWN: 256000c0ffc03622
+                               LUN: 0
+                       Remote Port WWN: 216000c0ff803622
+                               LUN: 0
+               Vendor: SUN
+               Product: StorEdge 3510
+               Device Type: Disk device
+        ......
+        """
+        try:
+            out, err = self.execute('/usr/sbin/fcinfo', 'logical-unit', '-v')
+        except putils.ProcessExecutionError as err:
+            return None
+
+        host_device = None
+        remote_port = None
+        if out is not None:
+            for line in [l.strip() for l in out.splitlines()]:
+                if line.startswith("OS Device Name:"):
+                    host_device = line.split()[-1]
+                if line.startswith("Remote Port WWN:"):
+                    remote_port = line.split()[-1]
+                if line.startswith("LUN:"):
+                    lun = line.split()[-1]
+                    if remote_port.upper() == wwn and \
+                       int(lun) == int(target_lun):
+                        return host_device
+
+        return None
+
+    def connect_volume(self, connection_properties, scan_tries):
+        """Attach the volume to instance_name.
+
+        connection_properties for Fibre Channel must include:
+        target_wwn - Specified port WWNs
+        target_lun - LUN id of the volume
+        """
+        device_info = {'type': 'block'}
+        target_wwn = connection_properties['target_wwn']
+        target_lun = connection_properties['target_lun']
+        wwns = []
+        if isinstance(target_wwn, list):
+            wwns = target_wwn
+        else:
+            wwns.append(target_wwn)
+
+        # The scsi_vhci disk node is not always present immediately.
+        # Sometimes we need to reinitialize the connection to trigger
+        # a refresh.
+        for i in range(1, scan_tries):
+            # initiator needs time to refresh the LU list
+            time.sleep(i * 2)
+            host_device = self._get_device_path(wwns[0], target_lun)
+
+            if host_device is not None and os.path.exists(host_device):
+                break
+            else:
+                self._refresh_connection()
+        else:
+            msg = _("Fibre Channel volume device not found.")
+            LOG.error(msg)
+            raise exception.NoFibreChannelVolumeDeviceFound()
+
+        # Set the label EFI to the disk on SPARC before it is accessed and
+        # make sure the correct device path with slice 0
+        # (like '/dev/rdsk/c0t600xxxd0s0').
+        if platform.processor() == 'sparc':
+            tmp_dev_name = host_device.rsplit('s', 1)
+            disk_name = tmp_dev_name[0].split('/')[-1]
+            (out, _err) = self.execute('/usr/sbin/format', '-L', 'efi', '-d',
+                                       disk_name)
+            host_device = '%ss0' % tmp_dev_name[0]
+
+        device_info['path'] = host_device
+        return device_info
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/os-brick/files/solaris/solarisiscsi.py	Wed Sep 07 14:48:24 2016 -0700
@@ -0,0 +1,151 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2015, 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.
+
+"""Generic Solaris iSCSI utilities."""
+
+import os
+import platform
+import time
+
+from oslo_concurrency import processutils as putils
+from oslo_log import log as logging
+
+from os_brick import exception
+import oslo_i18n
+
+LOG = logging.getLogger(__name__)
+
+
+class SolarisiSCSI(object):
+    def __init__(self, *args, **kwargs):
+        self.execute = putils.execute
+
+    def _get_device_path(self, connection_properties):
+        """Get the device path from the target info.
+
+        The output of cmd below is like this:
+        Target: iqn.2010-10.org.openstack:hostname1-tgt-grp-target
+        Alias: -
+        TPGT: 1
+        ISID: 4000002a0000
+        Connections: 1
+        LUN: 1
+             Vendor:  SUN
+             Product: COMSTAR
+             OS Device Name: /dev/rdsk/c0t600144F0FDFAD05D0000563C04030003d0s2
+        LUN: 0
+             Vendor:  SUN
+             Product: COMSTAR
+             OS Device Name: /dev/rdsk/c0t600144F0FDFAD05D0000563C02270002d0s2
+
+        """
+        (out, _err) = self.execute('/usr/sbin/iscsiadm', 'list',
+                                   'target', '-S',
+                                   connection_properties['target_iqn'])
+
+        found = False
+        for line in [l.strip() for l in out.splitlines()]:
+            if line.startswith("LUN:"):
+                lun = line.split()[-1]
+                if int(lun) == int(connection_properties['target_lun']):
+                    found = True
+                    continue
+            if found:
+                if line.startswith("OS Device Name:"):
+                    dev_path = line.split()[-1]
+                    return dev_path
+                elif line.startswith("LUN:"):
+                    found = False
+
+        if not found:
+            LOG.error(_("No device is found for the target %s LUN %s.") %
+                      (connection_properties['target_iqn'],
+                       connection_properties['target_lun']))
+            raise
+
+    def get_initiator(self):
+        """Return the iSCSI initiator node name IQN"""
+        out, err = self.execute('/usr/sbin/iscsiadm', 'list', 'initiator-node')
+
+        # Sample first line of command output:
+        # Initiator node name: iqn.1986-03.com.sun:01:e00000000000.4f757217
+        initiator_name_line = out.splitlines()[0]
+        return initiator_name_line.rsplit(' ', 1)[1]
+
+    def _connect_to_iscsi_portal(self, connection_properties):
+        # TODO(Strony): handle the CHAP authentication
+        target_ip = connection_properties['target_portal'].split(":")[0]
+        self.execute('/usr/sbin/iscsiadm', 'add', 'discovery-address',
+                     target_ip)
+        self.execute('/usr/sbin/iscsiadm', 'modify', 'discovery',
+                     '--sendtargets', 'enable')
+        (out, _err) = self.execute('/usr/sbin/iscsiadm', 'list',
+                                   'discovery-address', '-v',
+                                   target_ip)
+
+        lines = out.splitlines()
+        if not lines[0].strip().startswith('Discovery Address: ') or \
+                lines[1].strip().startswith('Unable to get targets.'):
+            msg = _("No iSCSI target is found.")
+            LOG.error(msg)
+            raise
+
+        target_iqn = connection_properties['target_iqn']
+        for line in [l.strip() for l in lines]:
+            if line.startswith("Target name:") and \
+                    line.split()[-1] == target_iqn:
+                return
+        else:
+            LOG.error(_("No active session is found for the target %s.") %
+                      target_iqn)
+            raise
+
+    def connect_volume(self, connection_properties, scan_tries):
+        """Attach the volume to instance_name.
+
+        connection_properties for iSCSI must include:
+        target_portal - ip and optional port
+        target_iqn - iSCSI Qualified Name
+        target_lun - LUN id of the volume
+        """
+        device_info = {'type': 'block'}
+
+        # TODO(Strony): support the iSCSI multipath on Solaris.
+        self._connect_to_iscsi_portal(connection_properties)
+
+        host_device = self._get_device_path(connection_properties)
+
+        # check if it is a valid device path.
+        for i in range(1, scan_tries):
+            if os.path.exists(host_device):
+                break
+            else:
+                time.sleep(i ** 2)
+        else:
+            raise exception.VolumeDeviceNotFound(device=host_device)
+
+        # Set the label EFI to the disk on SPARC before it is accessed and
+        # make sure the correct device path with slice 0
+        # (like '/dev/rdsk/c0t600xxxd0s0').
+        if platform.processor() == 'sparc':
+            tmp_dev_name = host_device.rsplit('s', 1)
+            disk_name = tmp_dev_name[0].split('/')[-1]
+            (out, _err) = self.execute('/usr/sbin/format', '-L', 'efi', '-d',
+                                       disk_name)
+            host_device = '%ss0' % tmp_dev_name[0]
+
+        device_info['path'] = host_device
+        return device_info
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/os-brick/os-brick-PYVER.p5m	Wed Sep 07 14:48:24 2016 -0700
@@ -0,0 +1,107 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+#
+
+set name=pkg.fmri \
+    value=pkg:/library/python/os-brick-$(PYV)@$(IPS_COMPONENT_VERSION),$(BUILD_VERSION)
+set name=pkg.summary \
+    value="OpenStack Cinder brick library for managing local volume attaches"
+set name=pkg.description \
+    value="OpenStack Cinder brick library for managing local volume attaches, including discovery of volumes being attached to a host for many transport protocols and removal of volumes from a host."
+set name=com.oracle.info.description value="the Python os-brick module"
+set name=com.oracle.info.tpno value=$(TPNO)
+set name=info.classification \
+    value=org.opensolaris.category.2008:Development/Python \
+    value="org.opensolaris.category.2008:System/Administration and Configuration" \
+    value="org.opensolaris.category.2008:System/Enterprise Management"
+set name=info.source-url value=$(COMPONENT_ARCHIVE_URL)
+set name=info.upstream value="OpenStack <[email protected]>"
+set name=info.upstream-url value=$(COMPONENT_PROJECT_URL)
+set name=org.opensolaris.arc-caseid value=PSARC/2016/361
+set name=org.opensolaris.consolidation value=$(CONSOLIDATION)
+#
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick-$(COMPONENT_VERSION)-py$(PYVER).egg-info/PKG-INFO
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick-$(COMPONENT_VERSION)-py$(PYVER).egg-info/SOURCES.txt
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick-$(COMPONENT_VERSION)-py$(PYVER).egg-info/dependency_links.txt
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick-$(COMPONENT_VERSION)-py$(PYVER).egg-info/not-zip-safe
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick-$(COMPONENT_VERSION)-py$(PYVER).egg-info/pbr.json
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick-$(COMPONENT_VERSION)-py$(PYVER).egg-info/requires.txt
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick-$(COMPONENT_VERSION)-py$(PYVER).egg-info/top_level.txt
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/__init__.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/exception.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/executor.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/i18n.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/initiator/__init__.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/initiator/connector.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/initiator/host_driver.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/initiator/linuxfc.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/initiator/linuxrbd.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/initiator/linuxscsi.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/initiator/solarisfc.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/initiator/solarisiscsi.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/local_dev/__init__.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/local_dev/lvm.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/remotefs/__init__.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/remotefs/remotefs.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/utils.py
+file path=usr/lib/python$(PYVER)/vendor-packages/os_brick/version.py
+#
+license LICENSE license="Apache v2.0"
+
+# force a dependency on the Python runtime
+depend type=require fmri=__TBD pkg.debug.depend.file=python$(PYVER) \
+    pkg.debug.depend.path=usr/bin
+
+# force a dependency on the os-brick package
+depend type=require \
+    fmri=library/python/os-brick@$(IPS_COMPONENT_VERSION),$(BUILD_VERSION)
+
+# force a dependency on oslo.concurrency; pkgdepend work is needed to flush this
+# out.
+depend type=require fmri=library/python/oslo.concurrency-$(PYV)
+
+# force a dependency on oslo.i18n; pkgdepend work is needed to flush this out.
+depend type=require fmri=library/python/oslo.i18n-$(PYV)
+
+# force a dependency on oslo.log; pkgdepend work is needed to flush this out.
+depend type=require fmri=library/python/oslo.log-$(PYV)
+
+# force a dependency on oslo.service; pkgdepend work is needed to flush this
+# out.
+depend type=require fmri=library/python/oslo.service-$(PYV)
+
+# force a dependency on oslo.utils; pkgdepend work is needed to flush this out.
+depend type=require fmri=library/python/oslo.utils-$(PYV)
+
+# force a dependency on pbr; pkgdepend work is needed to flush this out.
+depend type=require fmri=library/python/pbr-$(PYV)
+
+# force a dependency on requests; pkgdepend work is needed to flush this out.
+depend type=require fmri=library/python/requests-$(PYV)
+
+# force a dependency on retrying; pkgdepend work is needed to flush this out.
+depend type=require fmri=library/python/retrying-$(PYV)
+
+# force a dependency on six; pkgdepend work is needed to flush this out.
+depend type=require fmri=library/python/six-$(PYV)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/os-brick/patches/01-volume-backup.patch	Wed Sep 07 14:48:24 2016 -0700
@@ -0,0 +1,139 @@
+This patch is to replace Linux-specific code with conditional checks in
+the Cinder Brick code to support Cinder backup on Solaris. Patch has
+not yet been submitted upstream.
+
+--- os-brick-1.2.0/os_brick/initiator/connector.py.~1~	2016-03-28 09:30:49.000000000 +0000
++++ os-brick-1.2.0/os_brick/initiator/connector.py	2016-09-06 16:59:42.732438933 +0000
+@@ -54,6 +54,8 @@ from os_brick.initiator import linuxfc
+ from os_brick.initiator import linuxrbd
+ from os_brick.initiator import linuxscsi
+ from os_brick.remotefs import remotefs
++from os_brick.initiator import solarisfc
++from os_brick.initiator import solarisiscsi
+ from os_brick.i18n import _, _LE, _LI, _LW
+ 
+ LOG = logging.getLogger(__name__)
+@@ -122,7 +124,10 @@ def get_connector_properties(root_helper
+     """
+ 
+     iscsi = ISCSIConnector(root_helper=root_helper)
+-    fc = linuxfc.LinuxFibreChannel(root_helper=root_helper)
++    if sys.platform == 'sunos5':
++        fc = solarisfc.SolarisFibreChannel()
++    else:
++        fc = linuxfc.LinuxFibreChannel(root_helper=root_helper)
+ 
+     props = {}
+     props['ip'] = my_ip
+@@ -284,8 +289,11 @@ class InitiatorConnector(executor.Execut
+                'of=/dev/null', 'count=1')
+         out, info = None, None
+         try:
+-            out, info = self._execute(*cmd, run_as_root=run_as_root,
+-                                      root_helper=self._root_helper)
++            if sys.platform == 'sunos5':
++                out, info = self._execute(*cmd)
++            else:
++                out, info = self._execute(*cmd, run_as_root=run_as_root,
++                                          root_helper=self._root_helper)
+         except putils.ProcessExecutionError as e:
+             LOG.error(_LE("Failed to access the device on the path "
+                           "%(path)s: %(error)s %(info)s."),
+@@ -502,7 +510,10 @@ class ISCSIConnector(InitiatorConnector)
+                  execute=putils.execute, use_multipath=False,
+                  device_scan_attempts=DEVICE_SCAN_ATTEMPTS_DEFAULT,
+                  transport='default', *args, **kwargs):
+-        self._linuxscsi = linuxscsi.LinuxSCSI(root_helper, execute)
++        if sys.platform == 'sunos5':
++            self._solarisiscsi = solarisiscsi.SolarisiSCSI()
++        else:
++            self._linuxscsi = linuxscsi.LinuxSCSI(root_helper, execute)
+         super(ISCSIConnector, self).__init__(
+             root_helper, driver=driver,
+             execute=execute,
+@@ -678,6 +689,8 @@ class ISCSIConnector(InitiatorConnector)
+ 
+     def set_execute(self, execute):
+         super(ISCSIConnector, self).set_execute(execute)
++        if sys.platform == 'sunos5':
++            return
+         self._linuxscsi.set_execute(execute)
+ 
+     def _validate_iface_transport(self, transport_iface):
+@@ -840,6 +853,9 @@ class ISCSIConnector(InitiatorConnector)
+         Note that plural keys may be used when use_multipath=True
+         """
+ 
++        if sys.platform == 'sunos5':
++            return self._solarisiscsi.connect_volume(connection_properties,
++                                                     self.device_scan_attempts)
+         device_info = {'type': 'block'}
+ 
+         host_devices, target_props = self._get_potential_volume_paths(
+@@ -912,6 +928,9 @@ class ISCSIConnector(InitiatorConnector)
+         target_iqn(s) - iSCSI Qualified Name
+         target_lun(s) - LUN id of the volume
+         """
++        if sys.platform == 'sunos5':
++            return
++
+         if self.use_multipath:
+             self._rescan_multipath()
+             host_device = multipath_device = None
+@@ -1002,6 +1021,9 @@ class ISCSIConnector(InitiatorConnector)
+ 
+     def get_initiator(self):
+         """Secure helper to read file as root."""
++        if sys.platform == 'sunos5':
++            return self._solarisiscsi.get_initiator()
++
+         file_path = '/etc/iscsi/initiatorname.iscsi'
+         try:
+             lines, _err = self._execute('cat', file_path, run_as_root=True,
+@@ -1304,8 +1326,11 @@ class FibreChannelConnector(InitiatorCon
+                  execute=putils.execute, use_multipath=False,
+                  device_scan_attempts=DEVICE_SCAN_ATTEMPTS_DEFAULT,
+                  *args, **kwargs):
+-        self._linuxscsi = linuxscsi.LinuxSCSI(root_helper, execute)
+-        self._linuxfc = linuxfc.LinuxFibreChannel(root_helper, execute)
++        if sys.platform == 'sunos5':
++            self._solarisfc = solarisfc.SolarisFibreChannel()
++        else:
++            self._linuxscsi = linuxscsi.LinuxSCSI(root_helper, execute)
++            self._linuxfc = linuxfc.LinuxFibreChannel(root_helper, execute)
+         super(FibreChannelConnector, self).__init__(
+             root_helper, driver=driver,
+             execute=execute,
+@@ -1315,6 +1340,8 @@ class FibreChannelConnector(InitiatorCon
+ 
+     def set_execute(self, execute):
+         super(FibreChannelConnector, self).set_execute(execute)
++        if sys.platform == 'sunos5':
++            return
+         self._linuxscsi.set_execute(execute)
+         self._linuxfc.set_execute(execute)
+ 
+@@ -1373,6 +1400,10 @@ class FibreChannelConnector(InitiatorCon
+         target_wwn - World Wide Name
+         target_lun - LUN id of the volume
+         """
++        if sys.platform == 'sunos5':
++            return self._solarisfc.connect_volume(connection_properties,
++                                                  self.device_scan_attempts)
++
+         LOG.debug("execute = %s", self._execute)
+         device_info = {'type': 'block'}
+ 
+@@ -1505,6 +1536,12 @@ class FibreChannelConnector(InitiatorCon
+         target_wwn - World Wide Name
+         target_lun - LUN id of the volume
+         """
++        if sys.platform == 'sunos5':
++            # There is some latency before the next time connection happens.
++            # The best practice is to offline the state of the switch now
++            # and online it at the next connection.
++            # But now, we just return without any operation.
++            return
+ 
+         devices = []
+         volume_paths = self.get_volume_paths(connection_properties)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/os-brick/patches/02-remotefs.patch	Wed Sep 07 14:48:24 2016 -0700
@@ -0,0 +1,78 @@
+In-house patch to adapt Linux specific commands and command output
+parsing to Solaris. Patch may be suitable for pushing upsteam.
+
+--- os-brick-1.2.0/os_brick/remotefs/remotefs.py.~1~	2016-03-28 09:30:49.000000000 +0000
++++ os-brick-1.2.0/os_brick/remotefs/remotefs.py	2016-09-06 17:01:03.004025741 +0000
+@@ -17,6 +17,7 @@
+ 
+ import hashlib
+ import os
++import platform
+ import re
+ import tempfile
+ 
+@@ -86,14 +87,21 @@ class RemoteFsClient(object):
+                             self._get_hash_str(device_name))
+ 
+     def _read_mounts(self):
+-        (out, _err) = self._execute('mount', check_exit_code=0)
++        if platform.system() == "SunOS":
++            (out, _err) = self._execute('/usr/sbin/mount', check_exit_code=0)
++        else:
++            (out, _err) = self._execute('mount', check_exit_code=0)
+         lines = out.split('\n')
+         mounts = {}
+         for line in lines:
+             tokens = line.split()
+             if 2 < len(tokens):
+-                device = tokens[0]
+-                mnt_point = tokens[2]
++                if platform.system() == "SunOS":
++                    device = tokens[2]
++                    mnt_point = tokens[0]
++                else:
++                    device = tokens[0]
++                    mnt_point = tokens[2]
+                 mounts[mnt_point] = device
+         return mounts
+ 
+@@ -105,8 +113,12 @@ class RemoteFsClient(object):
+             LOG.info(_LI('Already mounted: %s'), mount_path)
+             return
+ 
+-        self._execute('mkdir', '-p', mount_path, check_exit_code=0)
+-        if self._mount_type == 'nfs':
++        if platform.system() == "SunOS":
++            self._execute('/usr/bin/mkdir', '-p', mount_path,
++                          check_exit_code=0)
++        else:
++            self._execute('mkdir', '-p', mount_path, check_exit_code=0)
++        if self._mount_type == 'nfs' and platform.system() != "SunOS":
+             self._mount_nfs(share, mount_path, flags)
+         elif self._mount_type == 'vzstorage':
+             self._mount_vzstorage(share, mount_path, flags)
+@@ -117,15 +129,21 @@ class RemoteFsClient(object):
+     def _do_mount(self, mount_type, share, mount_path, mount_options=None,
+                   flags=None):
+         """Mounts share based on the specified params."""
+-        mnt_cmd = ['mount', '-t', mount_type]
++        if platform.system() == "SunOS":
++            mnt_cmd = ['/usr/sbin/mount', '-F', mount_type]
++        else:
++            mnt_cmd = ['mount', '-t', mount_type]
+         if mount_options is not None:
+             mnt_cmd.extend(['-o', mount_options])
+         if flags is not None:
+             mnt_cmd.extend(flags)
+         mnt_cmd.extend([share, mount_path])
+ 
+-        self._execute(*mnt_cmd, root_helper=self.root_helper,
+-                      run_as_root=True, check_exit_code=0)
++        if platform.system() == "SunOS":
++            self._execute(*mnt_cmd, check_exit_code=0)
++        else:
++            self._execute(*mnt_cmd, root_helper=self.root_helper,
++                          run_as_root=True, check_exit_code=0)
+ 
+     def _mount_nfs(self, nfs_share, mount_path, flags=None):
+         """Mount nfs share using present mount types."""