Merge "Remove HDS SOP driver due to lack of CI"

This commit is contained in:
Jenkins 2015-09-11 15:51:05 +00:00 committed by Gerrit Code Review
commit 88e0465214
7 changed files with 0 additions and 1034 deletions

@ -50,8 +50,6 @@ Mapping of share drivers and share features support
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+----------------------------------------+
| HDFS | ? | ? | ? | ? | ? | ? | HDFS |
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+----------------------------------------+
| HDS SoP (Scale-out-Platform) | ? | ? | ? | ? | ? | ? | HDS SoP (Scale-out-Platform) |
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+----------------------------------------+
| HP 3PAR | ? | ? | ? | ? | ? | ? | HP 3PAR |
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+----------------------------------------+
| Huawei | ? | ? | ? | ? | ? | ? | Huawei |
@ -91,8 +89,6 @@ Mapping of share drivers and share access rules support
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
| HDFS | ? | ? | ? | ? | ? | ? |
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
| HDS SoP (Scale-out-Platform) | ? | ? | ? | ? | ? | ? |
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
| HP 3PAR | ? | ? | ? | ? | ? | ? |
+----------------------------------------+--------------+------------+------------+--------------+------------+------------+
| Huawei | ? | ? | ? | ? | ? | ? |
@ -126,8 +122,6 @@ Mapping of share drivers and security services support
+----------------------------------------+------------------+-----------------+------------------+
| HDFS | ? | ? | ? |
+----------------------------------------+------------------+-----------------+------------------+
| HDS SoP (Scale-out-Platform) | ? | ? | ? |
+----------------------------------------+------------------+-----------------+------------------+
| HP 3PAR | ? | ? | ? |
+----------------------------------------+------------------+-----------------+------------------+
| Huawei | ? | ? | ? |

@ -632,10 +632,6 @@ class SSHException(ManilaException):
message = _("Exception in SSH protocol negotiation or logic.")
class SopAPIError(Invalid):
message = _("%(err)s")
class HDFSException(ManilaException):
message = _("HDFS exception occurred!")

