Merge "[NetApp] Certificate based authentication for NetApp drivers"
This commit is contained in:
commit
6ea997549d
@ -411,6 +411,8 @@ def list_opts():
|
|||||||
cinder_volume_drivers_netapp_options.netapp_connection_opts,
|
cinder_volume_drivers_netapp_options.netapp_connection_opts,
|
||||||
cinder_volume_drivers_netapp_options.netapp_transport_opts,
|
cinder_volume_drivers_netapp_options.netapp_transport_opts,
|
||||||
cinder_volume_drivers_netapp_options.netapp_basicauth_opts,
|
cinder_volume_drivers_netapp_options.netapp_basicauth_opts,
|
||||||
|
cinder_volume_drivers_netapp_options.
|
||||||
|
netapp_certificateauth_opts,
|
||||||
cinder_volume_drivers_netapp_options.netapp_cluster_opts,
|
cinder_volume_drivers_netapp_options.netapp_cluster_opts,
|
||||||
cinder_volume_drivers_netapp_options.netapp_provisioning_opts,
|
cinder_volume_drivers_netapp_options.netapp_provisioning_opts,
|
||||||
cinder_volume_drivers_netapp_options.netapp_img_cache_opts,
|
cinder_volume_drivers_netapp_options.netapp_img_cache_opts,
|
||||||
|
@ -168,15 +168,23 @@ class NetAppApiServerTests(test.TestCase):
|
|||||||
|
|
||||||
mock_invoke.assert_called_with(zapi_fakes.FAKE_XML_STR)
|
mock_invoke.assert_called_with(zapi_fakes.FAKE_XML_STR)
|
||||||
|
|
||||||
def test__build_opener_not_implemented_error(self):
|
def test_build_opener_with_certificate_auth(self):
|
||||||
"""Tests whether certificate style authorization raises Exception"""
|
"""Tests whether build opener works with """
|
||||||
self.root._auth_style = 'not_basic_auth'
|
"""valid certificate parameters"""
|
||||||
|
self.root._private_key_file = 'fake_key.pem'
|
||||||
|
self.root._certificate_file = 'fake_cert.pem'
|
||||||
|
auth_handler = self.mock_object(self.root,
|
||||||
|
'_create_certificate_auth_handler',
|
||||||
|
mock.Mock(return_value='fake_auth'))
|
||||||
|
expected_opener = 'fake_auth'
|
||||||
|
self.mock_object(urllib.request, 'build_opener', auth_handler)
|
||||||
|
self.root._build_opener()
|
||||||
|
self.assertEqual(self.root._opener, expected_opener)
|
||||||
|
self.root._create_certificate_auth_handler.assert_called()
|
||||||
|
|
||||||
self.assertRaises(NotImplementedError, self.root._build_opener)
|
def test__build_opener_default(self):
|
||||||
|
"""Tests whether build opener works with """
|
||||||
def test__build_opener_valid(self):
|
"""default(basic auth) parameters"""
|
||||||
"""Tests whether build opener works with valid parameters"""
|
|
||||||
self.root._auth_style = 'basic_auth'
|
|
||||||
mock_invoke = self.mock_object(urllib.request, 'build_opener')
|
mock_invoke = self.mock_object(urllib.request, 'build_opener')
|
||||||
|
|
||||||
self.root._build_opener()
|
self.root._build_opener()
|
||||||
@ -837,7 +845,9 @@ class NetAppRestApiServerTests(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(expected_vserver, res)
|
self.assertEqual(expected_vserver, res)
|
||||||
|
|
||||||
def test__build_session(self):
|
def test__build_session_with_basic_auth(self):
|
||||||
|
"""Tests whether build session works with """
|
||||||
|
"""default(basic auth) parameters"""
|
||||||
fake_session = mock.Mock()
|
fake_session = mock.Mock()
|
||||||
mock_requests_session = self.mock_object(
|
mock_requests_session = self.mock_object(
|
||||||
requests, 'Session', mock.Mock(return_value=fake_session))
|
requests, 'Session', mock.Mock(return_value=fake_session))
|
||||||
@ -856,6 +866,28 @@ class NetAppRestApiServerTests(test.TestCase):
|
|||||||
mock_requests_session.assert_called_once_with()
|
mock_requests_session.assert_called_once_with()
|
||||||
mock_auth.assert_called_once_with()
|
mock_auth.assert_called_once_with()
|
||||||
|
|
||||||
|
def test__build_session_with_certificate_auth(self):
|
||||||
|
"""Tests whether build session works with """
|
||||||
|
"""valid certificate parameters"""
|
||||||
|
self.rest_client._private_key_file = 'fake_key.pem'
|
||||||
|
self.rest_client._certificate_file = 'fake_cert.pem'
|
||||||
|
self.rest_client._certificate_host_validation = False
|
||||||
|
fake_session = mock.Mock()
|
||||||
|
mock_requests_session = self.mock_object(
|
||||||
|
requests, 'Session', mock.Mock(return_value=fake_session))
|
||||||
|
mock_auth = self.mock_object(
|
||||||
|
self.rest_client, '_create_certificate_auth_handler',
|
||||||
|
mock.Mock(return_value=('fake_cert', 'fake_verify')))
|
||||||
|
self.rest_client._build_session(zapi_fakes.FAKE_HEADERS)
|
||||||
|
self.assertEqual(fake_session, self.rest_client._session)
|
||||||
|
self.assertEqual(('fake_cert', 'fake_verify'),
|
||||||
|
(self.rest_client._session.cert,
|
||||||
|
self.rest_client._session.verify))
|
||||||
|
self.assertEqual(zapi_fakes.FAKE_HEADERS,
|
||||||
|
self.rest_client._session.headers)
|
||||||
|
mock_requests_session.assert_called_once_with()
|
||||||
|
mock_auth.assert_called_once_with()
|
||||||
|
|
||||||
@ddt.data(True, False)
|
@ddt.data(True, False)
|
||||||
def test__build_headers(self, enable_tunneling):
|
def test__build_headers(self, enable_tunneling):
|
||||||
self.rest_client._vserver = zapi_fakes.VSERVER_NAME
|
self.rest_client._vserver = zapi_fakes.VSERVER_NAME
|
||||||
@ -880,3 +912,33 @@ class NetAppRestApiServerTests(test.TestCase):
|
|||||||
|
|
||||||
expected = auth.HTTPBasicAuth(username, password)
|
expected = auth.HTTPBasicAuth(username, password)
|
||||||
self.assertEqual(expected.__dict__, res.__dict__)
|
self.assertEqual(expected.__dict__, res.__dict__)
|
||||||
|
|
||||||
|
def test__create_certificate_auth_handler_default(self):
|
||||||
|
"""Test whether create certificate auth handler """
|
||||||
|
"""works with default params"""
|
||||||
|
self.rest_client._private_key_file = 'fake_key.pem'
|
||||||
|
self.rest_client._certificate_file = 'fake_cert.pem'
|
||||||
|
self.rest_client._certificate_host_validation = False
|
||||||
|
cert = self.rest_client._certificate_file, \
|
||||||
|
self.rest_client._private_key_file
|
||||||
|
self.rest_client._session = mock.Mock()
|
||||||
|
if not self.rest_client._certificate_host_validation:
|
||||||
|
self.assertFalse(self.rest_client._certificate_host_validation)
|
||||||
|
res = self.rest_client._create_certificate_auth_handler()
|
||||||
|
self.assertEqual(res,
|
||||||
|
(cert, self.rest_client._certificate_host_validation))
|
||||||
|
|
||||||
|
def test__create_certificate_auth_handler_with_host_validation(self):
|
||||||
|
"""Test whether create certificate auth handler """
|
||||||
|
"""works with host validation enabled"""
|
||||||
|
self.rest_client._private_key_file = 'fake_key.pem'
|
||||||
|
self.rest_client._certificate_file = 'fake_cert.pem'
|
||||||
|
self.rest_client._ca_certificate_file = 'fake_ca_cert.crt'
|
||||||
|
self.rest_client._certificate_host_validation = True
|
||||||
|
cert = self.rest_client._certificate_file, \
|
||||||
|
self.rest_client._private_key_file
|
||||||
|
self.rest_client._session = mock.Mock()
|
||||||
|
if self.rest_client._certificate_host_validation:
|
||||||
|
self.assertTrue(self.rest_client._certificate_host_validation)
|
||||||
|
res = self.rest_client._create_certificate_auth_handler()
|
||||||
|
self.assertEqual(res, (cert, self.rest_client._ca_certificate_file))
|
||||||
|
@ -35,7 +35,12 @@ CONNECTION_INFO = {'hostname': 'hostname',
|
|||||||
'port': 443,
|
'port': 443,
|
||||||
'username': 'admin',
|
'username': 'admin',
|
||||||
'password': 'passw0rd',
|
'password': 'passw0rd',
|
||||||
'api_trace_pattern': 'fake_regex'}
|
'api_trace_pattern': 'fake_regex',
|
||||||
|
'private_key_file': 'fake_private_key.pem',
|
||||||
|
'certificate_file': 'fake_cert.pem',
|
||||||
|
'ca_certificate_file': 'fake_ca_cert.crt',
|
||||||
|
'certificate_host_validation': 'False'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
|
@ -43,7 +43,11 @@ CONNECTION_INFO = {'hostname': 'hostname',
|
|||||||
'username': 'admin',
|
'username': 'admin',
|
||||||
'password': 'passw0rd',
|
'password': 'passw0rd',
|
||||||
'vserver': 'fake_vserver',
|
'vserver': 'fake_vserver',
|
||||||
'api_trace_pattern': 'fake_regex'}
|
'api_trace_pattern': 'fake_regex',
|
||||||
|
'private_key_file': 'fake_private_key.pem',
|
||||||
|
'certificate_file': 'fake_cert.pem',
|
||||||
|
'ca_certificate_file': 'fake_ca_cert.crt',
|
||||||
|
'certificate_host_validation': 'False'}
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
|
@ -41,7 +41,12 @@ CONNECTION_INFO = {'hostname': 'hostname',
|
|||||||
'password': 'passw0rd',
|
'password': 'passw0rd',
|
||||||
'vserver': 'fake_vserver',
|
'vserver': 'fake_vserver',
|
||||||
'ssl_cert_path': 'fake_ca',
|
'ssl_cert_path': 'fake_ca',
|
||||||
'api_trace_pattern': 'fake_regex'}
|
'api_trace_pattern': 'fake_regex',
|
||||||
|
'private_key_file': 'fake_private_key.pem',
|
||||||
|
'certificate_file': 'fake_cert.pem',
|
||||||
|
'ca_certificate_file': 'fake_ca_cert.crt',
|
||||||
|
'certificate_host_validation': 'False'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
|
@ -183,6 +183,7 @@ def get_fake_cmode_config(backend_name):
|
|||||||
config.append_config_values(na_opts.netapp_connection_opts)
|
config.append_config_values(na_opts.netapp_connection_opts)
|
||||||
config.append_config_values(na_opts.netapp_transport_opts)
|
config.append_config_values(na_opts.netapp_transport_opts)
|
||||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||||
|
config.append_config_values(na_opts.netapp_certificateauth_opts)
|
||||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||||
config.append_config_values(na_opts.netapp_cluster_opts)
|
config.append_config_values(na_opts.netapp_cluster_opts)
|
||||||
config.append_config_values(na_opts.netapp_san_opts)
|
config.append_config_values(na_opts.netapp_san_opts)
|
||||||
|
@ -76,6 +76,7 @@ class NetAppCDOTDataMotionMixinTestCase(test.TestCase):
|
|||||||
config.append_config_values(na_opts.netapp_connection_opts)
|
config.append_config_values(na_opts.netapp_connection_opts)
|
||||||
config.append_config_values(na_opts.netapp_transport_opts)
|
config.append_config_values(na_opts.netapp_transport_opts)
|
||||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||||
|
config.append_config_values(na_opts.netapp_certificateauth_opts)
|
||||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||||
config.append_config_values(na_opts.netapp_cluster_opts)
|
config.append_config_values(na_opts.netapp_cluster_opts)
|
||||||
config.append_config_values(na_opts.netapp_san_opts)
|
config.append_config_values(na_opts.netapp_san_opts)
|
||||||
|
@ -53,6 +53,14 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
|||||||
group=self.backend)
|
group=self.backend)
|
||||||
CONF.set_override('netapp_ssl_cert_path', 'fake_ca',
|
CONF.set_override('netapp_ssl_cert_path', 'fake_ca',
|
||||||
group=self.backend)
|
group=self.backend)
|
||||||
|
CONF.set_override('netapp_private_key_file', 'fake_private_key.pem',
|
||||||
|
group=self.backend)
|
||||||
|
CONF.set_override('netapp_certificate_file', 'fake_cert.pem',
|
||||||
|
group=self.backend)
|
||||||
|
CONF.set_override('netapp_ca_certificate_file', 'fake_ca_cert.crt',
|
||||||
|
group=self.backend)
|
||||||
|
CONF.set_override('netapp_certificate_host_validation', False,
|
||||||
|
group=self.backend)
|
||||||
|
|
||||||
def test_get_backend_configuration(self):
|
def test_get_backend_configuration(self):
|
||||||
self.mock_object(utils, 'CONF')
|
self.mock_object(utils, 'CONF')
|
||||||
@ -98,14 +106,22 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
|||||||
self.mock_cmode_client.assert_called_once_with(
|
self.mock_cmode_client.assert_called_once_with(
|
||||||
hostname='fake_hostname', password='fake_password',
|
hostname='fake_hostname', password='fake_password',
|
||||||
username='fake_user', transport_type='https', port=8866,
|
username='fake_user', transport_type='https', port=8866,
|
||||||
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex")
|
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex",
|
||||||
|
private_key_file='fake_private_key.pem',
|
||||||
|
certificate_file='fake_cert.pem',
|
||||||
|
ca_certificate_file='fake_ca_cert.crt',
|
||||||
|
certificate_host_validation=False)
|
||||||
self.mock_cmode_rest_client.assert_not_called()
|
self.mock_cmode_rest_client.assert_not_called()
|
||||||
else:
|
else:
|
||||||
self.mock_cmode_rest_client.assert_called_once_with(
|
self.mock_cmode_rest_client.assert_called_once_with(
|
||||||
hostname='fake_hostname', password='fake_password',
|
hostname='fake_hostname', password='fake_password',
|
||||||
username='fake_user', transport_type='https', port=8866,
|
username='fake_user', transport_type='https', port=8866,
|
||||||
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex",
|
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex",
|
||||||
ssl_cert_path='fake_ca', async_rest_timeout=60)
|
ssl_cert_path='fake_ca', async_rest_timeout=60,
|
||||||
|
private_key_file='fake_private_key.pem',
|
||||||
|
certificate_file='fake_cert.pem',
|
||||||
|
ca_certificate_file='fake_ca_cert.crt',
|
||||||
|
certificate_host_validation=False)
|
||||||
self.mock_cmode_client.assert_not_called()
|
self.mock_cmode_client.assert_not_called()
|
||||||
|
|
||||||
@ddt.data(True, False)
|
@ddt.data(True, False)
|
||||||
@ -124,7 +140,11 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
|||||||
hostname='fake_hostname', password='fake_password',
|
hostname='fake_hostname', password='fake_password',
|
||||||
username='fake_user', transport_type='https', port=8866,
|
username='fake_user', transport_type='https', port=8866,
|
||||||
trace=mock.ANY, vserver='fake_vserver',
|
trace=mock.ANY, vserver='fake_vserver',
|
||||||
api_trace_pattern="fake_regex")
|
api_trace_pattern="fake_regex",
|
||||||
|
private_key_file='fake_private_key.pem',
|
||||||
|
certificate_file='fake_cert.pem',
|
||||||
|
ca_certificate_file='fake_ca_cert.crt',
|
||||||
|
certificate_host_validation=False)
|
||||||
self.mock_cmode_rest_client.assert_not_called()
|
self.mock_cmode_rest_client.assert_not_called()
|
||||||
else:
|
else:
|
||||||
self.mock_cmode_rest_client.assert_called_once_with(
|
self.mock_cmode_rest_client.assert_called_once_with(
|
||||||
@ -132,7 +152,11 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
|||||||
username='fake_user', transport_type='https', port=8866,
|
username='fake_user', transport_type='https', port=8866,
|
||||||
trace=mock.ANY, vserver='fake_vserver',
|
trace=mock.ANY, vserver='fake_vserver',
|
||||||
api_trace_pattern="fake_regex", ssl_cert_path='fake_ca',
|
api_trace_pattern="fake_regex", ssl_cert_path='fake_ca',
|
||||||
async_rest_timeout = 60)
|
async_rest_timeout = 60,
|
||||||
|
private_key_file='fake_private_key.pem',
|
||||||
|
certificate_file='fake_cert.pem',
|
||||||
|
ca_certificate_file='fake_ca_cert.crt',
|
||||||
|
certificate_host_validation=False)
|
||||||
self.mock_cmode_client.assert_not_called()
|
self.mock_cmode_client.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@ -176,6 +176,7 @@ def create_configuration():
|
|||||||
config.append_config_values(na_opts.netapp_connection_opts)
|
config.append_config_values(na_opts.netapp_connection_opts)
|
||||||
config.append_config_values(na_opts.netapp_transport_opts)
|
config.append_config_values(na_opts.netapp_transport_opts)
|
||||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||||
|
config.append_config_values(na_opts.netapp_certificateauth_opts)
|
||||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -65,18 +65,19 @@ class NetAppLun(object):
|
|||||||
|
|
||||||
def __str__(self, *args, **kwargs):
|
def __str__(self, *args, **kwargs):
|
||||||
return 'NetApp LUN [handle:%s, name:%s, size:%s, metadata:%s]' % (
|
return 'NetApp LUN [handle:%s, name:%s, size:%s, metadata:%s]' % (
|
||||||
self.handle, self.name, self.size, self.metadata)
|
self.handle, self.name, self.size, self.metadata)
|
||||||
|
|
||||||
|
|
||||||
class NetAppBlockStorageLibrary(
|
class NetAppBlockStorageLibrary(
|
||||||
object,
|
object,
|
||||||
metaclass=volume_utils.TraceWrapperMetaclass):
|
metaclass=volume_utils.TraceWrapperMetaclass):
|
||||||
"""NetApp block storage library for Data ONTAP."""
|
"""NetApp block storage library for Data ONTAP."""
|
||||||
|
|
||||||
# do not increment this as it may be used in volume type definitions
|
# do not increment this as it may be used in volume type definitions
|
||||||
VERSION = "1.0.0"
|
VERSION = "1.0.0"
|
||||||
REQUIRED_FLAGS = ['netapp_login', 'netapp_password',
|
REQUIRED_FLAGS_BASIC = ['netapp_login', 'netapp_password',
|
||||||
'netapp_server_hostname']
|
'netapp_server_hostname']
|
||||||
|
REQUIRED_FLAGS_CERT = ['netapp_private_key_file',
|
||||||
|
'netapp_certificate_file']
|
||||||
ALLOWED_LUN_OS_TYPES = ['linux', 'aix', 'hpux', 'image', 'windows',
|
ALLOWED_LUN_OS_TYPES = ['linux', 'aix', 'hpux', 'image', 'windows',
|
||||||
'windows_2008', 'windows_gpt', 'solaris',
|
'windows_2008', 'windows_gpt', 'solaris',
|
||||||
'solaris_efi', 'netware', 'openvms', 'hyper_v']
|
'solaris_efi', 'netware', 'openvms', 'hyper_v']
|
||||||
@ -109,6 +110,8 @@ class NetAppBlockStorageLibrary(
|
|||||||
self.configuration = kwargs['configuration']
|
self.configuration = kwargs['configuration']
|
||||||
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
||||||
|
self.configuration.append_config_values(
|
||||||
|
na_opts.netapp_certificateauth_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
||||||
self.configuration.append_config_values(
|
self.configuration.append_config_values(
|
||||||
na_opts.netapp_provisioning_opts)
|
na_opts.netapp_provisioning_opts)
|
||||||
@ -137,13 +140,18 @@ class NetAppBlockStorageLibrary(
|
|||||||
reserved_percentage = 100 * int(reserved_ratio)
|
reserved_percentage = 100 * int(reserved_ratio)
|
||||||
msg = ('The "netapp_size_multiplier" configuration option is '
|
msg = ('The "netapp_size_multiplier" configuration option is '
|
||||||
'deprecated and will be removed in the Mitaka release. '
|
'deprecated and will be removed in the Mitaka release. '
|
||||||
'Please set "reserved_percentage = %d" instead.') % (
|
'Please set "reserved_percentage = %d" instead.') \
|
||||||
reserved_percentage)
|
% reserved_percentage
|
||||||
versionutils.report_deprecated_feature(LOG, msg)
|
versionutils.report_deprecated_feature(LOG, msg)
|
||||||
return reserved_percentage
|
return reserved_percentage
|
||||||
|
|
||||||
def do_setup(self, context):
|
def do_setup(self, context):
|
||||||
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
|
if self.configuration.netapp_private_key_file or\
|
||||||
|
self.configuration.netapp_certificate_file:
|
||||||
|
na_utils.check_flags(self.REQUIRED_FLAGS_CERT,
|
||||||
|
self.configuration)
|
||||||
|
else:
|
||||||
|
na_utils.check_flags(self.REQUIRED_FLAGS_BASIC, self.configuration)
|
||||||
self.lun_ostype = (self.configuration.netapp_lun_ostype
|
self.lun_ostype = (self.configuration.netapp_lun_ostype
|
||||||
or self.DEFAULT_LUN_OS)
|
or self.DEFAULT_LUN_OS)
|
||||||
self.host_type = (self.configuration.netapp_host_type
|
self.host_type = (self.configuration.netapp_host_type
|
||||||
@ -242,9 +250,9 @@ class NetAppBlockStorageLibrary(
|
|||||||
na_utils.get_qos_policy_group_name_from_info(
|
na_utils.get_qos_policy_group_name_from_info(
|
||||||
qos_policy_group_info))
|
qos_policy_group_info))
|
||||||
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
|
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
|
||||||
extra_specs.get('netapp:qos_policy_group_is_adaptive')) or
|
extra_specs.get('netapp:qos_policy_group_is_adaptive'))
|
||||||
na_utils.is_qos_policy_group_spec_adaptive(
|
or na_utils.is_qos_policy_group_spec_adaptive
|
||||||
qos_policy_group_info))
|
(qos_policy_group_info))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._create_lun(pool_name, lun_name, size, metadata,
|
self._create_lun(pool_name, lun_name, size, metadata,
|
||||||
@ -367,9 +375,9 @@ class NetAppBlockStorageLibrary(
|
|||||||
na_utils.get_qos_policy_group_name_from_info(
|
na_utils.get_qos_policy_group_name_from_info(
|
||||||
qos_policy_group_info))
|
qos_policy_group_info))
|
||||||
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
|
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
|
||||||
extra_specs.get('netapp:qos_policy_group_is_adaptive')) or
|
extra_specs.get('netapp:qos_policy_group_is_adaptive'))
|
||||||
na_utils.is_qos_policy_group_spec_adaptive(
|
or na_utils.is_qos_policy_group_spec_adaptive
|
||||||
qos_policy_group_info))
|
(qos_policy_group_info))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._clone_lun(
|
self._clone_lun(
|
||||||
@ -882,8 +890,8 @@ class NetAppBlockStorageLibrary(
|
|||||||
LOG.info("Unmanaged LUN with current path %(path)s and uuid "
|
LOG.info("Unmanaged LUN with current path %(path)s and uuid "
|
||||||
"%(uuid)s.",
|
"%(uuid)s.",
|
||||||
{'path': managed_lun.get_metadata_property('Path'),
|
{'path': managed_lun.get_metadata_property('Path'),
|
||||||
'uuid': managed_lun.get_metadata_property('UUID')
|
'uuid': managed_lun.get_metadata_property('UUID') or
|
||||||
or 'unknown'})
|
'unknown'})
|
||||||
|
|
||||||
def initialize_connection_iscsi(self, volume, connector):
|
def initialize_connection_iscsi(self, volume, connector):
|
||||||
"""Driver entry point to attach a volume to an instance.
|
"""Driver entry point to attach a volume to an instance.
|
||||||
|
@ -20,8 +20,10 @@
|
|||||||
Contains classes required to issue API calls to Data ONTAP and OnCommand DFM.
|
Contains classes required to issue API calls to Data ONTAP and OnCommand DFM.
|
||||||
"""
|
"""
|
||||||
import random
|
import random
|
||||||
|
import ssl
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
|
||||||
from eventlet import greenthread
|
from eventlet import greenthread
|
||||||
from eventlet import semaphore
|
from eventlet import semaphore
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
@ -66,21 +68,24 @@ class NaServer(object):
|
|||||||
URL_FILER = 'servlets/netapp.servlets.admin.XMLrequest_filer'
|
URL_FILER = 'servlets/netapp.servlets.admin.XMLrequest_filer'
|
||||||
URL_DFM = 'apis/XMLrequest'
|
URL_DFM = 'apis/XMLrequest'
|
||||||
NETAPP_NS = 'http://www.netapp.com/filer/admin'
|
NETAPP_NS = 'http://www.netapp.com/filer/admin'
|
||||||
STYLE_LOGIN_PASSWORD = 'basic_auth'
|
|
||||||
STYLE_CERTIFICATE = 'certificate_auth'
|
|
||||||
|
|
||||||
def __init__(self, host, server_type=SERVER_TYPE_FILER,
|
def __init__(self, host, server_type=SERVER_TYPE_FILER,
|
||||||
transport_type=TRANSPORT_TYPE_HTTP,
|
transport_type=TRANSPORT_TYPE_HTTP,
|
||||||
style=STYLE_LOGIN_PASSWORD, username=None,
|
username=None,
|
||||||
password=None, port=None, api_trace_pattern=None):
|
password=None, port=None, api_trace_pattern=None,
|
||||||
|
private_key_file=None, certificate_file=None,
|
||||||
|
ca_certificate_file=None, certificate_host_validation=None):
|
||||||
self._host = host
|
self._host = host
|
||||||
self.set_server_type(server_type)
|
self.set_server_type(server_type)
|
||||||
self.set_transport_type(transport_type)
|
self.set_transport_type(transport_type)
|
||||||
self.set_style(style)
|
|
||||||
if port:
|
if port:
|
||||||
self.set_port(port)
|
self.set_port(port)
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
|
self._private_key_file = private_key_file
|
||||||
|
self._certificate_file = certificate_file
|
||||||
|
self._ca_certificate_file = ca_certificate_file
|
||||||
|
self._certificate_host_validation = certificate_host_validation
|
||||||
self._refresh_conn = True
|
self._refresh_conn = True
|
||||||
|
|
||||||
if api_trace_pattern is not None:
|
if api_trace_pattern is not None:
|
||||||
@ -189,10 +194,8 @@ class NaServer(object):
|
|||||||
"""Invoke the API on the server."""
|
"""Invoke the API on the server."""
|
||||||
if not na_element or not isinstance(na_element, NaElement):
|
if not na_element or not isinstance(na_element, NaElement):
|
||||||
raise ValueError('NaElement must be supplied to invoke API')
|
raise ValueError('NaElement must be supplied to invoke API')
|
||||||
|
|
||||||
request, request_element = self._create_request(na_element,
|
request, request_element = self._create_request(na_element,
|
||||||
enable_tunneling)
|
enable_tunneling)
|
||||||
|
|
||||||
if not hasattr(self, '_opener') or not self._opener \
|
if not hasattr(self, '_opener') or not self._opener \
|
||||||
or self._refresh_conn:
|
or self._refresh_conn:
|
||||||
self._build_opener()
|
self._build_opener()
|
||||||
@ -223,14 +226,14 @@ class NaServer(object):
|
|||||||
result = self.send_http_request(na_element, enable_tunneling)
|
result = self.send_http_request(na_element, enable_tunneling)
|
||||||
if result.has_attr('status') and result.get_attr('status') == 'passed':
|
if result.has_attr('status') and result.get_attr('status') == 'passed':
|
||||||
return result
|
return result
|
||||||
code = result.get_attr('errno')\
|
code = result.get_attr('errno') \
|
||||||
or result.get_child_content('errorno')\
|
or result.get_child_content('errorno') \
|
||||||
or 'ESTATUSFAILED'
|
or 'ESTATUSFAILED'
|
||||||
if code == ESIS_CLONE_NOT_LICENSED:
|
if code == ESIS_CLONE_NOT_LICENSED:
|
||||||
msg = 'Clone operation failed: FlexClone not licensed.'
|
msg = 'Clone operation failed: FlexClone not licensed.'
|
||||||
else:
|
else:
|
||||||
msg = result.get_attr('reason')\
|
msg = result.get_attr('reason') \
|
||||||
or result.get_child_content('reason')\
|
or result.get_child_content('reason') \
|
||||||
or 'Execution status is failed due to unknown reason'
|
or 'Execution status is failed due to unknown reason'
|
||||||
raise NaApiError(code, msg)
|
raise NaApiError(code, msg)
|
||||||
|
|
||||||
@ -299,10 +302,10 @@ class NaServer(object):
|
|||||||
self._url)
|
self._url)
|
||||||
|
|
||||||
def _build_opener(self):
|
def _build_opener(self):
|
||||||
if self._auth_style == NaServer.STYLE_LOGIN_PASSWORD:
|
if self._private_key_file and self._certificate_file:
|
||||||
auth_handler = self._create_basic_auth_handler()
|
|
||||||
else:
|
|
||||||
auth_handler = self._create_certificate_auth_handler()
|
auth_handler = self._create_certificate_auth_handler()
|
||||||
|
else:
|
||||||
|
auth_handler = self._create_basic_auth_handler()
|
||||||
opener = urllib.request.build_opener(auth_handler)
|
opener = urllib.request.build_opener(auth_handler)
|
||||||
self._opener = opener
|
self._opener = opener
|
||||||
|
|
||||||
@ -314,7 +317,17 @@ class NaServer(object):
|
|||||||
return auth_handler
|
return auth_handler
|
||||||
|
|
||||||
def _create_certificate_auth_handler(self):
|
def _create_certificate_auth_handler(self):
|
||||||
raise NotImplementedError()
|
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||||
|
if not self._certificate_host_validation:
|
||||||
|
context.check_hostname = False
|
||||||
|
context.verify_mode = ssl.CERT_NONE
|
||||||
|
if self._certificate_file and self._private_key_file:
|
||||||
|
context.load_cert_chain(certfile=self._certificate_file,
|
||||||
|
keyfile=self._private_key_file)
|
||||||
|
if self._ca_certificate_file:
|
||||||
|
context.load_verify_locations(cafile=self._ca_certificate_file)
|
||||||
|
auth_handler = urllib.request.HTTPSHandler(context=context)
|
||||||
|
return auth_handler
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "server: %s" % self._host
|
return "server: %s" % self._host
|
||||||
@ -606,10 +619,9 @@ class SSHUtil(object):
|
|||||||
response = stdout.channel.recv(999)
|
response = stdout.channel.recv(999)
|
||||||
if expected_prompt_text not in response.strip().decode():
|
if expected_prompt_text not in response.strip().decode():
|
||||||
msg = _("Unexpected output. Expected [%(expected)s] but "
|
msg = _("Unexpected output. Expected [%(expected)s] but "
|
||||||
"received [%(output)s]") % {
|
"received [%(output)s]")\
|
||||||
'expected': expected_prompt_text,
|
% {'expected': expected_prompt_text,
|
||||||
'output': response.strip(),
|
'output': response.strip(), }
|
||||||
}
|
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
stdin.close()
|
stdin.close()
|
||||||
stdout.close()
|
stdout.close()
|
||||||
@ -651,7 +663,6 @@ REST_NAMESPACE_EOBJECTNOTFOUND = ('72090006', '72090006')
|
|||||||
|
|
||||||
|
|
||||||
class RestNaServer(object):
|
class RestNaServer(object):
|
||||||
|
|
||||||
TRANSPORT_TYPE_HTTP = 'http'
|
TRANSPORT_TYPE_HTTP = 'http'
|
||||||
TRANSPORT_TYPE_HTTPS = 'https'
|
TRANSPORT_TYPE_HTTPS = 'https'
|
||||||
HTTP_PORT = '80'
|
HTTP_PORT = '80'
|
||||||
@ -664,12 +675,18 @@ class RestNaServer(object):
|
|||||||
|
|
||||||
def __init__(self, host, transport_type=TRANSPORT_TYPE_HTTP,
|
def __init__(self, host, transport_type=TRANSPORT_TYPE_HTTP,
|
||||||
ssl_cert_path=None, username=None, password=None, port=None,
|
ssl_cert_path=None, username=None, password=None, port=None,
|
||||||
api_trace_pattern=None):
|
api_trace_pattern=None,
|
||||||
|
private_key_file=None, certificate_file=None,
|
||||||
|
ca_certificate_file=None, certificate_host_validation=None):
|
||||||
self._host = host
|
self._host = host
|
||||||
self.set_transport_type(transport_type)
|
self.set_transport_type(transport_type)
|
||||||
self.set_port(port=port)
|
self.set_port(port=port)
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
|
self._private_key_file = private_key_file
|
||||||
|
self._certificate_file = certificate_file
|
||||||
|
self._ca_certificate_file = ca_certificate_file
|
||||||
|
self._certificate_host_validation = certificate_host_validation
|
||||||
|
|
||||||
if api_trace_pattern is not None:
|
if api_trace_pattern is not None:
|
||||||
na_utils.setup_api_trace_pattern(api_trace_pattern)
|
na_utils.setup_api_trace_pattern(api_trace_pattern)
|
||||||
@ -799,9 +816,12 @@ class RestNaServer(object):
|
|||||||
max_retries = Retry(total=5, connect=5, read=2, backoff_factor=1)
|
max_retries = Retry(total=5, connect=5, read=2, backoff_factor=1)
|
||||||
adapter = HTTPAdapter(max_retries=max_retries)
|
adapter = HTTPAdapter(max_retries=max_retries)
|
||||||
self._session.mount('%s://' % self._protocol, adapter)
|
self._session.mount('%s://' % self._protocol, adapter)
|
||||||
|
if self._private_key_file and self._certificate_file:
|
||||||
self._session.auth = self._create_basic_auth_handler()
|
self._session.cert, self._session.verify\
|
||||||
self._session.verify = self._ssl_verify
|
= self._create_certificate_auth_handler()
|
||||||
|
else:
|
||||||
|
self._session.auth = self._create_basic_auth_handler()
|
||||||
|
self._session.verify = self._ssl_verify
|
||||||
self._session.headers = headers
|
self._session.headers = headers
|
||||||
|
|
||||||
def _build_headers(self, enable_tunneling):
|
def _build_headers(self, enable_tunneling):
|
||||||
@ -819,6 +839,20 @@ class RestNaServer(object):
|
|||||||
"""Creates and returns a basic HTTP auth handler."""
|
"""Creates and returns a basic HTTP auth handler."""
|
||||||
return auth.HTTPBasicAuth(self._username, self._password)
|
return auth.HTTPBasicAuth(self._username, self._password)
|
||||||
|
|
||||||
|
def _create_certificate_auth_handler(self):
|
||||||
|
"""Creates and returns a certificate auth handler."""
|
||||||
|
self._certificate_host_validation = self._session.verify
|
||||||
|
if self._certificate_file and self._private_key_file \
|
||||||
|
and self._ca_certificate_file:
|
||||||
|
self._session.cert = (self._certificate_file,
|
||||||
|
self._private_key_file)
|
||||||
|
if self._certificate_host_validation:
|
||||||
|
self._session.verify = self._ca_certificate_file
|
||||||
|
elif self._certificate_file and self._private_key_file:
|
||||||
|
self._session.cert = (self._certificate_file,
|
||||||
|
self._private_key_file)
|
||||||
|
return self._session.cert, self._session.verify
|
||||||
|
|
||||||
@volume_utils.trace_api(
|
@volume_utils.trace_api(
|
||||||
filter_function=na_utils.trace_filter_func_rest_api)
|
filter_function=na_utils.trace_filter_func_rest_api)
|
||||||
def send_http_request(self, method, url, body, headers):
|
def send_http_request(self, method, url, body, headers):
|
||||||
|
@ -38,13 +38,37 @@ class Client(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
|||||||
username = kwargs['username']
|
username = kwargs['username']
|
||||||
password = kwargs['password']
|
password = kwargs['password']
|
||||||
api_trace_pattern = kwargs['api_trace_pattern']
|
api_trace_pattern = kwargs['api_trace_pattern']
|
||||||
self.connection = netapp_api.NaServer(
|
private_key_file = kwargs['private_key_file']
|
||||||
host=host,
|
certificate_file = kwargs['certificate_file']
|
||||||
transport_type=kwargs['transport_type'],
|
ca_certificate_file = kwargs['ca_certificate_file']
|
||||||
port=kwargs['port'],
|
certificate_host_validation = kwargs['certificate_host_validation']
|
||||||
username=username,
|
if private_key_file and certificate_file and ca_certificate_file:
|
||||||
password=password,
|
self.connection = netapp_api.NaServer(
|
||||||
api_trace_pattern=api_trace_pattern)
|
host=host,
|
||||||
|
transport_type='https',
|
||||||
|
port=kwargs['port'],
|
||||||
|
private_key_file=private_key_file,
|
||||||
|
certificate_file=certificate_file,
|
||||||
|
ca_certificate_file=ca_certificate_file,
|
||||||
|
certificate_host_validation=certificate_host_validation,
|
||||||
|
api_trace_pattern=api_trace_pattern)
|
||||||
|
elif private_key_file and certificate_file:
|
||||||
|
self.connection = netapp_api.NaServer(
|
||||||
|
host=host,
|
||||||
|
transport_type='https',
|
||||||
|
port=kwargs['port'],
|
||||||
|
private_key_file=private_key_file,
|
||||||
|
certificate_file=certificate_file,
|
||||||
|
certificate_host_validation=certificate_host_validation,
|
||||||
|
api_trace_pattern=api_trace_pattern)
|
||||||
|
else:
|
||||||
|
self.connection = netapp_api.NaServer(
|
||||||
|
host=host,
|
||||||
|
transport_type=kwargs['transport_type'],
|
||||||
|
port=kwargs['port'],
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
api_trace_pattern=api_trace_pattern)
|
||||||
|
|
||||||
self.ssh_client = self._init_ssh_client(host, username, password)
|
self.ssh_client = self._init_ssh_client(host, username, password)
|
||||||
|
|
||||||
|
@ -71,14 +71,38 @@ class RestClient(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
|||||||
username = kwargs['username']
|
username = kwargs['username']
|
||||||
password = kwargs['password']
|
password = kwargs['password']
|
||||||
api_trace_pattern = kwargs['api_trace_pattern']
|
api_trace_pattern = kwargs['api_trace_pattern']
|
||||||
self.connection = netapp_api.RestNaServer(
|
private_key_file = kwargs['private_key_file']
|
||||||
host=host,
|
certificate_file = kwargs['certificate_file']
|
||||||
transport_type=kwargs['transport_type'],
|
ca_certificate_file = kwargs['ca_certificate_file']
|
||||||
ssl_cert_path=kwargs.pop('ssl_cert_path'),
|
certificate_host_validation = kwargs['certificate_host_validation']
|
||||||
port=kwargs['port'],
|
if private_key_file and certificate_file and ca_certificate_file:
|
||||||
username=username,
|
self.connection = netapp_api.RestNaServer(
|
||||||
password=password,
|
host=host,
|
||||||
api_trace_pattern=api_trace_pattern)
|
transport_type='https',
|
||||||
|
port=kwargs['port'],
|
||||||
|
private_key_file=private_key_file,
|
||||||
|
certificate_file=certificate_file,
|
||||||
|
ca_certificate_file=ca_certificate_file,
|
||||||
|
certificate_host_validation=certificate_host_validation,
|
||||||
|
api_trace_pattern=api_trace_pattern)
|
||||||
|
elif private_key_file and certificate_file:
|
||||||
|
self.connection = netapp_api.RestNaServer(
|
||||||
|
host=host,
|
||||||
|
transport_type='https',
|
||||||
|
port=kwargs['port'],
|
||||||
|
private_key_file=private_key_file,
|
||||||
|
certificate_file=certificate_file,
|
||||||
|
certificate_host_validation=certificate_host_validation,
|
||||||
|
api_trace_pattern=api_trace_pattern)
|
||||||
|
else:
|
||||||
|
self.connection = netapp_api.RestNaServer(
|
||||||
|
host=host,
|
||||||
|
transport_type=kwargs['transport_type'],
|
||||||
|
ssl_cert_path=kwargs.pop('ssl_cert_path'),
|
||||||
|
port=kwargs['port'],
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
api_trace_pattern=api_trace_pattern)
|
||||||
|
|
||||||
self.async_rest_timeout = kwargs.get('async_rest_timeout', 60)
|
self.async_rest_timeout = kwargs.get('async_rest_timeout', 60)
|
||||||
|
|
||||||
|
@ -49,7 +49,6 @@ from cinder.volume.drivers.netapp import utils as na_utils
|
|||||||
from cinder.volume.drivers import nfs
|
from cinder.volume.drivers import nfs
|
||||||
from cinder.volume import volume_utils
|
from cinder.volume import volume_utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
HOUSEKEEPING_INTERVAL_SECONDS = 600 # ten minutes
|
HOUSEKEEPING_INTERVAL_SECONDS = 600 # ten minutes
|
||||||
@ -67,8 +66,10 @@ class NetAppNfsDriver(driver.ManageableVD,
|
|||||||
# ThirdPartySystems wiki page
|
# ThirdPartySystems wiki page
|
||||||
CI_WIKI_NAME = "NetApp_CI"
|
CI_WIKI_NAME = "NetApp_CI"
|
||||||
|
|
||||||
REQUIRED_FLAGS = ['netapp_login', 'netapp_password',
|
REQUIRED_FLAGS_BASIC = ['netapp_login', 'netapp_password',
|
||||||
'netapp_server_hostname']
|
'netapp_server_hostname']
|
||||||
|
REQUIRED_FLAGS_CERT = ['netapp_private_key_file',
|
||||||
|
'netapp_certificate_file']
|
||||||
DEFAULT_FILTER_FUNCTION = 'capabilities.utilization < 70'
|
DEFAULT_FILTER_FUNCTION = 'capabilities.utilization < 70'
|
||||||
DEFAULT_GOODNESS_FUNCTION = '100 - capabilities.utilization'
|
DEFAULT_GOODNESS_FUNCTION = '100 - capabilities.utilization'
|
||||||
|
|
||||||
@ -81,6 +82,8 @@ class NetAppNfsDriver(driver.ManageableVD,
|
|||||||
super(NetAppNfsDriver, self).__init__(*args, **kwargs)
|
super(NetAppNfsDriver, self).__init__(*args, **kwargs)
|
||||||
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
||||||
|
self.configuration.append_config_values(
|
||||||
|
na_opts.netapp_certificateauth_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_img_cache_opts)
|
self.configuration.append_config_values(na_opts.netapp_img_cache_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_nfs_extra_opts)
|
self.configuration.append_config_values(na_opts.netapp_nfs_extra_opts)
|
||||||
@ -90,7 +93,12 @@ class NetAppNfsDriver(driver.ManageableVD,
|
|||||||
def do_setup(self, context):
|
def do_setup(self, context):
|
||||||
super(NetAppNfsDriver, self).do_setup(context)
|
super(NetAppNfsDriver, self).do_setup(context)
|
||||||
self._context = context
|
self._context = context
|
||||||
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
|
if self.configuration.netapp_private_key_file or\
|
||||||
|
self.configuration.netapp_certificate_file:
|
||||||
|
na_utils.check_flags(self.REQUIRED_FLAGS_CERT,
|
||||||
|
self.configuration)
|
||||||
|
else:
|
||||||
|
na_utils.check_flags(self.REQUIRED_FLAGS_BASIC, self.configuration)
|
||||||
self.zapi_client = None
|
self.zapi_client = None
|
||||||
|
|
||||||
def check_for_setup_error(self):
|
def check_for_setup_error(self):
|
||||||
@ -542,6 +550,7 @@ class NetAppNfsDriver(driver.ManageableVD,
|
|||||||
|
|
||||||
def _do_clone_rel_img_cache(self, src, dst, share, cache_file):
|
def _do_clone_rel_img_cache(self, src, dst, share, cache_file):
|
||||||
"""Do clone operation w.r.t image cache file."""
|
"""Do clone operation w.r.t image cache file."""
|
||||||
|
|
||||||
@utils.synchronized(cache_file, external=True)
|
@utils.synchronized(cache_file, external=True)
|
||||||
def _do_clone():
|
def _do_clone():
|
||||||
dir = self._get_mount_point_for_share(share)
|
dir = self._get_mount_point_for_share(share)
|
||||||
@ -552,6 +561,7 @@ class NetAppNfsDriver(driver.ManageableVD,
|
|||||||
share=share)
|
share=share)
|
||||||
src_path = '%s/%s' % (dir, src)
|
src_path = '%s/%s' % (dir, src)
|
||||||
os.utime(src_path, None)
|
os.utime(src_path, None)
|
||||||
|
|
||||||
_do_clone()
|
_do_clone()
|
||||||
|
|
||||||
def _clean_image_cache(self):
|
def _clean_image_cache(self):
|
||||||
|
@ -63,8 +63,10 @@ class NetAppNVMeStorageLibrary(
|
|||||||
|
|
||||||
# do not increment this as it may be used in volume type definitions.
|
# do not increment this as it may be used in volume type definitions.
|
||||||
VERSION = "1.0.0"
|
VERSION = "1.0.0"
|
||||||
REQUIRED_FLAGS = ['netapp_login', 'netapp_password',
|
REQUIRED_FLAGS_BASIC = ['netapp_login', 'netapp_password',
|
||||||
'netapp_server_hostname']
|
'netapp_server_hostname']
|
||||||
|
REQUIRED_FLAGS_CERT = ['netapp_private_key_file',
|
||||||
|
'netapp_certificate_file']
|
||||||
ALLOWED_NAMESPACE_OS_TYPES = ['aix', 'linux', 'vmware', 'windows']
|
ALLOWED_NAMESPACE_OS_TYPES = ['aix', 'linux', 'vmware', 'windows']
|
||||||
ALLOWED_SUBSYSTEM_HOST_TYPES = ['aix', 'linux', 'vmware', 'windows']
|
ALLOWED_SUBSYSTEM_HOST_TYPES = ['aix', 'linux', 'vmware', 'windows']
|
||||||
DEFAULT_NAMESPACE_OS = 'linux'
|
DEFAULT_NAMESPACE_OS = 'linux'
|
||||||
@ -93,6 +95,8 @@ class NetAppNVMeStorageLibrary(
|
|||||||
self.configuration = kwargs['configuration']
|
self.configuration = kwargs['configuration']
|
||||||
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
||||||
|
self.configuration.append_config_values(
|
||||||
|
na_opts.netapp_certificateauth_opts)
|
||||||
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
||||||
self.configuration.append_config_values(
|
self.configuration.append_config_values(
|
||||||
na_opts.netapp_provisioning_opts)
|
na_opts.netapp_provisioning_opts)
|
||||||
@ -107,7 +111,13 @@ class NetAppNVMeStorageLibrary(
|
|||||||
self.loopingcalls = loopingcalls.LoopingCalls()
|
self.loopingcalls = loopingcalls.LoopingCalls()
|
||||||
|
|
||||||
def do_setup(self, context):
|
def do_setup(self, context):
|
||||||
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
|
if self.configuration.netapp_private_key_file or\
|
||||||
|
self.configuration.netapp_certificate_file:
|
||||||
|
na_utils.check_flags(self.REQUIRED_FLAGS_CERT,
|
||||||
|
self.configuration)
|
||||||
|
else:
|
||||||
|
na_utils.check_flags(self.REQUIRED_FLAGS_BASIC,
|
||||||
|
self.configuration)
|
||||||
self.namespace_ostype = (self.configuration.netapp_namespace_ostype
|
self.namespace_ostype = (self.configuration.netapp_namespace_ostype
|
||||||
or self.DEFAULT_NAMESPACE_OS)
|
or self.DEFAULT_NAMESPACE_OS)
|
||||||
self.host_type = (self.configuration.netapp_host_type
|
self.host_type = (self.configuration.netapp_host_type
|
||||||
|
@ -52,6 +52,7 @@ def get_backend_configuration(backend_name):
|
|||||||
config.append_config_values(na_opts.netapp_connection_opts)
|
config.append_config_values(na_opts.netapp_connection_opts)
|
||||||
config.append_config_values(na_opts.netapp_transport_opts)
|
config.append_config_values(na_opts.netapp_transport_opts)
|
||||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||||
|
config.append_config_values(na_opts.netapp_certificateauth_opts)
|
||||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||||
config.append_config_values(na_opts.netapp_cluster_opts)
|
config.append_config_values(na_opts.netapp_cluster_opts)
|
||||||
config.append_config_values(na_opts.netapp_san_opts)
|
config.append_config_values(na_opts.netapp_san_opts)
|
||||||
@ -72,6 +73,11 @@ def get_client_for_backend(backend_name, vserver_name=None, force_rest=False):
|
|||||||
username=config.netapp_login,
|
username=config.netapp_login,
|
||||||
password=config.netapp_password,
|
password=config.netapp_password,
|
||||||
hostname=config.netapp_server_hostname,
|
hostname=config.netapp_server_hostname,
|
||||||
|
private_key_file=config.netapp_private_key_file,
|
||||||
|
certificate_file=config.netapp_certificate_file,
|
||||||
|
ca_certificate_file=config.netapp_ca_certificate_file,
|
||||||
|
certificate_host_validation=
|
||||||
|
config.netapp_certificate_host_validation,
|
||||||
port=config.netapp_server_port,
|
port=config.netapp_server_port,
|
||||||
vserver=vserver_name or config.netapp_vserver,
|
vserver=vserver_name or config.netapp_vserver,
|
||||||
trace=volume_utils.TRACE_API,
|
trace=volume_utils.TRACE_API,
|
||||||
@ -83,12 +89,16 @@ def get_client_for_backend(backend_name, vserver_name=None, force_rest=False):
|
|||||||
username=config.netapp_login,
|
username=config.netapp_login,
|
||||||
password=config.netapp_password,
|
password=config.netapp_password,
|
||||||
hostname=config.netapp_server_hostname,
|
hostname=config.netapp_server_hostname,
|
||||||
|
private_key_file=config.netapp_private_key_file,
|
||||||
|
certificate_file=config.netapp_certificate_file,
|
||||||
|
ca_certificate_file=config.netapp_ca_certificate_file,
|
||||||
|
certificate_host_validation=
|
||||||
|
config.netapp_certificate_host_validation,
|
||||||
port=config.netapp_server_port,
|
port=config.netapp_server_port,
|
||||||
vserver=vserver_name or config.netapp_vserver,
|
vserver=vserver_name or config.netapp_vserver,
|
||||||
trace=volume_utils.TRACE_API,
|
trace=volume_utils.TRACE_API,
|
||||||
api_trace_pattern=config.netapp_api_trace_pattern,
|
api_trace_pattern=config.netapp_api_trace_pattern,
|
||||||
async_rest_timeout=config.netapp_async_rest_timeout)
|
async_rest_timeout=config.netapp_async_rest_timeout)
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,6 +88,80 @@ netapp_basicauth_opts = [
|
|||||||
'specified in the netapp_login option.'),
|
'specified in the netapp_login option.'),
|
||||||
secret=True), ]
|
secret=True), ]
|
||||||
|
|
||||||
|
netapp_certificateauth_opts = [
|
||||||
|
cfg.StrOpt('netapp_private_key_file',
|
||||||
|
sample_default='/path/to/private_key.key',
|
||||||
|
help=("""
|
||||||
|
This option is applicable for both self signed and ca
|
||||||
|
verified certificates.
|
||||||
|
|
||||||
|
For self signed certificate: Absolute path to the file
|
||||||
|
containing the private key associated with the self
|
||||||
|
signed certificate. It is a sensitive file that should
|
||||||
|
be kept secure and protected. The private key is used
|
||||||
|
to sign the certificate and establish the authenticity
|
||||||
|
and integrity of the certificate during the
|
||||||
|
authentication process.
|
||||||
|
|
||||||
|
For ca verified certificate: Absolute path to the file
|
||||||
|
containing the private key associated with the
|
||||||
|
certificate. It is generated when creating the
|
||||||
|
certificate signingrequest (CSR) and should be kept
|
||||||
|
secure and protected. The private key is used to sign
|
||||||
|
the CSR and later used to establish secure connections
|
||||||
|
and authenticate the entity.
|
||||||
|
"""),
|
||||||
|
secret=True),
|
||||||
|
cfg.StrOpt('netapp_certificate_file',
|
||||||
|
sample_default='/path/to/certificate.pem',
|
||||||
|
help=("""
|
||||||
|
This option is applicable for both self signed and ca
|
||||||
|
verified certificates.
|
||||||
|
|
||||||
|
For self signed certificate: Absolute path to the file
|
||||||
|
containing the self-signed digital certificate itself.
|
||||||
|
It includes information about the entity such as the
|
||||||
|
common name (e.g., domain name), organization details,
|
||||||
|
validity period, and public key. The certificate file
|
||||||
|
is generated based on the private key and is used by
|
||||||
|
clients or systems to verify the entity identity during
|
||||||
|
the authentication process.
|
||||||
|
|
||||||
|
For ca verified certificate: Absolute path to the file
|
||||||
|
containing the digital certificate issued by the
|
||||||
|
trusted third-party certificate authority (CA). It
|
||||||
|
includes information about the entity identity, public
|
||||||
|
key, and the CA that issued the certificate. The
|
||||||
|
certificate file is used by clients or systems to verify
|
||||||
|
the authenticity and integrity of the entity during the
|
||||||
|
authentication process.
|
||||||
|
"""),
|
||||||
|
secret=True),
|
||||||
|
cfg.StrOpt('netapp_ca_certificate_file',
|
||||||
|
sample_default='/path/to/ca_certificate.crt',
|
||||||
|
help=("""
|
||||||
|
This option is applicable only for a ca verified
|
||||||
|
certificate.
|
||||||
|
|
||||||
|
Ca verified file: Absolute path to the file containing
|
||||||
|
the public key certificate of the trusted third-party
|
||||||
|
certificate authority (CA) that issued the certificate.
|
||||||
|
It is used by clients or systems to validate the
|
||||||
|
authenticity of the certificate presented by the
|
||||||
|
entity. The CA certificate file is typically pre
|
||||||
|
configured in the trust store of clients or systems to
|
||||||
|
establish trust in certificates issued by that CA.
|
||||||
|
"""),
|
||||||
|
secret=True),
|
||||||
|
cfg.BoolOpt('netapp_certificate_host_validation',
|
||||||
|
default=False,
|
||||||
|
help=('This option is used only if netapp_private_key_file'
|
||||||
|
' and netapp_certificate_file files are passed in the'
|
||||||
|
' configuration.'
|
||||||
|
' By default certificate verification is disabled'
|
||||||
|
' and to verify the certificates please set the value'
|
||||||
|
' to True.')), ]
|
||||||
|
|
||||||
netapp_provisioning_opts = [
|
netapp_provisioning_opts = [
|
||||||
cfg.FloatOpt('netapp_size_multiplier',
|
cfg.FloatOpt('netapp_size_multiplier',
|
||||||
default=NETAPP_SIZE_MULTIPLIER_DEFAULT,
|
default=NETAPP_SIZE_MULTIPLIER_DEFAULT,
|
||||||
@ -245,6 +319,7 @@ CONF.register_opts(netapp_proxy_opts, group=conf.SHARED_CONF_GROUP)
|
|||||||
CONF.register_opts(netapp_connection_opts, group=conf.SHARED_CONF_GROUP)
|
CONF.register_opts(netapp_connection_opts, group=conf.SHARED_CONF_GROUP)
|
||||||
CONF.register_opts(netapp_transport_opts, group=conf.SHARED_CONF_GROUP)
|
CONF.register_opts(netapp_transport_opts, group=conf.SHARED_CONF_GROUP)
|
||||||
CONF.register_opts(netapp_basicauth_opts, group=conf.SHARED_CONF_GROUP)
|
CONF.register_opts(netapp_basicauth_opts, group=conf.SHARED_CONF_GROUP)
|
||||||
|
CONF.register_opts(netapp_certificateauth_opts, group=conf.SHARED_CONF_GROUP)
|
||||||
CONF.register_opts(netapp_cluster_opts, group=conf.SHARED_CONF_GROUP)
|
CONF.register_opts(netapp_cluster_opts, group=conf.SHARED_CONF_GROUP)
|
||||||
CONF.register_opts(netapp_provisioning_opts, group=conf.SHARED_CONF_GROUP)
|
CONF.register_opts(netapp_provisioning_opts, group=conf.SHARED_CONF_GROUP)
|
||||||
CONF.register_opts(netapp_img_cache_opts, group=conf.SHARED_CONF_GROUP)
|
CONF.register_opts(netapp_img_cache_opts, group=conf.SHARED_CONF_GROUP)
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The NetApp ONTAP driver now supports Certificate-Based-Authentication (CBA)
|
||||||
|
for operators that desire certificate based authentication instead of user
|
||||||
|
and password.
|
||||||
|
Note: The options for cert-auth take precedence, if all the auth options
|
||||||
|
are defined in the config (both cert and legacy), the legacy ones are
|
||||||
|
ignored.
|
Loading…
x
Reference in New Issue
Block a user