Merge "EMC Isilon Driver Support For Extend Share"

This commit is contained in:
Jenkins 2015-12-24 22:59:52 +00:00 committed by Gerrit Code Review
commit 6f4a3090b7
4 changed files with 257 additions and 10 deletions

View File

@ -20,6 +20,7 @@ import os
from oslo_config import cfg
from oslo_log import log
from oslo_utils import units
import six
from manila import exception
@ -69,6 +70,12 @@ class IsilonStorageConnection(base.StorageConnection):
{'proto': share['share_proto']})
LOG.error(message)
raise exception.InvalidShare(message=message)
# apply directory quota based on share size
max_share_size = share['size'] * units.Gi
self._isilon_api.quota_create(
self._get_container_path(share), 'directory', max_share_size)
return location
def create_share_from_snapshot(self, context, share, snapshot,
@ -161,13 +168,15 @@ class IsilonStorageConnection(base.StorageConnection):
"""Is called to remove snapshot."""
self._isilon_api.delete_snapshot(snapshot['name'])
def extend_share(self, share, new_size, share_server):
"""Is called to extend share."""
raise NotImplementedError()
def ensure_share(self, context, share, share_server):
"""Invoked to ensure that share is exported."""
def extend_share(self, share, new_size, share_server=None):
"""Extends a share."""
new_quota_size = new_size * units.Gi
self._isilon_api.quota_set(
self._get_container_path(share), 'directory', new_quota_size)
def allow_access(self, context, share, access, share_server):
"""Allow access to the share."""

View File

@ -18,6 +18,9 @@ from oslo_serialization import jsonutils
import requests
import six
from manila import exception
from manila.i18n import _
LOG = log.getLogger(__name__)
@ -208,9 +211,65 @@ class IsilonApi(object):
.format(self.host_url, snapshot_name))
response.raise_for_status()
def request(self, method, url, headers=None, data=None):
def quota_create(self, path, quota_type, size):
thresholds = {'hard': size}
data = {
'path': path,
'type': quota_type,
'include_snapshots': False,
'thresholds_include_overhead': False,
'enforced': True,
'thresholds': thresholds,
}
response = self.request(
'POST', '{0}/platform/1/quota/quotas'.format(self.host_url),
data=data)
response.raise_for_status()
def quota_get(self, path, quota_type):
response = self.request(
'GET',
'{0}/platform/1/quota/quotas?path={1}'.format(self.host_url, path),
)
if response.status_code == 404:
return None
elif response.status_code != 200:
response.raise_for_status()
json = response.json()
len_returned_quotas = len(json['quotas'])
if len_returned_quotas == 0:
return None
elif len_returned_quotas == 1:
return json['quotas'][0]
else:
message = (_('Greater than one quota returned when querying '
'quotas associated with share path: %(path)s .') %
{'path': path})
raise exception.ShareBackendException(msg=message)
def quota_modify_size(self, quota_id, new_size):
data = {'thresholds': {'hard': new_size}}
response = self.request(
'PUT',
'{0}/platform/1/quota/quotas/{1}'.format(self.host_url, quota_id),
data=data
)
response.raise_for_status()
def quota_set(self, path, quota_type, size):
"""Sets a quota of the given type and size on the given path."""
quota_json = self.quota_get(path, quota_type)
if quota_json is None:
self.quota_create(path, quota_type, size)
else:
# quota already exists, modify it's size
quota_id = quota_json['id']
self.quota_modify_size(quota_id, size)
def request(self, method, url, headers=None, data=None, params=None):
if data is not None:
data = jsonutils.dumps(data)
r = self.session.request(method, url, headers=headers, data=data,
verify=self.verify_ssl_cert)
verify=self.verify_ssl_cert, params=params)
return r

View File