@ -58,7 +58,6 @@ import manila.share.drivers.glusterfs.layout
import manila.share.drivers.glusterfs.layout_directory
import manila.share.drivers.glusterfs.layout_volume
import manila.share.drivers.hdfs.hdfs_native
import manila.share.drivers.hds.sop
import manila.share.drivers.hitachi.hds_hnas
import manila.share.drivers.hp.hp_3par_driver
import manila.share.drivers.huawei.huawei_nas
@ -120,7 +119,6 @@ _global_opt_lists = [
glusterfs_directory_mapped_opts,
manila.share.drivers.glusterfs.layout_volume.glusterfs_volume_mapped_opts,
manila.share.drivers.hdfs.hdfs_native.hdfs_native_share_opts,
manila.share.drivers.hds.sop.hdssop_share_opts,
manila.share.drivers.hitachi.hds_hnas.hds_hnas_opts,
manila.share.drivers.hp.hp_3par_driver.HP3PAR_OPTS,
manila.share.drivers.huawei.huawei_nas.huawei_opts,

@ -1,404 +0,0 @@
# Copyright (c) 2015 Hitachi Data Systems.
# 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.
"""
Hitachi Data Systems Scale-out-Platform Manila Driver.
"""
import base64
import socket
import time
import httplib2
from oslo_config import cfg
from oslo_log import log
from oslo_serialization import jsonutils as json
from oslo_utils import units
import six
from manila import exception
from manila.i18n import _LW
from manila.share import driver
LOG = log.getLogger(__name__)
hdssop_share_opts = [
cfg.StrOpt('hdssop_target',
help='Specifies the SOPAPI cluster VIP. '
'It is of the form https://<SOPAPI cluster VIP>.'),
cfg.StrOpt('hdssop_adminuser',
help='Specifies the sop admin user'),
cfg.StrOpt('hdssop_adminpassword',
help='Specifies the sop admin user password',
secret=True)
]
CONF = cfg.CONF
CONF.register_opts(hdssop_share_opts)
class SopShareDriver(driver.ShareDriver):
"""Execute commands relating to Shares."""
def __init__(self, *args, **kwargs):
super(SopShareDriver, self).__init__(False, *args, **kwargs)
self.configuration.append_config_values(hdssop_share_opts)
self.backend_name = self.configuration.safe_get(
'share_backend_name') or 'HDS_SOP'
self.sop_target = self.configuration.safe_get('hdssop_target')
self.sopuser = self.configuration.safe_get('hdssop_adminuser')
self.soppassword = self.configuration.safe_get('hdssop_adminpassword')
def get_sop_auth_header(self):
return (six.b('Basic ') + base64.b64encode(
six.b(self.sopuser + ':' + self.soppassword))).decode('ascii')
def _wait_for_job_completion(self, httpclient, job_uri):
"""Wait for job identified by job_uri to complete."""
count = 0
headers = dict(Authorization=self.get_sop_auth_header())
# NOTE(jasonsb): timeout logic here needs be revisited after
# load testing results are in.
while True:
if count > 300:
raise exception.SopAPIError(err=_('job timed out'))
resp_headers, resp_content = httpclient.request(job_uri, 'GET',
body='',
headers=headers)
if int(resp_headers['status']) != 200:
raise exception.SopAPIError(err=_('error getting job status'))
job = json.loads(resp_content)
if job['properties']['completion-status'] == 'ERROR':
raise exception.SopAPIError(err=_('job errored out'))
if job['properties']['completion-status'] == 'COMPLETE':
return job
time.sleep(1)
count += 1
def _add_file_system_sopapi(self, httpclient, payload):
"""Add a new filesystem via SOPAPI."""
sopuri = '/file-systems/'
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi' + sopuri
payload_json = json.dumps(payload)
resp_headers, resp_content = httpclient.request(uri, 'POST',
body=payload_json,
headers=headers)
resp_code = int(resp_headers['status'])
if resp_code == 202:
job_loc = resp_headers['location']
self._wait_for_job_completion(httpclient, job_loc)
else:
raise exception.SopAPIError(
err=(_('received error: %s') %
resp_content['messages'][0]['message']))
def _add_share_sopapi(self, httpclient, payload):
"""Add a new filesystem via SOPAPI."""
sopuri = '/shares/'
headers = dict(Authorization=self.get_sop_auth_header())
payload_json = json.dumps(payload)
uri = self.sop_target + '/sopapi' + sopuri
resp_headers, resp_content = httpclient.request(uri, 'POST',
body=payload_json,
headers=headers)
resp_code = int(resp_headers['status'])
if resp_code == 202:
job_loc = resp_headers['location']
job = self._wait_for_job_completion(httpclient, job_loc)
if job['properties']['completion-status'] == 'COMPLETE':
return job['properties']['resource-name']
else:
raise exception.SopAPIError(err=_('received error: %s') %
resp_headers['status'])
def _get_file_system_id_by_name(self, httpclient, fsname):
sopuri = '/file-systems/list?name=' + fsname
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi' + sopuri
resp_headers, resp_content = httpclient.request(uri, 'GET',
body='',
headers=headers)
response = json.loads(resp_content)
num_of_resources = 0
if int(resp_headers['status']) != 200 and 'messages' in response:
raise exception.SopAPIError(
err=(_('received error: %s') %
response['messages'][0]['message']))
resource_list = []
resource_list = response['list']
num_of_resources = len(resource_list)
if num_of_resources <= 0:
return ''
return resource_list[0]['id']
def _get_share_id_by_name(self, httpclient, share_name):
"""Look up share given the share name."""
sopuri = '/shares/list?name=' + share_name
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi' + sopuri
resp_headers, resp_content = httpclient.request(uri, 'GET',
body='',
headers=headers)
response = json.loads(resp_content)
num_of_resources = 0
if int(resp_headers['status']) != 200 and 'messages' in response:
raise exception.SopAPIError(
err=(_('received error: %s') %
response['messages'][0]['message']))
resource_list = response['list']
num_of_resources = len(resource_list)
if num_of_resources == 0:
return ''
return resource_list[0]['id']
def create_share(self, ctx, share, share_server=None):
"""Create new share on HDS Scale-out Platform."""
sharesize = int(six.text_type(share['size']))
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
if share['share_proto'] != 'NFS':
raise exception.InvalidShare(
reason=(_('Invalid NAS protocol supplied: %s.') %
share['share_proto']))
payload = {
'quota': sharesize * units.Gi,
'enabled': True,
'description': '',
'record-access-time': True,
'tags': '',
'space-hwm': 90,
'space-lwm': 70,
'name': share['id'],
}
self._add_file_system_sopapi(httpclient, payload)
payload = {
'description': '',
'type': 'NFS',
'enabled': True,
'tags': '',
'name': share['id'],
'file-system-id': self._get_file_system_id_by_name(
httpclient, share['id']),
}
return self.sop_target + ':/' + self._add_share_sopapi(
httpclient, payload)
def _delete_file_system_sopapi(self, httpclient, fs_id):
"""Delete filesystem on SOP."""
sopuri = '/file-systems/' + fs_id
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi' + sopuri
resp_headers, resp_content = httpclient.request(uri, 'DELETE',
body='',
headers=headers)
resp_code = int(resp_headers['status'])
if resp_code == 202:
job_loc = resp_headers['location']
self._wait_for_job_completion(httpclient, job_loc)
else:
raise exception.SopAPIError(err=_('received error: %s') %
resp_headers['status'])
def _delete_share_sopapi(self, httpclient, share_id):
"""Delete share on SOP."""
sopuri = '/shares/' + share_id
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi' + sopuri
resp_headers, resp_content = httpclient.request(uri, 'DELETE',
body='',
headers=headers)
resp_code = int(resp_headers['status'])
if resp_code == 202:
job_loc = resp_headers['location']
self._wait_for_job_completion(httpclient, job_loc)
else:
raise exception.SopAPIError(err=_('received error: %s') %
resp_headers['status'])
def delete_share(self, context, share, share_server=None):
"""Remove a share from Sop volume."""
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
self._delete_share_sopapi(
httpclient,
self._get_share_id_by_name(httpclient, share['id']))
self._delete_file_system_sopapi(
httpclient,
self._get_file_system_id_by_name(httpclient, share['id']))
def create_snapshot(self, context, snapshot, share_server=None):
"""Not currently supported on HDS Scale-out Platform."""
raise NotImplementedError()
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None):
"""Not currently supported on HDS Scale-out Platform."""
raise NotImplementedError()
def delete_snapshot(self, context, snapshot, share_server=None):
"""Not currently supported on HDS Scale-out Platform."""
raise NotImplementedError()
def allow_access(self, context, share, access, share_server=None):
"""Allow access to a share.
Currently only IP based access control is supported.
"""
if access['access_type'] != 'ip':
raise exception.InvalidShareAccess(
reason=_('only IP access type allowed'))
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
sop_share_id = self._get_share_id_by_name(httpclient, share['id'])
if access['access_level'] == 'rw':
access_level = True
elif access['access_level'] == 'ro':
access_level = False
else:
raise exception.InvalidShareAccess(
reason=(_('Unsupported level of access was provided - %s') %
access['access_level']))
payload = {
'action': 'add-access-rule',
'all-squash': True,
'anongid': 65534,
'anonuid': 65534,
'host-specification': access['access_to'],
'description': '',
'read-write': access_level,
'root-squash': False,
'tags': 'nfs',
'name': '%s-%s' % (share['id'], access['access_to']),
}
sopuri = '/shares/'
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi' + sopuri + sop_share_id
resp_headers, resp_content = httpclient.request(
uri, 'POST',
body=json.dumps(payload),
headers=headers)
resp_code = int(resp_headers['status'])
if resp_code == 202:
job_loc = resp_headers['location']
self._wait_for_job_completion(httpclient, job_loc)
else:
raise exception.SopAPIError(err=_('received error: %s') %
resp_headers['status'])
def deny_access(self, context, share, access, share_server=None):
"""Deny access to a share.
Currently only IP based access control is supported.
"""
if access['access_type'] != 'ip':
LOG.warn(_LW('Only ip access type allowed.'))
return
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
sop_share_id = self._get_share_id_by_name(httpclient, share['id'])
payload = {
'action': 'delete-access-rule',
'name': '%s-%s' % (share['id'], access['access_to']),
}
sopuri = '/shares/' + sop_share_id
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi' + sopuri
resp_headers, resp_content = httpclient.request(
uri, 'POST',
body=json.dumps(payload),
headers=headers)
resp_code = int(resp_headers['status'])
if resp_code == 202:
job_loc = resp_headers['location']
self._wait_for_job_completion(httpclient, job_loc)
else:
raise exception.SopAPIError(err=_('received error: %s') %
resp_headers['status'])
def check_for_setup_error(self):
"""Check for setup error.
Socket timeout set for 5 seconds to verify SOPAPI rest
interface is reachable and the credentials will allow us
to login.
"""
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi/clusters'
try:
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=5)
resp_headers, resp_content = httpclient.request(uri, 'GET',
body='',
headers=headers)
response = json.loads(resp_content)
if 'messages' in response:
soperror = _('received error: %(code)s: %(msg)s') % {
'code': response['messages'][0]['code'],
'msg': response['messages'][0]['message'],
}
raise exception.SopAPIError(err=soperror)
except socket.timeout:
raise exception.SopAPIError(
err=_('connection to SOPAPI timed out'))
def _get_sop_filesystem_stats(self):
"""Calculate cluster storage capacity and return in GiB."""
headers = dict(Authorization=self.get_sop_auth_header())
uri = self.sop_target + '/sopapi/clusters'
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
resp_headers, resp_content = httpclient.request(uri, 'GET',
body='',
headers=headers)
response = json.loads(resp_content)
if resp_content is not None:
for cluster in response['element-links']:
(resp_headers, resp_content) = httpclient.request(
cluster,
'GET',
body='',
headers=headers)
response = json.loads(resp_content)
totalspace = int(response['properties']
['total-storage-capacity']) / units.Gi
spaceavail = int(response['properties']
['total-storage-available']) / units.Gi
return (totalspace, spaceavail)
def _update_share_stats(self):
"""Retrieve stats info from SOPAPI."""
totalspace, spaceavail = self._get_sop_filesystem_stats()
data = dict(
share_backend_name=self.backend_name,
vendor_name='Hitach Data Systems',
storage_protocol='NFS',
total_capacity_gb=totalspace,
free_capacity_gb=spaceavail)
super(SopShareDriver, self)._update_share_stats(data)

