Merge "Remove VxFlexOS connector external dependencies" into stable/stein
This commit is contained in:
commit
15160ab610
|
@ -19,13 +19,13 @@ import six
|
|||
from six.moves import urllib
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_concurrency import processutils as putils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_brick import exception
|
||||
from os_brick.i18n import _
|
||||
from os_brick import initiator
|
||||
from os_brick.initiator.connectors import base
|
||||
from os_brick.privileged import scaleio as priv_scaleio
|
||||
from os_brick import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -33,14 +33,26 @@ DEVICE_SCAN_ATTEMPTS_DEFAULT = 3
|
|||
synchronized = lockutils.synchronized_with_prefix('os-brick-')
|
||||
|
||||
|
||||
def io(_type, nr):
|
||||
"""Implementation of _IO macro from <sys/ioctl.h>."""
|
||||
|
||||
return ioc(0x0, _type, nr, 0)
|
||||
|
||||
|
||||
def ioc(direction, _type, nr, size):
|
||||
"""Implementation of _IOC macro from <sys/ioctl.h>."""
|
||||
|
||||
return direction | (size & 0x1fff) << 16 | ord(_type) << 8 | nr
|
||||
|
||||
|
||||
class ScaleIOConnector(base.BaseLinuxConnector):
|
||||
"""Class implements the connector driver for ScaleIO."""
|
||||
|
||||
OK_STATUS_CODE = 200
|
||||
VOLUME_NOT_MAPPED_ERROR = 84
|
||||
VOLUME_ALREADY_MAPPED_ERROR = 81
|
||||
GET_GUID_CMD = ['/opt/emc/scaleio/sdc/bin/drv_cfg', '--query_guid']
|
||||
RESCAN_VOLS_CMD = ['/opt/emc/scaleio/sdc/bin/drv_cfg', '--rescan']
|
||||
GET_GUID_OP_CODE = io('a', 14)
|
||||
RESCAN_VOLS_OP_CODE = io('a', 10)
|
||||
|
||||
def __init__(self, root_helper, driver=None,
|
||||
device_scan_attempts=initiator.DEVICE_SCAN_ATTEMPTS_DEFAULT,
|
||||
|
@ -64,6 +76,26 @@ class ScaleIOConnector(base.BaseLinuxConnector):
|
|||
self.iops_limit = None
|
||||
self.bandwidth_limit = None
|
||||
|
||||
def _get_guid(self):
|
||||
try:
|
||||
guid = priv_scaleio.get_guid(self.GET_GUID_OP_CODE)
|
||||
LOG.info("Current sdc guid: %s", guid)
|
||||
return guid
|
||||
except (IOError, OSError, ValueError) as e:
|
||||
msg = _("Error querying sdc guid: %s") % e
|
||||
LOG.error(msg)
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
def _rescan_vols(self):
|
||||
LOG.info("ScaleIO rescan volumes")
|
||||
|
||||
try:
|
||||
priv_scaleio.rescan_vols(self.RESCAN_VOLS_OP_CODE)
|
||||
except (IOError, OSError) as e:
|
||||
msg = _("Error querying volumes: %s") % e
|
||||
LOG.error(msg)
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
@staticmethod
|
||||
def get_connector_properties(root_helper, *args, **kwargs):
|
||||
"""The ScaleIO connector properties."""
|
||||
|
@ -298,7 +330,7 @@ class ScaleIOConnector(base.BaseLinuxConnector):
|
|||
"scaleIO Volume name: %(volume_name)s, SDC IP: %(sdc_ip)s, "
|
||||
"REST Server IP: %(server_ip)s, "
|
||||
"REST Server username: %(username)s, "
|
||||
"iops limit:%(iops_limit)s, "
|
||||
"iops limit: %(iops_limit)s, "
|
||||
"bandwidth limit: %(bandwidth_limit)s."
|
||||
), {
|
||||
'volume_name': self.volume_name,
|
||||
|
@ -311,24 +343,7 @@ class ScaleIOConnector(base.BaseLinuxConnector):
|
|||
}
|
||||
)
|
||||
|
||||
LOG.info("ScaleIO sdc query guid command: %(cmd)s",
|
||||
{'cmd': self.GET_GUID_CMD})
|
||||
|
||||
try:
|
||||
(out, err) = self._execute(*self.GET_GUID_CMD, run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
|
||||
LOG.info("Map volume %(cmd)s: stdout=%(out)s "
|
||||
"stderr=%(err)s",
|
||||
{'cmd': self.GET_GUID_CMD, 'out': out, 'err': err})
|
||||
|
||||
except putils.ProcessExecutionError as e:
|
||||
msg = (_("Error querying sdc guid: %(err)s") % {'err': e.stderr})
|
||||
LOG.error(msg)
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
guid = out
|
||||
LOG.info("Current sdc guid: %(guid)s", {'guid': guid})
|
||||
guid = self._get_guid()
|
||||
params = {'guid': guid, 'allowMultipleMappings': 'TRUE'}
|
||||
self.volume_id = self.volume_id or self._get_volume_id()
|
||||
|
||||
|
@ -424,6 +439,10 @@ class ScaleIOConnector(base.BaseLinuxConnector):
|
|||
:type connection_properties: dict
|
||||
:param device_info: historical difference, but same as connection_props
|
||||
:type device_info: dict
|
||||
:type force: bool
|
||||
:param ignore_errors: When force is True, this will decide whether to
|
||||
ignore errors or raise an exception once finished
|
||||
the operation. Default is False.
|
||||
"""
|
||||
self.get_config(connection_properties)
|
||||
self.volume_id = self.volume_id or self._get_volume_id()
|
||||
|
@ -438,25 +457,7 @@ class ScaleIOConnector(base.BaseLinuxConnector):
|
|||
'server_ip': self.server_ip}
|
||||
)
|
||||
|
||||
LOG.info("ScaleIO sdc query guid command: %(cmd)s",
|
||||
{'cmd': self.GET_GUID_CMD})
|
||||
|
||||
try:
|
||||
(out, err) = self._execute(*self.GET_GUID_CMD, run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
LOG.info(
|
||||
"Unmap volume %(cmd)s: stdout=%(out)s stderr=%(err)s",
|
||||
{'cmd': self.GET_GUID_CMD, 'out': out, 'err': err}
|
||||
)
|
||||
|
||||
except putils.ProcessExecutionError as e:
|
||||
msg = _("Error querying sdc guid: %(err)s") % {'err': e.stderr}
|
||||
LOG.error(msg)
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
guid = out
|
||||
LOG.info("Current sdc guid: %(guid)s", {'guid': guid})
|
||||
|
||||
guid = self._get_guid()
|
||||
params = {'guid': guid}
|
||||
headers = {'content-type': 'application/json'}
|
||||
request = (
|
||||
|
@ -499,21 +500,7 @@ class ScaleIOConnector(base.BaseLinuxConnector):
|
|||
for a ScaleIO volume.
|
||||
"""
|
||||
|
||||
LOG.info("ScaleIO rescan volumes: %(cmd)s",
|
||||
{'cmd': self.RESCAN_VOLS_CMD})
|
||||
|
||||
try:
|
||||
(out, err) = self._execute(*self.RESCAN_VOLS_CMD, run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
|
||||
LOG.debug("Rescan volumes %(cmd)s: stdout=%(out)s",
|
||||
{'cmd': self.RESCAN_VOLS_CMD, 'out': out})
|
||||
|
||||
except putils.ProcessExecutionError as e:
|
||||
msg = (_("Error querying volumes: %(err)s") % {'err': e.stderr})
|
||||
LOG.error(msg)
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
self._rescan_vols()
|
||||
volume_paths = self.get_volume_paths(connection_properties)
|
||||
if volume_paths:
|
||||
return self.get_device_size(volume_paths[0])
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# 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.
|
||||
|
||||
from binascii import hexlify
|
||||
from contextlib import contextmanager
|
||||
from fcntl import ioctl
|
||||
import os
|
||||
import struct
|
||||
import uuid
|
||||
|
||||
from os_brick import privileged
|
||||
|
||||
SCINI_DEVICE_PATH = '/dev/scini'
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_scini_device():
|
||||
"""Open scini device for low-level I/O using contextmanager.
|
||||
|
||||
File descriptor will be closed after all operations performed if it was
|
||||
opened successfully.
|
||||
|
||||
:return: scini device file descriptor
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
fd = None
|
||||
try:
|
||||
fd = os.open(SCINI_DEVICE_PATH, os.O_RDWR)
|
||||
yield fd
|
||||
finally:
|
||||
if fd:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
@privileged.default.entrypoint
|
||||
def get_guid(op_code):
|
||||
"""Query ScaleIO sdc GUID via ioctl request.
|
||||
|
||||
:param op_code: operational code
|
||||
:type op_code: int
|
||||
:return: ScaleIO sdc GUID
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
with open_scini_device() as fd:
|
||||
out = ioctl(fd, op_code, struct.pack('QQQ', 0, 0, 0))
|
||||
# The first 8 bytes contain a return code that is not used
|
||||
# so they can be discarded.
|
||||
out_to_hex = hexlify(out[8:]).decode()
|
||||
return str(uuid.UUID(out_to_hex))
|
||||
|
||||
|
||||
@privileged.default.entrypoint
|
||||
def rescan_vols(op_code):
|
||||
"""Rescan ScaleIO volumes via ioctl request.
|
||||
|
||||
:param op_code: operational code
|
||||
:type op_code: int
|
||||
"""
|
||||
|
||||
with open_scini_device() as fd:
|
||||
ioctl(fd, op_code, struct.pack('Q', 0))
|
|
@ -17,8 +17,6 @@ import os
|
|||
import requests
|
||||
import six
|
||||
|
||||
from oslo_concurrency import processutils as putils
|
||||
|
||||
from os_brick import exception
|
||||
from os_brick.initiator.connectors import scaleio
|
||||
from os_brick.tests.initiator import test_connector
|
||||
|
@ -35,7 +33,7 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
}
|
||||
|
||||
# Fake SDC GUID
|
||||
fake_guid = 'FAKE_GUID'
|
||||
fake_guid = '013a5304-d053-4b30-a34f-ee3ad983236d'
|
||||
|
||||
def setUp(self):
|
||||
super(ScaleIOConnectorTestCase, self).setUp()
|
||||
|
@ -84,6 +82,12 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
self.mock_object(os, 'listdir',
|
||||
return_value=["emc-vol-{}".format(self.vol['id'])])
|
||||
|
||||
# Patch scaleio privileged calls
|
||||
self.get_guid_mock = self.mock_object(scaleio.priv_scaleio, 'get_guid',
|
||||
return_value=self.fake_guid)
|
||||
self.rescan_vols_mock = self.mock_object(scaleio.priv_scaleio,
|
||||
'rescan_vols')
|
||||
|
||||
# The actual ScaleIO connector
|
||||
self.connector = scaleio.ScaleIOConnector(
|
||||
'sudo', execute=self.fake_execute)
|
||||
|
@ -117,14 +121,6 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
return super(ScaleIOConnectorTestCase.MockHTTPSResponse,
|
||||
self).text
|
||||
|
||||
def fake_execute(self, *cmd, **kwargs):
|
||||
"""Fakes the rootwrap call"""
|
||||
return self.fake_guid, None
|
||||
|
||||
def fake_missing_execute(self, *cmd, **kwargs):
|
||||
"""Error when trying to call rootwrap drv_cfg"""
|
||||
raise putils.ProcessExecutionError("Test missing drv_cfg.")
|
||||
|
||||
def handle_scaleio_request(self, url, *args, **kwargs):
|
||||
"""Fake REST server"""
|
||||
api_call = url.split(':', 2)[2].split('/', 1)[1].replace('api/', '')
|
||||
|
@ -170,6 +166,8 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
def test_connect_volume(self):
|
||||
"""Successful connect to volume"""
|
||||
self.connector.connect_volume(self.fake_connection_properties)
|
||||
self.get_guid_mock.assert_called_once_with(
|
||||
self.connector.GET_GUID_OP_CODE)
|
||||
|
||||
def test_connect_volume_without_volume_id(self):
|
||||
"""Successful connect to volume without a Volume Id"""
|
||||
|
@ -177,6 +175,8 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
connection_properties.pop('scaleIO_volume_id')
|
||||
|
||||
self.connector.connect_volume(connection_properties)
|
||||
self.get_guid_mock.assert_called_once_with(
|
||||
self.connector.GET_GUID_OP_CODE)
|
||||
|
||||
def test_connect_with_bandwidth_limit(self):
|
||||
"""Successful connect to volume with bandwidth limit"""
|
||||
|
@ -197,6 +197,8 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
def test_disconnect_volume(self):
|
||||
"""Successful disconnect from volume"""
|
||||
self.connector.disconnect_volume(self.fake_connection_properties, None)
|
||||
self.get_guid_mock.assert_called_once_with(
|
||||
self.connector.GET_GUID_OP_CODE)
|
||||
|
||||
def test_disconnect_volume_without_volume_id(self):
|
||||
"""Successful disconnect from volume without a Volume Id"""
|
||||
|
@ -204,6 +206,8 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
connection_properties.pop('scaleIO_volume_id')
|
||||
|
||||
self.connector.disconnect_volume(connection_properties, None)
|
||||
self.get_guid_mock.assert_called_once_with(
|
||||
self.connector.GET_GUID_OP_CODE)
|
||||
|
||||
def test_error_id(self):
|
||||
"""Fail to connect with bad volume name"""
|
||||
|
@ -232,11 +236,6 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
dict(errorCode=401, message='bad login'), 401)
|
||||
self.assertRaises(exception.BrickException, self.test_connect_volume)
|
||||
|
||||
def test_error_bad_drv_cfg(self):
|
||||
"""Fail to connect with missing rootwrap executable"""
|
||||
self.connector.set_execute(self.fake_missing_execute)
|
||||
self.assertRaises(exception.BrickException, self.test_connect_volume)
|
||||
|
||||
def test_error_map_volume(self):
|
||||
"""Fail to connect with REST API failure"""
|
||||
self.mock_calls[self.action_format.format(
|
||||
|
@ -294,3 +293,5 @@ class ScaleIOConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
self.fake_connection_properties)
|
||||
self.assertEqual(extended_size,
|
||||
mock_device_size.return_value)
|
||||
self.rescan_vols_mock.assert_called_once_with(
|
||||
self.connector.RESCAN_VOLS_OP_CODE)
|
||||
|
|
Loading…
Reference in New Issue