Skip to content

Commit e87c160

Browse files
Merge pull request #1407 from Rajarajan97/master
Adding disaster recovery failover api method
2 parents 9d98b3e + 687678c commit e87c160

File tree

11 files changed

+170
-7
lines changed

11 files changed

+170
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Failover an inaccessible block volume to its available replicant volume."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
import SoftLayer
6+
from SoftLayer.CLI import environment
7+
from SoftLayer.CLI import exceptions
8+
from SoftLayer.CLI import formatting
9+
10+
11+
@click.command(epilog="""Failover an inaccessible block volume to its available replicant volume.
12+
If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately
13+
failover to an available replica in another location. This method does not allow for failback via API.
14+
After using this method, to failback to the original volume, please open a support ticket.
15+
If you wish to test failover, please use replica-failover.""")
16+
@click.argument('volume-id')
17+
@click.option('--replicant-id', help="ID of the replicant volume")
18+
@environment.pass_env
19+
def cli(env, volume_id, replicant_id):
20+
"""Failover an inaccessible block volume to its available replicant volume."""
21+
block_storage_manager = SoftLayer.BlockStorageManager(env.client)
22+
23+
click.secho("""WARNING : Failover an inaccessible block volume to its available replicant volume."""
24+
"""If a volume (with replication) becomes inaccessible due to a disaster event,"""
25+
"""this method can be used to immediately failover to an available replica in another location."""
26+
"""This method does not allow for failback via the API."""
27+
"""To failback to the original volume after using this method, open a support ticket."""
28+
"""If you wish to test failover, use replica-failover instead.""", fg='red')
29+
30+
if not formatting.confirm('Are you sure you want to continue?'):
31+
raise exceptions.CLIAbort('Aborted.')
32+
33+
block_storage_manager.disaster_recovery_failover_to_replicant(
34+
volume_id,
35+
replicant_id
36+
)
37+
38+
click.echo("Disaster Recovery Failover to replicant is now in progress.")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Failover an inaccessible file volume to its available replicant volume."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
import SoftLayer
6+
from SoftLayer.CLI import environment
7+
from SoftLayer.CLI import exceptions
8+
from SoftLayer.CLI import formatting
9+
10+
11+
@click.command(epilog="""Failover an inaccessible file volume to its available replicant volume.
12+
If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately
13+
failover to an available replica in another location. This method does not allow for failback via API.
14+
After using this method, to failback to the original volume, please open a support ticket.
15+
If you wish to test failover, please use replica-failover.
16+
""")
17+
@click.argument('volume-id')
18+
@click.option('--replicant-id', help="ID of the replicant volume")
19+
@environment.pass_env
20+
def cli(env, volume_id, replicant_id):
21+
"""Failover an inaccessible file volume to its available replicant volume."""
22+
file_storage_manager = SoftLayer.FileStorageManager(env.client)
23+
24+
click.secho("""WARNING : Failover an inaccessible file volume to its available replicant volume."""
25+
"""If a volume (with replication) becomes inaccessible due to a disaster event,"""
26+
"""this method can be used to immediately failover to an available replica in another location."""
27+
"""This method does not allow for failback via the API."""
28+
"""To failback to the original volume after using this method, open a support ticket."""
29+
"""If you wish to test failover, use replica-failover instead.""", fg='red')
30+
31+
if not formatting.confirm('Are you sure you want to continue?'):
32+
raise exceptions.CLIAbort('Aborted.')
33+
34+
file_storage_manager.disaster_recovery_failover_to_replicant(
35+
volume_id,
36+
replicant_id
37+
)
38+
39+
click.echo("Disaster Recovery Failover to replicant is now in progress.")