@ -1,618 +0,0 @@
# Copyright (c) 2015 Hitachi Data Systems.
# 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.
"""Unit tests for the Hitachi Data Systems Scale-out Platform manila driver."""
import time
import httplib2
import mock
from oslo_config import cfg
from oslo_serialization import jsonutils as json
from oslo_utils import units
from six import moves
from manila import context
from manila import exception
from manila.share import configuration as config
from manila.share.drivers.hds import sop
from manila import test
from manila.tests import fake_share
CONF = cfg.CONF
fake_authorization = {'Authorization': u'Basic ZmFrZXVzZXI6ZmFrZXBhc3N3b3Jk'}
class SopShareDriverTestCase(test.TestCase):
"""Tests SopShareDriver."""
def setUp(self):
super(SopShareDriverTestCase, self).setUp()
self._context = context.get_admin_context()
self.server = {
'instance_id': 'fake_instance_id',
'ip': 'fake_ip',
'username': 'fake_username',
'password': 'fake_password',
'pk_path': 'fake_pk_path',
'backend_details': {
'ip': '1.2.3.4',
'instance_id': 'fake',
},
}
CONF.set_default('hdssop_target', 'https://1.2.3.4')
CONF.set_default('hdssop_adminuser', 'fakeuser')
CONF.set_default('hdssop_adminpassword', 'fakepassword')
CONF.set_default('driver_handles_share_servers', False)
self.fake_conf = config.Configuration(None)
self._driver = sop.SopShareDriver(configuration=self.fake_conf)
self.share = fake_share.fake_share(share_proto='NFS')
self._driver.share_backend_name = 'HDS_SOP'
self.mock_object(time, 'sleep')
def test_add_file_system_sopapi(self):
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
httpretval = ({'status': '202',
'content-length': '0',
'x-sopapi-version': '1.0.0',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'location': 'https://1.2.3.4/sopapi/jobs/fakeuuid',
'date': 'Tue, 20 Jan 2015 22:41:29 GMT'}, '')
self.mock_object(httpclient, 'request',
mock.Mock(return_value=httpretval))
self.mock_object(self._driver, '_wait_for_job_completion', mock.Mock())
fakepayload1 = {
'quota': 145 * units.Gi,
'enabled': True,
'description': '',
'record-access-time': True,
'tags': '',
'space-hwm': 90,
'space-lwm': 70,
'name': 'fakeid',
}
fsadd = self._driver._add_file_system_sopapi(httpclient, fakepayload1)
self.assertIsNone(fsadd)
httpclient.request.assert_called_once_with(
'https://' +
self.server['backend_details']['ip'] +
'/sopapi/file-systems/',
'POST',
body=json.dumps(fakepayload1),
headers=fake_authorization)
self._driver._wait_for_job_completion.assert_called_once_with(
httpclient,
'https://1.2.3.4/sopapi/jobs/fakeuuid')
def test_add_file_system_sopapi_belowminsize(self):
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
httpretval = ({'status': '400',
'content-type': 'application/jsson',
'transfer-encoding': 'chunked',
'x-sopapi-version': '1.0.0',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'location': 'https://1.2.3.4/sopapi/jobs/fakeuuid',
'date': 'Tue, 20 Jan 2015 22:41:29 GMT'},
{'messages': [{'category': 1,
'message': '''"Property 'quota' is inv'''
'alid. Specify a value from 137438953472 '
'to 6755399441055744."',
'code': 'schema_number_min_constraint',
'type': 'error'},
]})
self.mock_object(httpclient, 'request',
mock.Mock(return_value=httpretval))
self.mock_object(self._driver, '_wait_for_job_completion', mock.Mock())
fakepayload = {
'quota': 3 * units.Gi,
'enabled': True,
'description': '',
'record-access-time': True,
'tags': '',
'space-hwm': 90,
'space-lwm': 70,
'name': 'fakeid',
}
self.assertRaises(exception.SopAPIError,
self._driver._add_file_system_sopapi,
httpclient, fakepayload)
httpclient.request.assert_called_once_with(
'https://' +
self.server['backend_details']['ip'] +
'/sopapi/file-systems/',
'POST',
body=json.dumps(fakepayload),
headers=fake_authorization)
self.assertEqual(False, self._driver._wait_for_job_completion.called)
def test_wait_for_job_completion_simple(self):
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
httpreturn = [
({'status': '200',
'content-location': 'https://1.2.3.4/sopapi/jobs/fakeuuid',
'transfer-encoding': 'chunked',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'x-sopapi-version': '1.0.0',
'date': 'Wed, 21 Jan 2015 04:49:51 GMT',
'content-type': 'application/json'},
'{"id":"fakeuuid","properties":{"'
'resource-name":"","resource-type":"share","creation-timestam'
'p":1421815791,"completion-status":"PROCESSING","completion-d'
'etails":"Saving changes","completion-substatus":"RUNNING","r'
'esource-action":"ADD","percent-complete":75,"resource-id":"b'
'fakeuuid","target-node-name":"Node005","target-node-id":"fak'
'euuid","spawned-jobs":false,"spawned-jobs-list-uri":""}}'),
({'status': '200',
'content-location': 'https://1.2.3.4/sopapi/jobs/fakeuuid',
'transfer-encoding': 'chunked',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'x-sopapi-version': '1.0.0',
'date': 'Wed, 21 Jan 2015 04:49:51 GMT',
'content-type': 'application/json'},
'{"id":"fakeuuid","properties":{"'
'resource-name":"fakeuuid","resou'
'rce-type":"share","creation-timestamp":1421815791,"completio'
'n-status":"COMPLETE","completion-details":"Adding share comp'
'leted","completion-substatus":"OK","resource-action":"ADD","'
'percent-complete":100,"resource-id":"fakeuuid'
'","target-node-name":"Node005","target-node-id"'
':"fakeuuid","spawned-jobs":false'
',"spawned-jobs-list-uri":""}}'),
]
self.mock_object(httpclient, 'request',
mock.Mock(side_effect=httpreturn))
fsadd = self._driver._wait_for_job_completion(httpclient, 'fakeuri')
expectedresult = {
u'id': u'fakeuuid',
u'properties': {
u'completion-details':
u'Adding share completed',
u'completion-status': u'COMPLETE',
u'completion-substatus': u'OK',
u'creation-timestamp': 1421815791,
u'percent-complete': 100,
u'resource-action': u'ADD',
u'resource-id': u'fakeuuid',
u'resource-name': u'fakeuuid',
u'resource-type': u'share',
u'spawned-jobs': False,
u'spawned-jobs-list-uri': u'',
u'target-node-id': u'fakeuuid',
u'target-node-name': u'Node005',
},
}
self.assertEqual(expectedresult, fsadd)
httpcalls = [
mock.call('fakeuri', 'GET', body='', headers=fake_authorization)
for x in moves.range(2)]
self.assertEqual(httpcalls, httpclient.request.call_args_list)
def test_wait_for_job_completion_notimeout(self):
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
httpreturn = [({'status': '200',
'content-location':
'https://1.2.3.4/sopapi/jobs/fakeuuid',
'transfer-encoding': 'chunked',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'x-sopapi-version': '1.0.0',
'date': 'Wed, 21 Jan 2015 04:49:51 GMT',
'content-type': 'application/json'},
'{"id":"fakeuuid","properties":{"resource-name":"","re'
'source-type":"share","creation-timestamp":1421815791,'
'"completion-status":"PROCESSING","completion-details"'
':"Saving changes","completion-substatus":"RUNNING","r'
'esource-action":"ADD","percent-complete":75,"resource'
'-id":"fakeuuid","target-node-name":"Node005","target-'
'node-id":"fakeuuid","spawned-jobs":false,"spawned-job'
's-list-uri":""}}') for x in moves.range(200)
]
httpreturn.append(({'status': '200',
'content-location':
'https://1.2.3.4/sopapi/jobs/fakeuuid',
'transfer-encoding': 'chunked',
'set-cookie':
'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'x-sopapi-version': '1.0.0',
'date': 'Wed, 21 Jan 2015 04:49:51 GMT',
'content-type': 'application/json'},
'{"id":"fakeuuid","properties":{"resource-name":"fa'
'keuuid","resource-type":"share","creation-timestam'
'p":1421816291,"completion-status":"COMPLETE","comp'
'letion-details":"Adding share completed","completi'
'on-substatus":"OK","resource-action":"ADD","percen'
't-complete":100,"resource-id":"fakeuuid","target-n'
'ode-name":"Node005","target-node-id":"fakeuuid","s'
'pawned-jobs":false,"spawned-jobs-list-uri":""}}'))
self.mock_object(httpclient, 'request',
mock.Mock(side_effect=httpreturn))
self.mock_object(time, 'sleep', mock.Mock())
fsadd = self._driver._wait_for_job_completion(httpclient, 'fakeuri')
expectedresult = {
u'id': u'fakeuuid',
u'properties': {
u'completion-details':
u'Adding share completed',
u'completion-status': u'COMPLETE',
u'completion-substatus': u'OK',
u'creation-timestamp': 1421816291,
u'percent-complete': 100,
u'resource-action': u'ADD',
u'resource-id': u'fakeuuid',
u'resource-name': u'fakeuuid',
u'resource-type': u'share',
u'spawned-jobs': False,
u'spawned-jobs-list-uri': u'',
u'target-node-id': u'fakeuuid',
u'target-node-name': u'Node005',
},
}
self.assertEqual(expectedresult, fsadd)
httpcalls = [mock.call('fakeuri',
'GET',
body='',
headers=fake_authorization)
for x in moves.range(201)]
self.assertEqual(httpcalls, httpclient.request.call_args_list)
timecalls = [mock.call(1) for x in moves.range(200)]
self.assertEqual(timecalls, time.sleep.call_args_list)
def test_wait_for_job_completion_timeout(self):
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
httpret = [({'status': '200',
'content-location': 'https://1.2.3.4/sopapi/jobs/'
'fakeuuid',
'transfer-encoding': 'chunked',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'x-sopapi-version': '1.0.0',
'date': 'Wed, 21 Jan 2015 04:49:51 GMT',
'content-type': 'application/json'},
'{"id":"fakeuuid","properties'
'":{"resource-name":"","resource-type":"share","creation-'
'timestamp":1421815791,"completion-status":"PROCESSING","'
'completion-details":"Saving changes","completion-substat'
'us":"RUNNING","resource-action":"ADD","percent-complete"'
':75,"resource-id":"fakeuuid"'
',"target-node-name":"Node005","target-node-id":"fakeuuid'
'","spawned-jobs":false,"spawned-jobs-list-uri":""}}')
for x in moves.range(301)]
httpret.append(({'status': '200',
'content-location':
'https://1.2.3.4/sopapi/jobs/fakeuuid',
'transfer-encoding': 'chunked',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'x-sopapi-version': '1.0.0',
'date': 'Wed, 21 Jan 2015 04:49:51 GMT',
'content-type': 'application/json'},
'{"id":"fakeuuid","propert'
'ies":{"resource-name":"fakeuuid","resource-type":"sha'
're","creation-timestamp":1421815791,"completion-statu'
's":"COMPLETE","completion-details":"Adding share comp'
'leted","completion-substatus":"OK","resource-action"'
':"ADD","percent-complete": 100,"resource-id":"fakeuui'
'd","target-node-name":"Node005","target-node-id":"fa'
'keuuid","spawned-jobs":false,"spawned-jobs-list-uri"'
':""}}'))
self.mock_object(httpclient, 'request', mock.Mock(side_effect=httpret))
self.mock_object(time, 'sleep', mock.Mock())
self.assertRaises(exception.SopAPIError,
self._driver._wait_for_job_completion,
httpclient, 'fakeuri')
httpcalls = [mock.call('fakeuri',
'GET',
body='',
headers=fake_authorization)
for x in moves.range(301)]
self.assertEqual(httpcalls, httpclient.request.call_args_list)
timecalls = [mock.call(1) for x in moves.range(301)]
self.assertEqual(timecalls, time.sleep.call_args_list)
def test_add_share_sopapi(self):
httpclient = httplib2.Http(disable_ssl_certificate_validation=True,
timeout=None)
httpret = ({'status': '202',
'content-length': '0',
'x-sopapi-version': '1.0.0',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;Secure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'location': 'https://1.2.3.4/sopapi/jobs/fakeuuid',
'date': 'Wed, 21 Jan 2015 05:29:35 GMT'}, '')
self.mock_object(httpclient, 'request',
mock.Mock(return_value=httpret))
waitforret = json.loads('{"id":"fakeuuid'
'","properties":{"resource-name":"fakeuuid'
'","resource-type":"share",'
'"creation-timestamp":1421815791,"c'
'ompletion-status":"COMPLETE","completion-de'
'tails":"Adding share completed","completion'
'-substatus":"OK","resource-action":"ADD","p'
'ercent-complete":100,"resource-id":"fakeuui'
'd","target-node-name":"Node005","target-nod'
'e-id":"fakeuuid1","spawned-jobs":false,"spaw'
'ned-jobs-list-uri":""}}')
self.mock_object(self._driver, '_wait_for_job_completion',
mock.Mock(return_value=waitforret))
fakepayload = {
'description': '',
'type': 'NFS',
'enabled': True,
'tags': '',
'name': 'fakeuuid',
'file-system-id': 'fakeuuid',
}
fsadd = self._driver._add_share_sopapi(httpclient, fakepayload)
self.assertEqual('fakeuuid', fsadd)
httpcalls = [mock.call('https://' +
self.server['backend_details']['ip'] +
'/sopapi/shares/',
'POST',
body=json.dumps(fakepayload),
headers=fake_authorization)]
self.assertEqual(httpcalls, httpclient.request.call_args_list)
self._driver._wait_for_job_completion.assert_called_once_with(
httpclient, 'https://' +
self.server['backend_details']['ip'] +
'/sopapi/jobs/fakeuuid')
def test_create_share_success(self):
self.mock_object(self._driver, '_add_file_system_sopapi', mock.Mock())
self.mock_object(self._driver, '_get_file_system_id_by_name',
mock.Mock(return_value='fakeuuid'))
self.mock_object(self._driver, '_add_share_sopapi',
mock.Mock(return_value='fakeuuid'))
result = self._driver.create_share(
self._context, self.share, share_server=self.server)
self.assertEqual('https://1.2.3.4:/fakeuuid', result)
fakepayload = {
'quota': 1073741824,
'enabled': True,
'description': '',
'record-access-time': True,
'tags': '',
'space-hwm': 90,
'space-lwm': 70,
'name': 'fakeid',
}
fakepayload1 = {
'description': '',
'type': 'NFS',
'enabled': True,
'tags': '',
'name': 'fakeid',
'file-system-id': 'fakeuuid',
}
self._driver._add_file_system_sopapi.assert_called_once_with(
mock.ANY, fakepayload)
self._driver._get_file_system_id_by_name.assert_called_once_with(
mock.ANY, 'fakeid')
self._driver._add_share_sopapi.assert_called_once_with(
mock.ANY, fakepayload1)
def test_get_share_stats_refresh_false(self):
self._driver._stats = {'fake_key': 'fake_value'}
result = self._driver.get_share_stats(False)
self.assertEqual(result, self._driver._stats)
def test_get_share_stats_refresh_true(self):
test_data = {
'driver_handles_share_servers': False,
'share_backend_name': 'HDS_SOP',
'vendor_name': 'Hitach Data Systems',
'driver_version': '1.0',
'storage_protocol': 'NFS',
'reserved_percentage': 0,
'QoS_support': False,
'total_capacity_gb': 1234,
'free_capacity_gb': 2345,
'pools': None,
'snapshot_support': True,
}
self.mock_object(self._driver, '_get_sop_filesystem_stats',
mock.Mock(return_value=(1234, 2345)))
self._driver._update_share_stats()
self.assertEqual(test_data, self._driver._stats)
self._driver._get_sop_filesystem_stats.assert_called_once_with()
def test_allow_access_rw(self):
payload = {
'action': 'add-access-rule',
'all-squash': True,
'anongid': 65534,
'anonuid': 65534,
'host-specification': '1.2.3.4',
'description': '',
'read-write': True,
'root-squash': False,
'tags': 'nfs',
'name': 'fakeid-1.2.3.4'
}
self.mock_object(self._driver, '_get_share_id_by_name',
mock.Mock(return_value='fakeuuid'))
self.mock_object(self._driver, '_wait_for_job_completion', mock.Mock())
self.mock_object(httplib2.Http, 'request', mock.Mock(
return_value=({'status': '202',
'content-length': '0',
'x-sopapi-version': '1.0.0',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;S'
'ecure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'location': 'https://1.2.3.4/sopapi/jobs/fakeuu'
'id',
'date': 'Wed, 21 Jan 2015 05:29:35 GMT'}, '')))
access = {
'access_type': 'ip',
'access_to': '1.2.3.4',
'access_level': 'rw',
}
self._driver.allow_access(
self._context, self.share, access, share_server=self.server)
headers = dict(Authorization=self._driver.get_sop_auth_header())
httplib2.Http.request.assert_called_once_with(
'https://1.2.3.4/sopapi/shares/fakeuuid', 'POST',
body=json.dumps(payload),
headers=headers)
self._driver._get_share_id_by_name.assert_called_once_with(
mock.ANY, 'fakeid')
self._driver._wait_for_job_completion.assert_called_once_with(
mock.ANY, 'https://' +
self.server['backend_details']['ip'] +
'/sopapi/jobs/fakeuuid')
def test_allow_access_ro(self):
payload = {
'action': 'add-access-rule',
'all-squash': True,
'anongid': 65534,
'anonuid': 65534,
'host-specification': '1.2.3.4',
'description': '',
'read-write': False,
'root-squash': False,
'tags': 'nfs',
'name': 'fakeid-1.2.3.4'
}
self.mock_object(self._driver, '_get_share_id_by_name',
mock.Mock(return_value='fakeuuid'))
self.mock_object(self._driver, '_wait_for_job_completion', mock.Mock())
self.mock_object(httplib2.Http, 'request', mock.Mock(
return_value=({'status': '202',
'content-length': '0',
'x-sopapi-version': '1.0.0',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;S'
'ecure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'location': 'https://1.2.3.4/sopapi/jobs/fakeuu'
'id',
'date': 'Wed, 21 Jan 2015 05:29:35 GMT'}, '')))
access = {
'access_type': 'ip',
'access_to': '1.2.3.4',
'access_level': 'ro',
}
self._driver.allow_access(
self._context, self.share, access, share_server=self.server)
headers = dict(Authorization=self._driver.get_sop_auth_header())
httplib2.Http.request.assert_called_once_with(
'https://1.2.3.4/sopapi/shares/fakeuuid', 'POST',
body=json.dumps(payload),
headers=headers)
self._driver._get_share_id_by_name.assert_called_once_with(
mock.ANY, 'fakeid')
self._driver._wait_for_job_completion.assert_called_once_with(
mock.ANY, 'https://' +
self.server['backend_details']['ip'] +
'/sopapi/jobs/fakeuuid')
def test_deny_access(self):
payload = {
'action': 'delete-access-rule',
'name': 'fakeid-1.2.3.4',
}
self.mock_object(self._driver, '_get_share_id_by_name',
mock.Mock(return_value='fakeuuid'))
self.mock_object(self._driver, '_wait_for_job_completion', mock.Mock())
self.mock_object(httplib2.Http, 'request', mock.Mock(
return_value=({'status': '202', 'content-length': '0',
'x-sopapi-version': '1.0.0',
'set-cookie': 'JSESSIONID=abcdef;Path=/sopapi;S'
'ecure',
'expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
'server': 'Jetty(8.1.3.v20120416)',
'location': 'https://1.2.3.4/sopapi/jobs/fakeuuid',
'date': 'Wed, 21 Jan 2015 05:29:35 GMT'}, '')))
access = {
'access_type': 'ip',
'access_to': '1.2.3.4',
'access_level': 'rw',
}
self._driver.deny_access(
self._context, self.share, access, share_server=self.server)
headers = dict(Authorization=self._driver.get_sop_auth_header())
httplib2.Http.request.assert_called_once_with(
'https://1.2.3.4/sopapi/shares/fakeuuid', 'POST',
body=json.dumps(payload),
headers=headers)
self._driver._get_share_id_by_name.assert_called_once_with(
mock.ANY, 'fakeid')
self._driver._wait_for_job_completion.assert_called_once_with(
mock.ANY, 'https://' +
self.server['backend_details']['ip'] +
'/sopapi/jobs/fakeuuid')