Merge "Nexenta Edge iSCSI backend driver"
This commit is contained in:
commit
65a6c40b6e
@ -119,6 +119,8 @@ from cinder.volume.drivers.lenovo import lenovo_common as \
|
||||
from cinder.volume.drivers import lvm as cinder_volume_drivers_lvm
|
||||
from cinder.volume.drivers.netapp import options as \
|
||||
cinder_volume_drivers_netapp_options
|
||||
from cinder.volume.drivers.nexenta.nexentaedge import iscsi as \
|
||||
cinder_volume_drivers_nexenta_nexentaedge_iscsi
|
||||
from cinder.volume.drivers import nfs as cinder_volume_drivers_nfs
|
||||
from cinder.volume.drivers import nimble as cinder_volume_drivers_nimble
|
||||
from cinder.volume.drivers.prophetstor import options as \
|
||||
@ -285,6 +287,8 @@ def list_opts():
|
||||
cinder_volume_drivers_prophetstor_options.DPL_OPTS,
|
||||
cinder_volume_drivers_hitachi_hbsdiscsi.volume_opts,
|
||||
cinder_volume_manager.volume_manager_opts,
|
||||
cinder_volume_drivers_nexenta_nexentaedge_iscsi.
|
||||
nexenta_edge_opts,
|
||||
cinder_volume_drivers_ibm_flashsystemiscsi.
|
||||
flashsystem_iscsi_opts,
|
||||
cinder_volume_drivers_ibm_flashsystemcommon.flashsystem_opts,
|
||||
|
169
cinder/tests/unit/test_nexenta_edge.py
Normal file
169
cinder/tests/unit/test_nexenta_edge.py
Normal file
@ -0,0 +1,169 @@
|
||||
#
|
||||
# Copyright 2015 Nexenta Systems, Inc.
|
||||
# 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 mock
|
||||
|
||||
from cinder import context
|
||||
from cinder import test
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.nexenta.nexentaedge import iscsi
|
||||
|
||||
NEDGE_URL = 'service/isc/iscsi'
|
||||
NEDGE_BUCKET = 'c/t/bk'
|
||||
NEDGE_SERVICE = 'isc'
|
||||
NEDGE_BLOCKSIZE = 4096
|
||||
NEDGE_CHUNKSIZE = 16384
|
||||
|
||||
MOCK_VOL = {
|
||||
'id': 'vol1',
|
||||
'name': 'vol1',
|
||||
'size': 1
|
||||
}
|
||||
MOCK_VOL2 = {
|
||||
'id': 'vol2',
|
||||
'name': 'vol2',
|
||||
'size': 1
|
||||
}
|
||||
MOCK_SNAP = {
|
||||
'id': 'snap1',
|
||||
'name': 'snap1',
|
||||
'volume_name': 'vol1'
|
||||
}
|
||||
NEW_VOL_SIZE = 2
|
||||
ISCSI_TARGET_NAME = 'iscsi_target_name'
|
||||
ISCSI_TARGET_STATUS = 'Target 1: ' + ISCSI_TARGET_NAME
|
||||
|
||||
|
||||
class TestNexentaEdgeISCSIDriver(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNexentaEdgeISCSIDriver, self).setUp()
|
||||
self.cfg = mock.Mock(spec=conf.Configuration)
|
||||
self.cfg.nexenta_client_address = '0.0.0.0'
|
||||
self.cfg.nexenta_rest_address = '0.0.0.0'
|
||||
self.cfg.nexenta_rest_port = 8080
|
||||
self.cfg.nexenta_rest_protocol = 'http'
|
||||
self.cfg.nexenta_iscsi_target_portal_port = 3260
|
||||
self.cfg.nexenta_rest_user = 'admin'
|
||||
self.cfg.nexenta_rest_password = 'admin'
|
||||
self.cfg.nexenta_lun_container = NEDGE_BUCKET
|
||||
self.cfg.nexenta_iscsi_service = NEDGE_SERVICE
|
||||
self.cfg.nexenta_blocksize = NEDGE_BLOCKSIZE
|
||||
self.cfg.nexenta_chunksize = NEDGE_CHUNKSIZE
|
||||
|
||||
mock_exec = mock.Mock()
|
||||
mock_exec.return_value = ('', '')
|
||||
self.driver = iscsi.NexentaEdgeISCSIDriver(execute=mock_exec,
|
||||
configuration=self.cfg)
|
||||
self.api_patcher = mock.patch('cinder.volume.drivers.nexenta.'
|
||||
'nexentaedge.jsonrpc.'
|
||||
'NexentaEdgeJSONProxy.__call__')
|
||||
self.mock_api = self.api_patcher.start()
|
||||
|
||||
self.mock_api.return_value = {
|
||||
'data': {'value': ISCSI_TARGET_STATUS}
|
||||
}
|
||||
self.driver.do_setup(context.get_admin_context())
|
||||
|
||||
self.addCleanup(self.api_patcher.stop)
|
||||
|
||||
def test_check_do_setup(self):
|
||||
self.assertEqual(ISCSI_TARGET_NAME, self.driver.target_name)
|
||||
|
||||
def test_create_volume(self):
|
||||
self.driver.create_volume(MOCK_VOL)
|
||||
self.mock_api.assert_called_with(NEDGE_URL, {
|
||||
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
|
||||
'volSizeMB': MOCK_VOL['size'] * 1024,
|
||||
'blockSize': NEDGE_BLOCKSIZE,
|
||||
'chunkSize': NEDGE_CHUNKSIZE
|
||||
})
|
||||
|
||||
def test_create_volume_fail(self):
|
||||
self.mock_api.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError, self.driver.create_volume, MOCK_VOL)
|
||||
|
||||
def test_delete_volume(self):
|
||||
self.driver.delete_volume(MOCK_VOL)
|
||||
self.mock_api.assert_called_with(NEDGE_URL, {
|
||||
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id']
|
||||
})
|
||||
|
||||
def test_delete_volume_fail(self):
|
||||
self.mock_api.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError, self.driver.delete_volume, MOCK_VOL)
|
||||
|
||||
def test_extend_volume(self):
|
||||
self.driver.extend_volume(MOCK_VOL, NEW_VOL_SIZE)
|
||||
self.mock_api.assert_called_with(NEDGE_URL + '/resize', {
|
||||
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
|
||||
'newSizeMB': NEW_VOL_SIZE * 1024
|
||||
})
|
||||
|
||||
def test_extend_volume_fail(self):
|
||||
self.mock_api.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError, self.driver.extend_volume,
|
||||
MOCK_VOL, NEW_VOL_SIZE)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
self.driver.create_snapshot(MOCK_SNAP)
|
||||
self.mock_api.assert_called_with(NEDGE_URL + '/snapshot', {
|
||||
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
|
||||
'snapName': MOCK_SNAP['id']
|
||||
})
|
||||
|
||||
def test_create_snapshot_fail(self):
|
||||
self.mock_api.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError, self.driver.create_snapshot, MOCK_SNAP)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
self.driver.delete_snapshot(MOCK_SNAP)
|
||||
self.mock_api.assert_called_with(NEDGE_URL + '/snapshot', {
|
||||
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
|
||||
'snapName': MOCK_SNAP['id']
|
||||
})
|
||||
|
||||
def test_delete_snapshot_fail(self):
|
||||
self.mock_api.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError, self.driver.delete_snapshot, MOCK_SNAP)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
self.driver.create_volume_from_snapshot(MOCK_VOL2, MOCK_SNAP)
|
||||
self.mock_api.assert_called_with(NEDGE_URL + '/snapshot/clone', {
|
||||
'objectPath': NEDGE_BUCKET + '/' + MOCK_SNAP['volume_name'],
|
||||
'clonePath': NEDGE_BUCKET + '/' + MOCK_VOL2['id'],
|
||||
'snapName': MOCK_SNAP['id']
|
||||
})
|
||||
|
||||
def test_create_volume_from_snapshot_fail(self):
|
||||
self.mock_api.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError,
|
||||
self.driver.create_volume_from_snapshot,
|
||||
MOCK_VOL2, MOCK_SNAP)
|
||||
|
||||
def test_create_cloned_volume(self):
|
||||
self.driver.create_cloned_volume(MOCK_VOL2, MOCK_VOL)
|
||||
self.mock_api.assert_called_with(NEDGE_URL, {
|
||||
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL2['id'],
|
||||
'volSizeMB': MOCK_VOL2['size'] * 1024,
|
||||
'blockSize': NEDGE_BLOCKSIZE,
|
||||
'chunkSize': NEDGE_CHUNKSIZE
|
||||
})
|
||||
|
||||
def test_create_cloned_volume_fail(self):
|
||||
self.mock_api.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError, self.driver.create_cloned_volume,
|
||||
MOCK_VOL2, MOCK_VOL)
|
0
cinder/volume/drivers/nexenta/__init__.py
Normal file
0
cinder/volume/drivers/nexenta/__init__.py
Normal file
303
cinder/volume/drivers/nexenta/nexentaedge/iscsi.py
Normal file
303
cinder/volume/drivers/nexenta/nexentaedge/iscsi.py
Normal file
@ -0,0 +1,303 @@
|
||||
# Copyright 2015 Nexenta Systems, Inc.
|
||||
# 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_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.nexenta.nexentaedge import jsonrpc
|
||||
|
||||
|
||||
nexenta_edge_opts = [
|
||||
cfg.StrOpt('nexenta_rest_address',
|
||||
default='',
|
||||
help='IP address of NexentaEdge management REST API endpoint'),
|
||||
cfg.IntOpt('nexenta_rest_port',
|
||||
default=8080,
|
||||
help='HTTP port to connect to NexentaEdge REST API endpoint'),
|
||||
cfg.StrOpt('nexenta_rest_protocol',
|
||||
default='auto',
|
||||
help='Use http or https for REST connection (default auto)'),
|
||||
cfg.IntOpt('nexenta_iscsi_target_portal_port',
|
||||
default=3260,
|
||||
help='NexentaEdge target portal port'),
|
||||
cfg.StrOpt('nexenta_rest_user',
|
||||
default='admin',
|
||||
help='User name to connect to NexentaEdge'),
|
||||
cfg.StrOpt('nexenta_rest_password',
|
||||
default='nexenta',
|
||||
help='Password to connect to NexentaEdge',
|
||||
secret=True),
|
||||
cfg.StrOpt('nexenta_lun_container',
|
||||
default='',
|
||||
help='NexentaEdge logical path of bucket for LUNs'),
|
||||
cfg.StrOpt('nexenta_iscsi_service',
|
||||
default='',
|
||||
help='NexentaEdge iSCSI service name'),
|
||||
cfg.StrOpt('nexenta_client_address',
|
||||
default='',
|
||||
help='NexentaEdge iSCSI Gateway client '
|
||||
'address for non-VIP service'),
|
||||
cfg.StrOpt('nexenta_blocksize',
|
||||
default=4096,
|
||||
help='NexentaEdge iSCSI LUN block size'),
|
||||
cfg.StrOpt('nexenta_chunksize',
|
||||
default=16384,
|
||||
help='NexentaEdge iSCSI LUN object chunk size')
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(nexenta_edge_opts)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
||||
"""Executes volume driver commands on NexentaEdge cluster.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver version.
|
||||
"""
|
||||
|
||||
VERSION = '1.0.0'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NexentaEdgeISCSIDriver, self).__init__(*args, **kwargs)
|
||||
if self.configuration:
|
||||
self.configuration.append_config_values(nexenta_edge_opts)
|
||||
self.restapi_protocol = self.configuration.nexenta_rest_protocol
|
||||
self.restapi_host = self.configuration.nexenta_rest_address
|
||||
self.restapi_port = self.configuration.nexenta_rest_port
|
||||
self.restapi_user = self.configuration.nexenta_rest_user
|
||||
self.restapi_password = self.configuration.nexenta_rest_password
|
||||
self.iscsi_service = self.configuration.nexenta_iscsi_service
|
||||
self.bucket_path = self.configuration.nexenta_lun_container
|
||||
self.blocksize = self.configuration.nexenta_blocksize
|
||||
self.chunksize = self.configuration.nexenta_chunksize
|
||||
self.cluster, self.tenant, self.bucket = self.bucket_path.split('/')
|
||||
self.bucket_url = ('clusters/' + self.cluster + '/tenants/' +
|
||||
self.tenant + '/buckets/' + self.bucket)
|
||||
self.iscsi_target_port = (self.configuration.
|
||||
nexenta_iscsi_target_portal_port)
|
||||
self.target_vip = None
|
||||
|
||||
@property
|
||||
def backend_name(self):
|
||||
backend_name = None
|
||||
if self.configuration:
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
if not backend_name:
|
||||
backend_name = self.__class__.__name__
|
||||
return backend_name
|
||||
|
||||
def do_setup(self, context):
|
||||
if self.restapi_protocol == 'auto':
|
||||
protocol, auto = 'http', True
|
||||
else:
|
||||
protocol, auto = self.restapi_protocol, False
|
||||
|
||||
try:
|
||||
self.restapi = jsonrpc.NexentaEdgeJSONProxy(
|
||||
protocol, self.restapi_host, self.restapi_port, '/',
|
||||
self.restapi_user, self.restapi_password, auto=auto)
|
||||
|
||||
rsp = self.restapi.get('service/'
|
||||
+ self.iscsi_service + '/iscsi/status')
|
||||
data_keys = rsp['data'][list(rsp['data'].keys())[0]]
|
||||
self.target_name = data_keys.split('\n', 1)[0].split(' ')[2]
|
||||
|
||||
rsp = self.restapi.get('service/' + self.iscsi_service)
|
||||
if 'X-VIPS' in rsp['data']:
|
||||
vips = json.loads(rsp['data']['X-VIPS'])
|
||||
if len(vips[0]) == 1:
|
||||
self.target_vip = vips[0][0]['ip'].split('/', 1)[0]
|
||||
else:
|
||||
self.target_vip = vips[0][1]['ip'].split('/', 1)[0]
|
||||
else:
|
||||
self.target_vip = self.configuration.safe_get(
|
||||
'nexenta_client_address')
|
||||
if not self.target_vip:
|
||||
LOG.error(_LE('No VIP configured for service %s'),
|
||||
self.iscsi_service)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_('No service VIP configured and '
|
||||
'no nexenta_client_address'))
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error verifying iSCSI service %(serv)s on '
|
||||
'host %(hst)s'), {'serv': self.iscsi_service,
|
||||
'hst': self.restapi_host})
|
||||
|
||||
def check_for_setup_error(self):
|
||||
try:
|
||||
self.restapi.get(self.bucket_url + '/objects/')
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error verifying LUN container %(bkt)s'),
|
||||
{'bkt': self.bucket_path})
|
||||
|
||||
def _get_lun_number(self, volname):
|
||||
try:
|
||||
rsp = self.restapi.put(
|
||||
'service/' + self.iscsi_service + '/iscsi/number',
|
||||
{
|
||||
'objectPath': self.bucket_path + '/' + volname
|
||||
})
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error retrieving LUN %(vol)s number'),
|
||||
{'vol': volname})
|
||||
|
||||
return rsp['data']
|
||||
|
||||
def _get_target_address(self, volname):
|
||||
return self.target_vip
|
||||
|
||||
def _get_provider_location(self, volume):
|
||||
return '%(host)s:%(port)s,1 %(name)s %(number)s' % {
|
||||
'host': self._get_target_address(volume['name']),
|
||||
'port': self.iscsi_target_port,
|
||||
'name': self.target_name,
|
||||
'number': self._get_lun_number(volume['name'])
|
||||
}
|
||||
|
||||
def create_volume(self, volume):
|
||||
try:
|
||||
self.restapi.post('service/' + self.iscsi_service + '/iscsi', {
|
||||
'objectPath': self.bucket_path + '/' + volume['name'],
|
||||
'volSizeMB': int(volume['size']) * units.Ki,
|
||||
'blockSize': self.blocksize,
|
||||
'chunkSize': self.chunksize
|
||||
})
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error creating volume'))
|
||||
|
||||
def delete_volume(self, volume):
|
||||
try:
|
||||
self.restapi.delete('service/' + self.iscsi_service +
|
||||
'/iscsi', {'objectPath': self.bucket_path +
|
||||
'/' + volume['name']})
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error deleting volume'))
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
try:
|
||||
self.restapi.put('service/' + self.iscsi_service + '/iscsi/resize',
|
||||
{'objectPath': self.bucket_path +
|
||||
'/' + volume['name'],
|
||||
'newSizeMB': new_size * units.Ki})
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error extending volume'))
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
try:
|
||||
self.restapi.put(
|
||||
'service/' + self.iscsi_service + '/iscsi/snapshot/clone',
|
||||
{
|
||||
'objectPath': self.bucket_path + '/' +
|
||||
snapshot['volume_name'],
|
||||
'clonePath': self.bucket_path + '/' + volume['name'],
|
||||
'snapName': snapshot['name']
|
||||
})
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error cloning volume'))
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
try:
|
||||
self.restapi.post(
|
||||
'service/' + self.iscsi_service + '/iscsi/snapshot',
|
||||
{
|
||||
'objectPath': self.bucket_path + '/' +
|
||||
snapshot['volume_name'],
|
||||
'snapName': snapshot['name']
|
||||
})
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error creating snapshot'))
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
try:
|
||||
self.restapi.delete(
|
||||
'service/' + self.iscsi_service + '/iscsi/snapshot',
|
||||
{
|
||||
'objectPath': self.bucket_path + '/' +
|
||||
snapshot['volume_name'],
|
||||
'snapName': snapshot['name']
|
||||
})
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error deleting snapshot'))
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
vol_url = (self.bucket_url + '/objects/' +
|
||||
src_vref['name'] + '/clone')
|
||||
clone_body = {
|
||||
'tenant_name': self.tenant,
|
||||
'bucket_name': self.bucket,
|
||||
'object_name': volume['name']
|
||||
}
|
||||
try:
|
||||
self.restapi.post(vol_url, clone_body)
|
||||
self.restapi.post('service/' + self.iscsi_service + '/iscsi', {
|
||||
'objectPath': self.bucket_path + '/' + volume['name'],
|
||||
'volSizeMB': int(src_vref['size']) * units.Ki,
|
||||
'blockSize': self.blocksize,
|
||||
'chunkSize': self.chunksize
|
||||
})
|
||||
except exception.VolumeBackendAPIException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error creating cloned volume'))
|
||||
|
||||
def create_export(self, context, volume, connector=None):
|
||||
return {'provider_location': self._get_provider_location(volume)}
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def local_path(self, volume):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
location_info = '%(driver)s:%(host)s:%(bucket)s' % {
|
||||
'driver': self.__class__.__name__,
|
||||
'host': self._get_target_address(None),
|
||||
'bucket': self.bucket_path
|
||||
}
|
||||
return {
|
||||
'vendor_name': 'Nexenta',
|
||||
'driver_version': self.VERSION,
|
||||
'storage_protocol': 'iSCSI',
|
||||
'reserved_percentage': 0,
|
||||
'total_capacity_gb': 'unknown',
|
||||
'free_capacity_gb': 'unknown',
|
||||
'QoS_support': False,
|
||||
'volume_backend_name': self.backend_name,
|
||||
'location_info': location_info,
|
||||
'iscsi_target_portal_port': self.iscsi_target_port,
|
||||
'restapi_url': self.restapi.url
|
||||
}
|
100
cinder/volume/drivers/nexenta/nexentaedge/jsonrpc.py
Normal file
100
cinder/volume/drivers/nexenta/nexentaedge/jsonrpc.py
Normal file
@ -0,0 +1,100 @@
|
||||
# Copyright 2015 Nexenta Systems, Inc.
|
||||
# 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
|
||||
import requests
|
||||
import socket
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.utils import retry
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
socket.setdefaulttimeout(100)
|
||||
|
||||
|
||||
class NexentaEdgeJSONProxy(object):
|
||||
|
||||
retry_exc_tuple = (
|
||||
requests.exceptions.ConnectionError,
|
||||
requests.exceptions.ConnectTimeout
|
||||
)
|
||||
|
||||
def __init__(self, protocol, host, port, path, user, password, auto=False,
|
||||
method=None):
|
||||
self.protocol = protocol.lower()
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.path = path
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.auto = auto
|
||||
self.method = method
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return '%s://%s:%s%s' % (self.protocol,
|
||||
self.host, self.port, self.path)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if not self.method:
|
||||
method = name
|
||||
else:
|
||||
raise exception.VolumeDriverException(
|
||||
_("Wrong resource call syntax"))
|
||||
return NexentaEdgeJSONProxy(
|
||||
self.protocol, self.host, self.port, self.path,
|
||||
self.user, self.password, self.auto, method)
|
||||
|
||||
def __hash__(self):
|
||||
return self.url.__hash___()
|
||||
|
||||
def __repr__(self):
|
||||
return 'HTTP JSON proxy: %s' % self.url
|
||||
|
||||
@retry(retry_exc_tuple, interval=1, retries=6)
|
||||
def __call__(self, *args):
|
||||
self.path += args[0]
|
||||
data = None
|
||||
if len(args) > 1:
|
||||
data = json.dumps(args[1])
|
||||
|
||||
auth = ('%s:%s' % (self.user, self.password)).encode('base64')[:-1]
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Basic %s' % auth
|
||||
}
|
||||
|
||||
LOG.debug('Sending JSON data: %s', self.url)
|
||||
|
||||
if self.method == 'get':
|
||||
req = requests.get(self.url, headers=headers)
|
||||
if self.method == 'post':
|
||||
req = requests.post(self.url, data=data, headers=headers)
|
||||
if self.method == 'put':
|
||||
req = requests.put(self.url, data=data, headers=headers)
|
||||
if self.method == 'delete':
|
||||
req = requests.delete(self.url, data=data, headers=headers)
|
||||
|
||||
rsp = req.json()
|
||||
req.close()
|
||||
|
||||
LOG.debug('Got response: %s', rsp)
|
||||
if rsp.get('response') is None:
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_('Error response: %s') % rsp)
|
||||
return rsp.get('response')
|
@ -92,6 +92,7 @@ cinder.tests.unit.test_misc
|
||||
cinder.tests.unit.test_netapp
|
||||
cinder.tests.unit.test_netapp_nfs
|
||||
cinder.tests.unit.test_netapp_ssc
|
||||
cinder.tests.unit.test_nexenta_edge
|
||||
cinder.tests.unit.test_nfs
|
||||
cinder.tests.unit.test_nimble
|
||||
cinder.tests.unit.test_prophetstor_dpl
|
||||
|
Loading…
Reference in New Issue
Block a user