Merge "Remove VxFlexOS connector external dependencies" into stable/stein

This commit is contained in:
Zuul 2019-11-27 23:11:30 +00:00 committed by Gerrit Code Review
commit 15160ab610
3 changed files with 132 additions and 72 deletions

View File

@ -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])

View File

@ -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))

View File

@ -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)