Repository: softlayer/softlayer-python
Pull request #814 (2017-04-07 20:09:56)

New replication enhancements
Implemented replica-locations and replica-partners for improved experience and access to information when replicating or looking up information on replicants.


Patch:
diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py
index aa18a162..a5af2a67 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 00000000..80ba5d98
--- /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 00000000..f19be0af
--- /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 00000000..58f979f7
--- /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 00000000..866248fd
--- /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 7dcfa6b2..10f2a7da 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 43012eb7..91162e89 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 7a933b04..89660d13 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 638db547..22c73b45 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.
 


Test patch:
diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py
index 01759881..f65d1cbc 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 0b7f8c4e..df8abec9 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 d288ac95..99ec2441 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 a92ddcb4..3b761e0c 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)