4112 lines
152 KiB
Python
4112 lines
152 KiB
Python
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
|
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
|
|
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
|
# Copyright (c) 2016 Mike Rooney. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import copy
|
|
from unittest import mock
|
|
import uuid
|
|
|
|
import ddt
|
|
from oslo_utils import units
|
|
import six
|
|
|
|
from cinder import exception
|
|
from cinder.tests.unit import test
|
|
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
|
|
fakes as fake_client)
|
|
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
|
|
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
|
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
|
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
|
|
from cinder.volume.drivers.netapp.dataontap.client import client_cmode_rest
|
|
from cinder.volume.drivers.netapp import utils as netapp_utils
|
|
|
|
|
|
CONNECTION_INFO = {'hostname': 'hostname',
|
|
'transport_type': 'https',
|
|
'port': 443,
|
|
'username': 'admin',
|
|
'password': 'passw0rd',
|
|
'vserver': 'fake_vserver',
|
|
'ssl_cert_path': 'fake_ca',
|
|
'api_trace_pattern': 'fake_regex'}
|
|
|
|
|
|
@ddt.ddt
|
|
class NetAppRestCmodeClientTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(NetAppRestCmodeClientTestCase, self).setUp()
|
|
|
|
# Setup Client mocks
|
|
self.mock_object(client_cmode.Client, '_init_ssh_client')
|
|
# store the original reference so we can call it later in
|
|
# test__get_cluster_nodes_info
|
|
self.original_get_cluster_nodes_info = (
|
|
client_cmode.Client._get_cluster_nodes_info)
|
|
self.mock_object(client_cmode.Client, '_get_cluster_nodes_info',
|
|
return_value=fake.HYBRID_SYSTEM_NODES_INFO)
|
|
self.mock_object(client_cmode.Client, 'get_ontap_version',
|
|
return_value=(9, 11, 1))
|
|
self.mock_object(client_cmode.Client,
|
|
'get_ontapi_version',
|
|
return_value=(1, 20))
|
|
|
|
# Setup RestClient mocks
|
|
self.mock_object(client_cmode_rest.RestClient, '_init_ssh_client')
|
|
# store the original reference so we can call it later in
|
|
# test__get_cluster_nodes_info
|
|
self.original_get_cluster_nodes_info = (
|
|
client_cmode_rest.RestClient._get_cluster_nodes_info)
|
|
|
|
# Temporary fix because the function is under implementation
|
|
if not hasattr(client_cmode_rest.RestClient,
|
|
'_get_cluster_nodes_info'):
|
|
setattr(client_cmode_rest.RestClient,
|
|
'_get_cluster_nodes_info',
|
|
None)
|
|
self.original_get_cluster_nodes_info = (
|
|
client_cmode_rest.RestClient._get_cluster_nodes_info)
|
|
|
|
self.mock_object(client_cmode_rest.RestClient,
|
|
'_get_cluster_nodes_info',
|
|
return_value=fake.HYBRID_SYSTEM_NODES_INFO)
|
|
self.mock_object(client_cmode_rest.RestClient, 'get_ontap_version',
|
|
return_value=(9, 11, 1))
|
|
with mock.patch.object(client_cmode_rest.RestClient,
|
|
'get_ontap_version',
|
|
return_value=(9, 11, 1)):
|
|
self.client = client_cmode_rest.RestClient(**CONNECTION_INFO)
|
|
|
|
self.client.ssh_client = mock.MagicMock()
|
|
self.client.connection = mock.MagicMock()
|
|
self.connection = self.client.connection
|
|
|
|
self.vserver = CONNECTION_INFO['vserver']
|
|
self.fake_volume = six.text_type(uuid.uuid4())
|
|
self.fake_lun = six.text_type(uuid.uuid4())
|
|
# this line interferes in test__get_cluster_nodes_info
|
|
# self.mock_send_request = self.mock_object(
|
|
# self.client, 'send_request')
|
|
|
|
def _mock_api_error(self, code='fake'):
|
|
return mock.Mock(side_effect=netapp_api.NaApiError(code=code))
|
|
|
|
def test_send_request(self):
|
|
expected = 'fake_response'
|
|
mock_get_records = self.mock_object(
|
|
self.client, 'get_records',
|
|
mock.Mock(return_value=expected))
|
|
|
|
res = self.client.send_request(
|
|
fake_client.FAKE_ACTION_ENDPOINT, 'get',
|
|
body=fake_client.FAKE_BODY,
|
|
query=fake_client.FAKE_HTTP_QUERY, enable_tunneling=False)
|
|
|
|
self.assertEqual(expected, res)
|
|
mock_get_records.assert_called_once_with(
|
|
fake_client.FAKE_ACTION_ENDPOINT,
|
|
fake_client.FAKE_HTTP_QUERY, False, 10000)
|
|
|
|
def test_send_request_post(self):
|
|
expected = (201, 'fake_response')
|
|
mock_invoke = self.mock_object(
|
|
self.client.connection, 'invoke_successfully',
|
|
mock.Mock(return_value=expected))
|
|
|
|
res = self.client.send_request(
|
|
fake_client.FAKE_ACTION_ENDPOINT, 'post',
|
|
body=fake_client.FAKE_BODY,
|
|
query=fake_client.FAKE_HTTP_QUERY, enable_tunneling=False)
|
|
|
|
self.assertEqual(expected[1], res)
|
|
mock_invoke.assert_called_once_with(
|
|
fake_client.FAKE_ACTION_ENDPOINT, 'post',
|
|
body=fake_client.FAKE_BODY,
|
|
query=fake_client.FAKE_HTTP_QUERY, enable_tunneling=False)
|
|
|
|
def test_send_request_wait(self):
|
|
expected = (202, fake_client.JOB_RESPONSE_REST)
|
|
mock_invoke = self.mock_object(
|
|
self.client.connection, 'invoke_successfully',
|
|
mock.Mock(return_value=expected))
|
|
|
|
mock_wait = self.mock_object(
|
|
self.client, '_wait_job_result',
|
|
mock.Mock(return_value=expected[1]))
|
|
|
|
res = self.client.send_request(
|
|
fake_client.FAKE_ACTION_ENDPOINT, 'post',
|
|
body=fake_client.FAKE_BODY,
|
|
query=fake_client.FAKE_HTTP_QUERY, enable_tunneling=False)
|
|
|
|
self.assertEqual(expected[1], res)
|
|
mock_invoke.assert_called_once_with(
|
|
fake_client.FAKE_ACTION_ENDPOINT, 'post',
|
|
body=fake_client.FAKE_BODY,
|
|
query=fake_client.FAKE_HTTP_QUERY, enable_tunneling=False)
|
|
mock_wait.assert_called_once_with(
|
|
expected[1]['job']['_links']['self']['href'][4:])
|
|
|
|
@ddt.data(True, False)
|
|
def test_get_records(self, enable_tunneling):
|
|
api_responses = [
|
|
(200, fake_client.VOLUME_GET_ITER_RESPONSE_REST_PAGE),
|
|
(200, fake_client.VOLUME_GET_ITER_RESPONSE_REST_PAGE),
|
|
(200, fake_client.VOLUME_GET_ITER_RESPONSE_REST_LAST_PAGE),
|
|
]
|
|
|
|
mock_invoke = self.mock_object(
|
|
self.client.connection, 'invoke_successfully',
|
|
side_effect=copy.deepcopy(api_responses))
|
|
|
|
query = {
|
|
'fields': 'name'
|
|
}
|
|
|
|
result = self.client.get_records(
|
|
'/storage/volumes/', query=query,
|
|
enable_tunneling=enable_tunneling,
|
|
max_page_length=10)
|
|
|
|
num_records = result['num_records']
|
|
self.assertEqual(28, num_records)
|
|
self.assertEqual(28, len(result['records']))
|
|
|
|
expected_records = []
|
|
expected_records.extend(api_responses[0][1]['records'])
|
|
expected_records.extend(api_responses[1][1]['records'])
|
|
expected_records.extend(api_responses[2][1]['records'])
|
|
|
|
self.assertEqual(expected_records, result['records'])
|
|
|
|
next_tag = result.get('next')
|
|
self.assertIsNone(next_tag)
|
|
|
|
expected_query = copy.deepcopy(query)
|
|
expected_query['max_records'] = 10
|
|
|
|
next_url_1 = api_responses[0][1]['_links']['next']['href'][4:]
|
|
next_url_2 = api_responses[1][1]['_links']['next']['href'][4:]
|
|
|
|
mock_invoke.assert_has_calls([
|
|
mock.call('/storage/volumes/', 'get', query=expected_query,
|
|
enable_tunneling=enable_tunneling),
|
|
mock.call(next_url_1, 'get', query=None,
|
|
enable_tunneling=enable_tunneling),
|
|
mock.call(next_url_2, 'get', query=None,
|
|
enable_tunneling=enable_tunneling),
|
|
])
|
|
|
|
def test_get_records_single_page(self):
|
|
|
|
api_response = (
|
|
200, fake_client.VOLUME_GET_ITER_RESPONSE_REST_LAST_PAGE)
|
|
mock_invoke = self.mock_object(self.client.connection,
|
|
'invoke_successfully',
|
|
return_value=api_response)
|
|
|
|
query = {
|
|
'fields': 'name'
|
|
}
|
|
|
|
result = self.client.get_records(
|
|
'/storage/volumes/', query=query, max_page_length=10)
|
|
|
|
num_records = result['num_records']
|
|
self.assertEqual(8, num_records)
|
|
self.assertEqual(8, len(result['records']))
|
|
|
|
next_tag = result.get('next')
|
|
self.assertIsNone(next_tag)
|
|
|
|
args = copy.deepcopy(query)
|
|
args['max_records'] = 10
|
|
|
|
mock_invoke.assert_has_calls([
|
|
mock.call('/storage/volumes/', 'get', query=args,
|
|
enable_tunneling=True),
|
|
])
|
|
|
|
def test_get_records_not_found(self):
|
|
|
|
api_response = (200, fake_client.NO_RECORDS_RESPONSE_REST)
|
|
mock_invoke = self.mock_object(self.client.connection,
|
|
'invoke_successfully',
|
|
return_value=api_response)
|
|
|
|
result = self.client.get_records('/storage/volumes/')
|
|
|
|
num_records = result['num_records']
|
|
self.assertEqual(0, num_records)
|
|
self.assertEqual(0, len(result['records']))
|
|
|
|
args = {
|
|
'max_records': client_cmode_rest.DEFAULT_MAX_PAGE_LENGTH
|
|
}
|
|
|
|
mock_invoke.assert_has_calls([
|
|
mock.call('/storage/volumes/', 'get', query=args,
|
|
enable_tunneling=True),
|
|
])
|
|
|
|
def test_get_records_timeout(self):
|
|
# To simulate timeout, max_records is 30, but the API returns less
|
|
# records and fill the 'next url' pointing to the next page.
|
|
max_records = 30
|
|
api_responses = [
|
|
(200, fake_client.VOLUME_GET_ITER_RESPONSE_REST_PAGE),
|
|
(200, fake_client.VOLUME_GET_ITER_RESPONSE_REST_PAGE),
|
|
(200, fake_client.VOLUME_GET_ITER_RESPONSE_REST_LAST_PAGE),
|
|
]
|
|
|
|
mock_invoke = self.mock_object(
|
|
self.client.connection, 'invoke_successfully',
|
|
side_effect=copy.deepcopy(api_responses))
|
|
|
|
query = {
|
|
'fields': 'name'
|
|
}
|
|
|
|
result = self.client.get_records(
|
|
'/storage/volumes/', query=query, max_page_length=max_records)
|
|
|
|
num_records = result['num_records']
|
|
self.assertEqual(28, num_records)
|
|
self.assertEqual(28, len(result['records']))
|
|
|
|
expected_records = []
|
|
expected_records.extend(api_responses[0][1]['records'])
|
|
expected_records.extend(api_responses[1][1]['records'])
|
|
expected_records.extend(api_responses[2][1]['records'])
|
|
|
|
self.assertEqual(expected_records, result['records'])
|
|
|
|
next_tag = result.get('next', None)
|
|
self.assertIsNone(next_tag)
|
|
|
|
args1 = copy.deepcopy(query)
|
|
args1['max_records'] = max_records
|
|
|
|
next_url_1 = api_responses[0][1]['_links']['next']['href'][4:]
|
|
next_url_2 = api_responses[1][1]['_links']['next']['href'][4:]
|
|
|
|
mock_invoke.assert_has_calls([
|
|
mock.call('/storage/volumes/', 'get', query=args1,
|
|
enable_tunneling=True),
|
|
mock.call(next_url_1, 'get', query=None, enable_tunneling=True),
|
|
mock.call(next_url_2, 'get', query=None, enable_tunneling=True),
|
|
])
|
|
|
|
def test__get_unique_volume(self):
|
|
api_response = fake_client.VOLUME_GET_ITER_STYLE_RESPONSE_REST
|
|
|
|
result = self.client._get_unique_volume(api_response["records"])
|
|
|
|
expected = fake_client.VOLUME_FLEXGROUP_STYLE_REST
|
|
self.assertEqual(expected, result)
|
|
|
|
def test__get_unique_volume_raise_exception(self):
|
|
api_response = fake_client.VOLUME_GET_ITER_SAME_STYLE_RESPONSE_REST
|
|
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.client._get_unique_volume,
|
|
api_response["records"])
|
|
|
|
@ddt.data(fake.REST_FIELDS, None)
|
|
def test__get_volume_by_args(self, fields):
|
|
mock_get_unique_vol = self.mock_object(
|
|
self.client, '_get_unique_volume',
|
|
return_value=fake_client.VOLUME_GET_ITER_SSC_RESPONSE_STR_REST)
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.VOLUME_GET_ITER_SSC_RESPONSE_REST)
|
|
|
|
volume = self.client._get_volume_by_args(
|
|
vol_name=fake.VOLUME_NAME, vol_path=fake.VOLUME_PATH,
|
|
vserver=fake.VSERVER_NAME, fields=fields)
|
|
|
|
self.assertEqual(fake_client.VOLUME_GET_ITER_SSC_RESPONSE_STR_REST,
|
|
volume)
|
|
mock_get_unique_vol.assert_called_once_with(
|
|
fake_client.VOLUME_GET_ITER_SSC_RESPONSE_REST['records'])
|
|
expected_query = {
|
|
'type': 'rw',
|
|
'style': 'flex*',
|
|
'is_svm_root': 'false',
|
|
'error_state.is_inconsistent': 'false',
|
|
'state': 'online',
|
|
'name': fake.VOLUME_NAME,
|
|
'nas.path': fake.VOLUME_PATH,
|
|
'svm.name': fake.VSERVER_NAME,
|
|
'fields': 'name,style' if not fields else fields,
|
|
}
|
|
mock_send_request.assert_called_once_with('/storage/volumes/', 'get',
|
|
query=expected_query)
|
|
|
|
@ddt.data(False, True)
|
|
def test_get_flexvol(self, is_flexgroup):
|
|
|
|
if is_flexgroup:
|
|
api_response = \
|
|
fake_client.VOLUME_GET_ITER_SSC_RESPONSE_FLEXGROUP_REST
|
|
volume_response = \
|
|
fake_client.VOLUME_GET_ITER_SSC_RESPONSE_STR_FLEXGROUP_REST
|
|
else:
|
|
api_response = fake_client.VOLUME_GET_ITER_SSC_RESPONSE_REST
|
|
volume_response = \
|
|
fake_client.VOLUME_GET_ITER_SSC_RESPONSE_STR_REST
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
mock_get_unique_vol = self.mock_object(
|
|
self.client, '_get_volume_by_args', return_value=volume_response)
|
|
|
|
result = self.client.get_flexvol(
|
|
flexvol_name=fake_client.VOLUME_NAMES[0],
|
|
flexvol_path='/%s' % fake_client.VOLUME_NAMES[0])
|
|
|
|
fields = ('aggregates.name,name,svm.name,nas.path,'
|
|
'type,guarantee.honored,guarantee.type,'
|
|
'space.snapshot.reserve_percent,space.size,'
|
|
'qos.policy.name,snapshot_policy,language,style')
|
|
mock_get_unique_vol.assert_called_once_with(
|
|
vol_name=fake_client.VOLUME_NAMES[0],
|
|
vol_path='/%s' % fake_client.VOLUME_NAMES[0], fields=fields)
|
|
|
|
if is_flexgroup:
|
|
self.assertEqual(fake_client.VOLUME_INFO_SSC_FLEXGROUP, result)
|
|
else:
|
|
self.assertEqual(fake_client.VOLUME_INFO_SSC, result)
|
|
|
|
def test_list_flexvols(self):
|
|
api_response = fake_client.VOLUME_GET_ITER_LIST_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.list_flexvols()
|
|
|
|
query = {
|
|
'type': 'rw',
|
|
'style': 'flex*', # Match both 'flexvol' and 'flexgroup'
|
|
'is_svm_root': 'false',
|
|
'error_state.is_inconsistent': 'false',
|
|
# 'is-invalid': 'false',
|
|
'state': 'online',
|
|
'fields': 'name'
|
|
}
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/volumes/', 'get', query=query)
|
|
self.assertEqual(list(fake_client.VOLUME_NAMES), result)
|
|
|
|
def test_list_flexvols_not_found(self):
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.list_flexvols()
|
|
self.assertEqual([], result)
|
|
|
|
def test_is_flexvol_mirrored(self):
|
|
|
|
api_response = fake_client.GET_NUM_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.is_flexvol_mirrored(
|
|
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
|
|
|
query = {
|
|
'source.path': fake_client.VOLUME_VSERVER_NAME +
|
|
':' + fake_client.VOLUME_NAMES[0],
|
|
'state': 'snapmirrored',
|
|
'return_records': 'false',
|
|
}
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/snapmirror/relationships/', 'get', query=query)
|
|
self.assertTrue(result)
|
|
|
|
def test_is_flexvol_mirrored_not_mirrored(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.is_flexvol_mirrored(
|
|
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_is_flexvol_mirrored_api_error(self):
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=self._mock_api_error())
|
|
|
|
result = self.client.is_flexvol_mirrored(
|
|
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_is_flexvol_encrypted(self):
|
|
|
|
api_response = fake_client.GET_NUM_RECORDS_RESPONSE_REST
|
|
self.client.features.add_feature('FLEXVOL_ENCRYPTION')
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.is_flexvol_encrypted(
|
|
fake_client.VOLUME_NAME, fake_client.VOLUME_VSERVER_NAME)
|
|
|
|
query = {
|
|
'encryption.enabled': 'true',
|
|
'name': fake_client.VOLUME_NAME,
|
|
'svm.name': fake_client.VOLUME_VSERVER_NAME,
|
|
'return_records': 'false',
|
|
}
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/volumes/', 'get', query=query)
|
|
|
|
self.assertTrue(result)
|
|
|
|
def test_is_flexvol_encrypted_unsupported_version(self):
|
|
|
|
self.client.features.add_feature('FLEXVOL_ENCRYPTION', supported=False)
|
|
result = self.client.is_flexvol_encrypted(
|
|
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_is_flexvol_encrypted_no_records_found(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.is_flexvol_encrypted(
|
|
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_is_flexvol_encrypted_api_error(self):
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=self._mock_api_error())
|
|
|
|
result = self.client.is_flexvol_encrypted(
|
|
fake_client.VOLUME_NAMES[0], fake_client.VOLUME_VSERVER_NAME)
|
|
|
|
self.assertFalse(result)
|
|
|
|
@ddt.data({'types': {'FCAL'}, 'expected': ['FCAL']},
|
|
{'types': {'SATA', 'SSD'}, 'expected': ['SATA', 'SSD']},)
|
|
@ddt.unpack
|
|
def test_get_aggregate_disk_types(self, types, expected):
|
|
|
|
mock_get_aggregate_disk_types = self.mock_object(
|
|
self.client, '_get_aggregate_disk_types', return_value=types)
|
|
|
|
result = self.client.get_aggregate_disk_types(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertCountEqual(expected, result)
|
|
mock_get_aggregate_disk_types.assert_called_once_with(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
def test_get_aggregate_disk_types_not_found(self):
|
|
|
|
mock_get_aggregate_disk_types = self.mock_object(
|
|
self.client, '_get_aggregate_disk_types', return_value=set())
|
|
|
|
result = self.client.get_aggregate_disk_types(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertIsNone(result)
|
|
mock_get_aggregate_disk_types.assert_called_once_with(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
def test_get_aggregate_disk_types_api_not_found(self):
|
|
|
|
api_error = netapp_api.NaApiError()
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=api_error)
|
|
|
|
result = self.client.get_aggregate_disk_types(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test__get_aggregates(self):
|
|
|
|
api_response = fake_client.AGGR_GET_ITER_RESPONSE_REST
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client._get_aggregates()
|
|
|
|
mock_send_request.assert_has_calls(
|
|
[mock.call('/storage/aggregates', 'get', query={},
|
|
enable_tunneling=False)])
|
|
self.assertEqual(result, api_response['records'])
|
|
|
|
def test__get_aggregates_with_filters(self):
|
|
|
|
api_response = fake_client.AGGR_GET_ITER_RESPONSE_REST
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
query = {
|
|
'fields': 'space.block_storage.size,space.block_storage.available',
|
|
'name': ','.join(fake_client.VOLUME_AGGREGATE_NAMES),
|
|
}
|
|
|
|
result = self.client._get_aggregates(
|
|
aggregate_names=fake_client.VOLUME_AGGREGATE_NAMES,
|
|
fields=query['fields'])
|
|
|
|
mock_send_request.assert_has_calls([
|
|
mock.call('/storage/aggregates', 'get', query=query,
|
|
enable_tunneling=False)])
|
|
self.assertEqual(result, api_response['records'])
|
|
|
|
def test__get_aggregates_not_found(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client._get_aggregates()
|
|
|
|
mock_send_request.assert_has_calls([
|
|
mock.call('/storage/aggregates', 'get', query={},
|
|
enable_tunneling=False)])
|
|
self.assertEqual([], result)
|
|
|
|
def test_get_aggregate_none_specified(self):
|
|
|
|
result = self.client.get_aggregate('')
|
|
|
|
self.assertEqual({}, result)
|
|
|
|
def test_get_aggregate(self):
|
|
|
|
api_response = [fake_client.AGGR_GET_ITER_RESPONSE_REST['records'][1]]
|
|
|
|
mock__get_aggregates = self.mock_object(self.client,
|
|
'_get_aggregates',
|
|
return_value=api_response)
|
|
|
|
response = self.client.get_aggregate(fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
fields = ('name,block_storage.primary.raid_type,'
|
|
'block_storage.storage_type,home_node.name')
|
|
mock__get_aggregates.assert_has_calls([
|
|
mock.call(
|
|
aggregate_names=[fake_client.VOLUME_AGGREGATE_NAME],
|
|
fields=fields)])
|
|
|
|
expected = {
|
|
'name': fake_client.VOLUME_AGGREGATE_NAME,
|
|
'raid-type': 'raid0',
|
|
'is-hybrid': False,
|
|
'node-name': fake_client.NODE_NAME,
|
|
}
|
|
self.assertEqual(expected, response)
|
|
|
|
def test_get_aggregate_not_found(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.get_aggregate(fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertEqual({}, result)
|
|
|
|
def test_get_aggregate_api_error(self):
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=self._mock_api_error())
|
|
|
|
result = self.client.get_aggregate(fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertEqual({}, result)
|
|
|
|
def test_get_aggregate_api_not_found(self):
|
|
|
|
api_error = netapp_api.NaApiError(code=netapp_api.REST_API_NOT_FOUND)
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=api_error)
|
|
|
|
result = self.client.get_aggregate(fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertEqual({}, result)
|
|
|
|
@ddt.data(True, False)
|
|
def test_is_qos_min_supported(self, supported):
|
|
self.client.features.add_feature('test', supported=supported)
|
|
mock_name = self.mock_object(netapp_utils,
|
|
'qos_min_feature_name',
|
|
return_value='test')
|
|
result = self.client.is_qos_min_supported(True, 'node')
|
|
|
|
mock_name.assert_called_once_with(True, 'node')
|
|
self.assertEqual(result, supported)
|
|
|
|
def test_is_qos_min_supported_invalid_node(self):
|
|
mock_name = self.mock_object(netapp_utils,
|
|
'qos_min_feature_name',
|
|
return_value='invalid_feature')
|
|
result = self.client.is_qos_min_supported(True, 'node')
|
|
|
|
mock_name.assert_called_once_with(True, 'node')
|
|
self.assertFalse(result)
|
|
|
|
def test_is_qos_min_supported_none_node(self):
|
|
result = self.client.is_qos_min_supported(True, None)
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_get_flexvol_dedupe_info(self):
|
|
|
|
api_response = fake_client.VOLUME_GET_ITER_SSC_RESPONSE_REST
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.get_flexvol_dedupe_info(
|
|
fake_client.VOLUME_NAMES[0])
|
|
|
|
query = {
|
|
'efficiency.volume_path': '/vol/%s' % fake_client.VOLUME_NAMES[0],
|
|
'fields': 'efficiency.state,efficiency.compression'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/storage/volumes', 'get', query=query)
|
|
self.assertEqual(
|
|
fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA, result)
|
|
|
|
def test_get_flexvol_dedupe_info_no_logical_data_values(self):
|
|
|
|
api_response = fake_client.VOLUME_GET_ITER_SSC_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.get_flexvol_dedupe_info(
|
|
fake_client.VOLUME_NAMES[0])
|
|
|
|
self.assertEqual(fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA,
|
|
result)
|
|
|
|
def test_get_flexvol_dedupe_info_not_found(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.get_flexvol_dedupe_info(
|
|
fake_client.VOLUME_NAMES[0])
|
|
|
|
self.assertEqual(fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA,
|
|
result)
|
|
|
|
def test_get_flexvol_dedupe_info_api_error(self):
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=self._mock_api_error())
|
|
|
|
result = self.client.get_flexvol_dedupe_info(
|
|
fake_client.VOLUME_NAMES[0])
|
|
|
|
self.assertEqual(fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA,
|
|
result)
|
|
|
|
def test_get_flexvol_dedupe_info_api_insufficient_privileges(self):
|
|
|
|
api_error = netapp_api.NaApiError(code=netapp_api.EAPIPRIVILEGE)
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=api_error)
|
|
|
|
result = self.client.get_flexvol_dedupe_info(
|
|
fake_client.VOLUME_NAMES[0])
|
|
|
|
self.assertEqual(fake_client.VOLUME_DEDUPE_INFO_SSC_NO_LOGICAL_DATA,
|
|
result)
|
|
|
|
def test_get_lun_list(self):
|
|
response = fake_client.LUN_GET_ITER_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=response)
|
|
|
|
expected_result = fake_client.LUN_GET_ITER_RESULT
|
|
luns = self.client.get_lun_list()
|
|
|
|
self.assertEqual(expected_result, luns)
|
|
self.assertEqual(2, len(luns))
|
|
|
|
def test_get_lun_list_no_records(self):
|
|
response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=response)
|
|
|
|
luns = self.client.get_lun_list()
|
|
|
|
self.assertEqual([], luns)
|
|
|
|
def test_get_lun_sizes_by_volume(self):
|
|
volume_name = fake_client.VOLUME_NAME
|
|
query = {
|
|
'location.volume.name': volume_name,
|
|
'fields': 'space.size,name'
|
|
}
|
|
response = fake_client.LUN_GET_ITER_REST
|
|
expected_result = []
|
|
for lun in fake_client.LUN_GET_ITER_RESULT:
|
|
expected_result.append({
|
|
'size': lun['Size'],
|
|
'path': lun['Path'],
|
|
})
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=response)
|
|
|
|
luns = self.client.get_lun_sizes_by_volume(volume_name)
|
|
|
|
self.assertEqual(expected_result, luns)
|
|
self.assertEqual(2, len(luns))
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/luns/', 'get', query=query)
|
|
|
|
def test_get_lun_sizes_by_volume_no_records(self):
|
|
volume_name = fake_client.VOLUME_NAME
|
|
query = {
|
|
'location.volume.name': volume_name,
|
|
'fields': 'space.size,name'
|
|
}
|
|
response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=response)
|
|
|
|
luns = self.client.get_lun_sizes_by_volume(volume_name)
|
|
|
|
self.assertEqual([], luns)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/luns/', 'get', query=query)
|
|
|
|
def test_get_lun_by_args(self):
|
|
response = fake_client.LUN_GET_ITER_REST
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request', return_value=response)
|
|
|
|
lun_info_args = {
|
|
'vserver': fake.VSERVER_NAME,
|
|
'path': fake.LUN_PATH,
|
|
'uuid': fake.UUID1,
|
|
}
|
|
|
|
luns = self.client.get_lun_by_args(**lun_info_args)
|
|
|
|
query = {
|
|
'svm.name': fake.VSERVER_NAME,
|
|
'name': fake.LUN_PATH,
|
|
'uuid': fake.UUID1,
|
|
'fields': 'svm.name,location.volume.name,space.size,'
|
|
'location.qtree.name,name,os_type,'
|
|
'space.scsi_thin_provisioning_support_enabled,'
|
|
'space.guarantee.requested,uuid'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/storage/luns/', 'get', query=query)
|
|
|
|
self.assertEqual(2, len(luns))
|
|
|
|
def test_get_lun_by_args_no_lun_found(self):
|
|
response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=response)
|
|
|
|
luns = self.client.get_lun_by_args()
|
|
|
|
self.assertEqual([], luns)
|
|
|
|
def test_get_lun_by_args_with_one_arg(self):
|
|
path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
|
response = fake_client.LUN_GET_ITER_REST
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request', return_value=response)
|
|
|
|
luns = self.client.get_lun_by_args(path=path)
|
|
|
|
query = {
|
|
'name': path,
|
|
'fields': 'svm.name,location.volume.name,space.size,'
|
|
'location.qtree.name,name,os_type,'
|
|
'space.scsi_thin_provisioning_support_enabled,'
|
|
'space.guarantee.requested,uuid'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/storage/luns/', 'get', query=query)
|
|
|
|
self.assertEqual(2, len(luns))
|
|
|
|
def test_get_file_sizes_by_dir(self):
|
|
volume = fake_client.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
|
query = {
|
|
'type': 'file',
|
|
'fields': 'size,name'
|
|
}
|
|
response = fake_client.FILE_DIRECTORY_GET_ITER_REST
|
|
expected_result = fake_client.FILE_DIRECTORY_GET_ITER_RESULT_REST
|
|
|
|
self.mock_object(self.client,
|
|
'_get_volume_by_args',
|
|
return_value=volume)
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=response)
|
|
|
|
files = self.client.get_file_sizes_by_dir(volume['name'])
|
|
|
|
self.assertEqual(expected_result, files)
|
|
self.assertEqual(2, len(files))
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/storage/volumes/{volume["uuid"]}/files',
|
|
'get', query=query)
|
|
|
|
def test_get_file_sizes_by_dir_no_records(self):
|
|
volume = fake_client.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
|
query = {
|
|
'type': 'file',
|
|
'fields': 'size,name'
|
|
}
|
|
|
|
api_error = netapp_api.NaApiError(code=netapp_api.REST_NO_SUCH_FILE)
|
|
|
|
self.mock_object(self.client,
|
|
'_get_volume_by_args',
|
|
return_value=volume)
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=api_error)
|
|
|
|
files = self.client.get_file_sizes_by_dir(volume['name'])
|
|
|
|
self.assertEqual([], files)
|
|
self.assertEqual(0, len(files))
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/storage/volumes/{volume["uuid"]}/files',
|
|
'get', query=query)
|
|
|
|
def test_get_file_sizes_by_dir_exception(self):
|
|
volume = fake_client.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
|
api_error = netapp_api.NaApiError(code=0)
|
|
|
|
self.mock_object(self.client,
|
|
'_get_volume_by_args',
|
|
return_value=volume)
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=api_error)
|
|
self.assertRaises(netapp_api.NaApiError,
|
|
self.client.get_file_sizes_by_dir,
|
|
volume['name'])
|
|
|
|
@ddt.data({'junction_path': '/fake/vol'},
|
|
{'name': 'fake_volume'},
|
|
{'junction_path': '/fake/vol', 'name': 'fake_volume'})
|
|
def test_get_volume_state(self, kwargs):
|
|
query_args = {}
|
|
query_args['fields'] = 'state'
|
|
|
|
if 'name' in kwargs:
|
|
query_args['name'] = kwargs['name']
|
|
if 'junction_path' in kwargs:
|
|
query_args['nas.path'] = kwargs['junction_path']
|
|
|
|
response = fake_client.VOLUME_GET_ITER_STATE_RESPONSE_REST
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request', return_value=response)
|
|
|
|
state = self.client.get_volume_state(**kwargs)
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/storage/volumes/', 'get', query=query_args)
|
|
|
|
self.assertEqual(fake_client.VOLUME_STATE_ONLINE, state)
|
|
|
|
def test_delete_snapshot(self):
|
|
volume = fake_client.VOLUME_GET_ITER_SSC_RESPONSE_STR_REST
|
|
self.mock_object(
|
|
self.client, '_get_volume_by_args',
|
|
return_value=volume)
|
|
snap_name = fake.SNAPSHOT["name"]
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.delete_snapshot(volume["name"], snap_name)
|
|
|
|
self.client._get_volume_by_args.assert_called_once_with(
|
|
vol_name=volume["name"])
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/storage/volumes/{volume["uuid"]}/snapshots'
|
|
f'?name={snap_name}', 'delete')
|
|
|
|
def test_get_operational_lif_addresses(self):
|
|
expected_result = ['1.2.3.4', '99.98.97.96']
|
|
api_response = fake_client.GET_OPERATIONAL_LIF_ADDRESSES_RESPONSE_REST
|
|
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
address_list = self.client.get_operational_lif_addresses()
|
|
|
|
query = {
|
|
'state': 'up',
|
|
'fields': 'ip.address',
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/network/ip/interfaces/', 'get', query=query)
|
|
|
|
self.assertEqual(expected_result, address_list)
|
|
|
|
def test__list_vservers(self):
|
|
api_response = fake_client.VSERVER_DATA_LIST_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
result = self.client._list_vservers()
|
|
query = {
|
|
'fields': 'name',
|
|
}
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/svm/svms', 'get', query=query,
|
|
enable_tunneling=False)])
|
|
self.assertListEqual(
|
|
[fake_client.VSERVER_NAME, fake_client.VSERVER_NAME_2], result)
|
|
|
|
def test_list_vservers_not_found(self):
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
result = self.client._list_vservers()
|
|
self.assertListEqual([], result)
|
|
|
|
def test_get_ems_log_destination_vserver(self):
|
|
mock_list_vservers = self.mock_object(
|
|
self.client,
|
|
'_list_vservers',
|
|
return_value=[fake_client.VSERVER_NAME])
|
|
result = self.client._get_ems_log_destination_vserver()
|
|
mock_list_vservers.assert_called_once_with()
|
|
self.assertEqual(fake_client.VSERVER_NAME, result)
|
|
|
|
def test_get_ems_log_destination_vserver_not_found(self):
|
|
mock_list_vservers = self.mock_object(
|
|
self.client,
|
|
'_list_vservers',
|
|
return_value=[])
|
|
|
|
self.assertRaises(exception.NotFound,
|
|
self.client._get_ems_log_destination_vserver)
|
|
|
|
mock_list_vservers.assert_called_once_with()
|
|
|
|
def test_send_ems_log_message(self):
|
|
|
|
message_dict = {
|
|
'computer-name': '25-dev-vm',
|
|
'event-source': 'Cinder driver NetApp_iSCSI_Cluster_direct',
|
|
'app-version': '20.1.0.dev|vendor|Linux-5.4.0-120-generic-x86_64',
|
|
'category': 'provisioning',
|
|
'log-level': '5',
|
|
'auto-support': 'false',
|
|
'event-id': '1',
|
|
'event-description':
|
|
'{"pools": {"vserver": "vserver_name",'
|
|
+ '"aggregates": [], "flexvols": ["flexvol_01"]}}'
|
|
}
|
|
|
|
body = {
|
|
'computer_name': message_dict['computer-name'],
|
|
'event_source': message_dict['event-source'],
|
|
'app_version': message_dict['app-version'],
|
|
'category': message_dict['category'],
|
|
'severity': 'notice',
|
|
'autosupport_required': message_dict['auto-support'] == 'true',
|
|
'event_id': message_dict['event-id'],
|
|
'event_description': message_dict['event-description'],
|
|
}
|
|
|
|
self.mock_object(self.client, '_get_ems_log_destination_vserver',
|
|
return_value='vserver_name')
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.send_ems_log_message(message_dict)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/support/ems/application-logs', 'post', body=body)
|
|
|
|
@ddt.data('cp_phase_times', 'domain_busy')
|
|
def test_get_performance_counter_info(self, counter_name):
|
|
|
|
response1 = fake_client.PERF_COUNTER_LIST_INFO_WAFL_RESPONSE_REST
|
|
response2 = fake_client.PERF_COUNTER_TABLE_ROWS_WAFL
|
|
|
|
object_name = 'wafl'
|
|
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
side_effect=[response1, response2])
|
|
|
|
result = self.client.get_performance_counter_info(object_name,
|
|
counter_name)
|
|
|
|
expected = {
|
|
'name': 'cp_phase_times',
|
|
'base-counter': 'total_cp_msecs',
|
|
'labels': fake_client.PERF_COUNTER_TOTAL_CP_MSECS_LABELS_RESULT,
|
|
}
|
|
|
|
query1 = {
|
|
'counter_schemas.name': counter_name,
|
|
'fields': 'counter_schemas.*'
|
|
}
|
|
|
|
query2 = {
|
|
'counters.name': counter_name,
|
|
'fields': 'counters.*'
|
|
}
|
|
|
|
if counter_name == 'domain_busy':
|
|
expected['name'] = 'domain_busy'
|
|
expected['labels'] = (
|
|
fake_client.PERF_COUNTER_TOTAL_CP_MSECS_LABELS_REST)
|
|
query1['counter_schemas.name'] = 'domain_busy_percent'
|
|
query2['counters.name'] = 'domain_busy_percent'
|
|
|
|
self.assertEqual(expected, result)
|
|
|
|
mock_send_request.assert_has_calls([
|
|
mock.call(f'/cluster/counter/tables/{object_name}',
|
|
'get', query=query1, enable_tunneling=False),
|
|
mock.call(f'/cluster/counter/tables/{object_name}/rows',
|
|
'get', query=query2, enable_tunneling=False),
|
|
])
|
|
|
|
def test_get_performance_counter_info_not_found_rows(self):
|
|
response1 = fake_client.PERF_COUNTER_LIST_INFO_WAFL_RESPONSE_REST
|
|
response2 = fake_client.NO_RECORDS_RESPONSE_REST
|
|
|
|
object_name = 'wafl'
|
|
counter_name = 'cp_phase_times'
|
|
|
|
self.mock_object(
|
|
self.client, 'send_request',
|
|
side_effect=[response1, response2])
|
|
|
|
result = self.client.get_performance_counter_info(object_name,
|
|
counter_name)
|
|
|
|
expected = {
|
|
'name': 'cp_phase_times',
|
|
'base-counter': 'total_cp_msecs',
|
|
'labels': [],
|
|
}
|
|
self.assertEqual(expected, result)
|
|
|
|
def test_get_performance_instance_uuids(self):
|
|
response = fake_client.PERF_COUNTER_TABLE_ROWS_WAFL
|
|
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=response)
|
|
|
|
object_name = 'wafl'
|
|
result = self.client.get_performance_instance_uuids(
|
|
object_name, fake_client.NODE_NAME)
|
|
|
|
expected = [fake_client.NODE_NAME + ':wafl']
|
|
self.assertEqual(expected, result)
|
|
|
|
query = {
|
|
'id': fake_client.NODE_NAME + ':*',
|
|
}
|
|
mock_send_request.assert_called_once_with(
|
|
f'/cluster/counter/tables/{object_name}/rows',
|
|
'get', query=query, enable_tunneling=False)
|
|
|
|
def test_get_performance_counters(self):
|
|
response = fake_client.PERF_GET_INSTANCES_PROCESSOR_RESPONSE_REST
|
|
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=response)
|
|
|
|
instance_uuids = [
|
|
fake_client.NODE_NAME + ':processor0',
|
|
fake_client.NODE_NAME + ':processor1',
|
|
]
|
|
object_name = 'processor'
|
|
counter_names = ['domain_busy', 'processor_elapsed_time']
|
|
rest_counter_names = ['domain_busy_percent', 'elapsed_time']
|
|
result = self.client.get_performance_counters(object_name,
|
|
instance_uuids,
|
|
counter_names)
|
|
|
|
expected = fake_client.PERF_COUNTERS_PROCESSOR_EXPECTED
|
|
self.assertEqual(expected, result)
|
|
|
|
query = {
|
|
'id': '|'.join(instance_uuids),
|
|
'counters.name': '|'.join(rest_counter_names),
|
|
'fields': 'id,counter_table.name,counters.*',
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
f'/cluster/counter/tables/{object_name}/rows',
|
|
'get', query=query, enable_tunneling=False)
|
|
|
|
def test_get_aggregate_capacities(self):
|
|
aggr1_capacities = {
|
|
'percent-used': 50,
|
|
'size-available': 100.0,
|
|
'size-total': 200.0,
|
|
}
|
|
aggr2_capacities = {
|
|
'percent-used': 75,
|
|
'size-available': 125.0,
|
|
'size-total': 500.0,
|
|
}
|
|
mock_get_aggregate_capacity = self.mock_object(
|
|
self.client, '_get_aggregate_capacity',
|
|
side_effect=[aggr1_capacities, aggr2_capacities])
|
|
|
|
result = self.client.get_aggregate_capacities(['aggr1', 'aggr2'])
|
|
|
|
expected = {
|
|
'aggr1': aggr1_capacities,
|
|
'aggr2': aggr2_capacities,
|
|
}
|
|
self.assertEqual(expected, result)
|
|
mock_get_aggregate_capacity.assert_has_calls([
|
|
mock.call('aggr1'),
|
|
mock.call('aggr2'),
|
|
])
|
|
|
|
def test_get_aggregate_capacities_not_found(self):
|
|
mock_get_aggregate_capacity = self.mock_object(
|
|
self.client, '_get_aggregate_capacity', side_effect=[{}, {}])
|
|
|
|
result = self.client.get_aggregate_capacities(['aggr1', 'aggr2'])
|
|
|
|
expected = {
|
|
'aggr1': {},
|
|
'aggr2': {},
|
|
}
|
|
self.assertEqual(expected, result)
|
|
mock_get_aggregate_capacity.assert_has_calls([
|
|
mock.call('aggr1'),
|
|
mock.call('aggr2'),
|
|
])
|
|
|
|
def test_get_aggregate_capacities_not_list(self):
|
|
result = self.client.get_aggregate_capacities('aggr1')
|
|
self.assertEqual({}, result)
|
|
|
|
def test__get_aggregate_capacity(self):
|
|
api_response = fake_client.AGGR_GET_ITER_RESPONSE_REST['records']
|
|
mock_get_aggregates = self.mock_object(self.client,
|
|
'_get_aggregates',
|
|
return_value=api_response)
|
|
|
|
result = self.client._get_aggregate_capacity(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
fields = ('space.block_storage.available,space.block_storage.size,'
|
|
'space.block_storage.used')
|
|
mock_get_aggregates.assert_has_calls([
|
|
mock.call(aggregate_names=[fake_client.VOLUME_AGGREGATE_NAME],
|
|
fields=fields)])
|
|
|
|
available = float(fake_client.AGGR_SIZE_AVAILABLE)
|
|
total = float(fake_client.AGGR_SIZE_TOTAL)
|
|
used = float(fake_client.AGGR_SIZE_USED)
|
|
percent_used = int((used * 100) // total)
|
|
|
|
expected = {
|
|
'percent-used': percent_used,
|
|
'size-available': available,
|
|
'size-total': total,
|
|
}
|
|
self.assertEqual(expected, result)
|
|
|
|
def test__get_aggregate_capacity_not_found(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client._get_aggregate_capacity(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertEqual({}, result)
|
|
|
|
def test__get_aggregate_capacity_api_error(self):
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=self._mock_api_error())
|
|
|
|
result = self.client._get_aggregate_capacity(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertEqual({}, result)
|
|
|
|
def test__get_aggregate_capacity_api_not_found(self):
|
|
|
|
api_error = netapp_api.NaApiError(code=netapp_api.REST_API_NOT_FOUND)
|
|
self.mock_object(
|
|
self.client, 'send_request', side_effect=api_error)
|
|
|
|
result = self.client._get_aggregate_capacity(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertEqual({}, result)
|
|
|
|
def test_get_node_for_aggregate(self):
|
|
|
|
api_response = fake_client.AGGR_GET_ITER_RESPONSE_REST['records']
|
|
mock_get_aggregates = self.mock_object(self.client,
|
|
'_get_aggregates',
|
|
return_value=api_response)
|
|
|
|
result = self.client.get_node_for_aggregate(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
fields = 'home_node.name'
|
|
mock_get_aggregates.assert_has_calls([
|
|
mock.call(
|
|
aggregate_names=[fake_client.VOLUME_AGGREGATE_NAME],
|
|
fields=fields)])
|
|
|
|
self.assertEqual(fake_client.NODE_NAME, result)
|
|
|
|
def test_get_node_for_aggregate_none_requested(self):
|
|
result = self.client.get_node_for_aggregate(None)
|
|
self.assertIsNone(result)
|
|
|
|
def test_get_node_for_aggregate_api_not_found(self):
|
|
api_error = netapp_api.NaApiError(code=netapp_api.REST_API_NOT_FOUND)
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=api_error)
|
|
|
|
result = self.client.get_node_for_aggregate(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_get_node_for_aggregate_api_error(self):
|
|
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
self._mock_api_error())
|
|
|
|
self.assertRaises(netapp_api.NaApiError,
|
|
self.client.get_node_for_aggregate,
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
def test_get_node_for_aggregate_not_found(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.get_node_for_aggregate(
|
|
fake_client.VOLUME_AGGREGATE_NAME)
|
|
|
|
self.assertIsNone(result)
|
|
|
|
@ddt.data(None, {'legacy': 'fake'}, {})
|
|
def test_provision_qos_policy_group_invalid_policy_info(self, policy_info):
|
|
self.mock_object(self.client, '_validate_qos_policy_group')
|
|
self.mock_object(self.client, '_get_qos_first_policy_group_by_name')
|
|
self.mock_object(self.client, '_create_qos_policy_group')
|
|
self.mock_object(self.client, '_modify_qos_policy_group')
|
|
|
|
self.client.provision_qos_policy_group(policy_info, False)
|
|
|
|
self.client._validate_qos_policy_group.assert_not_called()
|
|
self.client._get_qos_first_policy_group_by_name.assert_not_called()
|
|
self.client._create_qos_policy_group.assert_not_called()
|
|
self.client._modify_qos_policy_group.assert_not_called()
|
|
|
|
@ddt.data(True, False)
|
|
def test_provision_qos_policy_group_qos_policy_create(self, is_adaptive):
|
|
policy_info = fake.QOS_POLICY_GROUP_INFO
|
|
policy_spec = fake.QOS_POLICY_GROUP_SPEC
|
|
if is_adaptive:
|
|
policy_info = fake.ADAPTIVE_QOS_POLICY_GROUP_INFO
|
|
policy_spec = fake.ADAPTIVE_QOS_SPEC
|
|
|
|
self.mock_object(self.client, '_validate_qos_policy_group')
|
|
self.mock_object(self.client, '_get_qos_first_policy_group_by_name',
|
|
return_value=None)
|
|
self.mock_object(self.client, '_create_qos_policy_group')
|
|
self.mock_object(self.client, '_modify_qos_policy_group')
|
|
|
|
self.client.provision_qos_policy_group(policy_info, True)
|
|
|
|
self.client._validate_qos_policy_group.assert_called_once_with(
|
|
is_adaptive, spec=policy_spec, qos_min_support=True)
|
|
(self.client._get_qos_first_policy_group_by_name.
|
|
assert_called_once_with(policy_spec['policy_name']))
|
|
self.client._create_qos_policy_group.assert_called_once_with(
|
|
policy_spec, is_adaptive)
|
|
self.client._modify_qos_policy_group.assert_not_called()
|
|
|
|
@ddt.data(True, False)
|
|
def test_provision_qos_policy_group_qos_policy_modify(self, is_adaptive):
|
|
policy_rest_item = fake.QOS_POLICY_BY_NAME_RESPONSE_REST['records'][0]
|
|
policy_info = fake.QOS_POLICY_GROUP_INFO
|
|
policy_spec = fake.QOS_POLICY_GROUP_SPEC
|
|
if is_adaptive:
|
|
policy_info = fake.ADAPTIVE_QOS_POLICY_GROUP_INFO
|
|
policy_spec = fake.ADAPTIVE_QOS_SPEC
|
|
|
|
self.mock_object(self.client, '_validate_qos_policy_group')
|
|
self.mock_object(self.client, '_get_qos_first_policy_group_by_name',
|
|
return_value=policy_rest_item)
|
|
self.mock_object(self.client, '_create_qos_policy_group')
|
|
self.mock_object(self.client, '_modify_qos_policy_group')
|
|
|
|
self.client.provision_qos_policy_group(policy_info, True)
|
|
|
|
self.client._validate_qos_policy_group.assert_called_once_with(
|
|
is_adaptive, spec=policy_spec, qos_min_support=True)
|
|
(self.client._get_qos_first_policy_group_by_name.
|
|
assert_called_once_with(policy_spec['policy_name']))
|
|
self.client._create_qos_policy_group.assert_not_called()
|
|
self.client._modify_qos_policy_group.assert_called_once_with(
|
|
policy_spec, is_adaptive, policy_rest_item)
|
|
|
|
@ddt.data(True, False)
|
|
def test__get_qos_first_policy_group_by_name(self, is_empty):
|
|
qos_rest_records = []
|
|
qos_item = fake.QOS_POLICY_BY_NAME_RESPONSE_REST['records'][0]
|
|
if not is_empty:
|
|
qos_rest_records = fake.QOS_POLICY_BY_NAME_RESPONSE_REST['records']
|
|
|
|
self.mock_object(self.client, '_get_qos_policy_group_by_name',
|
|
return_value=qos_rest_records)
|
|
|
|
result = self.client._get_qos_first_policy_group_by_name(
|
|
qos_item['name'])
|
|
|
|
self.client._get_qos_policy_group_by_name.assert_called_once_with(
|
|
qos_item['name']
|
|
)
|
|
if not is_empty:
|
|
self.assertEqual(qos_item, result)
|
|
else:
|
|
self.assertTrue(result is None)
|
|
|
|
@ddt.data(True, False)
|
|
def test__get_qos_policy_group_by_name(self, is_empty):
|
|
qos_rest_response = {}
|
|
qos_rest_records = []
|
|
qos_name = fake.QOS_POLICY_BY_NAME_RESPONSE_REST['records'][0]['name']
|
|
if not is_empty:
|
|
qos_rest_response = fake.QOS_POLICY_BY_NAME_RESPONSE_REST
|
|
qos_rest_records = qos_rest_response['records']
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=qos_rest_response)
|
|
|
|
result = self.client._get_qos_policy_group_by_name(qos_name)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/qos/policies/', 'get', query={'name': qos_name})
|
|
self.assertEqual(qos_rest_records, result)
|
|
|
|
@ddt.data(True, False)
|
|
def test__qos_spec_to_api_args(self, is_adaptive):
|
|
policy_spec = copy.deepcopy(fake.QOS_POLICY_GROUP_SPEC)
|
|
expected_args = fake.QOS_POLICY_GROUP_API_ARGS_REST
|
|
if is_adaptive:
|
|
policy_spec = fake.ADAPTIVE_QOS_SPEC
|
|
expected_args = fake.ADAPTIVE_QOS_API_ARGS_REST
|
|
|
|
result = self.client._qos_spec_to_api_args(
|
|
policy_spec, is_adaptive, vserver=fake.VSERVER_NAME)
|
|
|
|
self.assertEqual(expected_args, result)
|
|
|
|
def test__qos_spec_to_api_args_bps(self):
|
|
policy_spec = copy.deepcopy(fake.QOS_POLICY_GROUP_SPEC_BPS)
|
|
expected_args = fake.QOS_POLICY_GROUP_API_ARGS_REST_BPS
|
|
|
|
result = self.client._qos_spec_to_api_args(
|
|
policy_spec, False, vserver=fake.VSERVER_NAME)
|
|
|
|
self.assertEqual(expected_args, result)
|
|
|
|
@ddt.data('100IOPS', '100iops', '100B/s', '100b/s')
|
|
def test__sanitize_qos_spec_value(self, value):
|
|
result = self.client._sanitize_qos_spec_value(value)
|
|
|
|
self.assertEqual(100, result)
|
|
|
|
@ddt.data(True, False)
|
|
def test__create_qos_policy_group(self, is_adaptive):
|
|
self.client.vserver = fake.VSERVER_NAME
|
|
policy_spec = fake.QOS_POLICY_GROUP_SPEC
|
|
body_args = fake.QOS_POLICY_GROUP_API_ARGS_REST
|
|
if is_adaptive:
|
|
policy_spec = fake.ADAPTIVE_QOS_SPEC
|
|
body_args = fake.ADAPTIVE_QOS_API_ARGS_REST
|
|
|
|
self.mock_object(self.client, '_qos_spec_to_api_args',
|
|
return_value=body_args)
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client._create_qos_policy_group(policy_spec, is_adaptive)
|
|
|
|
self.client._qos_spec_to_api_args.assert_called_once_with(
|
|
policy_spec, is_adaptive, vserver=fake.VSERVER_NAME)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/qos/policies/', 'post', body=body_args,
|
|
enable_tunneling=False)
|
|
|
|
@ddt.data((False, False), (False, True), (True, False), (True, True))
|
|
@ddt.unpack
|
|
def test__modify_qos_policy_group(self, is_adaptive, same_name):
|
|
self.client.vserver = fake.VSERVER_NAME
|
|
policy_spec = fake.QOS_POLICY_GROUP_SPEC
|
|
body_args = copy.deepcopy(fake.QOS_POLICY_GROUP_API_ARGS_REST)
|
|
if is_adaptive:
|
|
policy_spec = fake.ADAPTIVE_QOS_SPEC
|
|
body_args = copy.deepcopy(fake.ADAPTIVE_QOS_API_ARGS_REST)
|
|
|
|
expected_body_args = copy.deepcopy(body_args)
|
|
qos_group_item = copy.deepcopy(
|
|
fake.QOS_POLICY_BY_NAME_RESPONSE_REST['records'][0])
|
|
if same_name:
|
|
qos_group_item['name'] = policy_spec['policy_name']
|
|
expected_body_args.pop('name')
|
|
|
|
self.mock_object(self.client, '_qos_spec_to_api_args',
|
|
return_value=body_args)
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client._modify_qos_policy_group(
|
|
policy_spec, is_adaptive, qos_group_item)
|
|
|
|
self.client._qos_spec_to_api_args.assert_called_once_with(
|
|
policy_spec, is_adaptive)
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/storage/qos/policies/{qos_group_item["uuid"]}', 'patch',
|
|
body=expected_body_args, enable_tunneling=False)
|
|
|
|
def test_get_vol_by_junc_vserver(self):
|
|
api_response = fake_client.VOLUME_LIST_SIMPLE_RESPONSE_REST
|
|
volume_response = fake_client.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
|
file_path = f'/vol/{fake_client.VOLUME_NAMES[0]}/cinder-vol'
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=api_response)
|
|
self.mock_object(self.client, '_get_unique_volume',
|
|
return_value=volume_response)
|
|
|
|
result = self.client.get_vol_by_junc_vserver(
|
|
fake_client.VOLUME_VSERVER_NAME, file_path)
|
|
|
|
query = {
|
|
'type': 'rw',
|
|
'style': 'flex*',
|
|
'is_svm_root': 'false',
|
|
'error_state.is_inconsistent': 'false',
|
|
'state': 'online',
|
|
'nas.path': file_path,
|
|
'svm.name': fake_client.VOLUME_VSERVER_NAME,
|
|
'fields': 'name,style'
|
|
}
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/volumes/', 'get', query=query)
|
|
self.client._get_unique_volume.assert_called_once_with(
|
|
api_response["records"])
|
|
|
|
self.assertEqual(volume_response['name'], result)
|
|
|
|
def test_file_assign_qos(self):
|
|
volume = fake_client.VOLUME_GET_ITER_SSC_RESPONSE_STR_REST
|
|
self.mock_object(
|
|
self.client, '_get_volume_by_args',
|
|
return_value=volume)
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.file_assign_qos(
|
|
volume['name'], fake.QOS_POLICY_GROUP_NAME, True, fake.VOLUME_NAME)
|
|
|
|
self.client._get_volume_by_args.assert_called_once_with(volume['name'])
|
|
body = {'qos_policy.name': fake.QOS_POLICY_GROUP_NAME}
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/storage/volumes/{volume["uuid"]}/files/{fake.VOLUME_NAME}',
|
|
'patch', body=body, enable_tunneling=False)
|
|
|
|
@ddt.data(None, {})
|
|
def test_mark_qos_policy_group_for_deletion_invalid_policy(self,
|
|
policy_info):
|
|
self.mock_object(self.client, '_rename_qos_policy_group')
|
|
self.mock_object(self.client, 'remove_unused_qos_policy_groups')
|
|
|
|
self.client.mark_qos_policy_group_for_deletion(policy_info, False)
|
|
|
|
self.client._rename_qos_policy_group.assert_not_called()
|
|
if policy_info is None:
|
|
self.client.remove_unused_qos_policy_groups.assert_not_called()
|
|
else:
|
|
(self.client.remove_unused_qos_policy_groups
|
|
.assert_called_once_with())
|
|
|
|
@ddt.data((False, False), (False, True), (True, False), (True, True))
|
|
@ddt.unpack
|
|
def test_mark_qos_policy_group_for_deletion(self, is_adaptive, has_error):
|
|
policy_info = fake.QOS_POLICY_GROUP_INFO
|
|
if is_adaptive:
|
|
policy_info = fake.ADAPTIVE_QOS_POLICY_GROUP_INFO
|
|
current_name = policy_info['spec']['policy_name']
|
|
deleted_name = client_base.DELETED_PREFIX + current_name
|
|
|
|
self.mock_object(self.client, 'remove_unused_qos_policy_groups')
|
|
if has_error:
|
|
self.mock_object(self.client, '_rename_qos_policy_group',
|
|
side_effect=self._mock_api_error())
|
|
else:
|
|
self.mock_object(self.client, '_rename_qos_policy_group')
|
|
|
|
self.client.mark_qos_policy_group_for_deletion(
|
|
policy_info, is_adaptive)
|
|
|
|
self.client._rename_qos_policy_group.assert_called_once_with(
|
|
current_name, deleted_name)
|
|
self.client.remove_unused_qos_policy_groups.assert_called_once_with()
|
|
|
|
def test__rename_qos_policy_group(self):
|
|
self.mock_object(self.client, 'send_request')
|
|
new_policy_name = 'fake_new_policy'
|
|
|
|
self.client._rename_qos_policy_group(fake.QOS_POLICY_GROUP_NAME,
|
|
new_policy_name)
|
|
|
|
body = {'name': new_policy_name}
|
|
query = {'name': fake.QOS_POLICY_GROUP_NAME}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/qos/policies/', 'patch', body=body, query=query,
|
|
enable_tunneling=False)
|
|
|
|
def test_remove_unused_qos_policy_groups(self):
|
|
deleted_preffix = f'{client_base.DELETED_PREFIX}*'
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.remove_unused_qos_policy_groups()
|
|
|
|
query = {'name': deleted_preffix}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/qos/policies', 'delete', query=query)
|
|
|
|
def test_create_lun(self):
|
|
metadata = copy.deepcopy(fake_client.LUN_GET_ITER_RESULT[0])
|
|
path = f'/vol/{fake.VOLUME_NAME}/{fake.LUN_NAME}'
|
|
size = 2048
|
|
initial_size = size
|
|
qos_policy_group_is_adaptive = False
|
|
|
|
self.mock_object(self.client, '_validate_qos_policy_group')
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
body = {
|
|
'name': path,
|
|
'space.size': str(initial_size),
|
|
'os_type': metadata['OsType'],
|
|
'space.guarantee.requested': metadata['SpaceReserved'],
|
|
'space.scsi_thin_provisioning_support_enabled':
|
|
metadata['SpaceAllocated'],
|
|
'qos_policy.name': fake.QOS_POLICY_GROUP_NAME
|
|
}
|
|
|
|
self.client.create_lun(
|
|
fake.VOLUME_NAME, fake.LUN_NAME, size, metadata,
|
|
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME,
|
|
qos_policy_group_is_adaptive=qos_policy_group_is_adaptive)
|
|
|
|
self.client._validate_qos_policy_group.assert_called_once_with(
|
|
qos_policy_group_is_adaptive)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/luns', 'post', body=body)
|
|
|
|
def test_do_direct_resize(self):
|
|
lun_path = f'/vol/{fake_client.VOLUME_NAMES[0]}/cinder-lun'
|
|
new_size_bytes = '1073741824'
|
|
body = {'name': lun_path, 'space.size': new_size_bytes}
|
|
|
|
self.mock_object(self.client, '_lun_update_by_path')
|
|
|
|
self.client.do_direct_resize(lun_path, new_size_bytes)
|
|
|
|
self.client._lun_update_by_path.assert_called_once_with(lun_path, body)
|
|
|
|
@ddt.data(True, False)
|
|
def test__get_lun_by_path(self, is_empty):
|
|
lun_path = f'/vol/{fake_client.VOLUME_NAMES[0]}/cinder-lun'
|
|
lun_response = fake_client.LUN_GET_ITER_REST
|
|
lun_records = fake_client.LUN_GET_ITER_REST['records']
|
|
if is_empty:
|
|
lun_response = {}
|
|
lun_records = []
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=lun_response)
|
|
|
|
result = self.client._get_lun_by_path(lun_path)
|
|
|
|
query = {'name': lun_path}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/luns', 'get', query=query)
|
|
self.assertEqual(result, lun_records)
|
|
|
|
@ddt.data(True, False)
|
|
def test__get_first_lun_by_path(self, is_empty):
|
|
lun_path = f'/vol/{fake_client.VOLUME_NAMES[0]}/cinder-lun'
|
|
lun_records = fake_client.LUN_GET_ITER_REST['records']
|
|
lun_item = lun_records[0]
|
|
if is_empty:
|
|
lun_records = []
|
|
|
|
self.mock_object(self.client, '_get_lun_by_path',
|
|
return_value=lun_records)
|
|
|
|
result = self.client._get_first_lun_by_path(lun_path)
|
|
|
|
self.client._get_lun_by_path.assert_called_once_with(
|
|
lun_path, fields=None)
|
|
if is_empty:
|
|
self.assertTrue(result is None)
|
|
else:
|
|
self.assertEqual(result, lun_item)
|
|
|
|
def test__lun_update_by_path(self):
|
|
lun_path = f'/vol/{fake_client.VOLUME_NAMES[0]}/cinder-lun'
|
|
lun_item = fake_client.LUN_GET_ITER_REST['records'][0]
|
|
new_size_bytes = '1073741824'
|
|
body = {
|
|
'name': lun_path,
|
|
'space.guarantee.requested': 'True',
|
|
'space.size': new_size_bytes
|
|
}
|
|
|
|
self.mock_object(self.client, '_get_first_lun_by_path',
|
|
return_value=lun_item)
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client._lun_update_by_path(lun_path, body)
|
|
|
|
self.client._get_first_lun_by_path.assert_called_once_with(lun_path)
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/storage/luns/{lun_item["uuid"]}', 'patch', body=body)
|
|
|
|
def test__lun_update_by_path_not_found(self):
|
|
lun_path = f'/vol/{fake_client.VOLUME_NAMES[0]}/cinder-lun'
|
|
lun_item = None
|
|
new_size_bytes = '1073741824'
|
|
body = {
|
|
'name': lun_path,
|
|
'space.guarantee.requested': 'True',
|
|
'space.size': new_size_bytes
|
|
}
|
|
|
|
self.mock_object(self.client, '_get_first_lun_by_path',
|
|
return_value=lun_item)
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.assertRaises(
|
|
netapp_api.NaApiError,
|
|
self.client._lun_update_by_path,
|
|
lun_path,
|
|
body
|
|
)
|
|
|
|
self.client._get_first_lun_by_path.assert_called_once_with(lun_path)
|
|
self.client.send_request.assert_not_called()
|
|
|
|
def test__validate_qos_policy_group_unsupported_qos(self):
|
|
is_adaptive = True
|
|
self.client.features.ADAPTIVE_QOS = False
|
|
|
|
self.assertRaises(
|
|
netapp_utils.NetAppDriverException,
|
|
self.client._validate_qos_policy_group,
|
|
is_adaptive
|
|
)
|
|
|
|
def test__validate_qos_policy_group_no_spec(self):
|
|
is_adaptive = True
|
|
self.client.features.ADAPTIVE_QOS = True
|
|
|
|
result = self.client._validate_qos_policy_group(is_adaptive)
|
|
|
|
self.assertTrue(result is None)
|
|
|
|
def test__validate_qos_policy_group_unsupported_feature(self):
|
|
is_adaptive = True
|
|
self.client.features.ADAPTIVE_QOS = True
|
|
spec = {
|
|
'min_throughput': fake.MIN_IOPS_REST
|
|
}
|
|
|
|
self.assertRaises(
|
|
netapp_utils.NetAppDriverException,
|
|
self.client._validate_qos_policy_group,
|
|
is_adaptive,
|
|
spec=spec,
|
|
qos_min_support=False
|
|
)
|
|
|
|
@ddt.data(True, False)
|
|
def test__validate_qos_policy_group(self, is_adaptive):
|
|
self.client.features.ADAPTIVE_QOS = True
|
|
spec = {
|
|
'max_throughput': fake.MAX_IOPS_REST,
|
|
'min_throughput': fake.MIN_IOPS_REST
|
|
}
|
|
|
|
self.client._validate_qos_policy_group(
|
|
is_adaptive, spec=spec, qos_min_support=True)
|
|
|
|
def test_delete_file(self):
|
|
"""Delete file at path."""
|
|
path_to_file = fake.VOLUME_PATH
|
|
volume_response = fake_client.VOLUME_LIST_SIMPLE_RESPONSE_REST
|
|
volume_item = fake_client.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
|
|
|
volume_name = path_to_file.split('/')[2]
|
|
relative_path = '/'.join(path_to_file.split('/')[3:])
|
|
|
|
query = {
|
|
'type': 'rw',
|
|
'style': 'flex*', # Match both 'flexvol' and 'flexgroup'
|
|
'is_svm_root': 'false',
|
|
'error_state.is_inconsistent': 'false',
|
|
'state': 'online',
|
|
'name': volume_name,
|
|
'fields': 'name,style'
|
|
}
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=volume_response)
|
|
self.mock_object(self.client, '_get_unique_volume',
|
|
return_value=volume_item)
|
|
self.client.delete_file(path_to_file)
|
|
|
|
relative_path = relative_path.replace('/', '%2F').replace('.', '%2E')
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/storage/volumes/', 'get', query=query),
|
|
mock.call(f'/storage/volumes/{volume_item["uuid"]}'
|
|
+ f'/files/{relative_path}', 'delete')
|
|
])
|
|
|
|
self.client._get_unique_volume.assert_called_once_with(
|
|
volume_response['records'])
|
|
|
|
def test_get_igroup_by_initiators_none_found(self):
|
|
initiator = 'initiator'
|
|
expected_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=expected_response)
|
|
|
|
igroup_list = self.client.get_igroup_by_initiators([initiator])
|
|
|
|
self.assertEqual([], igroup_list)
|
|
|
|
def test_get_igroup_by_initiators(self):
|
|
initiators = ['iqn.1993-08.org.fake:01:5b67769f5c5e']
|
|
expected_igroup = [{
|
|
'initiator-group-os-type': 'linux',
|
|
'initiator-group-type': 'iscsi',
|
|
'initiator-group-name':
|
|
'openstack-e6bf1584-bfb3-4cdb-950d-525bf6f26b53'
|
|
}]
|
|
|
|
expected_query = {
|
|
'svm.name': fake_client.VOLUME_VSERVER_NAME,
|
|
'initiators.name': ' '.join(initiators),
|
|
'fields': 'name,protocol,os_type'
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=fake_client.IGROUP_GET_ITER_REST)
|
|
|
|
igroup_list = self.client.get_igroup_by_initiators(initiators)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/san/igroups', 'get', query=expected_query)
|
|
self.assertEqual(expected_igroup, igroup_list)
|
|
|
|
def test_get_igroup_by_initiators_multiple(self):
|
|
initiators = ['iqn.1993-08.org.fake:01:5b67769f5c5e',
|
|
'iqn.1993-08.org.fake:02:5b67769f5c5e']
|
|
|
|
expected_igroup = [{
|
|
'initiator-group-os-type': 'linux',
|
|
'initiator-group-type': 'iscsi',
|
|
'initiator-group-name':
|
|
'openstack-e6bf1584-bfb3-4cdb-950d-525bf6f26b53'
|
|
}]
|
|
|
|
expected_query = {
|
|
'svm.name': fake_client.VOLUME_VSERVER_NAME,
|
|
'initiators.name': ' '.join(initiators),
|
|
'fields': 'name,protocol,os_type'
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=fake_client.IGROUP_GET_ITER_INITS_REST)
|
|
|
|
igroup_list = self.client.get_igroup_by_initiators(initiators)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/san/igroups', 'get', query=expected_query)
|
|
self.assertEqual(expected_igroup, igroup_list)
|
|
|
|
def test_get_igroup_by_initiators_multiple_records(self):
|
|
initiators = ['iqn.1993-08.org.fake:01:5b67769f5c5e']
|
|
expected_element = {
|
|
'initiator-group-os-type': 'linux',
|
|
'initiator-group-type': 'iscsi',
|
|
'initiator-group-name':
|
|
'openstack-e6bf1584-bfb3-4cdb-950d-525bf6f26b53'
|
|
}
|
|
expected_igroup = [expected_element, expected_element]
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=fake_client.IGROUP_GET_ITER_MULT_REST)
|
|
|
|
igroup_list = self.client.get_igroup_by_initiators(initiators)
|
|
self.assertEqual(expected_igroup, igroup_list)
|
|
|
|
def test_add_igroup_initiator(self):
|
|
igroup = 'fake_igroup'
|
|
initiator = 'fake_initator'
|
|
|
|
mock_return = fake_client.IGROUP_GET_ITER_REST
|
|
expected_uuid = fake_client.IGROUP_GET_ITER_REST['records'][0]['uuid']
|
|
mock_send_request = self.mock_object(self.client, 'send_request',
|
|
return_value = mock_return)
|
|
|
|
self.client.add_igroup_initiator(igroup, initiator)
|
|
|
|
expected_body = {
|
|
'name': initiator
|
|
}
|
|
mock_send_request.assert_has_calls([
|
|
mock.call('/protocols/san/igroups/' +
|
|
expected_uuid + '/initiators',
|
|
'post', body=expected_body)])
|
|
|
|
def test_create_igroup(self):
|
|
igroup = 'fake_igroup'
|
|
igroup_type = 'fake_type'
|
|
os_type = 'fake_os'
|
|
|
|
body = {
|
|
'name': igroup,
|
|
'protocol': igroup_type,
|
|
'os_type': os_type,
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
self.client.create_igroup(igroup, igroup_type, os_type)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/san/igroups', 'post', body=body)
|
|
|
|
@ddt.data(None, 0, 4095)
|
|
def test_map_lun(self, lun_id):
|
|
fake_record = fake_client.GET_LUN_MAP_REST['records'][0]
|
|
path = fake_record['lun']['name']
|
|
igroup_name = fake_record['igroup']['name']
|
|
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.GET_LUN_MAP_REST)
|
|
|
|
result = self.client.map_lun(path, igroup_name, lun_id)
|
|
|
|
self.assertEqual(0, result)
|
|
expected_body = {
|
|
'lun.name': path,
|
|
'igroup.name': igroup_name,
|
|
}
|
|
if lun_id is not None:
|
|
expected_body['logical_unit_number'] = lun_id
|
|
|
|
mock_send_request.assert_has_calls([
|
|
mock.call('/protocols/san/lun-maps', 'post',
|
|
body=expected_body, query={'return_records': 'true'})])
|
|
|
|
def test_get_lun_map(self):
|
|
fake_record = fake_client.GET_LUN_MAP_REST['records'][0]
|
|
path = fake_record['lun']['name']
|
|
|
|
expected_lun_map = [{
|
|
'initiator-group': fake_record['igroup']['name'],
|
|
'lun-id': fake_record['logical_unit_number'],
|
|
'vserver': fake_record['svm']['name'],
|
|
}]
|
|
|
|
expected_query = {
|
|
'lun.name': path,
|
|
'fields': 'igroup.name,logical_unit_number,svm.name',
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=fake_client.GET_LUN_MAP_REST)
|
|
|
|
lun_map = self.client.get_lun_map(path)
|
|
self.assertEqual(observed=lun_map, expected=expected_lun_map)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/san/lun-maps', 'get', query=expected_query)
|
|
|
|
def test_get_lun_map_no_luns_mapped(self):
|
|
fake_record = fake_client.GET_LUN_MAP_REST['records'][0]
|
|
path = fake_record['lun']['name']
|
|
|
|
expected_lun_map = []
|
|
expected_query = {
|
|
'lun.name': path,
|
|
'fields': 'igroup.name,logical_unit_number,svm.name',
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value = fake_client.NO_RECORDS_RESPONSE_REST)
|
|
|
|
lun_map = self.client.get_lun_map(path)
|
|
self.assertEqual(observed=lun_map, expected=expected_lun_map)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/san/lun-maps', 'get', query=expected_query)
|
|
|
|
def test_get_fc_target_wwpns(self):
|
|
fake_record = fake_client.FC_INTERFACE_REST['records'][0]
|
|
expected_wwpns = [fake_record['wwpn']]
|
|
expected_query = {
|
|
'fields': 'wwpn'
|
|
}
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value = fake_client.FC_INTERFACE_REST)
|
|
wwpns = self.client.get_fc_target_wwpns()
|
|
self.assertEqual(observed=wwpns, expected=expected_wwpns)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/network/fc/interfaces', 'get', query=expected_query)
|
|
|
|
def test_get_fc_target_wwpns_not_found(self):
|
|
expected_wwpns = []
|
|
expected_query = {
|
|
'fields': 'wwpn'
|
|
}
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value = fake_client.NO_RECORDS_RESPONSE_REST)
|
|
wwpns = self.client.get_fc_target_wwpns()
|
|
self.assertEqual(observed=wwpns, expected=expected_wwpns)
|
|
self.client.send_request.assert_called_once_with(
|
|
'/network/fc/interfaces', 'get', query=expected_query)
|
|
|
|
def test_unmap_lun(self):
|
|
get_uuid_response = fake_client.GET_LUN_MAP_REST
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
side_effect=[get_uuid_response, None])
|
|
|
|
self.client.unmap_lun(fake_client.LUN_NAME_PATH,
|
|
fake_client.IGROUP_NAME)
|
|
|
|
query_uuid = {
|
|
'igroup.name': fake_client.IGROUP_NAME,
|
|
'lun.name': fake_client.LUN_NAME_PATH,
|
|
'fields': 'lun.uuid,igroup.uuid'
|
|
}
|
|
|
|
lun_uuid = get_uuid_response['records'][0]['lun']['uuid']
|
|
igroup_uuid = get_uuid_response['records'][0]['igroup']['uuid']
|
|
|
|
mock_send_request.assert_has_calls([
|
|
mock.call('/protocols/san/lun-maps', 'get', query=query_uuid),
|
|
mock.call(f'/protocols/san/lun-maps/{lun_uuid}/{igroup_uuid}',
|
|
'delete'),
|
|
])
|
|
|
|
def test_unmap_lun_with_api_error(self):
|
|
get_uuid_response = fake_client.GET_LUN_MAP_REST
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
side_effect=[get_uuid_response, netapp_api.NaApiError()])
|
|
|
|
self.assertRaises(netapp_api.NaApiError,
|
|
self.client.unmap_lun,
|
|
fake_client.LUN_NAME_PATH,
|
|
fake_client.IGROUP_NAME)
|
|
|
|
query_uuid = {
|
|
'igroup.name': fake_client.IGROUP_NAME,
|
|
'lun.name': fake_client.LUN_NAME_PATH,
|
|
'fields': 'lun.uuid,igroup.uuid'
|
|
}
|
|
|
|
lun_uuid = get_uuid_response['records'][0]['lun']['uuid']
|
|
igroup_uuid = get_uuid_response['records'][0]['igroup']['uuid']
|
|
|
|
mock_send_request.assert_has_calls([
|
|
mock.call('/protocols/san/lun-maps', 'get', query=query_uuid),
|
|
mock.call(f'/protocols/san/lun-maps/{lun_uuid}/{igroup_uuid}',
|
|
'delete'),
|
|
])
|
|
|
|
def test_unmap_lun_invalid_input(self):
|
|
get_uuid_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
side_effect=[get_uuid_response,
|
|
None])
|
|
|
|
self.client.unmap_lun(fake_client.LUN_NAME_PATH,
|
|
fake_client.IGROUP_NAME)
|
|
|
|
query_uuid = {
|
|
'igroup.name': fake_client.IGROUP_NAME,
|
|
'lun.name': fake_client.LUN_NAME_PATH,
|
|
'fields': 'lun.uuid,igroup.uuid'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/protocols/san/lun-maps', 'get', query=query_uuid)
|
|
|
|
def test_unmap_lun_not_mapped_in_group(self):
|
|
get_uuid_response = fake_client.GET_LUN_MAP_REST
|
|
|
|
# Exception REST_NO_SUCH_LUN_MAP is handled inside the function
|
|
# and should not be re-raised
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
side_effect=[
|
|
get_uuid_response,
|
|
netapp_api.NaApiError(
|
|
code=netapp_api.REST_NO_SUCH_LUN_MAP)])
|
|
|
|
self.client.unmap_lun(fake_client.LUN_NAME_PATH,
|
|
fake_client.IGROUP_NAME)
|
|
|
|
query_uuid = {
|
|
'igroup.name': fake_client.IGROUP_NAME,
|
|
'lun.name': fake_client.LUN_NAME_PATH,
|
|
'fields': 'lun.uuid,igroup.uuid'
|
|
}
|
|
|
|
lun_uuid = get_uuid_response['records'][0]['lun']['uuid']
|
|
igroup_uuid = get_uuid_response['records'][0]['igroup']['uuid']
|
|
|
|
mock_send_request.assert_has_calls([
|
|
mock.call('/protocols/san/lun-maps', 'get', query=query_uuid),
|
|
mock.call(f'/protocols/san/lun-maps/{lun_uuid}/{igroup_uuid}',
|
|
'delete'),
|
|
])
|
|
|
|
def test_has_luns_mapped_to_initiators(self):
|
|
initiators = ['iqn.2005-03.org.open-iscsi:49ebe8a87d1']
|
|
api_response = fake_client.GET_LUN_MAPS
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request', return_value=api_response)
|
|
|
|
self.assertTrue(self.client.has_luns_mapped_to_initiators(initiators))
|
|
|
|
query = {
|
|
'initiators.name': ' '.join(initiators),
|
|
'fields': 'lun_maps'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/protocols/san/igroups', 'get', query=query)
|
|
|
|
def test_has_luns_mapped_to_initiators_no_records(self):
|
|
initiators = ['iqn.2005-03.org.open-iscsi:49ebe8a87d1']
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request', return_value=api_response)
|
|
|
|
self.assertFalse(self.client.has_luns_mapped_to_initiators(initiators))
|
|
|
|
query = {
|
|
'initiators.name': ' '.join(initiators),
|
|
'fields': 'lun_maps'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/protocols/san/igroups', 'get', query=query)
|
|
|
|
def test_has_luns_mapped_to_initiators_not_mapped(self):
|
|
initiators = ['iqn.2005-03.org.open-iscsi:49ebe8a87d1']
|
|
api_response = fake_client.GET_LUN_MAPS_NO_MAPS
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request', return_value=api_response)
|
|
|
|
self.assertFalse(self.client.has_luns_mapped_to_initiators(initiators))
|
|
|
|
query = {
|
|
'initiators.name': ' '.join(initiators),
|
|
'fields': 'lun_maps'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with(
|
|
'/protocols/san/igroups', 'get', query=query)
|
|
|
|
def test_iscsi_service_details(self):
|
|
fake_record = fake_client.GET_ISCSI_SERVICE_DETAILS_REST['records'][0]
|
|
expected_iqn = fake_record['target']['name']
|
|
expected_query = {
|
|
'fields': 'target.name'
|
|
}
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.GET_ISCSI_SERVICE_DETAILS_REST)
|
|
iqn = self.client.get_iscsi_service_details()
|
|
self.assertEqual(expected_iqn, iqn)
|
|
mock_send_request.assert_called_once_with(
|
|
'/protocols/san/iscsi/services', 'get', query=expected_query)
|
|
|
|
def test_iscsi_service_details_not_found(self):
|
|
expected_iqn = None
|
|
expected_query = {
|
|
'fields': 'target.name'
|
|
}
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.NO_RECORDS_RESPONSE_REST)
|
|
iqn = self.client.get_iscsi_service_details()
|
|
self.assertEqual(expected_iqn, iqn)
|
|
mock_send_request.assert_called_once_with(
|
|
'/protocols/san/iscsi/services', 'get', query=expected_query)
|
|
|
|
def test_check_iscsi_initiator_exists(self):
|
|
fake_record = fake_client.CHECK_ISCSI_INITIATOR_REST['records'][0]
|
|
iqn = fake_record['initiator']
|
|
expected_query = {
|
|
'initiator': iqn
|
|
}
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.CHECK_ISCSI_INITIATOR_REST)
|
|
initiator_exists = self.client.check_iscsi_initiator_exists(iqn)
|
|
self.assertEqual(expected=True, observed=initiator_exists)
|
|
mock_send_request.assert_called_once_with(
|
|
'/protocols/san/iscsi/credentials', 'get',
|
|
query=expected_query)
|
|
|
|
def test_check_iscsi_initiator_exists_not_found(self):
|
|
fake_record = fake_client.CHECK_ISCSI_INITIATOR_REST['records'][0]
|
|
iqn = fake_record['initiator']
|
|
expected_query = {
|
|
'initiator': iqn
|
|
}
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.NO_RECORDS_RESPONSE_REST)
|
|
initiator_exists = self.client.check_iscsi_initiator_exists(iqn)
|
|
self.assertEqual(expected=False, observed=initiator_exists)
|
|
mock_send_request.assert_called_once_with(
|
|
'/protocols/san/iscsi/credentials', 'get',
|
|
query=expected_query)
|
|
|
|
def test_get_iscsi_target_details(self):
|
|
fake_record = fake_client.GET_ISCSI_TARGET_DETAILS_REST['records'][0]
|
|
expected_details = [{
|
|
'address': fake_record['ip']['address'],
|
|
'port': 3260,
|
|
'tpgroup-tag': None,
|
|
'interface-enabled': fake_record['enabled'],
|
|
}]
|
|
expected_query = {
|
|
'services': 'data_iscsi',
|
|
'fields': 'ip.address,enabled'
|
|
}
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.GET_ISCSI_TARGET_DETAILS_REST)
|
|
details = self.client.get_iscsi_target_details()
|
|
self.assertEqual(expected_details, details)
|
|
mock_send_request.assert_called_once_with('/network/ip/interfaces',
|
|
'get', query=expected_query)
|
|
|
|
def test_get_iscsi_target_details_no_details(self):
|
|
expected_details = []
|
|
expected_query = {
|
|
'services': 'data_iscsi',
|
|
'fields': 'ip.address,enabled'
|
|
}
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.NO_RECORDS_RESPONSE_REST)
|
|
details = self.client.get_iscsi_target_details()
|
|
self.assertEqual(expected_details, details)
|
|
mock_send_request.assert_called_once_with('/network/ip/interfaces',
|
|
'get', query=expected_query)
|
|
|
|
def test_move_lun(self):
|
|
fake_cur_path = '/vol/fake_vol/fake_lun_cur'
|
|
fake_new_path = '/vol/fake_vol/fake_lun_new'
|
|
expected_query = {
|
|
'svm.name': self.vserver,
|
|
'name': fake_cur_path,
|
|
}
|
|
expected_body = {
|
|
'name': fake_new_path,
|
|
}
|
|
mock_send_request = self.mock_object(self.client, 'send_request')
|
|
self.client.move_lun(fake_cur_path, fake_new_path)
|
|
mock_send_request.assert_called_once_with(
|
|
'/storage/luns/', 'patch', query=expected_query,
|
|
body=expected_body)
|
|
|
|
@ddt.data(True, False)
|
|
def test_clone_file_snapshot(self, overwrite_dest):
|
|
fake_volume = fake_client.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
|
self.client.features.BACKUP_CLONE_PARAM = True
|
|
|
|
fake_name = fake.NFS_VOLUME['name']
|
|
fake_new_name = fake.SNAPSHOT_NAME
|
|
api_version = (1, 19)
|
|
|
|
expected_body = {
|
|
'volume': {
|
|
'uuid': fake_volume['uuid'],
|
|
'name': fake_volume['name']
|
|
},
|
|
'source_path': fake_name,
|
|
'destination_path': fake_new_name,
|
|
'is_backup': True
|
|
}
|
|
if overwrite_dest:
|
|
api_version = (1, 20)
|
|
expected_body['overwrite_destination'] = True
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
self.mock_object(self.client, '_get_volume_by_args',
|
|
return_value=fake_volume)
|
|
self.mock_object(self.client.connection, 'get_api_version',
|
|
return_value=api_version)
|
|
|
|
self.client.clone_file(
|
|
fake_volume['name'], fake_name, fake_new_name, fake.VSERVER_NAME,
|
|
is_snapshot=True, dest_exists=overwrite_dest)
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/storage/file/clone', 'post', body=expected_body),
|
|
])
|
|
|
|
def test_clone_lun(self):
|
|
self.client.vserver = fake.VSERVER_NAME
|
|
|
|
expected_body = {
|
|
'svm': {
|
|
'name': fake.VSERVER_NAME
|
|
},
|
|
'name': f'/vol/{fake.VOLUME_NAME}/{fake.SNAPSHOT_NAME}',
|
|
'clone': {
|
|
'source': {
|
|
'name': f'/vol/{fake.VOLUME_NAME}/{fake.LUN_NAME}',
|
|
}
|
|
},
|
|
'space': {
|
|
'guarantee': {
|
|
'requested': True,
|
|
}
|
|
},
|
|
'qos_policy': {
|
|
'name': fake.QOS_POLICY_GROUP_NAME,
|
|
}
|
|
}
|
|
|
|
mock_send_request = self.mock_object(
|
|
self.client, 'send_request', return_value=None)
|
|
mock_validate_policy = self.mock_object(
|
|
self.client, '_validate_qos_policy_group')
|
|
|
|
self.client.clone_lun(
|
|
volume=fake.VOLUME_NAME, name=fake.LUN_NAME,
|
|
new_name=fake.SNAPSHOT_NAME,
|
|
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME,
|
|
is_snapshot=True)
|
|
|
|
mock_validate_policy.assert_called_once_with(False)
|
|
mock_send_request.assert_called_once_with(
|
|
'/storage/luns', 'post', body=expected_body)
|
|
|
|
@ddt.data(True, False)
|
|
def test_destroy_lun(self, force=True):
|
|
path = f'/vol/{fake_client.VOLUME_NAME}/{fake_client.FILE_NAME}'
|
|
|
|
query = {}
|
|
query['name'] = path
|
|
query['svm'] = fake_client.VOLUME_VSERVER_NAME
|
|
if force:
|
|
query['allow_delete_while_mapped'] = 'true'
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.destroy_lun(path, force)
|
|
|
|
self.client.send_request.assert_called_once_with('/storage/luns/',
|
|
'delete', query=query)
|
|
|
|
def test_get_flexvol_capacity(self, ):
|
|
|
|
api_response = fake_client.VOLUME_GET_ITER_CAPACITY_RESPONSE_REST
|
|
volume_response = api_response['records'][0]
|
|
mock_get_unique_vol = self.mock_object(
|
|
self.client, '_get_volume_by_args', return_value=volume_response)
|
|
|
|
capacity = self.client.get_flexvol_capacity(
|
|
flexvol_path=fake.VOLUME_PATH, flexvol_name=fake.VOLUME_NAME)
|
|
|
|
mock_get_unique_vol.assert_called_once_with(
|
|
vol_name=fake.VOLUME_NAME, vol_path=fake.VOLUME_PATH,
|
|
fields='name,space.available,space.afs_total')
|
|
self.assertEqual(float(fake_client.VOLUME_SIZE_TOTAL),
|
|
capacity['size-total'])
|
|
self.assertEqual(float(fake_client.VOLUME_SIZE_AVAILABLE),
|
|
capacity['size-available'])
|
|
|
|
def test_get_flexvol_capacity_not_found(self):
|
|
|
|
self.mock_object(
|
|
self.client, '_get_volume_by_args',
|
|
side_effect=exception.VolumeBackendAPIException(data="fake"))
|
|
|
|
self.assertRaises(netapp_utils.NetAppDriverException,
|
|
self.client.get_flexvol_capacity,
|
|
flexvol_path='fake_path')
|
|
|
|
def test_check_api_permissions(self):
|
|
|
|
mock_log = self.mock_object(client_cmode_rest.LOG, 'warning')
|
|
self.mock_object(self.client, 'check_cluster_api', return_value=True)
|
|
|
|
self.client.check_api_permissions()
|
|
|
|
self.client.check_cluster_api.assert_has_calls(
|
|
[mock.call(key) for key in client_cmode_rest.SSC_API_MAP.keys()])
|
|
self.assertEqual(0, mock_log.call_count)
|
|
|
|
def test_check_api_permissions_failed_ssc_apis(self):
|
|
|
|
def check_cluster_api(api):
|
|
if api != '/storage/volumes':
|
|
return False
|
|
return True
|
|
|
|
self.mock_object(self.client, 'check_cluster_api',
|
|
side_effect=check_cluster_api)
|
|
|
|
mock_log = self.mock_object(client_cmode_rest.LOG, 'warning')
|
|
|
|
self.client.check_api_permissions()
|
|
|
|
self.assertEqual(1, mock_log.call_count)
|
|
|
|
def test_check_api_permissions_failed_volume_api(self):
|
|
|
|
def check_cluster_api(api):
|
|
if api == '/storage/volumes':
|
|
return False
|
|
return True
|
|
|
|
self.mock_object(self.client, 'check_cluster_api',
|
|
side_effect=check_cluster_api)
|
|
|
|
mock_log = self.mock_object(client_cmode_rest.LOG, 'warning')
|
|
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.client.check_api_permissions)
|
|
|
|
self.assertEqual(0, mock_log.call_count)
|
|
|
|
def test_check_cluster_api(self):
|
|
|
|
endpoint_api = '/storage/volumes'
|
|
endpoint_request = '/storage/volumes?return_records=false'
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=True)
|
|
|
|
result = self.client.check_cluster_api(endpoint_api)
|
|
|
|
mock_send_request.assert_has_calls([mock.call(endpoint_request, 'get',
|
|
enable_tunneling=False)])
|
|
self.assertTrue(result)
|
|
|
|
def test_check_cluster_api_error(self):
|
|
|
|
endpoint_api = '/storage/volumes'
|
|
api_error = netapp_api.NaApiError(code=netapp_api.REST_UNAUTHORIZED)
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=[api_error])
|
|
|
|
result = self.client.check_cluster_api(endpoint_api)
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_get_provisioning_options_from_flexvol(self):
|
|
|
|
self.mock_object(self.client, 'get_flexvol',
|
|
return_value=fake_client.VOLUME_INFO_SSC)
|
|
self.mock_object(self.client, 'get_flexvol_dedupe_info',
|
|
return_value=fake_client.VOLUME_DEDUPE_INFO_SSC)
|
|
|
|
expected_prov_opts = {
|
|
'aggregate': ['fake_aggr1'],
|
|
'compression_enabled': False,
|
|
'dedupe_enabled': True,
|
|
'language': 'c.utf_8',
|
|
'size': 1,
|
|
'snapshot_policy': 'default',
|
|
'snapshot_reserve': '5',
|
|
'space_guarantee_type': 'none',
|
|
'volume_type': 'rw',
|
|
'is_flexgroup': False,
|
|
}
|
|
|
|
actual_prov_opts = self.client.get_provisioning_options_from_flexvol(
|
|
fake_client.VOLUME_NAME)
|
|
|
|
self.assertEqual(expected_prov_opts, actual_prov_opts)
|
|
|
|
def test_flexvol_exists(self):
|
|
|
|
api_response = fake_client.GET_NUM_RECORDS_RESPONSE_REST
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.flexvol_exists(fake_client.VOLUME_NAME)
|
|
|
|
query = {
|
|
'name': fake_client.VOLUME_NAME,
|
|
'return_records': 'false'
|
|
}
|
|
|
|
mock_send_request.assert_has_calls([
|
|
mock.call('/storage/volumes/', 'get', query=query)])
|
|
self.assertTrue(result)
|
|
|
|
def test_flexvol_exists_not_found(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
self.assertFalse(self.client.flexvol_exists(fake_client.VOLUME_NAME))
|
|
|
|
@ddt.data(fake_client.VOLUME_AGGREGATE_NAME,
|
|
[fake_client.VOLUME_AGGREGATE_NAME],
|
|
[fake_client.VOLUME_AGGREGATE_NAMES[0],
|
|
fake_client.VOLUME_AGGREGATE_NAMES[1]])
|
|
def test_create_volume_async(self, aggregates):
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.create_volume_async(
|
|
fake_client.VOLUME_NAME, aggregates, 100, volume_type='dp')
|
|
|
|
body = {
|
|
'name': fake_client.VOLUME_NAME,
|
|
'size': 100 * units.Gi,
|
|
'type': 'dp'
|
|
}
|
|
|
|
if isinstance(aggregates, list):
|
|
body['style'] = 'flexgroup'
|
|
body['aggregates'] = [{'name': aggr} for aggr in aggregates]
|
|
else:
|
|
body['style'] = 'flexvol'
|
|
body['aggregates'] = [{'name': aggregates}]
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/volumes/', 'post', body=body, wait_on_accepted=False)
|
|
|
|
@ddt.data('dp', 'rw', None)
|
|
def test_create_volume_async_with_extra_specs(self, volume_type):
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
aggregates = [fake_client.VOLUME_AGGREGATE_NAME]
|
|
snapshot_policy = 'default'
|
|
size = 100
|
|
space_guarantee_type = 'volume'
|
|
language = 'en-US'
|
|
snapshot_reserve = 15
|
|
|
|
self.client.create_volume_async(
|
|
fake_client.VOLUME_NAME, aggregates, size,
|
|
space_guarantee_type=space_guarantee_type, language=language,
|
|
snapshot_policy=snapshot_policy, snapshot_reserve=snapshot_reserve,
|
|
volume_type=volume_type)
|
|
|
|
body = {
|
|
'name': fake_client.VOLUME_NAME,
|
|
'size': size * units.Gi,
|
|
'type': volume_type,
|
|
'guarantee': {'type': space_guarantee_type},
|
|
'space': {'snapshot': {'reserve_percent': str(snapshot_reserve)}},
|
|
'language': language,
|
|
}
|
|
|
|
if isinstance(aggregates, list):
|
|
body['style'] = 'flexgroup'
|
|
body['aggregates'] = [{'name': aggr} for aggr in aggregates]
|
|
else:
|
|
body['style'] = 'flexvol'
|
|
body['aggregates'] = [{'name': aggregates}]
|
|
|
|
if volume_type == 'dp':
|
|
snapshot_policy = None
|
|
else:
|
|
body['nas'] = {'path': '/%s' % fake_client.VOLUME_NAME}
|
|
|
|
if snapshot_policy is not None:
|
|
body['snapshot_policy'] = {'name': snapshot_policy}
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/volumes/', 'post', body=body, wait_on_accepted=False)
|
|
|
|
def test_create_flexvol(self):
|
|
aggregates = [fake_client.VOLUME_AGGREGATE_NAME]
|
|
size = 100
|
|
|
|
mock_response = {
|
|
'job': {
|
|
'uuid': fake.JOB_UUID,
|
|
}
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=mock_response)
|
|
|
|
expected_response = {
|
|
'status': None,
|
|
'jobid': fake.JOB_UUID,
|
|
'error-code': None,
|
|
'error-message': None
|
|
}
|
|
|
|
response = self.client.create_volume_async(fake_client.VOLUME_NAME,
|
|
aggregates, size_gb = size)
|
|
self.assertEqual(expected_response, response)
|
|
|
|
def test_enable_volume_dedupe_async(self):
|
|
query = {
|
|
'name': fake_client.VOLUME_NAME,
|
|
'fields': 'uuid,style',
|
|
}
|
|
|
|
# This is needed because the first calling to send_request inside
|
|
# enable_volume_dedupe_async must return a valid uuid for the given
|
|
# volume name.
|
|
mock_response = {
|
|
'records': [
|
|
{
|
|
'uuid': fake.JOB_UUID,
|
|
'name': fake_client.VOLUME_NAME,
|
|
"style": 'flexgroup',
|
|
}
|
|
],
|
|
"num_records": 1,
|
|
}
|
|
|
|
body = {
|
|
'efficiency': {'dedupe': 'background'}
|
|
}
|
|
|
|
mock_send_request = self.mock_object(self.client, 'send_request',
|
|
return_value=mock_response)
|
|
|
|
call_list = [mock.call('/storage/volumes/',
|
|
'patch', body=body, query=query,
|
|
wait_on_accepted=False)]
|
|
|
|
self.client.enable_volume_dedupe_async(fake_client.VOLUME_NAME)
|
|
mock_send_request.assert_has_calls(call_list)
|
|
|
|
def test_enable_volume_compression_async(self):
|
|
query = {
|
|
'name': fake_client.VOLUME_NAME,
|
|
}
|
|
|
|
# This is needed because the first calling to send_request inside
|
|
# enable_volume_compression_async must return a valid uuid for the
|
|
# given volume name.
|
|
mock_response = {
|
|
'records': [
|
|
{
|
|
'uuid': fake.JOB_UUID,
|
|
'name': fake_client.VOLUME_NAME,
|
|
"style": 'flexgroup',
|
|
}
|
|
],
|
|
"num_records": 1,
|
|
}
|
|
|
|
body = {
|
|
'efficiency': {'compression': 'background'}
|
|
}
|
|
|
|
mock_send_request = self.mock_object(self.client, 'send_request',
|
|
return_value=mock_response)
|
|
|
|
call_list = [mock.call('/storage/volumes/',
|
|
'patch', body=body, query=query,
|
|
wait_on_accepted=False)]
|
|
|
|
self.client.enable_volume_compression_async(fake_client.VOLUME_NAME)
|
|
mock_send_request.assert_has_calls(call_list)
|
|
|
|
def test__get_snapmirrors(self):
|
|
|
|
api_response = fake_client.SNAPMIRROR_GET_ITER_RESPONSE_REST
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client._get_snapmirrors(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
query = {
|
|
'source.path': (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME),
|
|
'destination.path': (fake_client.SM_DEST_VSERVER +
|
|
':' + fake_client.SM_DEST_VOLUME),
|
|
'fields': 'state,source.svm.name,source.path,destination.svm.name,'
|
|
'destination.path,transfer.state,transfer.end_time,'
|
|
'lag_time,healthy,uuid'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with('/snapmirror/relationships',
|
|
'get', query=query)
|
|
self.assertEqual(1, len(result))
|
|
|
|
def test__get_snapmirrors_not_found(self):
|
|
|
|
api_response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client._get_snapmirrors(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
query = {
|
|
'source.path': (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME),
|
|
'destination.path': (fake_client.SM_DEST_VSERVER +
|
|
':' + fake_client.SM_DEST_VOLUME),
|
|
'fields': 'state,source.svm.name,source.path,destination.svm.name,'
|
|
'destination.path,transfer.state,transfer.end_time,'
|
|
'lag_time,healthy,uuid'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with('/snapmirror/relationships',
|
|
'get', query=query)
|
|
self.assertEqual([], result)
|
|
|
|
def test_get_snapmirrors(self):
|
|
|
|
api_response = fake_client.SNAPMIRROR_GET_ITER_RESPONSE_REST
|
|
mock_send_request = self.mock_object(self.client,
|
|
'send_request',
|
|
return_value=api_response)
|
|
|
|
result = self.client.get_snapmirrors(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
expected = fake_client.REST_GET_SNAPMIRRORS_RESPONSE
|
|
|
|
query = {
|
|
'source.path': (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME),
|
|
'destination.path': (fake_client.SM_DEST_VSERVER +
|
|
':' + fake_client.SM_DEST_VOLUME),
|
|
'fields': 'state,source.svm.name,source.path,destination.svm.name,'
|
|
'destination.path,transfer.state,transfer.end_time,'
|
|
'lag_time,healthy,uuid'
|
|
}
|
|
|
|
mock_send_request.assert_called_once_with('/snapmirror/relationships',
|
|
'get', query=query)
|
|
self.assertEqual(expected, result)
|
|
|
|
@ddt.data({'policy': 'fake_policy'},
|
|
{'policy': None})
|
|
@ddt.unpack
|
|
def test_create_snapmirror(self, policy):
|
|
api_responses = [
|
|
{
|
|
"job": {
|
|
"uuid": fake_client.FAKE_UUID,
|
|
},
|
|
},
|
|
]
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect = copy.deepcopy(api_responses))
|
|
self.client.create_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
|
|
policy=policy)
|
|
|
|
body = {
|
|
'source': {
|
|
'path': (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME),
|
|
},
|
|
'destination': {
|
|
'path': (fake_client.SM_DEST_VSERVER + ':' +
|
|
fake_client.SM_DEST_VOLUME)
|
|
}
|
|
}
|
|
|
|
if policy:
|
|
body['policy'] = {'name': policy}
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/', 'post', body=body)])
|
|
|
|
def test_create_snapmirror_already_exists(self):
|
|
api_responses = netapp_api.NaApiError(
|
|
code=netapp_api.REST_ERELATION_EXISTS)
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=api_responses)
|
|
|
|
response = self.client.create_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME,
|
|
schedule=None,
|
|
policy=None,
|
|
relationship_type='data_protection')
|
|
self.assertIsNone(response)
|
|
self.assertTrue(self.client.send_request.called)
|
|
|
|
def test_create_snapmirror_error(self):
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=netapp_api.NaApiError(code=123))
|
|
|
|
self.assertRaises(netapp_api.NaApiError,
|
|
self.client.create_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME,
|
|
schedule=None,
|
|
policy=None,
|
|
relationship_type='data_protection')
|
|
self.assertTrue(self.client.send_request.called)
|
|
|
|
def test__set_snapmirror_state(self):
|
|
|
|
api_responses = [
|
|
fake_client.SNAPMIRROR_GET_ITER_RESPONSE_REST,
|
|
{
|
|
"job":
|
|
{
|
|
"uuid": fake_client.FAKE_UUID
|
|
},
|
|
"num_records": 1
|
|
}
|
|
]
|
|
|
|
expected_body = {'state': 'snapmirrored'}
|
|
self.mock_object(self.client,
|
|
'send_request',
|
|
side_effect=copy.deepcopy(api_responses))
|
|
|
|
result = self.client._set_snapmirror_state(
|
|
'snapmirrored',
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/' + fake_client.FAKE_UUID,
|
|
'patch', body=expected_body, wait_on_accepted=True)])
|
|
|
|
expected = {
|
|
'operation-id': None,
|
|
'status': None,
|
|
'jobid': fake_client.FAKE_UUID,
|
|
'error-code': None,
|
|
'error-message': None,
|
|
'relationship-uuid': fake_client.FAKE_UUID
|
|
}
|
|
self.assertEqual(expected, result)
|
|
|
|
def test_initialize_snapmirror(self):
|
|
|
|
expected_job = {
|
|
'operation-id': None,
|
|
'status': None,
|
|
'jobid': fake_client.FAKE_UUID,
|
|
'error-code': None,
|
|
'error-message': None,
|
|
}
|
|
|
|
mock_set_snapmirror_state = self.mock_object(
|
|
self.client,
|
|
'_set_snapmirror_state',
|
|
return_value=expected_job)
|
|
|
|
result = self.client.initialize_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
mock_set_snapmirror_state.assert_called_once_with(
|
|
'snapmirrored',
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
|
|
wait_result=False)
|
|
|
|
self.assertEqual(expected_job, result)
|
|
|
|
@ddt.data(True, False)
|
|
def test_abort_snapmirror(self, clear_checkpoint):
|
|
|
|
self.mock_object(
|
|
self.client, 'get_snapmirrors',
|
|
return_value=fake_client.REST_GET_SNAPMIRRORS_RESPONSE)
|
|
responses = [fake_client.TRANSFERS_GET_ITER_REST, None, None]
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=copy.deepcopy(responses))
|
|
|
|
self.client.abort_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
|
|
clear_checkpoint=clear_checkpoint)
|
|
|
|
body = {'state': 'hard_aborted' if clear_checkpoint else 'aborted'}
|
|
query = {'state': 'transferring'}
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/' +
|
|
fake_client.FAKE_UUID + '/transfers/', 'get',
|
|
query=query),
|
|
mock.call('/snapmirror/relationships/' +
|
|
fake_client.FAKE_UUID + '/transfers/' +
|
|
fake_client.FAKE_UUID, 'patch', body=body)])
|
|
self.client.get_snapmirrors.assert_called_once_with(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
def test_abort_snapmirror_no_transfer_in_progress(self):
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=fake_client.NO_RECORDS_RESPONSE_REST)
|
|
self.mock_object(
|
|
self.client, 'get_snapmirrors',
|
|
return_value=fake_client.REST_GET_SNAPMIRRORS_RESPONSE)
|
|
|
|
self.assertRaises(netapp_api.NaApiError,
|
|
self.client.abort_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME,
|
|
clear_checkpoint=True)
|
|
|
|
query = {'state': 'transferring'}
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/' + fake_client.FAKE_UUID +
|
|
'/transfers/', 'get', query=query)])
|
|
|
|
def test_delete_snapmirror(self):
|
|
|
|
response_list = [fake_client.SNAPMIRROR_GET_ITER_RESPONSE_REST,
|
|
fake_client.JOB_RESPONSE_REST,
|
|
fake_client.JOB_SUCCESSFUL_REST]
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=copy.deepcopy(response_list))
|
|
|
|
self.client.delete_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
query_uuid = {}
|
|
query_uuid['source.path'] = (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME)
|
|
query_uuid['destination.path'] = (fake_client.SM_DEST_VSERVER + ':' +
|
|
fake_client.SM_DEST_VOLUME)
|
|
query_uuid['fields'] = 'uuid'
|
|
|
|
query_delete = {"destination_only": "true"}
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/', 'get', query=query_uuid),
|
|
mock.call('/snapmirror/relationships/' + fake_client.FAKE_UUID,
|
|
'delete', query=query_delete)])
|
|
|
|
def test_delete_snapmirror_timeout(self):
|
|
# when a timeout happens, an exception is thrown by send_request
|
|
api_error = netapp_api.NaRetryableError()
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=api_error)
|
|
|
|
self.assertRaises(netapp_api.NaRetryableError,
|
|
self.client.delete_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
@ddt.data('async', 'sync')
|
|
def test_resume_snapmirror(self, snapmirror_policy):
|
|
snapmirror_response = copy.deepcopy(
|
|
fake_client.SNAPMIRROR_GET_ITER_RESPONSE_REST)
|
|
snapmirror_response['records'][0]['policy'] = {
|
|
'type': snapmirror_policy}
|
|
|
|
if snapmirror_policy == 'async':
|
|
snapmirror_response['state'] = 'snapmirrored'
|
|
elif snapmirror_policy == 'sync':
|
|
snapmirror_response['state'] = 'in_sync'
|
|
|
|
response_list = [snapmirror_response,
|
|
fake_client.JOB_RESPONSE_REST,
|
|
snapmirror_response]
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=copy.deepcopy(response_list))
|
|
|
|
self.client.resync_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
query_uuid = {}
|
|
query_uuid['source.path'] = (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME)
|
|
query_uuid['destination.path'] = (fake_client.SM_DEST_VSERVER + ':' +
|
|
fake_client.SM_DEST_VOLUME)
|
|
query_uuid['fields'] = 'uuid,policy.type'
|
|
|
|
body_resync = {}
|
|
if snapmirror_policy == 'async':
|
|
body_resync['state'] = 'snapmirrored'
|
|
elif snapmirror_policy == 'sync':
|
|
body_resync['state'] = 'in_sync'
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/', 'get', query=query_uuid),
|
|
mock.call('/snapmirror/relationships/' + fake_client.FAKE_UUID,
|
|
'patch', body=body_resync)])
|
|
|
|
def test_resume_snapmirror_not_found(self):
|
|
query_uuid = {}
|
|
query_uuid['source.path'] = (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME)
|
|
query_uuid['destination.path'] = (fake_client.SM_DEST_VSERVER + ':' +
|
|
fake_client.SM_DEST_VOLUME)
|
|
query_uuid['fields'] = 'uuid,policy.type'
|
|
|
|
self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value={'records': []})
|
|
|
|
self.assertRaises(
|
|
netapp_api.NaApiError,
|
|
self.client.resume_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/snapmirror/relationships/', 'get', query=query_uuid)
|
|
|
|
def test_resume_snapmirror_api_error(self):
|
|
query_resume = {}
|
|
query_resume['source.path'] = (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME)
|
|
query_resume['destination.path'] = (fake_client.SM_DEST_VSERVER + ':' +
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
query_uuid = copy.deepcopy(query_resume)
|
|
query_uuid['fields'] = 'uuid,policy.type'
|
|
|
|
api_error = netapp_api.NaApiError(code=0)
|
|
self.mock_object(
|
|
self.client, 'send_request',
|
|
side_effect=[fake_client.SNAPMIRROR_GET_ITER_RESPONSE_REST,
|
|
api_error])
|
|
|
|
self.assertRaises(netapp_api.NaApiError,
|
|
self.client.resume_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
@ddt.data(True, False)
|
|
def test_release_snapmirror(self, relationship_info_only):
|
|
|
|
response_list = [fake_client.SNAPMIRROR_GET_ITER_RESPONSE_REST,
|
|
fake_client.JOB_RESPONSE_REST,
|
|
fake_client.JOB_SUCCESSFUL_REST]
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=copy.deepcopy(response_list))
|
|
|
|
self.client.release_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
|
|
relationship_info_only)
|
|
|
|
query_uuid = {}
|
|
query_uuid['list_destinations_only'] = 'true'
|
|
query_uuid['source.path'] = (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME)
|
|
query_uuid['destination.path'] = (fake_client.SM_DEST_VSERVER + ':' +
|
|
fake_client.SM_DEST_VOLUME)
|
|
query_uuid['fields'] = 'uuid'
|
|
|
|
query_release = {}
|
|
if relationship_info_only:
|
|
# release WITHOUT removing related snapshots
|
|
query_release['source_info_only'] = 'true'
|
|
else:
|
|
# release and REMOVING all related snapshots
|
|
query_release['source_only'] = 'true'
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/', 'get', query=query_uuid),
|
|
mock.call('/snapmirror/relationships/' + fake_client.FAKE_UUID,
|
|
'delete', query=query_release)])
|
|
|
|
def test_release_snapmirror_timeout(self):
|
|
# when a timeout happens, an exception is thrown by send_request
|
|
api_error = netapp_api.NaRetryableError()
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=api_error)
|
|
|
|
self.assertRaises(netapp_api.NaRetryableError,
|
|
self.client.release_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
@ddt.data('async', 'sync')
|
|
def test_resync_snapmirror(self, snapmirror_policy):
|
|
|
|
snapmirror_response = copy.deepcopy(
|
|
fake_client.SNAPMIRROR_GET_ITER_RESPONSE_REST)
|
|
snapmirror_response['records'][0]['policy'] = {
|
|
'type': snapmirror_policy}
|
|
|
|
if snapmirror_policy == 'async':
|
|
snapmirror_response['state'] = 'snapmirrored'
|
|
elif snapmirror_policy == 'sync':
|
|
snapmirror_response['state'] = 'in_sync'
|
|
|
|
response_list = [snapmirror_response,
|
|
fake_client.JOB_RESPONSE_REST,
|
|
snapmirror_response]
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=copy.deepcopy(response_list))
|
|
|
|
self.client.resync_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
query_uuid = {}
|
|
query_uuid['source.path'] = (fake_client.SM_SOURCE_VSERVER + ':' +
|
|
fake_client.SM_SOURCE_VOLUME)
|
|
query_uuid['destination.path'] = (fake_client.SM_DEST_VSERVER + ':' +
|
|
fake_client.SM_DEST_VOLUME)
|
|
query_uuid['fields'] = 'uuid,policy.type'
|
|
|
|
body_resync = {}
|
|
if snapmirror_policy == 'async':
|
|
body_resync['state'] = 'snapmirrored'
|
|
elif snapmirror_policy == 'sync':
|
|
body_resync['state'] = 'in_sync'
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/', 'get', query=query_uuid),
|
|
mock.call('/snapmirror/relationships/' + fake_client.FAKE_UUID,
|
|
'patch', body=body_resync)])
|
|
|
|
def test_resync_snapmirror_timeout(self):
|
|
api_error = netapp_api.NaRetryableError()
|
|
self.mock_object(self.client, 'resume_snapmirror',
|
|
side_effect=api_error)
|
|
|
|
self.assertRaises(netapp_api.NaRetryableError,
|
|
self.client.resync_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
def test_quiesce_snapmirror(self):
|
|
|
|
expected_job = {
|
|
'operation-id': None,
|
|
'status': None,
|
|
'jobid': fake_client.FAKE_UUID,
|
|
'error-code': None,
|
|
'error-message': None,
|
|
'relationship-uuid': fake_client.FAKE_UUID,
|
|
}
|
|
|
|
mock_set_snapmirror_state = self.mock_object(
|
|
self.client,
|
|
'_set_snapmirror_state',
|
|
return_value=expected_job)
|
|
|
|
result = self.client.quiesce_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
mock_set_snapmirror_state.assert_called_once_with(
|
|
'paused',
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
self.assertEqual(expected_job, result)
|
|
|
|
def test_break_snapmirror(self):
|
|
fake_snapmirror = fake_client.REST_GET_SNAPMIRRORS_RESPONSE
|
|
fake_uuid = fake_snapmirror[0]['uuid']
|
|
fake_body = {'state': 'broken_off'}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
mock_get_snap = self.mock_object(
|
|
self.client, '_get_snapmirrors',
|
|
mock.Mock(return_value=fake_snapmirror))
|
|
|
|
self.client.break_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
mock_get_snap.assert_called_once()
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/snapmirror/relationships/{fake_uuid}', 'patch', body=fake_body)
|
|
|
|
def test_break_snapmirror_not_found(self):
|
|
self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value={'records': []})
|
|
|
|
self.assertRaises(
|
|
netapp_utils.NetAppDriverException,
|
|
self.client.break_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
def test__break_snapmirror_error(self):
|
|
fake_snapmirror = fake_client.REST_GET_SNAPMIRRORS_RESPONSE
|
|
self.mock_object(self.client, '_get_snapmirrors',
|
|
return_value=fake_snapmirror)
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=self._mock_api_error())
|
|
self.assertRaises(netapp_api.NaApiError,
|
|
self.client.break_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
def test__break_snapmirror_exception(self):
|
|
fake_snapmirror = copy.deepcopy(
|
|
fake_client.REST_GET_SNAPMIRRORS_RESPONSE)
|
|
fake_snapmirror[0]['transferring-state'] = 'error'
|
|
|
|
self.mock_object(
|
|
self.client, '_get_snapmirrors',
|
|
mock.Mock(return_value=fake_snapmirror))
|
|
|
|
self.assertRaises(netapp_utils.NetAppDriverException,
|
|
self.client.break_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
def test_update_snapmirror(self):
|
|
|
|
snapmirrors = fake_client.REST_GET_SNAPMIRRORS_RESPONSE
|
|
self.mock_object(self.client, 'send_request')
|
|
self.mock_object(self.client, 'get_snapmirrors',
|
|
return_value=snapmirrors)
|
|
|
|
self.client.update_snapmirror(
|
|
fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/' +
|
|
snapmirrors[0]['uuid'] + '/transfers/', 'post',
|
|
wait_on_accepted=False)])
|
|
|
|
def test_update_snapmirror_no_records(self):
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
self.mock_object(self.client, 'get_snapmirrors',
|
|
return_value=[])
|
|
|
|
self.assertRaises(netapp_utils.NetAppDriverException,
|
|
self.client.update_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
self.client.send_request.assert_not_called()
|
|
|
|
def test_update_snapmirror_exception(self):
|
|
|
|
snapmirrors = fake_client.REST_GET_SNAPMIRRORS_RESPONSE
|
|
api_error = netapp_api.NaApiError(
|
|
code=netapp_api.REST_UPDATE_SNAPMIRROR_FAILED)
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=api_error)
|
|
self.mock_object(self.client, 'get_snapmirrors',
|
|
return_value=snapmirrors)
|
|
|
|
self.assertRaises(netapp_api.NaApiError,
|
|
self.client.update_snapmirror,
|
|
fake_client.SM_SOURCE_VSERVER,
|
|
fake_client.SM_SOURCE_VOLUME,
|
|
fake_client.SM_DEST_VSERVER,
|
|
fake_client.SM_DEST_VOLUME)
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/snapmirror/relationships/' +
|
|
snapmirrors[0]['uuid'] + '/transfers/', 'post',
|
|
wait_on_accepted=False)])
|
|
|
|
def test_mount_flexvol(self):
|
|
volumes = fake_client.VOLUME_GET_ITER_SSC_RESPONSE_REST
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=[volumes, None])
|
|
|
|
fake_path = '/fake_path'
|
|
fake_vol_name = volumes['records'][0]['name']
|
|
|
|
body = {
|
|
'nas.path': fake_path
|
|
}
|
|
query = {
|
|
'name': fake_vol_name
|
|
}
|
|
|
|
self.client.mount_flexvol(fake_client.VOLUME_NAME,
|
|
junction_path=fake_path)
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/storage/volumes', 'patch', body=body, query=query)])
|
|
|
|
def test_mount_flexvol_default_junction_path(self):
|
|
volumes = fake_client.VOLUME_GET_ITER_SSC_RESPONSE_REST
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=[volumes, None])
|
|
|
|
fake_vol_name = volumes['records'][0]['name']
|
|
body = {
|
|
'nas.path': '/' + fake_client.VOLUME_NAME
|
|
}
|
|
query = {
|
|
'name': fake_vol_name
|
|
}
|
|
|
|
self.client.mount_flexvol(fake_client.VOLUME_NAME)
|
|
|
|
self.client.send_request.assert_has_calls([
|
|
mock.call('/storage/volumes', 'patch', body=body, query=query)])
|
|
|
|
def test_get_cluster_name(self):
|
|
query = {'fields': 'name'}
|
|
|
|
self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.GET_CLUSTER_NAME_RESPONSE_REST)
|
|
|
|
result = self.client.get_cluster_name()
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/cluster', 'get', query=query, enable_tunneling=False)
|
|
self.assertEqual(
|
|
fake_client.GET_CLUSTER_NAME_RESPONSE_REST['name'], result)
|
|
|
|
@ddt.data(
|
|
(fake_client.VSERVER_NAME, fake_client.VSERVER_NAME_2),
|
|
(fake_client.VSERVER_NAME, None),
|
|
(None, fake_client.VSERVER_NAME_2),
|
|
(None, None))
|
|
@ddt.unpack
|
|
def test_get_vserver_peers(self, svm_name, peer_svm_name):
|
|
query = {
|
|
'fields': 'svm.name,state,peer.svm.name,peer.cluster.name,'
|
|
'applications'
|
|
}
|
|
if peer_svm_name:
|
|
query['name'] = peer_svm_name
|
|
if svm_name:
|
|
query['svm.name'] = svm_name
|
|
|
|
vserver_info = fake_client.GET_VSERVER_PEERS_RECORDS_REST[0]
|
|
|
|
expected_result = [{
|
|
'vserver': vserver_info['svm']['name'],
|
|
'peer-vserver': vserver_info['peer']['svm']['name'],
|
|
'peer-state': vserver_info['state'],
|
|
'peer-cluster': vserver_info['peer']['cluster']['name'],
|
|
'applications': vserver_info['applications'],
|
|
}]
|
|
|
|
self.mock_object(
|
|
self.client, 'send_request',
|
|
return_value=fake_client.GET_VSERVER_PEERS_RESPONSE_REST)
|
|
|
|
result = self.client.get_vserver_peers(
|
|
vserver_name=svm_name, peer_vserver_name=peer_svm_name)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/svm/peers', 'get', query=query, enable_tunneling=False)
|
|
self.assertEqual(expected_result, result)
|
|
|
|
def test_get_vserver_peers_empty(self):
|
|
vserver_peers_response = copy.deepcopy(
|
|
fake_client.GET_VSERVER_PEERS_RESPONSE_REST)
|
|
vserver_peers_response['records'] = []
|
|
vserver_peers_response['num_records'] = 0
|
|
query = {
|
|
'fields': 'svm.name,state,peer.svm.name,peer.cluster.name,'
|
|
'applications'
|
|
}
|
|
self.mock_object(
|
|
self.client, 'send_request', return_value=vserver_peers_response)
|
|
|
|
result = self.client.get_vserver_peers()
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/svm/peers', 'get', query=query, enable_tunneling=False)
|
|
self.assertEqual([], result)
|
|
|
|
@ddt.data(['snapmirror', 'lun_copy'], None)
|
|
def test_create_vserver_peer(self, applications):
|
|
body = {
|
|
'svm.name': fake_client.VSERVER_NAME,
|
|
'name': fake_client.VSERVER_NAME_2,
|
|
'applications': applications if applications else ['snapmirror']
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.create_vserver_peer(
|
|
fake_client.VSERVER_NAME, fake_client.VSERVER_NAME_2,
|
|
vserver_peer_application=applications)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/svm/peers', 'post', body=body, enable_tunneling=False)
|
|
|
|
@ddt.data(
|
|
(fake.VOLUME_NAME, fake.LUN_NAME),
|
|
(None, fake.LUN_NAME),
|
|
(fake.VOLUME_NAME, None),
|
|
(None, None)
|
|
)
|
|
@ddt.unpack
|
|
def test_start_lun_move(self, src_vol, dest_lun):
|
|
src_lun = f'src-lun-{fake.LUN_NAME}'
|
|
dest_vol = f'dest-vol-{fake.VOLUME_NAME}'
|
|
|
|
src_path = f'/vol/{src_vol if src_vol else dest_vol}/{src_lun}'
|
|
dest_path = f'/vol/{dest_vol}/{dest_lun if dest_lun else src_lun}'
|
|
body = {'name': dest_path}
|
|
|
|
self.mock_object(self.client, '_lun_update_by_path')
|
|
|
|
result = self.client.start_lun_move(
|
|
src_lun, dest_vol, src_ontap_volume=src_vol,
|
|
dest_lun_name=dest_lun)
|
|
|
|
self.client._lun_update_by_path.assert_called_once_with(
|
|
src_path, body)
|
|
self.assertEqual(dest_path, result)
|
|
|
|
@ddt.data(fake_client.LUN_GET_MOVEMENT_REST, None)
|
|
def test_get_lun_move_status(self, lun_moved):
|
|
dest_path = f'/vol/{fake.VOLUME_NAME}/{fake.LUN_NAME}'
|
|
move_status = None
|
|
if lun_moved:
|
|
move_progress = lun_moved['movement']['progress']
|
|
move_status = {
|
|
'job-status': move_progress['state'],
|
|
'last-failure-reason': move_progress['failure']['message']
|
|
}
|
|
|
|
self.mock_object(self.client, '_get_first_lun_by_path',
|
|
return_value=lun_moved)
|
|
|
|
result = self.client.get_lun_move_status(dest_path)
|
|
|
|
self.client._get_first_lun_by_path.assert_called_once_with(
|
|
dest_path, fields='movement.progress')
|
|
self.assertEqual(move_status, result)
|
|
|
|
@ddt.data(
|
|
(fake.VOLUME_NAME, fake.LUN_NAME),
|
|
(None, fake.LUN_NAME),
|
|
(fake.VOLUME_NAME, None),
|
|
(None, None)
|
|
)
|
|
@ddt.unpack
|
|
def test_start_lun_copy(self, src_vol, dest_lun):
|
|
src_lun = f'src-lun-{fake.LUN_NAME}'
|
|
dest_vol = f'dest-vol-{fake.VOLUME_NAME}'
|
|
dest_vserver = f'dest-vserver-{fake.VSERVER_NAME}'
|
|
|
|
src_path = f'/vol/{src_vol if src_vol else dest_vol}/{src_lun}'
|
|
dest_path = f'/vol/{dest_vol}/{dest_lun if dest_lun else src_lun}'
|
|
body = {
|
|
'name': dest_path,
|
|
'copy.source.name': src_path,
|
|
'svm.name': dest_vserver
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
result = self.client.start_lun_copy(
|
|
src_lun, dest_vol, dest_vserver,
|
|
src_ontap_volume=src_vol, src_vserver=fake_client.VSERVER_NAME,
|
|
dest_lun_name=dest_lun)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/luns', 'post', body=body, enable_tunneling=False)
|
|
self.assertEqual(dest_path, result)
|
|
|
|
@ddt.data(fake_client.LUN_GET_COPY_REST, None)
|
|
def test_get_lun_copy_status(self, lun_copied):
|
|
dest_path = f'/vol/{fake.VOLUME_NAME}/{fake.LUN_NAME}'
|
|
copy_status = None
|
|
if lun_copied:
|
|
copy_progress = lun_copied['copy']['source']['progress']
|
|
copy_status = {
|
|
'job-status': copy_progress['state'],
|
|
'last-failure-reason': copy_progress['failure']['message']
|
|
}
|
|
|
|
self.mock_object(self.client, '_get_first_lun_by_path',
|
|
return_value=lun_copied)
|
|
|
|
result = self.client.get_lun_copy_status(dest_path)
|
|
|
|
self.client._get_first_lun_by_path.assert_called_once_with(
|
|
dest_path, fields='copy.source.progress')
|
|
self.assertEqual(copy_status, result)
|
|
|
|
def test_cancel_lun_copy(self):
|
|
dest_path = f'/vol/{fake_client.VOLUME_NAME}/{fake_client.FILE_NAME}'
|
|
|
|
query = {
|
|
'name': dest_path,
|
|
'svm.name': fake_client.VSERVER_NAME
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.cancel_lun_copy(dest_path)
|
|
|
|
self.client.send_request.assert_called_once_with('/storage/luns/',
|
|
'delete', query=query)
|
|
|
|
def test_cancel_lun_copy_exception(self):
|
|
dest_path = f'/vol/{fake_client.VOLUME_NAME}/{fake_client.FILE_NAME}'
|
|
query = {
|
|
'name': dest_path,
|
|
'svm.name': fake_client.VSERVER_NAME
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request',
|
|
side_effect=self._mock_api_error())
|
|
|
|
self.assertRaises(
|
|
netapp_utils.NetAppDriverException,
|
|
self.client.cancel_lun_copy,
|
|
dest_path)
|
|
self.client.send_request.assert_called_once_with('/storage/luns/',
|
|
'delete', query=query)
|
|
|
|
# TODO(rfluisa): Add ddt data with None values for optional parameters to
|
|
# improve coverage.
|
|
def test_start_file_copy(self):
|
|
volume = fake_client.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
|
file_name = fake_client.FILE_NAME
|
|
dest_ontap_volume = fake_client.VOLUME_NAME
|
|
src_ontap_volume = dest_ontap_volume
|
|
dest_file_name = file_name
|
|
response = {'job': {'uuid': 'fake-uuid'}}
|
|
|
|
body = {
|
|
'files_to_copy': [
|
|
{
|
|
'source': {
|
|
'path': f'{src_ontap_volume}/{file_name}',
|
|
'volume': {
|
|
'uuid': volume['uuid']
|
|
}
|
|
},
|
|
'destination': {
|
|
'path': f'{dest_ontap_volume}/{dest_file_name}',
|
|
'volume': {
|
|
'uuid': volume['uuid']
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
self.mock_object(self.client, '_get_volume_by_args',
|
|
return_value=volume)
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=response)
|
|
|
|
result = self.client.start_file_copy(
|
|
file_name, dest_ontap_volume, src_ontap_volume=src_ontap_volume,
|
|
dest_file_name=dest_file_name)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/file/copy', 'post', body=body, enable_tunneling=False)
|
|
self.assertEqual(response['job']['uuid'], result)
|
|
|
|
# TODO(rfluisa): Add ddt data with None values for possible api responses
|
|
# to improve coverage.
|
|
def test_get_file_copy_status(self):
|
|
job_uuid = fake_client.FAKE_UUID
|
|
query = {}
|
|
query['fields'] = '*'
|
|
response = {
|
|
'state': 'fake-state',
|
|
'error': {
|
|
'message': 'fake-error-message'
|
|
}
|
|
}
|
|
expected_result = {
|
|
'job-status': response['state'],
|
|
'last-failure-reason': response['error']['message']
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request', return_value=response)
|
|
result = self.client.get_file_copy_status(job_uuid)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/cluster/jobs/{job_uuid}', 'get', query=query,
|
|
enable_tunneling=False)
|
|
self.assertEqual(expected_result, result)
|
|
|
|
@ddt.data(('success', 'complete'), ('failure', 'destroyed'))
|
|
@ddt.unpack
|
|
def test_get_file_copy_status_translate_state(self, from_state, to_state):
|
|
job_uuid = fake_client.FAKE_UUID
|
|
query = {}
|
|
query['fields'] = '*'
|
|
response = {
|
|
'state': from_state,
|
|
'error': {
|
|
'message': 'fake-error-message'
|
|
}
|
|
}
|
|
expected_result = {
|
|
'job-status': to_state,
|
|
'last-failure-reason': response['error']['message']
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request', return_value=response)
|
|
result = self.client.get_file_copy_status(job_uuid)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/cluster/jobs/{job_uuid}', 'get', query=query,
|
|
enable_tunneling=False)
|
|
self.assertEqual(expected_result, result)
|
|
|
|
def test_rename_file(self):
|
|
volume = fake_client.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
|
orig_file_name = f'/vol/{fake_client.VOLUME_NAMES[0]}/cinder-vol'
|
|
new_file_name = f'/vol/{fake_client.VOLUME_NAMES[0]}/new-cinder-vol'
|
|
body = {'path': new_file_name.split('/')[3]}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
self.mock_object(self.client, '_get_volume_by_args',
|
|
return_value=volume)
|
|
|
|
self.client.rename_file(orig_file_name, new_file_name)
|
|
|
|
orig_file_name = orig_file_name.split('/')[3]
|
|
self.client.send_request.assert_called_once_with(
|
|
f'/storage/volumes/{volume["uuid"]}/files/{orig_file_name}',
|
|
'patch', body=body)
|
|
self.client._get_volume_by_args.assert_called_once_with(
|
|
vol_name=fake_client.VOLUME_NAMES[0])
|
|
|
|
def test_get_namespace_list(self):
|
|
response = fake_client.GET_NAMESPACE_RESPONSE_REST
|
|
|
|
fake_query = {
|
|
'svm.name': 'fake_vserver',
|
|
'fields': 'svm.name,location.volume.name,space.size,'
|
|
'location.qtree.name,name,os_type,'
|
|
'space.guarantee.requested,uuid'
|
|
}
|
|
|
|
expected_result = [
|
|
{
|
|
'Vserver': 'fake_vserver1',
|
|
'Volume': 'fake_vol_001',
|
|
'Size': 999999,
|
|
'Qtree': '',
|
|
'Path': '/vol/fake_vol_001/test',
|
|
'OsType': 'linux',
|
|
'SpaceReserved': True,
|
|
'UUID': 'fake_uuid1'
|
|
},
|
|
{
|
|
'Vserver': 'fake_vserver2',
|
|
'Volume': 'fake_vol_002',
|
|
'Size': 8888888,
|
|
'Qtree': '',
|
|
'Path': '/vol/fake_vol_002/test',
|
|
'OsType': 'linux',
|
|
'SpaceReserved': True,
|
|
'UUID': 'fake_uuid2'
|
|
},
|
|
]
|
|
|
|
self.mock_object(self.client, 'send_request', return_value=response)
|
|
|
|
result = self.client.get_namespace_list()
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces/', 'get', query=fake_query)
|
|
self.assertEqual(expected_result, result)
|
|
|
|
def test_get_namespace_list_no_response(self):
|
|
response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
fake_query = {
|
|
'svm.name': 'fake_vserver',
|
|
'fields': 'svm.name,location.volume.name,space.size,'
|
|
'location.qtree.name,name,os_type,'
|
|
'space.guarantee.requested,uuid'
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request', return_value=response)
|
|
|
|
result = self.client.get_namespace_list()
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces/', 'get', query=fake_query)
|
|
self.assertEqual([], result)
|
|
|
|
def test_destroy_namespace(self):
|
|
|
|
fake_query = {
|
|
'name': '/vol/fake_vol_001/test',
|
|
'svm': 'fake_vserver'
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.destroy_namespace('/vol/fake_vol_001/test', force=False)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'delete', query=fake_query)
|
|
|
|
def test_destroy_namespace_force_true(self):
|
|
|
|
fake_query = {
|
|
'name': '/vol/fake_vol_001/test',
|
|
'svm': 'fake_vserver',
|
|
'allow_delete_while_mapped': 'true'
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.destroy_namespace('/vol/fake_vol_001/test', force=True)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'delete', query=fake_query)
|
|
|
|
def test_clone_namespace(self):
|
|
|
|
fake_body = {
|
|
'svm': {
|
|
'name': 'fake_vserver'
|
|
},
|
|
'name': '/vol/fake_volume/fake_new_name',
|
|
'clone': {
|
|
'source': {
|
|
'name': '/vol/fake_volume/fake_name',
|
|
}
|
|
}
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.clone_namespace('fake_volume',
|
|
'fake_name',
|
|
'fake_new_name')
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'post', body=fake_body)
|
|
|
|
def test_get_namespace_by_args(self):
|
|
response = fake_client.GET_NAMESPACE_RESPONSE_REST
|
|
|
|
lun_info_args = {
|
|
'vserver': fake.VSERVER_NAME,
|
|
'path': fake.LUN_PATH,
|
|
'uuid': fake.UUID1}
|
|
|
|
fake_query = {
|
|
'fields': 'svm.name,location.volume.name,space.size,'
|
|
'location.qtree.name,name,os_type,'
|
|
'space.guarantee.requested,uuid,space.block_size',
|
|
'svm.name': fake.VSERVER_NAME,
|
|
'name': fake.LUN_PATH,
|
|
'uuid': fake.UUID1,
|
|
}
|
|
|
|
expected_result = [
|
|
{
|
|
'Vserver': 'fake_vserver1',
|
|
'Volume': 'fake_vol_001',
|
|
'Size': 999999,
|
|
'Qtree': '',
|
|
'Path': '/vol/fake_vol_001/test',
|
|
'OsType': 'linux',
|
|
'SpaceReserved': True,
|
|
'UUID': 'fake_uuid1',
|
|
'BlockSize': 9999
|
|
},
|
|
{
|
|
'Vserver': 'fake_vserver2',
|
|
'Volume': 'fake_vol_002',
|
|
'Size': 8888888,
|
|
'Qtree': '',
|
|
'Path': '/vol/fake_vol_002/test',
|
|
'OsType': 'linux',
|
|
'SpaceReserved': True,
|
|
'UUID': 'fake_uuid2',
|
|
'BlockSize': 8888
|
|
},
|
|
]
|
|
|
|
self.mock_object(self.client, 'send_request', return_value=response)
|
|
|
|
result = self.client.get_namespace_by_args(**lun_info_args)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'get', query=fake_query)
|
|
self.assertEqual(expected_result, result)
|
|
|
|
def test_get_namespace_by_args_no_response(self):
|
|
response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
|
|
lun_info_args = {
|
|
'vserver': fake.VSERVER_NAME,
|
|
'path': fake.LUN_PATH,
|
|
'uuid': fake.UUID1}
|
|
|
|
fake_query = {
|
|
'fields': 'svm.name,location.volume.name,space.size,'
|
|
'location.qtree.name,name,os_type,'
|
|
'space.guarantee.requested,uuid,space.block_size',
|
|
'svm.name': fake.VSERVER_NAME,
|
|
'name': fake.LUN_PATH,
|
|
'uuid': fake.UUID1,
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request', return_value=response)
|
|
|
|
result = self.client.get_namespace_by_args(**lun_info_args)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'get', query=fake_query)
|
|
self.assertEqual([], result)
|
|
|
|
def test_namespace_resize(self):
|
|
fake_body = {'space.size': 9999}
|
|
fake_query = {'name': fake.LUN_PATH}
|
|
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.namespace_resize(fake.LUN_PATH, 9999)
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'patch', body=fake_body, query=fake_query)
|
|
|
|
def test_get_namespace_sizes_by_volume(self):
|
|
response = fake_client.GET_NAMESPACE_RESPONSE_REST
|
|
|
|
fake_query = {
|
|
'location.volume.name': 'fake_volume',
|
|
'fields': 'space.size,name'
|
|
}
|
|
|
|
expected_result = [
|
|
{
|
|
'path': '/vol/fake_vol_001/test',
|
|
'size': 999999,
|
|
},
|
|
{
|
|
'path': '/vol/fake_vol_002/test',
|
|
'size': 8888888,
|
|
},
|
|
]
|
|
|
|
self.mock_object(self.client, 'send_request', return_value=response)
|
|
|
|
result = self.client.get_namespace_sizes_by_volume('fake_volume')
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'get', query=fake_query)
|
|
self.assertEqual(expected_result, result)
|
|
|
|
def test_get_namespace_sizes_by_volume_no_response(self):
|
|
response = fake_client.NO_RECORDS_RESPONSE_REST
|
|
|
|
fake_query = {
|
|
'location.volume.name': 'fake_volume',
|
|
'fields': 'space.size,name'
|
|
}
|
|
|
|
self.mock_object(self.client, 'send_request', return_value=response)
|
|
|
|
result = self.client.get_namespace_sizes_by_volume('fake_volume')
|
|
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'get', query=fake_query)
|
|
self.assertEqual([], result)
|
|
|
|
def test_create_namespace(self):
|
|
"""Issues API request for creating namespace on volume."""
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.create_namespace(
|
|
fake_client.VOLUME_NAME, fake_client.NAMESPACE_NAME,
|
|
fake_client.VOLUME_SIZE_TOTAL, {'OsType': 'linux'})
|
|
|
|
path = f'/vol/{fake_client.VOLUME_NAME}/{fake_client.NAMESPACE_NAME}'
|
|
body = {
|
|
'name': path,
|
|
'space.size': str(fake_client.VOLUME_SIZE_TOTAL),
|
|
'os_type': 'linux',
|
|
}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/storage/namespaces', 'post', body=body)
|
|
|
|
def test_create_namespace_error(self):
|
|
api_error = netapp_api.NaApiError(code=0)
|
|
self.mock_object(self.client, 'send_request', side_effect=api_error)
|
|
|
|
self.assertRaises(
|
|
netapp_api.NaApiError,
|
|
self.client.create_namespace,
|
|
fake_client.VOLUME_NAME, fake_client.NAMESPACE_NAME,
|
|
fake_client.VOLUME_SIZE_TOTAL, {'OsType': 'linux'})
|
|
|
|
def test_get_subsystem_by_host(self):
|
|
response = fake_client.GET_SUBSYSTEM_RESPONSE_REST
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=response)
|
|
|
|
res = self.client.get_subsystem_by_host(fake_client.HOST_NQN)
|
|
|
|
expected_res = [
|
|
{'name': fake_client.SUBSYSTEM, 'os_type': 'linux'}]
|
|
self.assertEqual(expected_res, res)
|
|
query = {
|
|
'svm.name': self.client.vserver,
|
|
'hosts.nqn': fake_client.HOST_NQN,
|
|
'fields': 'name,os_type',
|
|
'name': 'openstack-*',
|
|
}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/nvme/subsystems', 'get', query=query)
|
|
|
|
def test_create_subsystem(self):
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.create_subsystem(fake_client.SUBSYSTEM, 'linux',
|
|
fake_client.HOST_NQN)
|
|
|
|
body = {
|
|
'svm.name': self.client.vserver,
|
|
'name': fake_client.SUBSYSTEM,
|
|
'os_type': 'linux',
|
|
'hosts': [{'nqn': fake_client.HOST_NQN}]
|
|
}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/nvme/subsystems', 'post', body=body)
|
|
|
|
def test_get_namespace_map(self):
|
|
response = fake_client.GET_SUBSYSTEM_MAP_RESPONSE_REST
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=response)
|
|
|
|
res = self.client.get_namespace_map(fake_client.NAMESPACE_NAME)
|
|
|
|
expected_res = [
|
|
{'subsystem': fake_client.SUBSYSTEM,
|
|
'uuid': fake_client.FAKE_UUID,
|
|
'vserver': fake_client.VSERVER_NAME}]
|
|
self.assertEqual(expected_res, res)
|
|
query = {
|
|
'namespace.name': fake_client.NAMESPACE_NAME,
|
|
'fields': 'subsystem.name,namespace.uuid,svm.name',
|
|
}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/nvme/subsystem-maps', 'get', query=query)
|
|
|
|
def test_map_namespace(self):
|
|
response = fake_client.GET_SUBSYSTEM_MAP_RESPONSE_REST
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=response)
|
|
|
|
res = self.client.map_namespace(fake_client.NAMESPACE_NAME,
|
|
fake_client.SUBSYSTEM)
|
|
|
|
self.assertEqual(fake_client.FAKE_UUID, res)
|
|
body = {
|
|
'namespace.name': fake_client.NAMESPACE_NAME,
|
|
'subsystem.name': fake_client.SUBSYSTEM
|
|
}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/nvme/subsystem-maps', 'post', body=body,
|
|
query={'return_records': 'true'})
|
|
|
|
def test_map_namespace_error(self):
|
|
api_error = netapp_api.NaApiError(code=0)
|
|
self.mock_object(self.client, 'send_request', side_effect=api_error)
|
|
|
|
self.assertRaises(
|
|
netapp_api.NaApiError,
|
|
self.client.map_namespace,
|
|
fake_client.VOLUME_NAME, fake_client.SUBSYSTEM)
|
|
|
|
@ddt.data(
|
|
{'response': fake_client.GET_SUBSYSTEM_RESPONSE_REST,
|
|
'expected': fake_client.TARGET_NQN},
|
|
{'response': fake_client.NO_RECORDS_RESPONSE_REST,
|
|
'expected': None})
|
|
@ddt.unpack
|
|
def test_get_nvme_subsystem_nqn(self, response, expected):
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=response)
|
|
|
|
res = self.client.get_nvme_subsystem_nqn(fake_client.SUBSYSTEM)
|
|
|
|
self.assertEqual(expected, res)
|
|
query = {
|
|
'fields': 'target_nqn',
|
|
'name': fake_client.SUBSYSTEM,
|
|
'svm.name': self.client.vserver
|
|
}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/nvme/subsystems', 'get', query=query)
|
|
|
|
def test_get_nvme_target_portals(self):
|
|
response = fake_client.GET_INTERFACES_NVME_REST
|
|
self.mock_object(self.client, 'send_request',
|
|
return_value=response)
|
|
|
|
res = self.client.get_nvme_target_portals()
|
|
|
|
expected = ["10.10.10.10"]
|
|
self.assertEqual(expected, res)
|
|
query = {
|
|
'services': 'data_nvme_tcp',
|
|
'fields': 'ip.address',
|
|
'enabled': 'true',
|
|
}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/network/ip/interfaces', 'get', query=query)
|
|
|
|
def test_unmap_namespace(self):
|
|
self.mock_object(self.client, 'send_request')
|
|
|
|
self.client.unmap_namespace(fake_client.NAMESPACE_NAME,
|
|
fake_client.SUBSYSTEM)
|
|
|
|
query = {
|
|
'subsystem.name': fake_client.SUBSYSTEM,
|
|
'namespace.name': fake_client.NAMESPACE_NAME,
|
|
}
|
|
self.client.send_request.assert_called_once_with(
|
|
'/protocols/nvme/subsystem-maps', 'delete', query=query)
|