Revise Synology DSM storage driver
This patch addressed last comments from https://review.openstack.org/#/c/335870/, including: 1. Add an exception message for SynoAuthError 2. Change time.sleep to eventlet.sleep 3. Report reserved_percentage from cinder.conf 4. Report provisioned_capacity_gb and max_over_subscription_ratio in update_volume_stats Closes-Bug: #1602942 Co-Authored-By: Taylor Huang<taylorh@synology.com> Change-Id: I0ab0b8ab651830412bad87a0fe5d9ac8a601aacb
This commit is contained in:
parent
15d2229d72
commit
5564f2277d
|
@ -1285,7 +1285,7 @@ class SynoAPIHTTPError(CinderException):
|
|||
|
||||
|
||||
class SynoAuthError(CinderException):
|
||||
pass
|
||||
message = _("Synology driver authentication failed: %(reason)s.")
|
||||
|
||||
|
||||
class SynoLUNNotExist(CinderException):
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"""Tests for the Synology iSCSI volume driver."""
|
||||
|
||||
import copy
|
||||
import math
|
||||
|
||||
import mock
|
||||
from oslo_utils import units
|
||||
|
@ -109,6 +110,7 @@ POOL_INFO = {
|
|||
'readonly': False,
|
||||
'fs_type': 'ext4',
|
||||
'location': 'internal',
|
||||
'eppool_used_byte': '139177984',
|
||||
'size_total_byte': '487262806016',
|
||||
'volume_id': 1,
|
||||
'size_free_byte': '486521139200',
|
||||
|
@ -367,6 +369,8 @@ class SynoCommonTestCase(test.TestCase):
|
|||
config.pool_name = POOL_NAME
|
||||
config.chap_username = 'abcd'
|
||||
config.chap_password = 'qwerty'
|
||||
config.reserved_percentage = 0
|
||||
config.max_over_subscription_ratio = 20
|
||||
|
||||
return config
|
||||
|
||||
|
@ -457,13 +461,58 @@ class SynoCommonTestCase(test.TestCase):
|
|||
result = self.common._get_pool_size()
|
||||
|
||||
self.assertEqual((int(int(POOL_INFO['size_free_byte']) / units.Gi),
|
||||
int(int(POOL_INFO['size_total_byte']) / units.Gi)),
|
||||
int(int(POOL_INFO['size_total_byte']) / units.Gi),
|
||||
math.ceil((float(POOL_INFO['size_total_byte']) -
|
||||
float(POOL_INFO['size_free_byte']) -
|
||||
float(POOL_INFO['eppool_used_byte'])) /
|
||||
units.Gi)),
|
||||
result)
|
||||
|
||||
del pool_info['size_free_byte']
|
||||
self.assertRaises(exception.MalformedResponse,
|
||||
self.common._get_pool_size)
|
||||
|
||||
def test__get_pool_lun_provisioned_size(self):
|
||||
out = {
|
||||
'data': {
|
||||
'luns': [{
|
||||
'lun_id': 1,
|
||||
'location': '/' + POOL_NAME,
|
||||
'size': 5368709120
|
||||
}, {
|
||||
'lun_id': 2,
|
||||
'location': '/' + POOL_NAME,
|
||||
'size': 3221225472
|
||||
}]
|
||||
},
|
||||
'success': True
|
||||
}
|
||||
self.common.exec_webapi = mock.Mock(return_value=out)
|
||||
|
||||
result = self.common._get_pool_lun_provisioned_size()
|
||||
(self.common.exec_webapi.
|
||||
assert_called_with('SYNO.Core.ISCSI.LUN',
|
||||
'list',
|
||||
mock.ANY,
|
||||
location='/' + POOL_NAME))
|
||||
self.assertEqual(int(math.ceil(float(5368709120 + 3221225472) /
|
||||
units.Gi)),
|
||||
result)
|
||||
|
||||
def test__get_pool_lun_provisioned_size_error(self):
|
||||
out = {
|
||||
'data': {},
|
||||
'success': True
|
||||
}
|
||||
self.common.exec_webapi = mock.Mock(return_value=out)
|
||||
|
||||
self.assertRaises(exception.MalformedResponse,
|
||||
self.common._get_pool_lun_provisioned_size)
|
||||
|
||||
self.conf.pool_name = ''
|
||||
self.assertRaises(exception.InvalidConfigurationValue,
|
||||
self.common._get_pool_lun_provisioned_size)
|
||||
|
||||
def test__get_lun_info(self):
|
||||
out = {
|
||||
'data': {
|
||||
|
@ -847,7 +896,7 @@ class SynoCommonTestCase(test.TestCase):
|
|||
VOLUME['name'],
|
||||
NEW_VOLUME['name'])
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
@mock.patch('eventlet.sleep')
|
||||
def test__check_lun_status_normal(self, _patched_sleep):
|
||||
self.common._get_lun_status = (
|
||||
mock.Mock(side_effect=[
|
||||
|
@ -869,7 +918,7 @@ class SynoCommonTestCase(test.TestCase):
|
|||
self.common._check_lun_status_normal,
|
||||
VOLUME['name'])
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
@mock.patch('eventlet.sleep')
|
||||
def test__check_snapshot_status_healthy(self, _patched_sleep):
|
||||
self.common._get_snapshot_status = (
|
||||
mock.Mock(side_effect=[
|
||||
|
@ -1180,7 +1229,9 @@ class SynoCommonTestCase(test.TestCase):
|
|||
self.assertIsNone(result)
|
||||
|
||||
def test_update_volume_stats(self):
|
||||
self.common._get_pool_size = mock.Mock(return_value=(10, 100))
|
||||
self.common._get_pool_size = mock.Mock(return_value=(10, 100, 50))
|
||||
self.common._get_pool_lun_provisioned_size = (
|
||||
mock.Mock(return_value=300))
|
||||
|
||||
data = {
|
||||
'volume_backend_name': 'DiskStation',
|
||||
|
@ -1193,6 +1244,8 @@ class SynoCommonTestCase(test.TestCase):
|
|||
'reserved_percentage': 0,
|
||||
'free_capacity_gb': 10,
|
||||
'total_capacity_gb': 100,
|
||||
'provisioned_capacity_gb': 350,
|
||||
'max_over_subscription_ratio': 20,
|
||||
'iscsi_ip_address': '10.0.0.1',
|
||||
'pool_name': 'volume1',
|
||||
'backend_info':
|
||||
|
@ -1400,7 +1453,7 @@ class SynoCommonTestCase(test.TestCase):
|
|||
SNAPSHOT)
|
||||
|
||||
self.common.exec_webapi = (
|
||||
mock.Mock(side_effect=exception.SynoAuthError))
|
||||
mock.Mock(side_effect=exception.SynoAuthError(reason='dont care')))
|
||||
|
||||
self.assertRaises(exception.SynoAuthError,
|
||||
self.common.create_snapshot,
|
||||
|
|
|
@ -17,14 +17,15 @@ import base64
|
|||
import functools
|
||||
from hashlib import md5
|
||||
import json
|
||||
import math
|
||||
from random import randint
|
||||
import string
|
||||
import time
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Cipher import PKCS1_v1_5
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto import Random
|
||||
import eventlet
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
|
@ -162,7 +163,7 @@ class Session(object):
|
|||
if one_time_pass and not device_id:
|
||||
self._did = result['data']['did']
|
||||
else:
|
||||
raise exception.SynoAuthError(_('Login failed.'))
|
||||
raise exception.SynoAuthError(reason=_('Login failed.'))
|
||||
|
||||
def _random_AES_passpharse(self, length):
|
||||
available = ('0123456789'
|
||||
|
@ -369,7 +370,8 @@ class APIRequest(object):
|
|||
|
||||
if ('error' in result and 'code' in result["error"]
|
||||
and result['error']['code'] == 105):
|
||||
raise exception.SynoAuthError(_('Session might have expired.'))
|
||||
raise exception.SynoAuthError(reason=_('Session might have '
|
||||
'expired.'))
|
||||
|
||||
return result
|
||||
|
||||
|
@ -462,8 +464,39 @@ class SynoCommon(object):
|
|||
|
||||
free_capacity_gb = int(int(info['size_free_byte']) / units.Gi)
|
||||
total_capacity_gb = int(int(info['size_total_byte']) / units.Gi)
|
||||
other_user_data_gb = int(math.ceil((float(info['size_total_byte']) -
|
||||
float(info['size_free_byte']) -
|
||||
float(info['eppool_used_byte'])) /
|
||||
units.Gi))
|
||||
|
||||
return free_capacity_gb, total_capacity_gb
|
||||
return free_capacity_gb, total_capacity_gb, other_user_data_gb
|
||||
|
||||
def _get_pool_lun_provisioned_size(self):
|
||||
pool_name = self.config.pool_name
|
||||
if not pool_name:
|
||||
raise exception.InvalidConfigurationValue(option='pool_name',
|
||||
value=pool_name)
|
||||
try:
|
||||
out = self.exec_webapi('SYNO.Core.ISCSI.LUN',
|
||||
'list',
|
||||
1,
|
||||
location='/' + pool_name)
|
||||
|
||||
self.check_response(out)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Failed to _get_pool_lun_provisioned_size.'))
|
||||
|
||||
if not self.check_value_valid(out, ['data', 'luns'], list):
|
||||
raise exception.MalformedResponse(
|
||||
cmd='_get_pool_lun_provisioned_size',
|
||||
reason=_('no data found'))
|
||||
|
||||
size = 0
|
||||
for lun in out['data']['luns']:
|
||||
size += lun['size']
|
||||
|
||||
return int(math.ceil(float(size) / units.Gi))
|
||||
|
||||
def _get_lun_info(self, lun_name, additional=None):
|
||||
if not lun_name:
|
||||
|
@ -503,7 +536,7 @@ class SynoCommon(object):
|
|||
LOG.exception(_LE('Failed to _get_lun_uuid. [%s]'), lun_name)
|
||||
|
||||
if not self.check_value_valid(lun_info, ['uuid'], string_types):
|
||||
raise exception.MalformedResponse(cmd='_get_lun_info',
|
||||
raise exception.MalformedResponse(cmd='_get_lun_uuid',
|
||||
reason=_('uuid not found'))
|
||||
|
||||
return lun_info['uuid']
|
||||
|
@ -719,7 +752,7 @@ class SynoCommon(object):
|
|||
status, locked = self._get_lun_status(volume_name)
|
||||
if not locked:
|
||||
break
|
||||
time.sleep(2)
|
||||
eventlet.sleep(2)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Failed to get lun status. [%s]'),
|
||||
|
@ -737,7 +770,7 @@ class SynoCommon(object):
|
|||
status, locked = self._get_snapshot_status(snapshot_uuid)
|
||||
if not locked:
|
||||
break
|
||||
time.sleep(2)
|
||||
eventlet.sleep(2)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Failed to get snapshot status. [%s]'),
|
||||
|
@ -760,7 +793,7 @@ class SynoCommon(object):
|
|||
LUN_NO_SUCH_SNAPSHOT = 18990532
|
||||
|
||||
if not self.check_value_valid(out, ['error', 'code'], int):
|
||||
raise exception.MalformedResponse(cmd='exec_webapi',
|
||||
raise exception.MalformedResponse(cmd='_check_iscsi_response',
|
||||
reason=_('no error code found'))
|
||||
|
||||
code = out['error']['code']
|
||||
|
@ -785,7 +818,7 @@ class SynoCommon(object):
|
|||
def _check_ds_pool_status(self):
|
||||
pool_info = self._get_pool_info()
|
||||
if not self.check_value_valid(pool_info, ['readonly'], bool):
|
||||
raise exception.MalformedResponse(cmd='check_for_setup_error',
|
||||
raise exception.MalformedResponse(cmd='_check_ds_pool_status',
|
||||
reason=_('no readonly found'))
|
||||
|
||||
if pool_info['readonly']:
|
||||
|
@ -955,9 +988,22 @@ class SynoCommon(object):
|
|||
self._check_ds_ability()
|
||||
|
||||
def update_volume_stats(self):
|
||||
"""Update volume statistics."""
|
||||
"""Update volume statistics.
|
||||
|
||||
free_capacity_gb, total_capacity_gb = self._get_pool_size()
|
||||
Three kinds of data are stored on the Synology backend pool:
|
||||
1. Thin volumes (LUNs on the pool),
|
||||
2. Thick volumes (LUNs on the pool),
|
||||
3. Other user data.
|
||||
|
||||
other_user_data_gb is the size of the 3rd one.
|
||||
lun_provisioned_gb is the summation of all thin/thick volume
|
||||
provisioned size.
|
||||
|
||||
Only thin type is available for Cinder volumes.
|
||||
"""
|
||||
|
||||
free_gb, total_gb, other_user_data_gb = self._get_pool_size()
|
||||
lun_provisioned_gb = self._get_pool_lun_provisioned_size()
|
||||
|
||||
data = {}
|
||||
data['volume_backend_name'] = self.volume_backend_name
|
||||
|
@ -967,10 +1013,14 @@ class SynoCommon(object):
|
|||
data['QoS_support'] = False
|
||||
data['thin_provisioning_support'] = True
|
||||
data['thick_provisioning_support'] = False
|
||||
data['reserved_percentage'] = 0
|
||||
data['reserved_percentage'] = self.config.reserved_percentage
|
||||
|
||||
data['free_capacity_gb'] = free_capacity_gb
|
||||
data['total_capacity_gb'] = total_capacity_gb
|
||||
data['free_capacity_gb'] = free_gb
|
||||
data['total_capacity_gb'] = total_gb
|
||||
data['provisioned_capacity_gb'] = (lun_provisioned_gb +
|
||||
other_user_data_gb)
|
||||
data['max_over_subscription_ratio'] = (self.config.
|
||||
max_over_subscription_ratio)
|
||||
|
||||
data['iscsi_ip_address'] = self.config.iscsi_ip_address
|
||||
data['pool_name'] = self.config.pool_name
|
||||
|
@ -1091,7 +1141,7 @@ class SynoCommon(object):
|
|||
if not self.check_value_valid(resp,
|
||||
['data', 'snapshot_uuid'],
|
||||
string_types):
|
||||
raise exception.MalformedResponse(cmd='take_snapshot',
|
||||
raise exception.MalformedResponse(cmd='create_snapshot',
|
||||
reason=_('uuid not found'))
|
||||
|
||||
snapshot_uuid = resp['data']['snapshot_uuid']
|
||||
|
|
Loading…
Reference in New Issue