@ -15,6 +15,7 @@
import mock
from oslo_log import log
from oslo_utils import units
from manila import exception
from manila.share.drivers.emc.plugins.isilon import isilon
@ -312,7 +313,7 @@ class IsilonTest(test.TestCase):
self.assertFalse(self._mock_isilon_api.create_nfs_export.called)
# create the share
share = {"name": self.SHARE_NAME, "share_proto": 'NFS'}
share = {"name": self.SHARE_NAME, "share_proto": 'NFS', "size": 8}
location = self.storage_connection.create_share(self.mock_context,
share, None)
@ -322,12 +323,16 @@ class IsilonTest(test.TestCase):
self._mock_isilon_api.create_directory.assert_called_with(share_path)
self._mock_isilon_api.create_nfs_export.assert_called_with(share_path)
# verify directory quota call made
self._mock_isilon_api.quota_create.assert_called_with(
share_path, 'directory', 8 * units.Gi)
def test_create_share_cifs(self):
self.assertFalse(self._mock_isilon_api.create_directory.called)
self.assertFalse(self._mock_isilon_api.create_smb_share.called)
# create the share
share = {"name": self.SHARE_NAME, "share_proto": 'CIFS'}
share = {"name": self.SHARE_NAME, "share_proto": 'CIFS', "size": 8}
location = self.storage_connection.create_share(self.mock_context,
share, None)
@ -339,6 +344,10 @@ class IsilonTest(test.TestCase):
self._mock_isilon_api.create_smb_share.assert_called_once_with(
self.SHARE_NAME, self.SHARE_DIR)
# verify directory quota call made
self._mock_isilon_api.quota_create.assert_called_with(
self.SHARE_DIR, 'directory', 8 * units.Gi)
def test_create_share_invalid_share_protocol(self):
share = {"name": self.SHARE_NAME, "share_proto": 'FOO_PROTOCOL'}
@ -378,7 +387,7 @@ class IsilonTest(test.TestCase):
# execute method under test
snapshot = {'name': snapshot_name, 'share_name': snapshot_path}
share = {"name": self.SHARE_NAME, "share_proto": 'NFS'}
share = {"name": self.SHARE_NAME, "share_proto": 'NFS', 'size': 5}
location = self.storage_connection.create_share_from_snapshot(
self.mock_context, share, snapshot, None)
@ -393,6 +402,10 @@ class IsilonTest(test.TestCase):
self.ISILON_ADDR, self.SHARE_DIR)
self.assertEqual(expected_location, location)
# verify directory quota call made
self._mock_isilon_api.quota_create.assert_called_with(
self.SHARE_DIR, 'directory', 5 * units.Gi)
def test_create_share_from_snapshot_cifs(self):
# assertions
self.assertFalse(self._mock_isilon_api.create_smb_share.called)
@ -404,7 +417,7 @@ class IsilonTest(test.TestCase):
# execute method under test
snapshot = {'name': snapshot_name, 'share_name': snapshot_path}
share = {"name": new_share_name, "share_proto": 'CIFS'}
share = {"name": new_share_name, "share_proto": 'CIFS', "size": 2}
location = self.storage_connection.create_share_from_snapshot(
self.mock_context, share, snapshot, None)
@ -417,6 +430,11 @@ class IsilonTest(test.TestCase):
new_share_name)
self.assertEqual(expected_location, location)
# verify directory quota call made
expected_share_path = '{0}/{1}'.format(self.ROOT_DIR, new_share_name)
self._mock_isilon_api.quota_create.assert_called_with(
expected_share_path, 'directory', 2 * units.Gi)
def test_delete_share_nfs(self):
share = {"name": self.SHARE_NAME, "share_proto": 'NFS'}
fake_share_num = 42
@ -553,3 +571,21 @@ class IsilonTest(test.TestCase):
num = self.storage_connection.get_network_allocations_number()
self.assertEqual(0, num)
def test_extend_share(self):
quota_id = 'abcdef'
new_share_size = 8
share = {
"name": self.SHARE_NAME,
"share_proto": 'NFS',
"size": new_share_size
}
self._mock_isilon_api.quota_get.return_value = {'id': quota_id}
self.assertFalse(self._mock_isilon_api.quota_set.called)
self.storage_connection.extend_share(share, new_share_size)
share_path = '{0}/{1}'.format(self.ROOT_DIR, self.SHARE_NAME)
expected_quota_size = new_share_size * units.Gi
self._mock_isilon_api.quota_set.assert_called_once_with(
share_path, 'directory', expected_quota_size)

View File

