Repository: softlayer/softlayer-python
Pull request #840 (2017-07-07 21:32:56)

STORAUTO-62 support for setting LUN ID on block volumes
This depends on FBLOCK-3 being released, but I would like to address any comments in the meantime so it can be ready to go when FBLOCK-3 is released.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/12301973/badge)](https://coveralls.io/builds/12301973)

Coverage increased (+0.03%) to 84.271% when pulling **2bb735524e5a643d2813e70134743e95cfc361b4 on causton81:feature/STORAUTO-62** into **172964c3db9756a2057e69f03a036b6fd2d70759 on softlayer:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/12322767/badge)](https://coveralls.io/builds/12322767)

Coverage increased (+0.03%) to 84.271% when pulling **eb645d81b0bb7658619c358c1968439e7867c22d on causton81:feature/STORAUTO-62** into **172964c3db9756a2057e69f03a036b6fd2d70759 on softlayer:master**.

allmightyspiff: @causton81 looks like my change in #841 is causing a merge conflict with this pull request.

When you get a chance can you update your changes to include mine to resolve this conflict.

Thanks.

Patch:
diff --git a/SoftLayer/CLI/block/lun.py b/SoftLayer/CLI/block/lun.py
new file mode 100644
index 00000000..ee33a23b
--- /dev/null
+++ b/SoftLayer/CLI/block/lun.py
@@ -0,0 +1,36 @@
+"""Set the LUN ID on an iSCSI volume."""
+# :license: MIT, see LICENSE for more details.
+
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+
+
+@click.command()
+@click.argument('volume-id')
+@click.argument('lun-id')
+@environment.pass_env
+def cli(env, volume_id, lun_id):
+    """Set the LUN ID on an existing block storage volume.
+
+    The LUN ID only takes effect during the Host Authorization process. It is
+    recommended (but not necessary) to de-authorize all hosts before using this
+    method. See `block access-revoke`.
+
+    VOLUME_ID - the volume ID on which to set the LUN ID.
+
+    LUN_ID - recommended range is an integer between 0 and 255. Advanced users
+    can use an integer between 0 and 4095.
+    """
+
+    block_storage_manager = SoftLayer.BlockStorageManager(env.client)
+
+    res = block_storage_manager.create_or_update_lun_id(volume_id, lun_id)
+
+    if 'value' in res and lun_id == res['value']:
+        click.echo(
+            'Block volume with id %s is reporting LUN ID %s' % (res['volumeId'], res['value']))
+    else:
+        click.echo(
+            'Failed to confirm the new LUN ID on volume %s' % (volume_id))
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 541c9d98..fe2bb12f 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -80,6 +80,7 @@
     ('block:volume-duplicate', 'SoftLayer.CLI.block.duplicate:cli'),
     ('block:volume-list', 'SoftLayer.CLI.block.list:cli'),
     ('block:volume-order', 'SoftLayer.CLI.block.order:cli'),
+    ('block:volume-set-lun-id', 'SoftLayer.CLI.block.lun:cli'),
 
     ('file', 'SoftLayer.CLI.file'),
     ('file:access-authorize', 'SoftLayer.CLI.file.access.authorize:cli'),
diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py
index 817232a4..37e73d6f 100644
--- a/SoftLayer/managers/block.py
+++ b/SoftLayer/managers/block.py
@@ -547,7 +547,7 @@ def failback_from_replicant(self, volume_id, replicant_id):
         """Failback from a volume replicant.
 
         :param integer volume_id: The id of the volume
-        :param integer: ID of replicant to failback from
+        :param integer replicant_id: ID of replicant to failback from
         :return: Returns whether failback was successful or not
         """
 
@@ -563,3 +563,13 @@ def set_credential_password(self, access_id, password):
 
         return self.client.call('Network_Storage_Allowed_Host', 'setCredentialPassword',
                                 password, id=access_id)
+
+    def create_or_update_lun_id(self, volume_id, lun_id):
+        """Set the LUN ID on a volume.
+
+        :param integer volume_id: The id of the volume
+        :param integer lun_id: LUN ID to set on the volume
+        :return: a SoftLayer_Network_Storage_Property object
+        """
+        return self.client.call('Network_Storage', 'createOrUpdateLunId',
+                                lun_id, id=volume_id)


Test patch:
diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py
index 944acb84..c754e25d 100644
--- a/tests/CLI/modules/block_tests.py
+++ b/tests/CLI/modules/block_tests.py
@@ -4,6 +4,7 @@
 
     :license: MIT, see LICENSE for more details.
 """
+from SoftLayer import exceptions
 from SoftLayer import testing
 
 import json
@@ -28,6 +29,32 @@ def test_volume_cancel(self):
         self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem',
                                 args=(False, True, None))
 
+    def test_volume_set_lun_id_in_range(self):
+        lun_mock = self.set_mock('SoftLayer_Network_Storage', 'createOrUpdateLunId')
+        lun_mock.return_value = dict(volumeId=1234, value='42')
+        result = self.run_command('block volume-set-lun-id 1234 42'.split())
+        self.assert_no_fail(result)
+        self.assertEqual('Block volume with id 1234 is reporting LUN ID 42\n',
+                         result.output)
+
+    def test_volume_set_lun_id_in_range_missing_value(self):
+        lun_mock = self.set_mock('SoftLayer_Network_Storage', 'createOrUpdateLunId')
+        lun_mock.return_value = dict(volumeId=1234)
+        result = self.run_command('block volume-set-lun-id 1234 42'.split())
+        self.assert_no_fail(result)
+        self.assertEqual('Failed to confirm the new LUN ID on volume 1234\n',
+                         result.output)
+
+    def test_volume_set_lun_id_not_in_range(self):
+        value = '-1'
+        lun_mock = self.set_mock('SoftLayer_Network_Storage', 'createOrUpdateLunId')
+        lun_mock.side_effect = exceptions.SoftLayerAPIError(
+            'SoftLayer_Exception_Network_Storage_Iscsi_InvalidLunId',
+            'The LUN ID specified is out of the valid range: %s [min: 0 max: 4095]' % (value))
+        result = self.run_command('block volume-set-lun-id 1234 42'.split())
+        self.assertIsNotNone(result.exception)
+        self.assertIn('The LUN ID specified is out of the valid range', result.exception.faultString)
+
     def test_volume_detail(self):
         result = self.run_command(['block', 'volume-detail', '1234'])