SoftLayer/CLI/routes.py

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
('block:subnets-remove', 'SoftLayer.CLI.block.subnets.remove:cli'),
9393
('block:replica-failback', 'SoftLayer.CLI.block.replication.failback:cli'),
9494
('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'),
95+
('block:disaster-recovery-failover', 'SoftLayer.CLI.block.replication.disaster_recovery_failover:cli'),
9596
('block:replica-order', 'SoftLayer.CLI.block.replication.order:cli'),
9697
('block:replica-partners', 'SoftLayer.CLI.block.replication.partners:cli'),
9798
('block:replica-locations', 'SoftLayer.CLI.block.replication.locations:cli'),
@@ -127,6 +128,7 @@
127128
('file:access-revoke', 'SoftLayer.CLI.file.access.revoke:cli'),
128129
('file:replica-failback', 'SoftLayer.CLI.file.replication.failback:cli'),
129130
('file:replica-failover', 'SoftLayer.CLI.file.replication.failover:cli'),
131+
('file:disaster-recovery-failover', 'SoftLayer.CLI.file.replication.disaster_recovery_failover:cli'),
130132
('file:replica-order', 'SoftLayer.CLI.file.replication.order:cli'),
131133
('file:replica-partners', 'SoftLayer.CLI.file.replication.partners:cli'),
132134
('file:replica-locations', 'SoftLayer.CLI.file.replication.locations:cli'),

SoftLayer/fixtures/SoftLayer_Network_Storage.py

+1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@
221221
failoverToReplicant = True
222222
failbackFromReplicant = True
223223
restoreFromSnapshot = True
224+
disasterRecoveryFailoverToReplicant = True
224225

225226
createSnapshot = {
226227
'id': 449

SoftLayer/managers/storage.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,21 @@ def failover_to_replicant(self, volume_id, replicant_id):
398398
"""
399399
return self.client.call('Network_Storage', 'failoverToReplicant', replicant_id, id=volume_id)
400400

401+
def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id):
402+
"""Disaster Recovery Failover to a volume replicant.
403+
404+
:param integer volume_id: The id of the volume
405+
:param integer replicant: ID of replicant to failover to
406+
:return: Returns whether failover to successful or not
407+
"""
408+
return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id)
409+
401410
def failback_from_replicant(self, volume_id):
402411
"""Failback from a volume replicant.
403412
404413
:param integer volume_id: The id of the volume
405414
:return: Returns whether failback was successful or not
406415
"""
407-
408416
return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id)
409417

410418
def cancel_volume(self, volume_id, reason='No longer needed', immediate=False):

docs/cli/block.rst

+4
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,7 @@ Block Commands
142142
.. click:: SoftLayer.CLI.block.set_note:cli
143143
:prog: block volume-set-note
144144
:show-nested:
145+
146+
.. click:: SoftLayer.CLI.block.replication.disaster_recovery_failover:cli
147+
:prog: block disaster-recovery-failover
148+
:show-nested:

docs/cli/file.rst

+4
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,8 @@ File Commands
121121

122122
.. click:: SoftLayer.CLI.file.set_note:cli
123123
:prog: file volume-set-note
124+
:show-nested:
125+
126+
.. click:: SoftLayer.CLI.file.replication.disaster_recovery_failover:cli
127+
:prog: file disaster-recovery-failover
124128
:show-nested:

tests/CLI/modules/block_tests.py

+26-4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
55
:license: MIT, see LICENSE for more details.
66
"""
7-
from SoftLayer import exceptions
7+
from SoftLayer.CLI import exceptions
8+
from SoftLayer import SoftLayerAPIError
89
from SoftLayer import testing
910

11+
1012
import json
1113
import mock
1214

@@ -48,7 +50,7 @@ def test_volume_set_lun_id_in_range_missing_value(self):
4850
def test_volume_set_lun_id_not_in_range(self):
4951
value = '-1'
5052
lun_mock = self.set_mock('SoftLayer_Network_Storage', 'createOrUpdateLunId')
51-
lun_mock.side_effect = exceptions.SoftLayerAPIError(
53+
lun_mock.side_effect = SoftLayerAPIError(
5254
'SoftLayer_Exception_Network_Storage_Iscsi_InvalidLunId',
5355
'The LUN ID specified is out of the valid range: %s [min: 0 max: 4095]' % (value))
5456
result = self.run_command('block volume-set-lun-id 1234 42'.split())
@@ -496,8 +498,18 @@ def test_replicant_failover(self):
496498
'--replicant-id=5678'])
497499

498500
self.assert_no_fail(result)
499-
self.assertEqual('Failover to replicant is now in progress.\n',
500-
result.output)
501+
self.assertEqual('Failover to replicant is now in progress.\n', result.output)
502+
503+
@mock.patch('SoftLayer.CLI.formatting.confirm')
504+
@mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant')
505+
def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock):
506+
confirm_mock.return_value = True
507+
disaster_recovery_failover_mock.return_value = True
508+
result = self.run_command(['block', 'disaster-recovery-failover', '12345678',
509+
'--replicant-id=5678'])
510+
511+
self.assert_no_fail(result)
512+
self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output)
501513

