diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index aa18a1626..a5af2a671 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -79,7 +79,7 @@ def cli(env, storage_type, size, iops, tier, os_type, order = block_manager.order_block_volume( storage_type='performance_storage_iscsi', location=location, - size=size, + size=int(size), iops=iops, os_type=os_type ) @@ -97,7 +97,7 @@ def cli(env, storage_type, size, iops, tier, os_type, order = block_manager.order_block_volume( storage_type='storage_service_enterprise', location=location, - size=size, + size=int(size), tier_level=float(tier), os_type=os_type, snapshot_size=snapshot_size diff --git a/SoftLayer/CLI/block/replication/locations.py b/SoftLayer/CLI/block/replication/locations.py new file mode 100644 index 000000000..80ba5d98b --- /dev/null +++ b/SoftLayer/CLI/block/replication/locations.py @@ -0,0 +1,49 @@ +"""List suitable replication datacenters for the given volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('ID', ('id',), mask="id"), + column_helper.Column('Long Name', ('longName',), mask="longName"), + column_helper.Column('Short Name', ('name',), mask="name"), +] + +DEFAULT_COLUMNS = [ + 'ID', + 'Long Name', + 'Short Name', +] + + +@click.command() +@click.argument('volume-id') +@click.option('--sortby', help='Column to sort by', default='Long Name') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, columns, sortby, volume_id): + """List suitable replication datacenters for the given volume.""" + block_storage_manager = SoftLayer.BlockStorageManager(env.client) + + legal_centers = block_storage_manager.get_replication_locations( + volume_id + ) + + if not legal_centers: + click.echo("No data centers compatible for replication.") + else: + table = formatting.KeyValueTable(columns.columns) + table.sortby = sortby + for legal_center in legal_centers: + table.add_row([value or formatting.blank() + for value in columns.row(legal_center)]) + + env.fout(table) diff --git a/SoftLayer/CLI/block/replication/partners.py b/SoftLayer/CLI/block/replication/partners.py new file mode 100644 index 000000000..f19be0af5 --- /dev/null +++ b/SoftLayer/CLI/block/replication/partners.py @@ -0,0 +1,57 @@ +"""List existing replicant volumes for a block volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('ID', ('id',)), + column_helper.Column('Username', ('username',), mask="username"), + column_helper.Column('Account ID', ('accountId',), mask="accountId"), + column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), + column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), + column_helper.Column('Guest ID', ('guestId',), mask="guestId"), + column_helper.Column('Host ID', ('hostId',), mask="hostId"), +] + +DEFAULT_COLUMNS = [ + 'ID', + 'Username', + 'Account ID', + 'Capacity (GB)', + 'Hardware ID', + 'Guest ID', + 'Host ID' +] + + +@click.command() +@click.argument('volume-id') +@click.option('--sortby', help='Column to sort by', default='Username') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, columns, sortby, volume_id): + """List existing replicant volumes for a block volume.""" + block_storage_manager = SoftLayer.BlockStorageManager(env.client) + + legal_volumes = block_storage_manager.get_replication_partners( + volume_id + ) + + if not legal_volumes: + click.echo("There are no replication partners for the given volume.") + else: + table = formatting.Table(columns.columns) + table.sortby = sortby + for legal_volume in legal_volumes: + table.add_row([value or formatting.blank() + for value in columns.row(legal_volume)]) + + env.fout(table) diff --git a/SoftLayer/CLI/file/replication/locations.py b/SoftLayer/CLI/file/replication/locations.py new file mode 100644 index 000000000..58f979f70 --- /dev/null +++ b/SoftLayer/CLI/file/replication/locations.py @@ -0,0 +1,49 @@ +"""List suitable replication datacenters for the given volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('ID', ('id',), mask="id"), + column_helper.Column('Long Name', ('longName',), mask="longName"), + column_helper.Column('Short Name', ('name',), mask="name"), +] + +DEFAULT_COLUMNS = [ + 'ID', + 'Long Name', + 'Short Name', +] + + +@click.command() +@click.argument('volume-id') +@click.option('--sortby', help='Column to sort by', default='Long Name') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, columns, sortby, volume_id): + """List suitable replication datacenters for the given volume.""" + file_storage_manager = SoftLayer.FileStorageManager(env.client) + + legal_centers = file_storage_manager.get_replication_locations( + volume_id + ) + + if not legal_centers: + click.echo("No data centers compatible for replication.") + else: + table = formatting.KeyValueTable(columns.columns) + table.sortby = sortby + for legal_center in legal_centers: + table.add_row([value or formatting.blank() + for value in columns.row(legal_center)]) + + env.fout(table) diff --git a/SoftLayer/CLI/file/replication/partners.py b/SoftLayer/CLI/file/replication/partners.py new file mode 100644 index 000000000..866248fdf --- /dev/null +++ b/SoftLayer/CLI/file/replication/partners.py @@ -0,0 +1,60 @@ +"""List existing replicant volumes for a file volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +COLUMNS = [ + column_helper.Column('ID', ('id',)), + column_helper.Column('Username', ('username',), mask="username"), + column_helper.Column('Account ID', ('accountId',), mask="accountId"), + column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), + column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), + column_helper.Column('Guest ID', ('guestId',), mask="guestId"), + column_helper.Column('Host ID', ('hostId',), mask="hostId"), +] + +# In-line comment to avoid similarity flag with block version + +DEFAULT_COLUMNS = [ + 'ID', + 'Username', + 'Account ID', + 'Capacity (GB)', + 'Hardware ID', + 'Guest ID', + 'Host ID' +] + + +@click.command() +@click.argument('volume-id') +@click.option('--sortby', help='Column to sort by', default='Username') +@click.option('--columns', + callback=column_helper.get_formatter(COLUMNS), + help='Columns to display. Options: {0}'.format( + ', '.join(column.name for column in COLUMNS)), + default=','.join(DEFAULT_COLUMNS)) +@environment.pass_env +def cli(env, columns, sortby, volume_id): + """List existing replicant volumes for a file volume.""" + file_storage_manager = SoftLayer.FileStorageManager(env.client) + + legal_volumes = file_storage_manager.get_replication_partners( + volume_id + ) + + if not legal_volumes: + click.echo("There are no replication partners for the given volume.") + else: + table = formatting.Table(columns.columns) + table.sortby = sortby + + for legal_volume in legal_volumes: + table.add_row([value or formatting.blank() + for value in columns.row(legal_volume)]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 7dcfa6b27..10f2a7da1 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -63,6 +63,9 @@ ('block:replica-failback', 'SoftLayer.CLI.block.replication.failback:cli'), ('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'), ('block:replica-order', 'SoftLayer.CLI.block.replication.order:cli'), + ('block:replica-partners', 'SoftLayer.CLI.block.replication.partners:cli'), + ('block:replica-locations', + 'SoftLayer.CLI.block.replication.locations:cli'), ('block:snapshot-cancel', 'SoftLayer.CLI.block.snapshot.cancel:cli'), ('block:snapshot-create', 'SoftLayer.CLI.block.snapshot.create:cli'), ('block:snapshot-delete', 'SoftLayer.CLI.block.snapshot.delete:cli'), @@ -83,6 +86,8 @@ ('file:replica-failback', 'SoftLayer.CLI.file.replication.failback:cli'), ('file:replica-failover', 'SoftLayer.CLI.file.replication.failover:cli'), ('file:replica-order', 'SoftLayer.CLI.file.replication.order:cli'), + ('file:replica-partners', 'SoftLayer.CLI.file.replication.partners:cli'), + ('file:replica-locations', 'SoftLayer.CLI.file.replication.locations:cli'), ('file:snapshot-cancel', 'SoftLayer.CLI.file.snapshot.cancel:cli'), ('file:snapshot-create', 'SoftLayer.CLI.file.snapshot.create:cli'), ('file:snapshot-delete', 'SoftLayer.CLI.file.snapshot.delete:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 43012eb7a..91162e892 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -84,12 +84,16 @@ 'id': 1784, 'username': 'TEST_REP_1', 'serviceResourceBackendIpAddress': '10.3.174.79', + 'nasType': 'ISCSI_REPLICANT', + 'createDate': '2017:50:15-04:00', 'serviceResource': {'datacenter': {'name': 'wdc01'}}, 'replicationSchedule': {'type': {'keyname': 'REPLICATION_HOURLY'}}, }, { 'id': 1785, 'username': 'TEST_REP_2', 'serviceResourceBackendIpAddress': '10.3.177.84', + 'nasType': 'ISCSI_REPLICANT', + 'createDate': '2017:50:15-04:00', 'serviceResource': {'datacenter': {'name': 'dal01'}}, 'replicationSchedule': {'type': {'keyname': 'REPLICATION_DAILY'}}, }], @@ -102,6 +106,41 @@ 'snapshotSizeBytes': '42', }] +getReplicationPartners = [{ + 'id': 1784, + 'accountId': 3000, + 'capacityGb': 20, + 'username': 'TEST_REP_1', + 'serviceResourceBackendIpAddress': '10.3.174.79', + 'nasType': 'ISCSI_REPLICANT', + 'hostId': None, + 'guestId': None, + 'hardwareId': None, + 'createDate': '2017:50:15-04:00', + 'serviceResource': {'datacenter': {'name': 'wdc01'}}, + 'replicationSchedule': {'type': {'keyname': 'REPLICATION_HOURLY'}}, +}, { + 'id': 1785, + 'accountId': 3001, + 'capacityGb': 20, + 'username': 'TEST_REP_2', + 'serviceResourceBackendIpAddress': '10.3.177.84', + 'nasType': 'ISCSI_REPLICANT', + 'hostId': None, + 'guestId': None, + 'hardwareId': None, + 'createDate': '2017:50:15-04:00', + 'serviceResource': {'datacenter': {'name': 'dal01'}}, + 'replicationSchedule': {'type': {'keyname': 'REPLICATION_DAILY'}}, +}] + +getValidReplicationTargetDatacenterLocations = [{ + 'id': 12345, + 'longName': 'Dallas 05', + 'name': 'dal05' +}] + + deleteObject = True allowAccessFromHostList = True removeAccessFromHostList = True diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 7a933b048..89660d13e 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -195,6 +195,26 @@ def deauthorize_host_to_volume(self, volume_id, return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id, **kwargs) + def get_replication_partners(self, volume_id): + """Acquires list of replicant volumes pertaining to the given volume. + + :param volume_id: The ID of the primary volume to be replicated + :return: Returns an array of SoftLayer_Location objects + """ + return self.client.call('Network_Storage', + 'getReplicationPartners', + id=volume_id) + + def get_replication_locations(self, volume_id): + """Acquires list of the datacenters to which a volume can be replicated. + + :param volume_id: The ID of the primary volume to be replicated + :return: Returns an array of SoftLayer_Network_Storage objects + """ + return self.client.call('Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + id=volume_id) + def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=None, os_type=None): """Places an order for a replicant block volume. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 638db5477..22c73b45b 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -220,6 +220,26 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, return self.client.call('Product_Order', 'placeOrder', order) + def get_replication_partners(self, volume_id): + """Acquires list of replicant volumes pertaining to the given volume. + + :param volume_id: The ID of the primary volume to be replicated + :return: Returns an array of SoftLayer_Location objects + """ + return self.client.call('Network_Storage', + 'getReplicationPartners', + id=volume_id) + + def get_replication_locations(self, volume_id): + """Acquires list of the datacenters to which a volume can be replicated. + + :param volume_id: The ID of the primary volume to be replicated + :return: Returns an array of SoftLayer_Network_Storage objects + """ + return self.client.call('Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + id=volume_id) + def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 017598817..f65d1cbc9 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -344,6 +344,54 @@ def test_replicant_failover(self): self.assertEqual('Failover to replicant is now in progress.\n', result.output) + def test_replication_locations(self): + result = self.run_command(['block', 'replica-locations', '1234']) + self.assert_no_fail(result) + self.assertEqual( + { + '12345': 'Dallas 05', + }, + json.loads(result.output)) + + @mock.patch('SoftLayer.BlockStorageManager.get_replication_locations') + def test_replication_locations_unsuccessful(self, locations_mock): + locations_mock.return_value = False + result = self.run_command(['block', 'replica-locations', '1234']) + self.assertEqual('No data centers compatible for replication.\n', + result.output) + + def test_replication_partners(self): + result = self.run_command(['block', 'replica-partners', '1234']) + self.assert_no_fail(result) + self.assertEqual([ + { + 'ID': 1784, + 'Account ID': 3000, + 'Capacity (GB)': 20, + 'Host ID': None, + 'Guest ID': None, + 'Hardware ID': None, + 'Username': 'TEST_REP_1', + }, + { + 'ID': 1785, + 'Account ID': 3001, + 'Host ID': None, + 'Guest ID': None, + 'Hardware ID': None, + 'Capacity (GB)': 20, + 'Username': 'TEST_REP_2', + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.BlockStorageManager.get_replication_partners') + def test_replication_partners_unsuccessful(self, partners_mock): + partners_mock.return_value = False + result = self.run_command(['block', 'replica-partners', '1234']) + self.assertEqual( + 'There are no replication partners for the given volume.\n', + result.output) + @mock.patch('SoftLayer.BlockStorageManager.failover_to_replicant') def test_replicant_failover_unsuccessful(self, failover_mock): failover_mock.return_value = False diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 0b7f8c4e9..df8abec92 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -418,3 +418,51 @@ def test_replicant_order(self, order_mock): ' > 20 GB Storage Space\n' ' > 10 GB Storage Space (Snapshot Space)\n' ' > 20 GB Storage Space Replicant of: TEST\n') + + def test_replication_locations(self): + result = self.run_command(['file', 'replica-locations', '1234']) + self.assert_no_fail(result) + self.assertEqual( + { + '12345': 'Dallas 05', + }, + json.loads(result.output)) + + @mock.patch('SoftLayer.FileStorageManager.get_replication_locations') + def test_replication_locations_unsuccessful(self, locations_mock): + locations_mock.return_value = False + result = self.run_command(['file', 'replica-locations', '1234']) + self.assertEqual('No data centers compatible for replication.\n', + result.output) + + def test_replication_partners(self): + result = self.run_command(['file', 'replica-partners', '1234']) + self.assert_no_fail(result) + self.assertEqual([ + { + 'ID': 1784, + 'Account ID': 3000, + 'Capacity (GB)': 20, + 'Host ID': None, + 'Guest ID': None, + 'Hardware ID': None, + 'Username': 'TEST_REP_1', + }, + { + 'ID': 1785, + 'Account ID': 3001, + 'Host ID': None, + 'Guest ID': None, + 'Hardware ID': None, + 'Capacity (GB)': 20, + 'Username': 'TEST_REP_2', + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.FileStorageManager.get_replication_partners') + def test_replication_partners_unsuccessful(self, partners_mock): + partners_mock.return_value = False + result = self.run_command(['file', 'replica-partners', '1234']) + self.assertEqual( + 'There are no replication partners for the given volume.\n', + result.output) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index d288ac954..99ec24411 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -157,6 +157,24 @@ def test_replicant_failback(self): identifier=1234, ) + def test_get_replication_partners(self): + self.block.get_replication_partners(1234) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'getReplicationPartners', + identifier=1234, + ) + + def test_get_replication_locations(self): + self.block.get_replication_locations(1234) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + identifier=1234, + ) + def test_order_block_volume_invalid_storage_type(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [{}] diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index a92ddcb43..3b761e0c1 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -166,6 +166,24 @@ def test_replicant_failback(self): identifier=1234, ) + def test_get_replication_partners(self): + self.file.get_replication_partners(1234) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'getReplicationPartners', + identifier=1234, + ) + + def test_get_replication_locations(self): + self.file.get_replication_locations(1234) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + identifier=1234, + ) + def test_delete_snapshot(self): result = self.file.delete_snapshot(100)