Merge "NetApp ONTAP: Add volume migration functions on REST client"
This commit is contained in:
commit
55239a7fc0
@ -2994,3 +2994,67 @@ JOB_ERROR_REST = {
|
||||
"start_time": "2022-02-18T20:08:03+00:00",
|
||||
"end_time": "2022-02-18T20:08:04+00:00",
|
||||
}
|
||||
|
||||
GET_CLUSTER_NAME_RESPONSE_REST = {
|
||||
"name": CLUSTER_NAME,
|
||||
"uuid": "fake-cluster-uuid"
|
||||
}
|
||||
|
||||
GET_VSERVER_PEERS_RECORDS_REST = [
|
||||
{
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "/api/resourcelink"
|
||||
}
|
||||
},
|
||||
"applications": [
|
||||
"snapmirror",
|
||||
"lun_copy"
|
||||
],
|
||||
"name": CLUSTER_NAME,
|
||||
"peer": {
|
||||
"cluster": {
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "/api/resourcelink"
|
||||
}
|
||||
},
|
||||
"name": REMOTE_CLUSTER_NAME,
|
||||
"uuid": "fake-cluster-uuid-2"
|
||||
},
|
||||
"svm": {
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "/api/resourcelink"
|
||||
}
|
||||
},
|
||||
"name": VSERVER_NAME_2,
|
||||
"uuid": "fake-svm-uuid-2"
|
||||
}
|
||||
},
|
||||
"state": "peered",
|
||||
"svm": {
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "/api/resourcelink"
|
||||
}
|
||||
},
|
||||
"name": VSERVER_NAME,
|
||||
"uuid": "fake-svm-uuid"
|
||||
},
|
||||
"uuid": "fake-cluster-uuid"
|
||||
}
|
||||
]
|
||||
|
||||
GET_VSERVER_PEERS_RESPONSE_REST = {
|
||||
"_links": {
|
||||
"next": {
|
||||
"href": "/api/resourcelink"
|
||||
},
|
||||
"self": {
|
||||
"href": "/api/resourcelink"
|
||||
}
|
||||
},
|
||||
"num_records": 1,
|
||||
"records": GET_VSERVER_PEERS_RECORDS_REST
|
||||
}
|
||||
|
@ -1734,7 +1734,8 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
|
||||
result = self.client._get_first_lun_by_path(lun_path)
|
||||
|
||||
self.client._get_lun_by_path.assert_called_once_with(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:
|
||||
@ -2391,6 +2392,7 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
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}'
|
||||
|
||||
@ -2402,7 +2404,7 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
|
||||
self.client.destroy_lun(path)
|
||||
self.client.destroy_lun(path, force)
|
||||
|
||||
self.client.send_request.assert_called_once_with('/storage/luns/',
|
||||
'delete', query=query)
|
||||
@ -3380,3 +3382,309 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
|
||||
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)
|
||||
|
@ -616,25 +616,23 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
|
||||
def _move_lun(self, volume, src_ontap_volume, dest_ontap_volume,
|
||||
dest_lun_name=None):
|
||||
"""Moves LUN from an ONTAP volume to another."""
|
||||
job_uuid = self.zapi_client.start_lun_move(
|
||||
operation_info = self.zapi_client.start_lun_move(
|
||||
volume.name, dest_ontap_volume, src_ontap_volume=src_ontap_volume,
|
||||
dest_lun_name=dest_lun_name)
|
||||
LOG.debug('Start moving LUN %s from %s to %s. '
|
||||
'Job UUID is %s.', volume.name, src_ontap_volume,
|
||||
dest_ontap_volume, job_uuid)
|
||||
LOG.debug('Start moving LUN %s from %s to %s. ',
|
||||
volume.name, src_ontap_volume,
|
||||
dest_ontap_volume)
|
||||
|
||||
def _wait_lun_move_complete():
|
||||
move_status = self.zapi_client.get_lun_move_status(job_uuid)
|
||||
LOG.debug('Waiting for LUN move job %s to complete. '
|
||||
'Current status is: %s.', job_uuid,
|
||||
move_status['job-status'])
|
||||
move_status = self.zapi_client.get_lun_move_status(operation_info)
|
||||
LOG.debug('Waiting for LUN move to complete. '
|
||||
'Current status is: %s.', move_status['job-status'])
|
||||
|
||||
if not move_status:
|
||||
status_error_msg = (_("Error moving LUN %s. The "
|
||||
"corresponding Job UUID % doesn't "
|
||||
"exist."))
|
||||
status_error_msg = (_("Error moving LUN %s. The movement"
|
||||
"status could not be retrieved."))
|
||||
raise na_utils.NetAppDriverException(
|
||||
status_error_msg % (volume.id, job_uuid))
|
||||
status_error_msg % (volume.id))
|
||||
elif move_status['job-status'] == 'destroyed':
|
||||
status_error_msg = (_('Error moving LUN %s. %s.'))
|
||||
raise na_utils.NetAppDriverException(
|
||||
@ -676,29 +674,27 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
|
||||
dest_ontap_volume, dest_vserver, dest_lun_name=None,
|
||||
dest_backend_name=None, cancel_on_error=False):
|
||||
"""Copies LUN from an ONTAP volume to another."""
|
||||
job_uuid = self.zapi_client.start_lun_copy(
|
||||
operation_info = self.zapi_client.start_lun_copy(
|
||||
volume.name, dest_ontap_volume, dest_vserver,
|
||||
src_ontap_volume=src_ontap_volume, src_vserver=src_vserver,
|
||||
dest_lun_name=dest_lun_name)
|
||||
LOG.debug('Start copying LUN %(vol)s from '
|
||||
'%(src_vserver)s:%(src_ontap_vol)s to '
|
||||
'%(dest_vserver)s:%(dest_ontap_vol)s. Job UUID is %(job)s.',
|
||||
'%(dest_vserver)s:%(dest_ontap_vol)s.',
|
||||
{'vol': volume.name, 'src_vserver': src_vserver,
|
||||
'src_ontap_vol': src_ontap_volume,
|
||||
'dest_vserver': dest_vserver,
|
||||
'dest_ontap_vol': dest_ontap_volume,
|
||||
'job': job_uuid})
|
||||
'dest_ontap_vol': dest_ontap_volume})
|
||||
|
||||
def _wait_lun_copy_complete():
|
||||
copy_status = self.zapi_client.get_lun_copy_status(job_uuid)
|
||||
LOG.debug('Waiting for LUN copy job %s to complete. Current '
|
||||
'status is: %s.', job_uuid, copy_status['job-status'])
|
||||
copy_status = self.zapi_client.get_lun_copy_status(operation_info)
|
||||
LOG.debug('Waiting for LUN copy job to complete. Current '
|
||||
'status is: %s.', copy_status['job-status'])
|
||||
if not copy_status:
|
||||
status_error_msg = (_("Error copying LUN %s. The "
|
||||
"corresponding Job UUID % doesn't "
|
||||
"exist."))
|
||||
status_error_msg = (_("Error copying LUN %s. The copy"
|
||||
"status could not be retrieved."))
|
||||
raise na_utils.NetAppDriverException(
|
||||
status_error_msg % (volume.id, job_uuid))
|
||||
status_error_msg % (volume.id))
|
||||
elif copy_status['job-status'] == 'destroyed':
|
||||
status_error_msg = (_('Error copying LUN %s. %s.'))
|
||||
raise na_utils.NetAppDriverException(
|
||||
@ -717,7 +713,8 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception() as ctxt:
|
||||
if cancel_on_error:
|
||||
self._cancel_lun_copy(job_uuid, volume, dest_ontap_volume,
|
||||
self._cancel_lun_copy(operation_info, volume,
|
||||
dest_ontap_volume,
|
||||
dest_backend_name=dest_backend_name)
|
||||
if isinstance(e, loopingcall.LoopingCallTimeOut):
|
||||
ctxt.reraise = False
|
||||
|
@ -1338,15 +1338,19 @@ class RestClient(object):
|
||||
|
||||
self._lun_update_by_path(path, body)
|
||||
|
||||
def _get_lun_by_path(self, path):
|
||||
def _get_lun_by_path(self, path, fields=None):
|
||||
query = {'name': path}
|
||||
|
||||
if fields:
|
||||
query['fields'] = fields
|
||||
|
||||
response = self.send_request('/storage/luns', 'get', query=query)
|
||||
records = response.get('records', [])
|
||||
|
||||
return records
|
||||
|
||||
def _get_first_lun_by_path(self, path):
|
||||
records = self._get_lun_by_path(path)
|
||||
def _get_first_lun_by_path(self, path, fields=None):
|
||||
records = self._get_lun_by_path(path, fields=fields)
|
||||
if len(records) == 0:
|
||||
return None
|
||||
|
||||
@ -2282,8 +2286,214 @@ class RestClient(object):
|
||||
|
||||
def mount_flexvol(self, flexvol_name, junction_path=None):
|
||||
"""Mounts a volume on a junction path."""
|
||||
|
||||
query = {'name': flexvol_name}
|
||||
body = {'nas.path': (
|
||||
junction_path if junction_path else '/%s' % flexvol_name)}
|
||||
self.send_request('/storage/volumes', 'patch', query=query, body=body)
|
||||
|
||||
def get_cluster_name(self):
|
||||
"""Gets cluster name."""
|
||||
query = {'fields': 'name'}
|
||||
|
||||
response = self.send_request('/cluster', 'get', query=query,
|
||||
enable_tunneling=False)
|
||||
|
||||
return response['name']
|
||||
|
||||
def get_vserver_peers(self, vserver_name=None, peer_vserver_name=None):
|
||||
"""Gets one or more Vserver peer relationships."""
|
||||
query = {
|
||||
'fields': 'svm.name,state,peer.svm.name,peer.cluster.name,'
|
||||
'applications'
|
||||
}
|
||||
|
||||
if peer_vserver_name:
|
||||
query['name'] = peer_vserver_name
|
||||
if vserver_name:
|
||||
query['svm.name'] = vserver_name
|
||||
|
||||
response = self.send_request('/svm/peers', 'get', query=query,
|
||||
enable_tunneling=False)
|
||||
records = response.get('records', [])
|
||||
|
||||
vserver_peers = []
|
||||
for vserver_info in records:
|
||||
vserver_peer = {
|
||||
'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'],
|
||||
}
|
||||
vserver_peers.append(vserver_peer)
|
||||
|
||||
return vserver_peers
|
||||
|
||||
def create_vserver_peer(self, vserver_name, peer_vserver_name,
|
||||
vserver_peer_application=None):
|
||||
"""Creates a Vserver peer relationship."""
|
||||
# default peering application to `snapmirror` if none is specified.
|
||||
if not vserver_peer_application:
|
||||
vserver_peer_application = ['snapmirror']
|
||||
|
||||
body = {
|
||||
'svm.name': vserver_name,
|
||||
'name': peer_vserver_name,
|
||||
'applications': vserver_peer_application
|
||||
}
|
||||
|
||||
self.send_request('/svm/peers', 'post', body=body,
|
||||
enable_tunneling=False)
|
||||
|
||||
def start_lun_move(self, lun_name, dest_ontap_volume,
|
||||
src_ontap_volume=None, dest_lun_name=None):
|
||||
"""Starts a lun move operation between ONTAP volumes."""
|
||||
if dest_lun_name is None:
|
||||
dest_lun_name = lun_name
|
||||
if src_ontap_volume is None:
|
||||
src_ontap_volume = dest_ontap_volume
|
||||
|
||||
src_path = f'/vol/{src_ontap_volume}/{lun_name}'
|
||||
dest_path = f'/vol/{dest_ontap_volume}/{dest_lun_name}'
|
||||
body = {'name': dest_path}
|
||||
self._lun_update_by_path(src_path, body)
|
||||
|
||||
return dest_path
|
||||
|
||||
def get_lun_move_status(self, dest_path):
|
||||
"""Get lun move job status from a given dest_path."""
|
||||
lun = self._get_first_lun_by_path(
|
||||
dest_path, fields='movement.progress')
|
||||
|
||||
if not lun:
|
||||
return None
|
||||
|
||||
move_progress = lun['movement']['progress']
|
||||
move_status = {
|
||||
'job-status': move_progress['state'],
|
||||
'last-failure-reason': (move_progress
|
||||
.get('failure', {})
|
||||
.get('message', None))
|
||||
}
|
||||
|
||||
return move_status
|
||||
|
||||
def start_lun_copy(self, lun_name, dest_ontap_volume, dest_vserver,
|
||||
src_ontap_volume=None, src_vserver=None,
|
||||
dest_lun_name=None):
|
||||
"""Starts a lun copy operation between ONTAP volumes."""
|
||||
if src_ontap_volume is None:
|
||||
src_ontap_volume = dest_ontap_volume
|
||||
if src_vserver is None:
|
||||
src_vserver = dest_vserver
|
||||
if dest_lun_name is None:
|
||||
dest_lun_name = lun_name
|
||||
|
||||
src_path = f'/vol/{src_ontap_volume}/{lun_name}'
|
||||
dest_path = f'/vol/{dest_ontap_volume}/{dest_lun_name}'
|
||||
|
||||
body = {
|
||||
'name': dest_path,
|
||||
'copy.source.name': src_path,
|
||||
'svm.name': dest_vserver
|
||||
}
|
||||
|
||||
self.send_request('/storage/luns', 'post', body=body,
|
||||
enable_tunneling=False)
|
||||
|
||||
return dest_path
|
||||
|
||||
def get_lun_copy_status(self, dest_path):
|
||||
"""Get lun copy job status from a given dest_path."""
|
||||
lun = self._get_first_lun_by_path(
|
||||
dest_path, fields='copy.source.progress')
|
||||
|
||||
if not lun:
|
||||
return None
|
||||
|
||||
copy_progress = lun['copy']['source']['progress']
|
||||
copy_status = {
|
||||
'job-status': copy_progress['state'],
|
||||
'last-failure-reason': (copy_progress
|
||||
.get('failure', {})
|
||||
.get('message', None))
|
||||
}
|
||||
|
||||
return copy_status
|
||||
|
||||
def cancel_lun_copy(self, dest_path):
|
||||
"""Cancel an in-progress lun copy by deleting the lun."""
|
||||
query = {
|
||||
'name': dest_path,
|
||||
'svm.name': self.vserver
|
||||
}
|
||||
|
||||
try:
|
||||
self.send_request('/storage/luns/', 'delete', query=query)
|
||||
except netapp_api.NaApiError as e:
|
||||
msg = (_('Could not cancel lun copy by deleting lun at %s. %s'))
|
||||
raise na_utils.NetAppDriverException(msg % (dest_path, e))
|
||||
|
||||
def start_file_copy(self, file_name, dest_ontap_volume,
|
||||
src_ontap_volume=None,
|
||||
dest_file_name=None):
|
||||
"""Starts a file copy operation between ONTAP volumes."""
|
||||
if src_ontap_volume is None:
|
||||
src_ontap_volume = dest_ontap_volume
|
||||
if dest_file_name is None:
|
||||
dest_file_name = file_name
|
||||
|
||||
source_vol = self._get_volume_by_args(src_ontap_volume)
|
||||
|
||||
dest_vol = source_vol
|
||||
if dest_ontap_volume != src_ontap_volume:
|
||||
dest_vol = self._get_volume_by_args(dest_ontap_volume)
|
||||
|
||||
body = {
|
||||
'files_to_copy': [
|
||||
{
|
||||
'source': {
|
||||
'path': f'{src_ontap_volume}/{file_name}',
|
||||
'volume': {
|
||||
'uuid': source_vol['uuid']
|
||||
}
|
||||
},
|
||||
'destination': {
|
||||
'path': f'{dest_ontap_volume}/{dest_file_name}',
|
||||
'volume': {
|
||||
'uuid': dest_vol['uuid']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
result = self.send_request('/storage/file/copy', 'post', body=body,
|
||||
enable_tunneling=False)
|
||||
return result['job']['uuid']
|
||||
|
||||
def get_file_copy_status(self, job_uuid):
|
||||
"""Get file copy job status from a given job's UUID."""
|
||||
# TODO(rfluisa): Select only the fields that are needed here.
|
||||
query = {}
|
||||
query['fields'] = '*'
|
||||
|
||||
result = self.send_request(
|
||||
f'/cluster/jobs/{job_uuid}', 'get', query=query,
|
||||
enable_tunneling=False)
|
||||
|
||||
if not result or not result.get('state', None):
|
||||
return None
|
||||
|
||||
state = result.get('state')
|
||||
if state == 'success':
|
||||
state = 'complete'
|
||||
elif state == 'failure':
|
||||
state = 'destroyed'
|
||||
|
||||
copy_status = {
|
||||
'job-status': state,
|
||||
'last-failure-reason': result.get('error', {}).get('message', None)
|
||||
}
|
||||
|
||||
return copy_status
|
||||
|
Loading…
x
Reference in New Issue
Block a user