502514
def test_replication_locations(self):
503515
result = self.run_command(['block', 'replica-locations', '1234'])
@@ -558,6 +570,16 @@ def test_replicant_failover_unsuccessful(self, failover_mock):
558570
self.assertEqual('Failover operation could not be initiated.\n',
559571
result.output)
560572

573+
@mock.patch('SoftLayer.CLI.formatting.confirm')
574+
def test_disaster_recovery_failover_aborted(self, confirm_mock):
575+
confirm_mock.return_value = False
576+
577+
result = self.run_command(['block', 'disaster-recovery-failover', '12345678',
578+
'--replicant-id=5678'])
579+
580+
self.assertEqual(result.exit_code, 2)
581+
self.assertIsInstance(result.exception, exceptions.CLIAbort)
582+
561583
def test_replicant_failback(self):
562584
result = self.run_command(['block', 'replica-failback', '12345678'])
563585

tests/CLI/modules/file_tests.py

+23-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
55
:license: MIT, see LICENSE for more details.
66
"""
7-
from SoftLayer import exceptions
7+
from SoftLayer.CLI import exceptions
8+
from SoftLayer import SoftLayerError
89
from SoftLayer import testing
910

1011
import json
@@ -126,7 +127,7 @@ def test_volume_cancel_without_billing_item(self):
126127
result = self.run_command([
127128
'--really', 'file', 'volume-cancel', '1234'])
128129

129-
self.assertIsInstance(result.exception, exceptions.SoftLayerError)
130+
self.assertIsInstance(result.exception, SoftLayerError)
130131

131132
def test_volume_detail(self):
132133
result = self.run_command(['file', 'volume-detail', '1234'])
@@ -493,6 +494,26 @@ def test_replicant_failover_unsuccessful(self, failover_mock):
493494
self.assertEqual('Failover operation could not be initiated.\n',
494495
result.output)
495496

497+
@mock.patch('SoftLayer.CLI.formatting.confirm')
498+
@mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant')
499+
def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock):
500+
confirm_mock.return_value = True
501+
disaster_recovery_failover_mock.return_value = True
502+
result = self.run_command(['file', 'disaster-recovery-failover', '12345678', '--replicant-id=5678'])
503+
504+
self.assert_no_fail(result)
505+
self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output)
506+
507+
@mock.patch('SoftLayer.CLI.formatting.confirm')
508+
def test_disaster_recovery_failover_aborted(self, confirm_mock):
509+
confirm_mock.return_value = False
510+
511+
result = self.run_command(['file', 'disaster-recovery-failover', '12345678',
512+
'--replicant-id=5678'])
513+
514+
self.assertEqual(result.exit_code, 2)
515+
self.assertIsInstance(result.exception, exceptions.CLIAbort)
516+
496517
def test_replicant_failback(self):
497518
result = self.run_command(['file', 'replica-failback', '12345678'])
498519

tests/managers/block_tests.py

+12
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,18 @@ def test_replicant_failover(self):
384384
identifier=1234,
385385
)
386386

387+
def test_disaster_recovery_failover(self):
388+
result = self.block.disaster_recovery_failover_to_replicant(1234, 5678)
389+
390+
self.assertEqual(
391+
SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result)
392+
self.assert_called_with(
393+
'SoftLayer_Network_Storage',
394+
'disasterRecoveryFailoverToReplicant',
395+
args=(5678,),
396+
identifier=1234,
397+
)
398+
387399
def test_replicant_failback(self):
388400
result = self.block.failback_from_replicant(1234)
389401

tests/managers/file_tests.py

+12
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,18 @@ def test_replicant_failover(self):
289289
identifier=1234,
290290
)
291291

292+
def test_disaster_recovery_failover(self):
293+
result = self.file.disaster_recovery_failover_to_replicant(1234, 5678)
294+
295+
self.assertEqual(
296+
SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result)
297+
self.assert_called_with(
298+
'SoftLayer_Network_Storage',
299+
'disasterRecoveryFailoverToReplicant',
300+
args=(5678,),
301+
identifier=1234,
302+
)
303+
292304
def test_replicant_failback(self):
293305
result = self.file.failback_from_replicant(1234)
294306

0 commit comments

Comments
 (0)