os-brick connector for Veritas HyperScale
Implementation of an os-brick connector for Veritas HyperScale. The Nova volume driver implementation is being reviewed here: https://review.openstack.org/#/c/443951/ Change-Id: I01e26642f8fe4bc737c69493bb6ea6628bf72108 Implements: blueprint veritas-hyperscale-cinder-driver Depends-On: Ie1af5f5d54b0115974a4024a1756e4e0aa07399a
This commit is contained in:
parent
c454d1c63a
commit
74c7c7713a
@ -58,3 +58,4 @@ VZSTORAGE = "VZSTORAGE"
|
||||
SHEEPDOG = "SHEEPDOG"
|
||||
VMDK = "VMDK"
|
||||
GPFS = "GPFS"
|
||||
VERITAS_HYPERSCALE = "VERITAS_HYPERSCALE"
|
||||
|
@ -98,6 +98,7 @@ connector_list = [
|
||||
'os_brick.initiator.windows.iscsi.WindowsISCSIConnector',
|
||||
'os_brick.initiator.windows.fibre_channel.WindowsFCConnector',
|
||||
'os_brick.initiator.windows.smbfs.WindowsSMBFSConnector',
|
||||
'os_brick.initiator.connectors.vrtshyperscale.HyperScaleConnector',
|
||||
]
|
||||
|
||||
# Mappings used to determine who to contruct in the factory
|
||||
@ -143,7 +144,8 @@ _connector_mapping_linux = {
|
||||
'os_brick.initiator.connectors.vmware.VmdkConnector',
|
||||
initiator.GPFS:
|
||||
'os_brick.initiator.connectors.gpfs.GPFSConnector',
|
||||
|
||||
initiator.VERITAS_HYPERSCALE:
|
||||
'os_brick.initiator.connectors.vrtshyperscale.HyperScaleConnector',
|
||||
}
|
||||
|
||||
# Mapping for the S390X platform
|
||||
|
163
os_brick/initiator/connectors/vrtshyperscale.py
Normal file
163
os_brick/initiator/connectors/vrtshyperscale.py
Normal file
@ -0,0 +1,163 @@
|
||||
# Copyright (c) 2017 Veritas Technologies LLC.
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
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.initiator.connectors import base
|
||||
from os_brick import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
synchronized = lockutils.synchronized_with_prefix('os-brick-vrts-hyperscale-')
|
||||
|
||||
GUID_STR_LEN = 38
|
||||
|
||||
|
||||
class HyperScaleConnector(base.BaseLinuxConnector):
|
||||
"""Class implements the os-brick connector for HyperScale volumes."""
|
||||
|
||||
def __init__(self, root_helper, driver=None,
|
||||
execute=putils.execute,
|
||||
*args, **kwargs):
|
||||
|
||||
super(HyperScaleConnector, self).__init__(
|
||||
root_helper, driver=driver,
|
||||
execute=execute,
|
||||
*args, **kwargs)
|
||||
|
||||
def get_volume_paths(self, connection_properties):
|
||||
return []
|
||||
|
||||
def get_search_path(self):
|
||||
return None
|
||||
|
||||
def extend_volume(self, connection_properties):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def get_connector_properties(root_helper, *args, **kwargs):
|
||||
"""The HyperScale connector properties."""
|
||||
return {}
|
||||
|
||||
@utils.trace
|
||||
@synchronized('connect_volume')
|
||||
def connect_volume(self, connection_properties):
|
||||
"""Connect a volume to an instance."""
|
||||
|
||||
out = None
|
||||
err = None
|
||||
device_info = {}
|
||||
volume_name = None
|
||||
|
||||
if 'name' in connection_properties.keys():
|
||||
volume_name = connection_properties['name']
|
||||
|
||||
if volume_name is None or len(volume_name) != GUID_STR_LEN:
|
||||
msg = _("Failed to connect volume: invalid volume name.")
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
cmd_arg = {'operation': 'connect_volume'}
|
||||
cmd_arg['volume_guid'] = volume_name
|
||||
cmdarg_json = json.dumps(cmd_arg)
|
||||
|
||||
LOG.debug("HyperScale command hscli: %(cmd_arg)s",
|
||||
{'cmd_arg': cmdarg_json})
|
||||
try:
|
||||
(out, err) = self._execute('hscli', cmdarg_json,
|
||||
run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
|
||||
except putils.ProcessExecutionError as e:
|
||||
msg = (_("Error executing hscli: %(err)s") % {'err': e.stderr})
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
LOG.debug("Result of hscli: stdout=%(out)s "
|
||||
"stderr=%(err)s",
|
||||
{'out': out, 'err': err})
|
||||
|
||||
if err != 0:
|
||||
msg = (_("Failed to connect volume with stdout=%(out)s "
|
||||
"stderr=%(err)s") % {'out': out, 'err': err})
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
output = json.loads(out)
|
||||
payload = output.get('payload')
|
||||
if payload is None:
|
||||
msg = _("Failed to connect volume: "
|
||||
"hscli returned invalid payload")
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
if ('vsa_ip' not in payload.keys() or
|
||||
'refl_factor' not in payload.keys()):
|
||||
msg = _("Failed to connect volume: "
|
||||
"hscli returned invalid results")
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
device_info['vsa_ip'] = payload.get('vsa_ip')
|
||||
device_info['device_path'] = (
|
||||
"oflame://" + device_info['vsa_ip'] + ":9999/%7B" +
|
||||
volume_name[1:-1] + "%7D")
|
||||
|
||||
refl_factor = int(payload.get('refl_factor'))
|
||||
device_info['refl_factor'] = str(refl_factor)
|
||||
|
||||
if refl_factor > 0:
|
||||
if 'refl_targets' not in payload.keys():
|
||||
msg = _("Failed to connect volume: "
|
||||
"hscli returned inconsistent results")
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
device_info['refl_targets'] = (
|
||||
payload.get('refl_targets'))
|
||||
|
||||
return device_info
|
||||
|
||||
@utils.trace
|
||||
@synchronized('connect_volume')
|
||||
def disconnect_volume(self, connection_properties, device_info):
|
||||
"""Disconnect a volume from an instance."""
|
||||
volume_name = None
|
||||
|
||||
if 'name' in connection_properties.keys():
|
||||
volume_name = connection_properties['name']
|
||||
|
||||
if volume_name is None or len(volume_name) != GUID_STR_LEN:
|
||||
msg = _("Failed to disconnect volume: invalid volume name")
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
cmd_arg = {'operation': 'disconnect_volume'}
|
||||
cmd_arg['volume_guid'] = volume_name
|
||||
cmdarg_json = json.dumps(cmd_arg)
|
||||
|
||||
LOG.debug("HyperScale command hscli: %(cmd_arg)s",
|
||||
{'cmd_arg': cmdarg_json})
|
||||
try:
|
||||
(out, err) = self._execute('hscli', cmdarg_json,
|
||||
run_as_root=True,
|
||||
root_helper=self._root_helper)
|
||||
|
||||
except putils.ProcessExecutionError as e:
|
||||
msg = (_("Error executing hscli: %(err)s") % {'err': e.stderr})
|
||||
raise exception.BrickException(message=msg)
|
||||
|
||||
if err:
|
||||
msg = (_("Failed to connect volume: stdout=%(out)s "
|
||||
"stderr=%(err)s") % {'out': out, 'err': err})
|
||||
raise exception.BrickException(message=msg)
|
168
os_brick/tests/initiator/connectors/test_vrtshyperscale.py
Normal file
168
os_brick/tests/initiator/connectors/test_vrtshyperscale.py
Normal file
@ -0,0 +1,168 @@
|
||||
# Copyright (c) 2017 Veritas Technologies LLC
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
from os_brick import exception
|
||||
from os_brick.initiator.connectors import vrtshyperscale
|
||||
from os_brick.tests.initiator import test_connector
|
||||
|
||||
|
||||
class HyperScaleConnectorTestCase(test_connector.ConnectorTestCase):
|
||||
"""Test cases for Veritas HyperScale os-brick connector."""
|
||||
|
||||
def _fake_execute_success(self, *cmd, **kwargs):
|
||||
"""Mock successful execution of hscli"""
|
||||
result_json = ""
|
||||
err = 0
|
||||
args = json.loads(cmd[1])
|
||||
if args['operation'] == 'connect_volume':
|
||||
result = {}
|
||||
payload = {}
|
||||
payload['vsa_ip'] = '192.0.2.2'
|
||||
payload['refl_factor'] = '2'
|
||||
payload['refl_targets'] = '192.0.2.3,192.0.2.4'
|
||||
result['payload'] = payload
|
||||
result_json = json.dumps(result)
|
||||
return (result_json, err)
|
||||
|
||||
def _fake_execute_hscli_missing(self, *cmd, **kwargs):
|
||||
"""Mock attempt to execute missing hscli"""
|
||||
raise processutils.ProcessExecutionError()
|
||||
return ("", 0)
|
||||
|
||||
def _fake_execute_hscli_err(self, *cmd, **kwargs):
|
||||
"""Mock hscli returning error"""
|
||||
result_json = ""
|
||||
err = 'fake_hscli_error_msg'
|
||||
return (result_json, err)
|
||||
|
||||
def _fake_execute_hscli_res_inval(self, *cmd, **kwargs):
|
||||
"""Mock hscli returning unexpected values"""
|
||||
result_json = ""
|
||||
err = 0
|
||||
result = {}
|
||||
payload = {}
|
||||
payload['unexpected'] = 'junk'
|
||||
result['payload'] = payload
|
||||
result_json = json.dumps(result)
|
||||
return (result_json, err)
|
||||
|
||||
def test_connect_volume_normal(self):
|
||||
"""Test results of successful connect_volume()"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_success)
|
||||
fake_connection_properties = {
|
||||
'name': '{8ee71c33-dcd0-4267-8f2b-e0742ecabe9f}'
|
||||
}
|
||||
device_info = connector.connect_volume(fake_connection_properties)
|
||||
|
||||
self.assertEqual('192.0.2.2', device_info['vsa_ip'])
|
||||
self.assertEqual('2', device_info['refl_factor'])
|
||||
self.assertEqual('192.0.2.3,192.0.2.4', device_info['refl_targets'])
|
||||
self.assertEqual(
|
||||
'oflame://192.0.2.2:9999/'
|
||||
'%7B8ee71c33-dcd0-4267-8f2b-e0742ecabe9f%7D',
|
||||
device_info['device_path'])
|
||||
|
||||
def test_connect_volume_arg_missing(self):
|
||||
"""Test connect_volume with missing missing arguments"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_success)
|
||||
fake_connection_properties = {}
|
||||
self.assertRaises(exception.BrickException,
|
||||
connector.connect_volume,
|
||||
fake_connection_properties)
|
||||
|
||||
def test_connect_volume_arg_inval(self):
|
||||
"""Test connect_volume with bad volume name argument"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_success)
|
||||
fake_connection_properties = {
|
||||
'name': 'x'
|
||||
}
|
||||
self.assertRaises(exception.BrickException,
|
||||
connector.connect_volume,
|
||||
fake_connection_properties)
|
||||
|
||||
def test_connect_volume_hscli_missing(self):
|
||||
"""Test connect_volume that can't call hscli"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_hscli_missing)
|
||||
fake_connection_properties = {
|
||||
'name': '{8ee71c33-dcd0-4267-8f2b-e0742ecabe9f}'
|
||||
}
|
||||
self.assertRaises(exception.BrickException,
|
||||
connector.connect_volume,
|
||||
fake_connection_properties)
|
||||
|
||||
def test_connect_volume_hscli_err(self):
|
||||
"""Test connect_volume when hscli returns an error"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_hscli_err)
|
||||
fake_connection_properties = {
|
||||
'name': '{8ee71c33-dcd0-4267-8f2b-e0742ecabe9f}'
|
||||
}
|
||||
self.assertRaises(exception.BrickException,
|
||||
connector.connect_volume,
|
||||
fake_connection_properties)
|
||||
|
||||
def test_connect_volume_hscli_res_inval(self):
|
||||
"""Test connect_volume if hscli returns an invalid result"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_hscli_res_inval)
|
||||
fake_connection_properties = {
|
||||
'name': '{8ee71c33-dcd0-4267-8f2b-e0742ecabe9f}'
|
||||
}
|
||||
self.assertRaises(exception.BrickException,
|
||||
connector.connect_volume,
|
||||
fake_connection_properties)
|
||||
|
||||
def test_disconnect_volume_normal(self):
|
||||
"""Test successful disconnect_volume call"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_success)
|
||||
fake_connection_properties = {
|
||||
'name': '{8ee71c33-dcd0-4267-8f2b-e0742ecabe9f}'
|
||||
}
|
||||
fake_device_info = {}
|
||||
connector.disconnect_volume(fake_connection_properties,
|
||||
fake_device_info)
|
||||
|
||||
def test_disconnect_volume_arg_missing(self):
|
||||
"""Test disconnect_volume with missing arguments"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_success)
|
||||
fake_connection_properties = {}
|
||||
fake_device_info = {}
|
||||
self.assertRaises(exception.BrickException,
|
||||
connector.disconnect_volume,
|
||||
fake_connection_properties,
|
||||
fake_device_info)
|
||||
|
||||
def test_disconnect_volume_arg_inval(self):
|
||||
"""Test disconnect_volume with invalid volume name argument"""
|
||||
connector = vrtshyperscale.HyperScaleConnector(
|
||||
'sudo', execute=self._fake_execute_success)
|
||||
fake_connection_properties = {
|
||||
'name': 'x'
|
||||
}
|
||||
fake_device_info = {}
|
||||
self.assertRaises(exception.BrickException,
|
||||
connector.disconnect_volume,
|
||||
fake_connection_properties,
|
||||
fake_device_info)
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Add Veritas HyperScale connector support
|
Loading…
Reference in New Issue
Block a user