@ -469,6 +469,149 @@ class IsilonApiTest(test.TestCase):
self.assertRaises(requests.exceptions.HTTPError,
self.isilon_api.delete_snapshot, "my_snapshot")
@requests_mock.mock()
def test_quota_create(self, m):
quota_path = '/ifs/manila/test'
quota_size = 256
self.assertEqual(0, len(m.request_history))
m.post(self._mock_url + '/platform/1/quota/quotas', status_code=201)
self.isilon_api.quota_create(quota_path, 'directory', quota_size)
self.assertEqual(1, len(m.request_history))
expected_request_json = {
'path': quota_path,
'type': 'directory',
'include_snapshots': False,
'thresholds_include_overhead': False,
'enforced': True,
'thresholds': {'hard': quota_size},
}
call_body = m.request_history[0].body
self.assertEqual(expected_request_json, json.loads(call_body))
@requests_mock.mock()
def test_quota_create__path_does_not_exist(self, m):
quota_path = '/ifs/test2'
self.assertEqual(0, len(m.request_history))
m.post(self._mock_url + '/platform/1/quota/quotas', status_code=400)
self.assertRaises(
requests.exceptions.HTTPError,
self.isilon_api.quota_create,
quota_path, 'directory', 2
)
@requests_mock.mock()
def test_quota_get(self, m):
self.assertEqual(0, len(m.request_history))
response_json = {'quotas': [{}]}
m.get(self._mock_url + '/platform/1/quota/quotas', json=response_json,
status_code=200)
quota_path = "/ifs/manila/test"
quota_type = "directory"
self.isilon_api.quota_get(quota_path, quota_type)
self.assertEqual(1, len(m.request_history))
request_query_string = m.request_history[0].qs
expected_query_string = {'path': [quota_path]}
self.assertEqual(expected_query_string, request_query_string)
@requests_mock.mock()
def test_quota_get__path_does_not_exist(self, m):
self.assertEqual(0, len(m.request_history))
m.get(self._mock_url + '/platform/1/quota/quotas', status_code=404)
response = self.isilon_api.quota_get(
'/ifs/does_not_exist', 'directory')
self.assertIsNone(response)
@requests_mock.mock()
def test_quota_modify(self, m):
self.assertEqual(0, len(m.request_history))
quota_id = "ADEF1G"
new_size = 1024
m.put('{0}/platform/1/quota/quotas/{1}'.format(
self._mock_url, quota_id), status_code=204)
self.isilon_api.quota_modify_size(quota_id, new_size)
self.assertEqual(1, len(m.request_history))
expected_request_body = {'thresholds': {'hard': new_size}}
request_body = m.request_history[0].body
self.assertEqual(expected_request_body, json.loads(request_body))
@requests_mock.mock()
def test_quota_modify__given_id_does_not_exist(self, m):
quota_id = 'ADE2F'
m.put('{0}/platform/1/quota/quotas/{1}'.format(
self._mock_url, quota_id), status_code=404)
self.assertRaises(
requests.exceptions.HTTPError,
self.isilon_api.quota_modify_size,
quota_id, 1024
)
@requests_mock.mock()
def test_quota_set__quota_already_exists(self, m):
self.assertEqual(0, len(m.request_history))
quota_path = '/ifs/manila/test'
quota_type = 'directory'
quota_size = 256
quota_id = 'AFE2C'
m.get('{0}/platform/1/quota/quotas'.format(
self._mock_url), json={'quotas': [{'id': quota_id}]},
status_code=200)
m.put(
'{0}/platform/1/quota/quotas/{1}'.format(self._mock_url, quota_id),
status_code=204
)
self.isilon_api.quota_set(quota_path, quota_type, quota_size)
expected_quota_modify_json = {'thresholds': {'hard': quota_size}}
quota_put_json = json.loads(m.request_history[1].body)
self.assertEqual(expected_quota_modify_json, quota_put_json)
@requests_mock.mock()
def test_quota_set__quota_does_not_already_exist(self, m):
self.assertEqual(0, len(m.request_history))
m.get('{0}/platform/1/quota/quotas'.format(
self._mock_url), status_code=404)
m.post('{0}/platform/1/quota/quotas'.format(self._mock_url),
status_code=201)
quota_path = '/ifs/manila/test'
quota_type = 'directory'
quota_size = 256
self.isilon_api.quota_set(quota_path, quota_type, quota_size)
# verify a call is made to create a quota
expected_create_json = {
six.text_type('path'): quota_path,
six.text_type('type'): 'directory',
six.text_type('include_snapshots'): False,
six.text_type('thresholds_include_overhead'): False,
six.text_type('enforced'): True,
six.text_type('thresholds'): {six.text_type('hard'): quota_size},
}
create_request_json = json.loads(m.request_history[1].body)
self.assertEqual(expected_create_json, create_request_json)
@requests_mock.mock()
def test_quota_set__path_does_not_already_exist(self, m):
m.get(self._mock_url + '/platform/1/quota/quotas', status_code=400)
e = self.assertRaises(
requests.exceptions.HTTPError,
self.isilon_api.quota_set,
'/ifs/does_not_exist', 'directory', 2048
)
self.assertEqual(400, e.response.status_code)
def _add_create_directory_response(self, m, path, is_recursive):
url = '{0}/namespace{1}?recursive={2}'.format(
self._mock_url, path, six.text_type(is_recursive))