From 64c52db78a3c9d4e7a6e2f9a73735c46cd46d069 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 30 May 2018 17:58:44 -0500 Subject: [PATCH 0001/1385] upstream commit --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a14625c19..226f4dfd6 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.1+git' # check versioning +version: '5.4.4.2+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 0ebe32190cea0b8b9083782b0c03fb35a5e864b7 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 27 Jun 2018 18:08:20 -0500 Subject: [PATCH 0002/1385] updated assoc packages --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 226f4dfd6..b756f344f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.2+git' # check versioning +version: '5.4.4.3+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From b9a0303e28286b8e4b27e41d70c75b3be49da7c1 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Fri, 29 Jun 2018 18:59:21 -0500 Subject: [PATCH 0003/1385] minor update https://github.com/softlayer/softlayer-python/commit/474e3386f9e7ecd2a22d47d04badf9648f617c39 --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b756f344f..42e328b0b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.3+git' # check versioning +version: '5.4.4.4+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 3d11f842536bfa8e025c784e8527a24927eedce3 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Thu, 5 Jul 2018 17:00:56 -0500 Subject: [PATCH 0004/1385] patch for slcli shell --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 42e328b0b..64a38cea2 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.4+git' # check versioning +version: '5.4.4.5+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From b184c840c83a7ffa0f1c6f67b9a67ebc2e1cc1ec Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Tue, 10 Jul 2018 20:08:46 -0500 Subject: [PATCH 0005/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 64a38cea2..4cf19b464 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.5+git' # check versioning +version: '5.4.4.6+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 474265d80e5ee99115f6956e2d4b1371750a3ff9 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Tue, 10 Jul 2018 20:51:35 -0500 Subject: [PATCH 0006/1385] fixed versioning 5.5.1 --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 4cf19b464..c05b79e30 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.6+git' # check versioning +version: '5.5.1+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 2ba0561a59bd1cb05bd552594290457f0cd80425 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Tue, 10 Jul 2018 20:51:57 -0500 Subject: [PATCH 0007/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c05b79e30..9ede1600d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.1+git' # check versioning +version: '5.5.0.1+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From ad1661b51cf74bc5d524e19f720a68bcb6239a94 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Sun, 22 Jul 2018 20:06:52 -0500 Subject: [PATCH 0008/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9ede1600d..938101da5 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.0.1+git' # check versioning +version: '5.5.0.2+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 3cfa16dbf4e20c9d6df955aae89975f4296600e1 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Fri, 27 Jul 2018 16:30:34 -0500 Subject: [PATCH 0009/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 938101da5..bc5dd3199 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.0.2+git' # check versioning +version: '5.5.0.3+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 99c2126e658ed2b0f6e21f0fd59c59c5be1ab8c6 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Thu, 2 Aug 2018 17:06:37 -0500 Subject: [PATCH 0010/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index bc5dd3199..81870776b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.0.3+git' # check versioning +version: '5.5.0.4+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 9445a89837a0df7cf0d0cd54c658d5976213da08 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 8 Aug 2018 22:44:03 -0500 Subject: [PATCH 0011/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 81870776b..c05b79e30 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.0.4+git' # check versioning +version: '5.5.1+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 7818cb43e36836e1c31f399fa5a4b4a0f6e7d674 Mon Sep 17 00:00:00 2001 From: Osbel Rosales Date: Tue, 14 Jan 2020 16:33:52 -0600 Subject: [PATCH 0012/1385] issue#1210 file block storage - adding new feature to list, assign, and remove subnets from/to ACL host record. --- SoftLayer/CLI/block/subnets/__init__.py | 1 + SoftLayer/CLI/block/subnets/assign.py | 31 ++++++++++ SoftLayer/CLI/block/subnets/list.py | 38 ++++++++++++ SoftLayer/CLI/block/subnets/remove.py | 31 ++++++++++ SoftLayer/CLI/routes.py | 3 + .../SoftLayer_Network_Storage_Allowed_Host.py | 60 +++++++++++++++++++ SoftLayer/managers/block.py | 34 +++++++++++ tests/CLI/modules/block_tests.py | 17 ++++++ tests/managers/block_tests.py | 37 ++++++++++++ 9 files changed, 252 insertions(+) create mode 100644 SoftLayer/CLI/block/subnets/__init__.py create mode 100644 SoftLayer/CLI/block/subnets/assign.py create mode 100644 SoftLayer/CLI/block/subnets/list.py create mode 100644 SoftLayer/CLI/block/subnets/remove.py diff --git a/SoftLayer/CLI/block/subnets/__init__.py b/SoftLayer/CLI/block/subnets/__init__.py new file mode 100644 index 000000000..8824c4279 --- /dev/null +++ b/SoftLayer/CLI/block/subnets/__init__.py @@ -0,0 +1 @@ +"""Block Storage Subnets Control.""" diff --git a/SoftLayer/CLI/block/subnets/assign.py b/SoftLayer/CLI/block/subnets/assign.py new file mode 100644 index 000000000..29a2a2c6b --- /dev/null +++ b/SoftLayer/CLI/block/subnets/assign.py @@ -0,0 +1,31 @@ +"""Assign block storage subnets to the given host id.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('access_id') +@click.option('--subnet-id', multiple=True, type=int, + help="ID of the subnets to assign; e.g.: --subnet-id 1234") +@environment.pass_env +def cli(env, access_id, subnet_id): + """Assign block storage subnets to the given host id. + + access_id is the allowed_host_id from slcli block access-list + """ + subnets_id = list(subnet_id) + block_manager = SoftLayer.BlockStorageManager(env.client) + assigned_subnets = block_manager.assign_subnets_to_acl(access_id, + subnets_id) + + for subnet in assigned_subnets: + click.echo("Successfully assigned subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') + + failed_to_assign_subnets = list(set(subnets_id) - set(assigned_subnets)) + for subnet in failed_to_assign_subnets: + click.echo("Failed to assign subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py new file mode 100644 index 000000000..2bae4f8d3 --- /dev/null +++ b/SoftLayer/CLI/block/subnets/list.py @@ -0,0 +1,38 @@ +"""List block storage assigned subnets for the given host id.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +COLUMNS = [ + 'id', + 'createDate', + 'networkIdentifier', + 'cidr' +] + + +@click.command() +@click.argument('access_id') +@environment.pass_env +def cli(env, access_id): + """List block storage assigned subnets for the given host id. + + access_id is the allowed_host_id from slcli block access-list + """ + + block_manager = SoftLayer.BlockStorageManager(env.client) + subnets = block_manager.get_subnets_in_acl(access_id) + + table = formatting.Table(COLUMNS) + for subnet in subnets: + row = ["{0}".format(subnet['id']), + "{0}".format(subnet['createDate']), + "{0}".format(subnet['networkIdentifier']), + "{0}".format(subnet['cidr'])] + table.add_row(row) + + env.fout(table) diff --git a/SoftLayer/CLI/block/subnets/remove.py b/SoftLayer/CLI/block/subnets/remove.py new file mode 100644 index 000000000..b7528b789 --- /dev/null +++ b/SoftLayer/CLI/block/subnets/remove.py @@ -0,0 +1,31 @@ +"""Remove block storage subnets for the given host id.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('access_id') +@click.option('--subnet-id', multiple=True, type=int, + help="ID of the subnets to remove; e.g.: --subnet-id 1234") +@environment.pass_env +def cli(env, access_id, subnet_id): + """Remove block storage subnets for the given host id. + + access_id is the allowed_host_id from slcli block access-list + """ + subnets_id = list(subnet_id) + block_manager = SoftLayer.BlockStorageManager(env.client) + removed_subnets = block_manager.remove_subnets_from_acl(access_id, + subnets_id) + + for subnet in removed_subnets: + click.echo("Successfully removed subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') + + failed_to_remove_subnets = list(set(subnets_id) - set(removed_subnets)) + for subnet in failed_to_remove_subnets: + click.echo("Failed to remove subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5c37541df..76c9fb77f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -80,6 +80,9 @@ ('block:access-list', 'SoftLayer.CLI.block.access.list:cli'), ('block:access-revoke', 'SoftLayer.CLI.block.access.revoke:cli'), ('block:access-password', 'SoftLayer.CLI.block.access.password:cli'), + ('block:subnets-list', 'SoftLayer.CLI.block.subnets.list:cli'), + ('block:subnets-assign', 'SoftLayer.CLI.block.subnets.assign:cli'), + ('block:subnets-remove', 'SoftLayer.CLI.block.subnets.remove:cli'), ('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'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py index 0582e04b6..5bf8c3354 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py @@ -1 +1,61 @@ +TEST_ALLOWED_HOST = { + 'id': 12345, + 'name': 'Test Allowed Host', + 'accountId': 1234, + 'credentialId': None, + 'createDate': '2020-01-01 00:00:01', + 'iscsiAclCredentials': { + 'id': 129, + 'allowedHostId': 12345, + 'subnetId': 12345678 + }, + 'subnetsInAcl': [{ + 'id': 12345678, + 'accountId': 1234, + 'networkIdentifier': '10.11.12.13', + 'cidr': '14', + 'billingRecordId': None, + 'parentId': None, + 'networkVlanId': None, + 'createDate': '2020-01-02 00:00:01', + 'modifyDate': None, + 'subnetType': 'SECONDARY_ON_VLAN', + 'restrictAllocationFlag': 0, + 'leafFlag': 1, + 'ownerId': 1, + 'ipAddressBegin': 129123, + 'ipAddressEnd': 129145, + 'purgeFlag': 0 + }] +} + +getObject = TEST_ALLOWED_HOST + +getSubnetsInAcl = [{ + 'id': 12345678, + 'accountId': 1234, + 'networkIdentifier': '10.11.12.13', + 'cidr': '14', + 'billingRecordId': None, + 'parentId': None, + 'networkVlanId': None, + 'createDate': '2020-01-02 00:00:01', + 'modifyDate': None, + 'subnetType': 'SECONDARY_ON_VLAN', + 'restrictAllocationFlag': 0, + 'leafFlag': 1, + 'ownerId': 1, + 'ipAddressBegin': 129123, + 'ipAddressEnd': 129145, + 'purgeFlag': 0 +}] + +assignSubnetsToAcl = [ + 12345678 +] + +removeSubnetsFromAcl = [ + 12345678 +] + setCredentialPassword = True diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 1f8c6ec5c..9ee2a3cf0 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -201,6 +201,40 @@ def deauthorize_host_to_volume(self, volume_id, return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id, **kwargs) + def assign_subnets_to_acl(self, access_id, subnets_id): + """Assigns subnet records to ACL for the access host. + + :param integer access_id: id of the access host + :param integer subnets_id: The ids of the subnets to be assigned + :return: Returns int array of assigned subnet ids + """ + return self.client.call('Network_Storage_Allowed_Host', + 'assignSubnetsToAcl', + subnets_id, + id=access_id) + + def remove_subnets_from_acl(self, access_id, subnets_id): + """Removes subnet records from ACL for the access host. + + :param integer access_id: id of the access host + :param integer subnets_id: The ids of the subnets to be removed + :return: Returns int array of removed subnet ids + """ + return self.client.call('Network_Storage_Allowed_Host', + 'removeSubnetsFromAcl', + subnets_id, + id=access_id) + + def get_subnets_in_acl(self, access_id): + """Returns a list of subnet records for the access host. + + :param integer access_id: id of the access host + :return: Returns an array of SoftLayer_Network_Subnet objects + """ + return self.client.call('Network_Storage_Allowed_Host', + 'getSubnetsInAcl', + id=access_id) + def get_replication_partners(self, volume_id): """Acquires list of replicant volumes pertaining to the given volume. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index b8629de8a..ed2916c56 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -438,6 +438,23 @@ def test_deauthorize_host_to_volume(self): self.assert_no_fail(result) + def test_assign_subnets_to_acl(self): + result = self.run_command(['block', 'subnets-assign', '12345', + '--subnet-id=12345678']) + + self.assert_no_fail(result) + + def test_remove_subnets_from_acl(self): + result = self.run_command(['block', 'subnets-remove', '12345', + '--subnet-id=12345678']) + + self.assert_no_fail(result) + + def test_get_subnets_in_acl(self): + result = self.run_command(['block', 'subnets-list', '12345']) + + self.assert_no_fail(result) + def test_replicant_failover(self): result = self.run_command(['block', 'replica-failover', '12345678', '--replicant-id=5678']) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index f5b3b4371..8fd56ad77 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -486,6 +486,43 @@ def test_deauthorize_host_to_volume(self): 'removeAccessFromHostList', identifier=50) + def test_assign_subnets_to_acl(self): + result = self.block.assign_subnets_to_acl( + 12345, + subnets_id=[12345678]) + + self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + assignSubnetsToAcl, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage_Allowed_Host', + 'assignSubnetsToAcl', + identifier=12345) + + def test_remove_subnets_from_acl(self): + result = self.block.remove_subnets_from_acl( + 12345, + subnets_id=[12345678]) + + self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + removeSubnetsFromAcl, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage_Allowed_Host', + 'removeSubnetsFromAcl', + identifier=12345) + + def test_get_subnets_in_acl(self): + result = self.block.get_subnets_in_acl(12345) + + self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + getSubnetsInAcl, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage_Allowed_Host', + 'getSubnetsInAcl', + identifier=12345) + def test_create_snapshot(self): result = self.block.create_snapshot(123, 'hello world') From ee1d940d81ed5852234b1ff13ff509fa1a381157 Mon Sep 17 00:00:00 2001 From: Osbel Rosales Date: Thu, 23 Jan 2020 17:41:49 -0600 Subject: [PATCH 0013/1385] issue#1210 - Correcting formatting and adding input check. --- SoftLayer/CLI/block/subnets/assign.py | 32 ++++++++++++++++----------- SoftLayer/CLI/block/subnets/list.py | 30 +++++++++++++++---------- SoftLayer/CLI/block/subnets/remove.py | 32 ++++++++++++++++----------- SoftLayer/managers/block.py | 18 ++++++++++----- tests/managers/block_tests.py | 4 ++-- 5 files changed, 70 insertions(+), 46 deletions(-) diff --git a/SoftLayer/CLI/block/subnets/assign.py b/SoftLayer/CLI/block/subnets/assign.py index 29a2a2c6b..d349c66ae 100644 --- a/SoftLayer/CLI/block/subnets/assign.py +++ b/SoftLayer/CLI/block/subnets/assign.py @@ -7,25 +7,31 @@ @click.command() -@click.argument('access_id') +@click.argument('access_id', type=int) @click.option('--subnet-id', multiple=True, type=int, help="ID of the subnets to assign; e.g.: --subnet-id 1234") @environment.pass_env def cli(env, access_id, subnet_id): """Assign block storage subnets to the given host id. - access_id is the allowed_host_id from slcli block access-list + access_id is the host_id obtained by: slcli block access-list """ - subnets_id = list(subnet_id) - block_manager = SoftLayer.BlockStorageManager(env.client) - assigned_subnets = block_manager.assign_subnets_to_acl(access_id, - subnets_id) + try: + subnet_ids = list(subnet_id) + block_manager = SoftLayer.BlockStorageManager(env.client) + assigned_subnets = block_manager.assign_subnets_to_acl(access_id, subnet_ids) - for subnet in assigned_subnets: - click.echo("Successfully assigned subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + for subnet in assigned_subnets: + message = "{0}".format("Successfully assigned subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') + click.echo(message) - failed_to_assign_subnets = list(set(subnets_id) - set(assigned_subnets)) - for subnet in failed_to_assign_subnets: - click.echo("Failed to assign subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + failed_to_assign_subnets = list(set(subnet_ids) - set(assigned_subnets)) + for subnet in failed_to_assign_subnets: + message = "{0}".format("Failed to assign subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') + click.echo(message) + + except SoftLayer.SoftLayerAPIError as ex: + message = "{0}".format("Unable to assign subnets.\nReason: " + ex.faultString) + click.echo(message) diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py index 2bae4f8d3..7256cac66 100644 --- a/SoftLayer/CLI/block/subnets/list.py +++ b/SoftLayer/CLI/block/subnets/list.py @@ -16,23 +16,29 @@ @click.command() -@click.argument('access_id') +@click.argument('access_id', type=int) @environment.pass_env def cli(env, access_id): """List block storage assigned subnets for the given host id. - access_id is the allowed_host_id from slcli block access-list + access_id is the host_id obtained by: slcli block access-list """ - block_manager = SoftLayer.BlockStorageManager(env.client) - subnets = block_manager.get_subnets_in_acl(access_id) + try: + block_manager = SoftLayer.BlockStorageManager(env.client) + subnets = block_manager.get_subnets_in_acl(access_id) - table = formatting.Table(COLUMNS) - for subnet in subnets: - row = ["{0}".format(subnet['id']), - "{0}".format(subnet['createDate']), - "{0}".format(subnet['networkIdentifier']), - "{0}".format(subnet['cidr'])] - table.add_row(row) + table = formatting.Table(COLUMNS) + for subnet in subnets: + row = ["{0}".format(subnet['id']), + "{0}".format(subnet['createDate']), + "{0}".format(subnet['networkIdentifier']), + "{0}".format(subnet['cidr'])] + table.add_row(row) - env.fout(table) + env.fout(table) + + except SoftLayer.SoftLayerAPIError as ex: + message = "{0}".format("Unable to list assigned subnets for access-id: " + + str(access_id) + ".\nReason: " + ex.faultString) + click.echo(message) diff --git a/SoftLayer/CLI/block/subnets/remove.py b/SoftLayer/CLI/block/subnets/remove.py index b7528b789..2a77b42ff 100644 --- a/SoftLayer/CLI/block/subnets/remove.py +++ b/SoftLayer/CLI/block/subnets/remove.py @@ -7,25 +7,31 @@ @click.command() -@click.argument('access_id') +@click.argument('access_id', type=int) @click.option('--subnet-id', multiple=True, type=int, help="ID of the subnets to remove; e.g.: --subnet-id 1234") @environment.pass_env def cli(env, access_id, subnet_id): """Remove block storage subnets for the given host id. - access_id is the allowed_host_id from slcli block access-list + access_id is the host_id obtained by: slcli block access-list """ - subnets_id = list(subnet_id) - block_manager = SoftLayer.BlockStorageManager(env.client) - removed_subnets = block_manager.remove_subnets_from_acl(access_id, - subnets_id) + try: + subnet_ids = list(subnet_id) + block_manager = SoftLayer.BlockStorageManager(env.client) + removed_subnets = block_manager.remove_subnets_from_acl(access_id, subnet_ids) - for subnet in removed_subnets: - click.echo("Successfully removed subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + for subnet in removed_subnets: + message = "{0}".format("Successfully removed subnet id: " + str(subnet) + + ' for allowed host id: ' + str(access_id) + '.') + click.echo(message) - failed_to_remove_subnets = list(set(subnets_id) - set(removed_subnets)) - for subnet in failed_to_remove_subnets: - click.echo("Failed to remove subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + failed_to_remove_subnets = list(set(subnet_ids) - set(removed_subnets)) + for subnet in failed_to_remove_subnets: + message = "{0}".format("Failed to remove subnet id: " + str(subnet) + + ' for allowed host id: ' + str(access_id) + '.') + click.echo(message) + + except SoftLayer.SoftLayerAPIError as ex: + message = "{0}".format("Unable to remove subnets.\nReason: " + ex.faultString) + click.echo(message) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 9ee2a3cf0..306e0f37f 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -201,33 +201,39 @@ def deauthorize_host_to_volume(self, volume_id, return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id, **kwargs) - def assign_subnets_to_acl(self, access_id, subnets_id): + def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. + access_id is the host_id obtained by: slcli block access-list + :param integer access_id: id of the access host - :param integer subnets_id: The ids of the subnets to be assigned + :param list subnet_ids: The ids of the subnets to be assigned :return: Returns int array of assigned subnet ids """ return self.client.call('Network_Storage_Allowed_Host', 'assignSubnetsToAcl', - subnets_id, + subnet_ids, id=access_id) - def remove_subnets_from_acl(self, access_id, subnets_id): + def remove_subnets_from_acl(self, access_id, subnet_ids): """Removes subnet records from ACL for the access host. + access_id is the host_id obtained by: slcli block access-list + :param integer access_id: id of the access host - :param integer subnets_id: The ids of the subnets to be removed + :param list subnet_ids: The ids of the subnets to be removed :return: Returns int array of removed subnet ids """ return self.client.call('Network_Storage_Allowed_Host', 'removeSubnetsFromAcl', - subnets_id, + subnet_ids, id=access_id) def get_subnets_in_acl(self, access_id): """Returns a list of subnet records for the access host. + access_id is the host_id obtained by: slcli block access-list + :param integer access_id: id of the access host :return: Returns an array of SoftLayer_Network_Subnet objects """ diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 8fd56ad77..06d4d6ce4 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -489,7 +489,7 @@ def test_deauthorize_host_to_volume(self): def test_assign_subnets_to_acl(self): result = self.block.assign_subnets_to_acl( 12345, - subnets_id=[12345678]) + subnet_ids=[12345678]) self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. assignSubnetsToAcl, result) @@ -502,7 +502,7 @@ def test_assign_subnets_to_acl(self): def test_remove_subnets_from_acl(self): result = self.block.remove_subnets_from_acl( 12345, - subnets_id=[12345678]) + subnet_ids=[12345678]) self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. removeSubnetsFromAcl, result) From 816408657d3c45bcd58df7d06269bfcfeb95ce65 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jan 2020 12:24:51 -0400 Subject: [PATCH 0014/1385] 1211 Fix checking against literal empty string --- SoftLayer/managers/storage_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 7cce7671b..80ec60368 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -660,14 +660,14 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, """ # Ensure the primary volume and snapshot space are not set for cancellation if 'billingItem' not in volume\ - or volume['billingItem']['cancellationDate'] != '': + or volume['billingItem'].get('cancellationDate'): raise exceptions.SoftLayerError( 'This volume is set for cancellation; ' 'unable to order replicant volume') for child in volume['billingItem']['activeChildren']: if child['categoryCode'] == 'storage_snapshot_space'\ - and child['cancellationDate'] != '': + and child.get('cancellationDate'): raise exceptions.SoftLayerError( 'The snapshot space for this volume is set for ' 'cancellation; unable to order replicant volume') From f9359139096241fd5506ce3b9ace92098690e57c Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 24 Jan 2020 18:00:09 -0600 Subject: [PATCH 0015/1385] #1215 adding in a bunch of CLI documentation --- docs/cli/block.rst | 112 +++++++++++++++++++++++++++++++++++++++++ docs/cli/call_api.rst | 9 ---- docs/cli/cdn.rst | 2 +- docs/cli/commands.rst | 17 +++++++ docs/cli/config.rst | 7 ++- docs/cli/dedicated.rst | 33 ++++++++++++ docs/cli/dns.rst | 41 +++++++++++++++ docs/cli/file.rst | 104 ++++++++++++++++++++++++++++++++++++++ docs/cli/firewall.rst | 24 +++++++++ docs/cli/global_ip.rst | 24 +++++++++ docs/cli/hardware.rst | 2 +- docs/dev/cli.rst | 35 +++++++++++++ 12 files changed, 398 insertions(+), 12 deletions(-) create mode 100644 docs/cli/block.rst delete mode 100644 docs/cli/call_api.rst create mode 100644 docs/cli/commands.rst create mode 100644 docs/cli/dedicated.rst create mode 100644 docs/cli/dns.rst create mode 100644 docs/cli/file.rst create mode 100644 docs/cli/firewall.rst create mode 100644 docs/cli/global_ip.rst diff --git a/docs/cli/block.rst b/docs/cli/block.rst new file mode 100644 index 000000000..5ca8140b7 --- /dev/null +++ b/docs/cli/block.rst @@ -0,0 +1,112 @@ +.. _cli_block: + +Block Commands +============== + +.. click:: SoftLayer.CLI.block.access.authorize:cli + :prog: block access-authorize + :show-nested: + +.. click:: SoftLayer.CLI.block.access.list:cli + :prog: block access-list + :show-nested: + +.. click:: SoftLayer.CLI.block.access.revoke:cli + :prog: block access-revoke + :show-nested: + +.. click:: SoftLayer.CLI.block.access.password:cli + :prog: block access-password + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.failback:cli + :prog: block replica-failback + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.failover:cli + :prog: block replica-failover + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.order:cli + :prog: block replica-order + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.partners:cli + :prog: block replica-partners + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.locations:cli + :prog: block replica-locations + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.cancel:cli + :prog: block snapshot-cancel + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.create:cli + :prog: block snapshot-create + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.delete:cli + :prog: block snapshot-delete + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.disable:cli + :prog: block snapshot-disable + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.enable:cli + :prog: block snapshot-enable + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.schedule_list:cli + :prog: block snapshot-schedule-list + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.list:cli + :prog: block snapshot-list + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.order:cli + :prog: block snapshot-order + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.restore:cli + :prog: block snapshot-restore + :show-nested: + +.. click:: SoftLayer.CLI.block.cancel:cli + :prog: block volume-cancel + :show-nested: + +.. click:: SoftLayer.CLI.block.count:cli + :prog: block volume-count + :show-nested: + +.. click:: SoftLayer.CLI.block.detail:cli + :prog: block volume-detail + :show-nested: + +.. click:: SoftLayer.CLI.block.duplicate:cli + :prog: block volume-duplicate + :show-nested: + +.. click:: SoftLayer.CLI.block.list:cli + :prog: block volume-list + :show-nested: + +.. click:: SoftLayer.CLI.block.modify:cli + :prog: block volume-modify + :show-nested: + +.. click:: SoftLayer.CLI.block.order:cli + :prog: block volume-order + :show-nested: + +.. click:: SoftLayer.CLI.block.lun:cli + :prog: block volume-set-lun-id + :show-nested: + +.. click:: SoftLayer.CLI.block.limit:cli + :prog: block volume-limits + :show-nested: diff --git a/docs/cli/call_api.rst b/docs/cli/call_api.rst deleted file mode 100644 index e309f16eb..000000000 --- a/docs/cli/call_api.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _cli_call_api: - -Call API -======== - - -.. click:: SoftLayer.CLI.call_api:cli - :prog: call-api - :show-nested: diff --git a/docs/cli/cdn.rst b/docs/cli/cdn.rst index fce54f731..e334cd6f3 100644 --- a/docs/cli/cdn.rst +++ b/docs/cli/cdn.rst @@ -1,7 +1,7 @@ .. _cli_cdn: Interacting with CDN -============================== +===================== .. click:: SoftLayer.CLI.cdn.detail:cli diff --git a/docs/cli/commands.rst b/docs/cli/commands.rst new file mode 100644 index 000000000..e29b1d0e8 --- /dev/null +++ b/docs/cli/commands.rst @@ -0,0 +1,17 @@ +.. _cli_commands: + +Call API +======== + + +.. click:: SoftLayer.CLI.call_api:cli + :prog: call-api + :show-nested: + + +Shell +===== + +.. click:: SoftLayer.shell.core:cli + :prog: shell + :show-nested: diff --git a/docs/cli/config.rst b/docs/cli/config.rst index b49e5d5ad..dd8bf1a93 100644 --- a/docs/cli/config.rst +++ b/docs/cli/config.rst @@ -1,7 +1,7 @@ .. _cli_config: Config -======== +====== `Creating an IBMID apikey `_ `IBMid for services `_ @@ -16,3 +16,8 @@ Config .. click:: SoftLayer.CLI.config.show:cli :prog: config show :show-nested: + + +.. click:: SoftLayer.CLI.config.setup:cli + :prog: setup + :show-nested: diff --git a/docs/cli/dedicated.rst b/docs/cli/dedicated.rst new file mode 100644 index 000000000..ba11fb536 --- /dev/null +++ b/docs/cli/dedicated.rst @@ -0,0 +1,33 @@ +.. _cli_dedicated: + +Dedicated Host Commands +======================= + + +.. click:: SoftLayer.CLI.dedicatedhost.list:cli + :prog: dedicatedhost list + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.create:cli + :prog: dedicatedhost create + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.create_options:cli + :prog: dedicatedhost create-options + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.detail:cli + :prog: dedicatedhost detail + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.cancel:cli + :prog: dedicatedhost cancel + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.cancel_guests:cli + :prog: dedicatedhost cancel-guests + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.list_guests:cli + :prog: dedicatedhost list-guests + :show-nested: diff --git a/docs/cli/dns.rst b/docs/cli/dns.rst new file mode 100644 index 000000000..c62bb7ada --- /dev/null +++ b/docs/cli/dns.rst @@ -0,0 +1,41 @@ +.. _cli_dns: + +DNS Management +============== + + +.. click:: SoftLayer.CLI.dns.zone_import:cli + :prog: dns import + :show-nested: + +.. click:: SoftLayer.CLI.dns.record_add:cli + :prog: dns record-add + :show-nested: + +.. click:: SoftLayer.CLI.dns.record_edit:cli + :prog: dns record-edit + :show-nested: + +.. click:: SoftLayer.CLI.dns.record_list:cli + :prog: dns record-list + :show-nested: + +.. click:: SoftLayer.CLI.dns.record_remove:cli + :prog: dns record-remove + :show-nested: + +.. click:: SoftLayer.CLI.dns.zone_create:cli + :prog: dns zone-create + :show-nested: + +.. click:: SoftLayer.CLI.dns.zone_delete:cli + :prog: dns zone-delete + :show-nested: + +.. click:: SoftLayer.CLI.dns.zone_list:cli + :prog: dns zone-list + :show-nested: + +.. click:: SoftLayer.CLI.dns.zone_print:cli + :prog: dns zone-print + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst new file mode 100644 index 000000000..ad01b0337 --- /dev/null +++ b/docs/cli/file.rst @@ -0,0 +1,104 @@ +.. _cli_file: + +File Commands +============= + +.. click:: SoftLayer.CLI.file.access.authorize:cli + :prog: file access-authorize + :show-nested: + +.. click:: SoftLayer.CLI.file.access.list:cli + :prog: file access-list + :show-nested: + +.. click:: SoftLayer.CLI.file.access.revoke:cli + :prog: file access-revoke + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.failback:cli + :prog: file replica-failback + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.failover:cli + :prog: file replica-failover + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.order:cli + :prog: file replica-order + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.partners:cli + :prog: file replica-partners + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.locations:cli + :prog: file replica-locations + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.cancel:cli + :prog: file snapshot-cancel + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.create:cli + :prog: file snapshot-create + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.delete:cli + :prog: file snapshot-delete + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.disable:cli + :prog: file snapshot-disable + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.enable:cli + :prog: file snapshot-enable + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.list:cli + :prog: file snapshot-list + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.order:cli + :prog: file snapshot-order + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.restore:cli + :prog: file snapshot-restore + :show-nested: + +.. click:: SoftLayer.CLI.file.cancel:cli + :prog: file volume-cancel + :show-nested: + +.. click:: SoftLayer.CLI.file.count:cli + :prog: file volume-count + :show-nested: + +.. click:: SoftLayer.CLI.file.detail:cli + :prog: file volume-detail + :show-nested: + +.. click:: SoftLayer.CLI.file.duplicate:cli + :prog: file volume-duplicate + :show-nested: + +.. click:: SoftLayer.CLI.file.list:cli + :prog: file volume-list + :show-nested: + +.. click:: SoftLayer.CLI.file.modify:cli + :prog: file volume-modify + :show-nested: + +.. click:: SoftLayer.CLI.file.order:cli + :prog: file volume-order + :show-nested: + +.. click:: SoftLayer.CLI.file.limit:cli + :prog: file volume-limits + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.schedule_list:cli + :prog: file snapshot-schedule-list + :show-nested: diff --git a/docs/cli/firewall.rst b/docs/cli/firewall.rst new file mode 100644 index 000000000..bae1c99d7 --- /dev/null +++ b/docs/cli/firewall.rst @@ -0,0 +1,24 @@ +.. _cli_firewall: + +Firewall Management +=================== + +.. click:: SoftLayer.CLI.firewall.add:cli + :prog: firewall add + :show-nested: + +.. click:: SoftLayer.CLI.firewall.cancel:cli + :prog: firewall cancel + :show-nested: + +.. click:: SoftLayer.CLI.firewall.detail:cli + :prog: firewall detail + :show-nested: + +.. click:: SoftLayer.CLI.firewall.edit:cli + :prog: firewall edit + :show-nested: + +.. click:: SoftLayer.CLI.firewall.list:cli + :prog: firewall list + :show-nested: diff --git a/docs/cli/global_ip.rst b/docs/cli/global_ip.rst new file mode 100644 index 000000000..0e07b4a44 --- /dev/null +++ b/docs/cli/global_ip.rst @@ -0,0 +1,24 @@ +.. _cli_global_ip: + +Global IP Addresses +=================== + +.. click:: SoftLayer.CLI.globalip.assign:cli + :prog: globalip assign + :show-nested: + +.. click:: SoftLayer.CLI.globalip.cancel:cli + :prog: globalip cancel + :show-nested: + +.. click:: SoftLayer.CLI.globalip.create:cli + :prog: globalip create + :show-nested: + +.. click:: SoftLayer.CLI.globalip.list:cli + :prog: globalip list + :show-nested: + +.. click:: SoftLayer.CLI.globalip.unassign:cli + :prog: globalip unassign + :show-nested: diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 08fc273d6..c11402bd3 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -94,6 +94,6 @@ This function updates the firmware of a server. If already at the latest version :prog: hw ready :show-nested: -.. click:: SoftLayer.CLI.hardware.dns-sync:cli +.. click:: SoftLayer.CLI.hardware.dns:cli :prog: hw dns-sync :show-nested: diff --git a/docs/dev/cli.rst b/docs/dev/cli.rst index 3962181ac..c1922b268 100644 --- a/docs/dev/cli.rst +++ b/docs/dev/cli.rst @@ -133,3 +133,38 @@ When a confirmation fails, you probably want to stop execution and give a non-ze :: raise CLIAbort("Aborting. Failed confirmation") + + + +Documenting Commands +-------------------- + +All commands should be documented, luckily there is a sphinx module that makes this pretty easy. + +If you were adding a summary command to `slcli account` you would find the documentation in `docs/cli/account.rst` and you would just need to add this for your command + +``` +.. click:: SoftLayer.CLI.account.summary:cli + :prog: account summary + :show-nested: +``` + + +The following REGEX can take the route entry and turn it into a document entry. + +``` +s/^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$/.. click:: $3\n :prog: $1 $2\n :show-nested:\n/ +``` + +FIND: +``` +^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$ +``` + +REPLACE: +``` +.. click:: $3 + :prog: $1 $2 + :show-nested: + +``` From 906f11baa585f840f72c70c1a84ed2f60a63f059 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 24 Jan 2020 18:03:01 -0600 Subject: [PATCH 0016/1385] adding comment about trying to use sphinx-click to auto document everything and how that didn't work --- docs/dev/cli.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/dev/cli.rst b/docs/dev/cli.rst index c1922b268..31853174e 100644 --- a/docs/dev/cli.rst +++ b/docs/dev/cli.rst @@ -168,3 +168,7 @@ REPLACE: :show-nested: ``` + + +I tried to get sphinx-click to auto document the ENTIRE slcli, but the results were all on one page, and required a few changes to sphinx-click itself to work. This is due to the fact that most commands in SLCLI use the function name "cli", and some hacks would have to be put inplace to use the path name instead. + From 90ecc36c4c148ea9bc165e399f687dd985dd2f77 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Mon, 27 Jan 2020 11:00:29 -0400 Subject: [PATCH 0017/1385] fix the issue 1217 about globalip create ipv6 --- SoftLayer/CLI/globalip/create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/globalip/create.py b/SoftLayer/CLI/globalip/create.py index a47c1eb78..339d290c7 100644 --- a/SoftLayer/CLI/globalip/create.py +++ b/SoftLayer/CLI/globalip/create.py @@ -10,7 +10,7 @@ @click.command() -@click.option('--v6', '--ipv6', is_flag=True, help='Order a IPv6 IP') +@click.option('-v6', '--ipv6', is_flag=True, help='Order a IPv6 IP') @click.option('--test', help='test order') @environment.pass_env def cli(env, ipv6, test): From 54943d3059b7f46e980411fa93f6bbd45cd226dd Mon Sep 17 00:00:00 2001 From: Osbel Rosales Date: Mon, 27 Jan 2020 16:49:00 -0600 Subject: [PATCH 0018/1385] issue#1210 - Adding doc notes describing ISCSI Isolation must be enabled for new commands to work. --- SoftLayer/CLI/block/subnets/assign.py | 10 +++++----- SoftLayer/CLI/block/subnets/list.py | 3 +-- SoftLayer/CLI/block/subnets/remove.py | 10 +++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/block/subnets/assign.py b/SoftLayer/CLI/block/subnets/assign.py index d349c66ae..3ff2ab758 100644 --- a/SoftLayer/CLI/block/subnets/assign.py +++ b/SoftLayer/CLI/block/subnets/assign.py @@ -15,6 +15,8 @@ def cli(env, access_id, subnet_id): """Assign block storage subnets to the given host id. access_id is the host_id obtained by: slcli block access-list + + SoftLayer_Account::iscsiisolationdisabled must be False to use this command """ try: subnet_ids = list(subnet_id) @@ -22,16 +24,14 @@ def cli(env, access_id, subnet_id): assigned_subnets = block_manager.assign_subnets_to_acl(access_id, subnet_ids) for subnet in assigned_subnets: - message = "{0}".format("Successfully assigned subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + message = "Successfully assigned subnet id: {} to allowed host id: {}".format(subnet, access_id) click.echo(message) failed_to_assign_subnets = list(set(subnet_ids) - set(assigned_subnets)) for subnet in failed_to_assign_subnets: - message = "{0}".format("Failed to assign subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + message = "Failed to assign subnet id: {} to allowed host id: {}".format(subnet, access_id) click.echo(message) except SoftLayer.SoftLayerAPIError as ex: - message = "{0}".format("Unable to assign subnets.\nReason: " + ex.faultString) + message = "Unable to assign subnets.\nReason: {}".format(ex.faultString) click.echo(message) diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py index 7256cac66..e111de1b3 100644 --- a/SoftLayer/CLI/block/subnets/list.py +++ b/SoftLayer/CLI/block/subnets/list.py @@ -39,6 +39,5 @@ def cli(env, access_id): env.fout(table) except SoftLayer.SoftLayerAPIError as ex: - message = "{0}".format("Unable to list assigned subnets for access-id: " + - str(access_id) + ".\nReason: " + ex.faultString) + message = "Unable to list assigned subnets for access-id: {}.\nReason: {}".format(access_id, ex.faultString) click.echo(message) diff --git a/SoftLayer/CLI/block/subnets/remove.py b/SoftLayer/CLI/block/subnets/remove.py index 2a77b42ff..d700fb1dd 100644 --- a/SoftLayer/CLI/block/subnets/remove.py +++ b/SoftLayer/CLI/block/subnets/remove.py @@ -15,6 +15,8 @@ def cli(env, access_id, subnet_id): """Remove block storage subnets for the given host id. access_id is the host_id obtained by: slcli block access-list + + SoftLayer_Account::iscsiisolationdisabled must be False to use this command """ try: subnet_ids = list(subnet_id) @@ -22,16 +24,14 @@ def cli(env, access_id, subnet_id): removed_subnets = block_manager.remove_subnets_from_acl(access_id, subnet_ids) for subnet in removed_subnets: - message = "{0}".format("Successfully removed subnet id: " + str(subnet) + - ' for allowed host id: ' + str(access_id) + '.') + message = "Successfully removed subnet id: {} for allowed host id: {}".format(subnet, access_id) click.echo(message) failed_to_remove_subnets = list(set(subnet_ids) - set(removed_subnets)) for subnet in failed_to_remove_subnets: - message = "{0}".format("Failed to remove subnet id: " + str(subnet) + - ' for allowed host id: ' + str(access_id) + '.') + message = "Failed to remove subnet id: {} for allowed host id: {}".format(subnet, access_id) click.echo(message) except SoftLayer.SoftLayerAPIError as ex: - message = "{0}".format("Unable to remove subnets.\nReason: " + ex.faultString) + message = "Unable to remove subnets.\nReason: {}".format(ex.faultString) click.echo(message) From b7416ff5f93766965ad17cf3b6a9f82b545cb5b6 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 29 Jan 2020 15:43:00 -0600 Subject: [PATCH 0019/1385] #1215 added docs for every CLI command. --- SoftLayer/CLI/metadata.py | 17 +-- SoftLayer/CLI/user/create.py | 9 +- SoftLayer/CLI/user/delete.py | 1 + SoftLayer/CLI/user/edit_details.py | 5 +- SoftLayer/CLI/user/edit_permissions.py | 1 + SoftLayer/managers/autoscale.py | 2 + docs/cli.rst | 169 +++++++++++++------------ docs/cli/commands.rst | 12 ++ docs/cli/image.rst | 28 ++++ docs/cli/loadbal.rst | 2 +- docs/cli/object_storage.rst | 30 +++++ docs/cli/rwhois.rst | 12 ++ docs/cli/security_groups.rst | 56 ++++++++ docs/cli/sshkey.rst | 24 ++++ docs/cli/ssl.rst | 25 ++++ docs/cli/subnet.rst | 24 ++++ docs/cli/tickets.rst | 40 ++++++ docs/cli/users.rst | 103 +++------------ docs/cli/vlan.rst | 12 ++ docs/cli/vs.rst | 167 ++++++++++++------------ docs/cli_directory.rst | 10 ++ docs/dev/cli.rst | 51 +++++--- docs/index.rst | 1 + 23 files changed, 520 insertions(+), 281 deletions(-) create mode 100644 docs/cli/image.rst create mode 100644 docs/cli/object_storage.rst create mode 100644 docs/cli/rwhois.rst create mode 100644 docs/cli/security_groups.rst create mode 100644 docs/cli/sshkey.rst create mode 100644 docs/cli/ssl.rst create mode 100644 docs/cli/subnet.rst create mode 100644 docs/cli/tickets.rst create mode 100644 docs/cli/vlan.rst create mode 100644 docs/cli_directory.rst diff --git a/SoftLayer/CLI/metadata.py b/SoftLayer/CLI/metadata.py index 3cc3e384d..26d6f2d48 100644 --- a/SoftLayer/CLI/metadata.py +++ b/SoftLayer/CLI/metadata.py @@ -28,16 +28,13 @@ 'ip': 'primary_ip', } -HELP = """Find details about this machine - -\b -PROP Choices -%s -\b -Examples : -%s -""" % ('*' + '\n*'.join(META_CHOICES), - 'slcli metadata ' + '\nslcli metadata '.join(META_CHOICES)) +HELP = """Find details about the machine making these API calls. + +.. csv-table:: Choices + + {choices} + +""".format(choices="\n ".join(META_CHOICES)) @click.command(help=HELP, diff --git a/SoftLayer/CLI/user/create.py b/SoftLayer/CLI/user/create.py index 6ab19884a..ccfe55955 100644 --- a/SoftLayer/CLI/user/create.py +++ b/SoftLayer/CLI/user/create.py @@ -34,10 +34,13 @@ def cli(env, username, email, password, from_user, template, api_key): """Creates a user Users. - :Example: slcli user create my@email.com -e my@email.com -p generate -a - -t '{"firstName": "Test", "lastName": "Testerson"}' - Remember to set the permissions and access for this new user. + + Example:: + + slcli user create my@email.com -e my@email.com -p generate -a + -t '{"firstName": "Test", "lastName": "Testerson"}' + """ mgr = SoftLayer.UserManager(env.client) diff --git a/SoftLayer/CLI/user/delete.py b/SoftLayer/CLI/user/delete.py index 409c94661..9c00af754 100644 --- a/SoftLayer/CLI/user/delete.py +++ b/SoftLayer/CLI/user/delete.py @@ -17,6 +17,7 @@ def cli(env, identifier): and will eventually be fully removed from the account by an automated internal process. Example: slcli user delete userId + """ mgr = SoftLayer.UserManager(env.client) diff --git a/SoftLayer/CLI/user/edit_details.py b/SoftLayer/CLI/user/edit_details.py index 024421ed0..5e88427a4 100644 --- a/SoftLayer/CLI/user/edit_details.py +++ b/SoftLayer/CLI/user/edit_details.py @@ -22,7 +22,10 @@ def cli(env, user, template): JSON strings should be enclosed in '' and each item should be enclosed in "" - :Example: slcli user edit-details testUser -t '{"firstName": "Test", "lastName": "Testerson"}' + Example:: + + slcli user edit-details testUser -t '{"firstName": "Test", "lastName": "Testerson"}' + """ mgr = SoftLayer.UserManager(env.client) user_id = helpers.resolve_id(mgr.resolve_ids, user, 'username') diff --git a/SoftLayer/CLI/user/edit_permissions.py b/SoftLayer/CLI/user/edit_permissions.py index e86ee256a..64791d312 100644 --- a/SoftLayer/CLI/user/edit_permissions.py +++ b/SoftLayer/CLI/user/edit_permissions.py @@ -21,6 +21,7 @@ @environment.pass_env def cli(env, identifier, enable, permission, from_user): """Enable or Disable specific permissions.""" + mgr = SoftLayer.UserManager(env.client) user_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'username') result = False diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 40d7ebe80..78fa18e31 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -99,6 +99,7 @@ def get_virtual_guests(self, identifier, mask=None): :param identifier: SoftLayer_Scale_Group Id :param mask: optional SoftLayer_Scale_Member objectMask + .. _SoftLayer_Scale_Group::getVirtualGuestMembers(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getVirtualGuestMembers/ """ @@ -109,6 +110,7 @@ def edit(self, identifier, template): :param identifier: SoftLayer_Scale_Group id :param template: `SoftLayer_Scale_Group`_ + .. _SoftLayer_Scale_Group::editObject(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/editObject/ .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ diff --git a/docs/cli.rst b/docs/cli.rst index 7b09fdc17..dc82da29f 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -9,43 +9,39 @@ SoftLayer API bindings for python and how to efficiently make API calls. See the :ref:`usage-examples` section to see how to discover all of the functionality not fully documented here. -.. toctree:: - :maxdepth: 2 - :glob: - - cli/* - .. _config_setup: Configuration Setup ------------------- To update the configuration, you can use `slcli setup`. + :: - $ slcli setup - Username []: username - API Key or Password []: - Endpoint (public|private|custom): public - :..............:..................................................................: - : Name : Value : - :..............:..................................................................: - : Username : username : - : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : - : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : - :..............:..................................................................: - Are you sure you want to write settings to "/home/me/.softlayer"? [y/N]: y + $ slcli setup + Username []: username + API Key or Password []: + Endpoint (public|private|custom): public + :..............:..................................................................: + : Name : Value : + :..............:..................................................................: + : Username : username : + : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : + : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : + :..............:..................................................................: + Are you sure you want to write settings to "/home/me/.softlayer"? [y/N]: y To check the configuration, you can use `slcli config show`. + :: - $ slcli config show - :..............:..................................................................: - : Name : Value : - :..............:..................................................................: - : Username : username : - : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : - : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : - :..............:..................................................................: + $ slcli config show + :..............:..................................................................: + : Name : Value : + :..............:..................................................................: + : Username : username : + : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : + : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : + :..............:..................................................................: If you are using an account created from the https://cloud.ibm.com portal, your username will be literally `apikey`, and use the key provided. `How to create an IBM apikey `_ @@ -57,6 +53,8 @@ To see more about the config file format, see :ref:`config_file`. Usage Examples -------------- To discover the available commands, simply type `slcli`. + + :: $ slcli @@ -112,71 +110,76 @@ To discover the available commands, simply type `slcli`. As you can see, there are a number of commands/sections. To look at the list of subcommands for virtual servers type `slcli vs`. For example: -:: - $ slcli vs - Usage: slcli vs [OPTIONS] COMMAND [ARGS]... - - Virtual Servers. - - Options: - --help Show this message and exit. - - Commands: - cancel Cancel virtual servers. - capture Capture SoftLayer image. - create Order/create virtual servers. - create-options Virtual server order options. - credentials List virtual server credentials. - detail Get details for a virtual server. - dns-sync Sync DNS records. - edit Edit a virtual server's details. - list List virtual servers. - network Manage network settings. - pause Pauses an active virtual server. - power_off Power off an active virtual server. - power_on Power on a virtual server. - ready Check if a virtual server is ready. - reboot Reboot an active virtual server. - reload Reload operating system on a virtual server. - rescue Reboot into a rescue image. - resume Resumes a paused virtual server. - upgrade Upgrade a virtual server. - -Finally, we can make an actual call. Let's list out the virtual servers on our -account by using `slcli vs list`. :: - $ slcli vs list - :.........:............:....................:.......:........:................:..............:....................: - : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : - :.........:............:....................:.......:........:................:..............:....................: - : 1234567 : sjc01 : test.example.com : 4 : 4G : 12.34.56 : 65.43.21 : - : - :.........:............:....................:.......:........:................:..............:....................: + $ slcli vs + Usage: slcli vs [OPTIONS] COMMAND [ARGS]... + + Virtual Servers. + + Options: + --help Show this message and exit. + + Commands: + cancel Cancel virtual servers. + capture Capture SoftLayer image. + create Order/create virtual servers. + create-options Virtual server order options. + credentials List virtual server credentials. + detail Get details for a virtual server. + dns-sync Sync DNS records. + edit Edit a virtual server's details. + list List virtual servers. + network Manage network settings. + pause Pauses an active virtual server. + power_off Power off an active virtual server. + power_on Power on a virtual server. + ready Check if a virtual server is ready. + reboot Reboot an active virtual server. + reload Reload operating system on a virtual server. + rescue Reboot into a rescue image. + resume Resumes a paused virtual server. + upgrade Upgrade a virtual server. + + +Finally, we can make an actual call. Let's list out the virtual servers on our account by using `slcli vs list`. + + +Example:: + + $ slcli vs list + :.........:............:....................:.......:........:................:..............:....................: + : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : + :.........:............:....................:.......:........:................:..............:....................: + : 1234567 : sjc01 : test.example.com : 4 : 4G : 12.34.56 : 65.43.21 : - : + :.........:............:....................:.......:........:................:..............:....................: Most commands will take in additional options/arguments. To see all available actions, use `--help`. + + :: - $ slcli vs list --help - Usage: slcli vs list [OPTIONS] - - List virtual servers. - - Options: - --sortby [guid|hostname|primary_ip|backend_ip|datacenter] - Column to sort by - -c, --cpu INTEGER Number of CPU cores - -D, --domain TEXT Domain portion of the FQDN - -d, --datacenter TEXT Datacenter shortname - -H, --hostname TEXT Host portion of the FQDN - -m, --memory INTEGER Memory in mebibytes - -n, --network TEXT Network port speed in Mbps - --hourly Show only hourly instances - --monthly Show only monthly instances - --tags TEXT Show instances that have one of these comma- - separated tags - --help Show this message and exit. + $ slcli vs list --help + Usage: slcli vs list [OPTIONS] + + List virtual servers. + + Options: + --sortby [guid|hostname|primary_ip|backend_ip|datacenter] + Column to sort by + -c, --cpu INTEGER Number of CPU cores + -D, --domain TEXT Domain portion of the FQDN + -d, --datacenter TEXT Datacenter shortname + -H, --hostname TEXT Host portion of the FQDN + -m, --memory INTEGER Memory in mebibytes + -n, --network TEXT Network port speed in Mbps + --hourly Show only hourly instances + --monthly Show only monthly instances + --tags TEXT Show instances that have one of these comma- + separated tags + --help Show this message and exit. diff --git a/docs/cli/commands.rst b/docs/cli/commands.rst index e29b1d0e8..a577adcdb 100644 --- a/docs/cli/commands.rst +++ b/docs/cli/commands.rst @@ -15,3 +15,15 @@ Shell .. click:: SoftLayer.shell.core:cli :prog: shell :show-nested: + + + +MetaData +======== + +Used to retrieve information about the server making the API call. +Can be called with an un-authenticated API call. + +.. click:: SoftLayer.CLI.metadata:cli + :prog: metadata + :show-nested: diff --git a/docs/cli/image.rst b/docs/cli/image.rst new file mode 100644 index 000000000..771abd16c --- /dev/null +++ b/docs/cli/image.rst @@ -0,0 +1,28 @@ +.. _cli_image: + +Disk Image Commands +=================== + +.. click:: SoftLayer.CLI.image.delete:cli + :prog: image delete + :show-nested: + +.. click:: SoftLayer.CLI.image.detail:cli + :prog: image detail + :show-nested: + +.. click:: SoftLayer.CLI.image.edit:cli + :prog: image edit + :show-nested: + +.. click:: SoftLayer.CLI.image.list:cli + :prog: image list + :show-nested: + +.. click:: SoftLayer.CLI.image.import:cli + :prog: image import + :show-nested: + +.. click:: SoftLayer.CLI.image.export:cli + :prog: image export + :show-nested: diff --git a/docs/cli/loadbal.rst b/docs/cli/loadbal.rst index b1a6a0dcc..cec4200fd 100644 --- a/docs/cli/loadbal.rst +++ b/docs/cli/loadbal.rst @@ -23,7 +23,7 @@ LBaaS Commands :prog: loadbal member-add :show-nested: .. click:: SoftLayer.CLI.loadbal.members:remove - :prog: loadbal member-remote + :prog: loadbal member-remove :show-nested: .. click:: SoftLayer.CLI.loadbal.pools:add :prog: loadbal pool-add diff --git a/docs/cli/object_storage.rst b/docs/cli/object_storage.rst new file mode 100644 index 000000000..43bf03bb8 --- /dev/null +++ b/docs/cli/object_storage.rst @@ -0,0 +1,30 @@ +.. _cli_object_storage: + +Object Storage Commands +======================= + + +.. click:: SoftLayer.CLI.object_storage.list_accounts:cli + :prog: object-storage accounts + :show-nested: + +.. click:: SoftLayer.CLI.object_storage.list_endpoints:cli + :prog: object-storage endpoints + :show-nested: + +.. click:: SoftLayer.CLI.object_storage.credential.list:cli + :prog: object-storage credential list + :show-nested: + + +.. click:: SoftLayer.CLI.object_storage.credential.limit:cli + :prog: object-storage credential limit + :show-nested: + +.. click:: SoftLayer.CLI.object_storage.credential.delete:cli + :prog: object-storage credential delete + :show-nested: + +.. click:: SoftLayer.CLI.object_storage.credential.create:cli + :prog: object-storage credential create + :show-nested: diff --git a/docs/cli/rwhois.rst b/docs/cli/rwhois.rst new file mode 100644 index 000000000..10d2004c9 --- /dev/null +++ b/docs/cli/rwhois.rst @@ -0,0 +1,12 @@ +.. _cli_rwhois: + +Reverse Whois Commands +====================== + +.. click:: SoftLayer.CLI.rwhois.edit:cli + :prog: rwhois edit + :show-nested: + +.. click:: SoftLayer.CLI.rwhois.show:cli + :prog: rwhois show + :show-nested: diff --git a/docs/cli/security_groups.rst b/docs/cli/security_groups.rst new file mode 100644 index 000000000..37385882d --- /dev/null +++ b/docs/cli/security_groups.rst @@ -0,0 +1,56 @@ +.. _cli_security_groups: + +Security Groups +=============== + +.. click:: SoftLayer.CLI.securitygroup.list:cli + :prog: securitygroup list + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.detail:cli + :prog: securitygroup detail + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.create:cli + :prog: securitygroup create + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.edit:cli + :prog: securitygroup edit + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.delete:cli + :prog: securitygroup delete + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.rule:rule_list + :prog: securitygroup rule-list + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.rule:add + :prog: securitygroup rule-add + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.rule:edit + :prog: securitygroup rule-edit + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.rule:remove + :prog: securitygroup rule-remove + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.interface:interface_list + :prog: securitygroup interface-list + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.interface:add + :prog: securitygroup interface-add + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.interface:remove + :prog: securitygroup interface-remove + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.event_log:get_by_request_id + :prog: securitygroup event-log + :show-nested: diff --git a/docs/cli/sshkey.rst b/docs/cli/sshkey.rst new file mode 100644 index 000000000..d12d2f424 --- /dev/null +++ b/docs/cli/sshkey.rst @@ -0,0 +1,24 @@ +.. _cli_sshkey: + +SSH Keys +======== + +.. click:: SoftLayer.CLI.sshkey.add:cli + :prog: sshkey add + :show-nested: + +.. click:: SoftLayer.CLI.sshkey.remove:cli + :prog: sshkey remove + :show-nested: + +.. click:: SoftLayer.CLI.sshkey.edit:cli + :prog: sshkey edit + :show-nested: + +.. click:: SoftLayer.CLI.sshkey.list:cli + :prog: sshkey list + :show-nested: + +.. click:: SoftLayer.CLI.sshkey.print:cli + :prog: sshkey print + :show-nested: diff --git a/docs/cli/ssl.rst b/docs/cli/ssl.rst new file mode 100644 index 000000000..f8c95f058 --- /dev/null +++ b/docs/cli/ssl.rst @@ -0,0 +1,25 @@ +.. _cli_ssl: + +SSL Certificates +================ + +.. click:: SoftLayer.CLI.ssl.add:cli + :prog: ssl add + :show-nested: + +.. click:: SoftLayer.CLI.ssl.download:cli + :prog: ssl download + :show-nested: + +.. click:: SoftLayer.CLI.ssl.edit:cli + :prog: ssl edit + :show-nested: + +.. click:: SoftLayer.CLI.ssl.list:cli + :prog: ssl list + :show-nested: + +.. click:: SoftLayer.CLI.ssl.remove:cli + :prog: ssl remove + :show-nested: + diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst new file mode 100644 index 000000000..20fce0def --- /dev/null +++ b/docs/cli/subnet.rst @@ -0,0 +1,24 @@ +.. _cli_subnets: + +Subnets +======= + +.. click:: SoftLayer.CLI.subnet.cancel:cli + :prog: subnet cancel + :show-nested: + +.. click:: SoftLayer.CLI.subnet.create:cli + :prog: subnet create + :show-nested: + +.. click:: SoftLayer.CLI.subnet.detail:cli + :prog: subnet detail + :show-nested: + +.. click:: SoftLayer.CLI.subnet.list:cli + :prog: subnet list + :show-nested: + +.. click:: SoftLayer.CLI.subnet.lookup:cli + :prog: subnet lookup + :show-nested: diff --git a/docs/cli/tickets.rst b/docs/cli/tickets.rst new file mode 100644 index 000000000..ad5f23428 --- /dev/null +++ b/docs/cli/tickets.rst @@ -0,0 +1,40 @@ +.. _cli_tickets: + +Support Tickets +=============== + +.. click:: SoftLayer.CLI.ticket.create:cli + :prog: ticket create + :show-nested: + +.. click:: SoftLayer.CLI.ticket.detail:cli + :prog: ticket detail + :show-nested: + +.. click:: SoftLayer.CLI.ticket.list:cli + :prog: ticket list + :show-nested: + +.. click:: SoftLayer.CLI.ticket.update:cli + :prog: ticket update + :show-nested: + +.. click:: SoftLayer.CLI.ticket.upload:cli + :prog: ticket upload + :show-nested: + +.. click:: SoftLayer.CLI.ticket.subjects:cli + :prog: ticket subjects + :show-nested: + +.. click:: SoftLayer.CLI.ticket.summary:cli + :prog: ticket summary + :show-nested: + +.. click:: SoftLayer.CLI.ticket.attach:cli + :prog: ticket attach + :show-nested: + +.. click:: SoftLayer.CLI.ticket.detach:cli + :prog: ticket detach + :show-nested: diff --git a/docs/cli/users.rst b/docs/cli/users.rst index 3c98199a7..5058d0652 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -4,93 +4,32 @@ Users ============= Version 5.6.0 introduces the ability to interact with user accounts from the cli. -.. _cli_user_create: +.. click:: SoftLayer.CLI.user.list:cli + :prog: user list + :show-nested: -user create ------------ -This command will create a user on your account. +.. click:: SoftLayer.CLI.user.detail:cli + :prog: user detail + :show-nested: -Options -^^^^^^^ --e, --email TEXT Email address for this user. Required for creation. [required] --p, --password TEXT Password to set for this user. If no password is provided, user will be sent an email to generate one, which expires in 24 hours. '-p generate' will create a password for you (Requires Python 3.6+). Passwords require 8+ characters, upper and lowercase, a number and a symbol. --u, --from-user TEXT Base user to use as a template for creating this user. Will default to the user running this command. Information provided in --template supersedes this template. --t, --template TEXT A json string describing https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/ --a, --api-key Create an API key for this user. --h, --help Show this message and exit. +.. click:: SoftLayer.CLI.user.permissions:cli + :prog: user permissions + :show-nested: -:: +.. click:: SoftLayer.CLI.user.edit_permissions:cli + :prog: user edit-permissions + :show-nested: - slcli user create my@email.com -e my@email.com -p generate -a -t '{"firstName": "Test", "lastName": "Testerson"}' +.. click:: SoftLayer.CLI.user.edit_details:cli + :prog: user edit-details + :show-nested: -.. _cli_user_list: +.. click:: SoftLayer.CLI.user.create:cli + :prog: user create + :show-nested: -user list ----------- -This command will list all Active users on the account that your user has access to view. -There is the option to also filter by username +.. click:: SoftLayer.CLI.user.delete:cli + :prog: user delete + :show-nested: -.. _cli_user_detail: - -user detail -------------------- -Gives a variety of details about a specific user. can be a user id, or username. Will always print a basic set of information about the user, but there are a few extra flags to pull in more detailed information. - -user detail -p, --permissions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Will list the permissions the user has. To see a list of all possible permissions, or to change a user's permissions, see :ref:`cli_user_permissions` - -user detail -h, --hardware -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Will list the Hardware and Dedicated Hosts the user is able to access. - - -user detail -v, --virtual -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Will list the Virtual Guests the user has access to. - -user detail -l, --logins -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Show login history of this user for the last 30 days. IBMId Users will show logins properly, but may not show failed logins. - -user detail -e, --events -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Shows things that are logged in the Event_Log service. Logins, reboots, reloads, and other such actions will show up here. - -.. _cli_user_permissions: - -user permissions -^^^^^^^^^^^^^^^^^^^^^^^ -Will list off all permission keyNames, along with which are assigned to that specific user. - -.. _cli_user_permissions_edit: - -user edit-permissions ---------------------- -Enable or Disable specific permissions. It is possible to set multiple permissions in one command as well. - -:: - - $ slcli user edit-permissions USERID --enable -p TICKET_EDIT -p TICKET_ADD -p TICKET_SEARCH - -Will enable TICKET_EDIT, TICKET_ADD, and TICKET_SEARCH permissions for the USERID - -.. _cli_user_edit_details: - -user edit-details ------------------ -Edit a User's details - -JSON strings should be enclosed in '' and each item should be enclosed in "\" - -:: - - slcli user edit-details testUser -t '{"firstName": "Test", "lastName": "Testerson"}' - -Options -^^^^^^^ - --t, --template TEXT A json string describing `SoftLayer_User_Customer `_ . [required] --h, --help Show this message and exit. - diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst new file mode 100644 index 000000000..1733f40e4 --- /dev/null +++ b/docs/cli/vlan.rst @@ -0,0 +1,12 @@ +.. _cli_vlan: + +VLANs +===== + +.. click:: SoftLayer.CLI.vlan.detail:cli + :prog: vlan detail + :show-nested: + +.. click:: SoftLayer.CLI.vlan.list:cli + :prog: vlan list + :show-nested: diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index f855238d5..afa0f8b2d 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -10,18 +10,19 @@ using SoftLayer's command-line client. .. note:: - The following assumes that the client is already - :ref:`configured with valid SoftLayer credentials`. + The following assumes that the client is already + :ref:`configured with valid SoftLayer credentials`. First, let's list the current virtual servers with `slcli vs list`. + :: - $ slcli vs list - :.....:............:.........................:.......:........:..............:.............:....................:........: - : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : owner : - :.....:............:.........................:.......:........:..............:.............:....................:........: - :.....:............:.........................:.......:........:..............:.............:....................:........: + $ slcli vs list + :.....:............:.........................:.......:........:..............:.............:....................:........: + : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : owner : + :.....:............:.........................:.......:........:..............:.............:....................:........: + :.....:............:.........................:.......:........:..............:.............:....................:........: We don't have any virtual servers yet! Let's fix that. Before we can create a virtual server (VS), we need to know what options are available to us: RAM, @@ -32,47 +33,47 @@ Luckily, there's a simple command to show all options: `slcli vs create-options` :: - $ slcli vs create-options - :................................:.................................................................................: - : name : value : - :................................:.................................................................................: - : datacenter : ams01 : - : : ams03 : - : : wdc07 : - : flavors (balanced) : B1_1X2X25 : - : : B1_1X2X25 : - : : B1_1X2X100 : - : cpus (standard) : 1,2,4,8,12,16,32,56 : - : cpus (dedicated) : 1,2,4,8,16,32,56 : - : cpus (dedicated host) : 1,2,4,8,12,16,32,56 : - : memory : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : - : memory (dedicated host) : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : - : os (CENTOS) : CENTOS_5_64 : - : : CENTOS_LATEST_64 : - : os (CLOUDLINUX) : CLOUDLINUX_5_64 : - : : CLOUDLINUX_6_64 : - : : CLOUDLINUX_LATEST : - : : CLOUDLINUX_LATEST_64 : - : os (COREOS) : COREOS_CURRENT_64 : - : : COREOS_LATEST : - : : COREOS_LATEST_64 : - : os (DEBIAN) : DEBIAN_6_64 : - : : DEBIAN_LATEST_64 : - : os (OTHERUNIXLINUX) : OTHERUNIXLINUX_1_64 : - : : OTHERUNIXLINUX_LATEST : - : : OTHERUNIXLINUX_LATEST_64 : - : os (REDHAT) : REDHAT_5_64 : - : : REDHAT_6_64 : - : : REDHAT_7_64 : - : : REDHAT_LATEST : - : : REDHAT_LATEST_64 : - : san disk(0) : 25,100 : - : san disk(2) : 10,20,25,30,40,50,75,100,125,150,175,200,250,300,350,400,500,750,1000,1500,2000 : - : local disk(0) : 25,100 : - : local disk(2) : 25,100,150,200,300 : - : local (dedicated host) disk(0) : 25,100 : - : nic (dedicated host) : 100,1000 : - :................................:.................................................................................: + $ slcli vs create-options + :................................:.................................................................................: + : name : value : + :................................:.................................................................................: + : datacenter : ams01 : + : : ams03 : + : : wdc07 : + : flavors (balanced) : B1_1X2X25 : + : : B1_1X2X25 : + : : B1_1X2X100 : + : cpus (standard) : 1,2,4,8,12,16,32,56 : + : cpus (dedicated) : 1,2,4,8,16,32,56 : + : cpus (dedicated host) : 1,2,4,8,12,16,32,56 : + : memory : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : + : memory (dedicated host) : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : + : os (CENTOS) : CENTOS_5_64 : + : : CENTOS_LATEST_64 : + : os (CLOUDLINUX) : CLOUDLINUX_5_64 : + : : CLOUDLINUX_6_64 : + : : CLOUDLINUX_LATEST : + : : CLOUDLINUX_LATEST_64 : + : os (COREOS) : COREOS_CURRENT_64 : + : : COREOS_LATEST : + : : COREOS_LATEST_64 : + : os (DEBIAN) : DEBIAN_6_64 : + : : DEBIAN_LATEST_64 : + : os (OTHERUNIXLINUX) : OTHERUNIXLINUX_1_64 : + : : OTHERUNIXLINUX_LATEST : + : : OTHERUNIXLINUX_LATEST_64 : + : os (REDHAT) : REDHAT_5_64 : + : : REDHAT_6_64 : + : : REDHAT_7_64 : + : : REDHAT_LATEST : + : : REDHAT_LATEST_64 : + : san disk(0) : 25,100 : + : san disk(2) : 10,20,25,30,40,50,75,100,125,150,175,200,250,300,350,400,500,750,1000,1500,2000 : + : local disk(0) : 25,100 : + : local disk(2) : 25,100,150,200,300 : + : local (dedicated host) disk(0) : 25,100 : + : nic (dedicated host) : 100,1000 : + :................................:.................................................................................: Here's the command to create a 2-core virtual server with 1GiB memory, running @@ -81,8 +82,8 @@ datacenter using the command `slcli vs create`. :: - $ slcli vs create --hostname=example --domain=softlayer.com -f B1_1X2X25 -o DEBIAN_LATEST_64 --datacenter=ams01 --billing=hourly - This action will incur charges on your account. Continue? [y/N]: y + $ slcli vs create --hostname=example --domain=softlayer.com -f B1_1X2X25 -o DEBIAN_LATEST_64 --datacenter=ams01 --billing=hourly + This action will incur charges on your account. Continue? [y/N]: y :..........:.................................:......................................:...........................: : ID : FQDN : guid : Order Date : :..........:.................................:......................................:...........................: @@ -115,20 +116,20 @@ instantly appear in your virtual server list now. :: - $ slcli vs list - :.........:............:.......................:.......:........:................:..............:....................: - : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : - :.........:............:.......................:.......:........:................:..............:....................: - : 1234567 : ams01 : example.softlayer.com : 2 : 1G : 108.168.200.11 : 10.54.80.200 : Assign Host : - :.........:............:.......................:.......:........:................:..............:....................: + $ slcli vs list + :.........:............:.......................:.......:........:................:..............:....................: + : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : + :.........:............:.......................:.......:........:................:..............:....................: + : 1234567 : ams01 : example.softlayer.com : 2 : 1G : 108.168.200.11 : 10.54.80.200 : Assign Host : + :.........:............:.......................:.......:........:................:..............:....................: Cool. You may ask, "It's creating... but how do I know when it's done?" Well, here's how: :: - $ slcli vs ready 'example' --wait=600 - READY + $ slcli vs ready 'example' --wait=600 + READY When the previous command returns, you'll know that the virtual server has finished the provisioning process and is ready to use. This is *very* useful @@ -140,34 +141,34 @@ username is 'root' and password is 'ABCDEFGH'. .. warning:: - Be careful when using the `--passwords` flag. This will print the virtual - server's password on the screen. Make sure no one is looking over your - shoulder. It's also advisable to change your root password soon after - creating your virtual server, or to create a user with sudo access and - disable SSH-based login directly to the root account. + Be careful when using the `--passwords` flag. This will print the virtual + server's password on the screen. Make sure no one is looking over your + shoulder. It's also advisable to change your root password soon after + creating your virtual server, or to create a user with sudo access and + disable SSH-based login directly to the root account. :: - $ slcli vs detail example --passwords - :..............:...........................: - : Name : Value : - :..............:...........................: - : id : 1234567 : - : hostname : example.softlayer.com : - : status : Active : - : state : Running : - : datacenter : ams01 : - : cores : 2 : - : memory : 1G : - : public_ip : 108.168.200.11 : - : private_ip : 10.54.80.200 : - : os : Debian : - : private_only : False : - : private_cpu : False : - : created : 2013-06-13T08:29:44-06:00 : - : modified : 2013-06-13T08:31:57-06:00 : - : users : root ABCDEFGH : - :..............:...........................: + $ slcli vs detail example --passwords + :..............:...........................: + : Name : Value : + :..............:...........................: + : id : 1234567 : + : hostname : example.softlayer.com : + : status : Active : + : state : Running : + : datacenter : ams01 : + : cores : 2 : + : memory : 1G : + : public_ip : 108.168.200.11 : + : private_ip : 10.54.80.200 : + : os : Debian : + : private_only : False : + : private_cpu : False : + : created : 2013-06-13T08:29:44-06:00 : + : modified : 2013-06-13T08:31:57-06:00 : + : users : root ABCDEFGH : + :..............:...........................: diff --git a/docs/cli_directory.rst b/docs/cli_directory.rst new file mode 100644 index 000000000..6be40cc90 --- /dev/null +++ b/docs/cli_directory.rst @@ -0,0 +1,10 @@ +.. _cli_directory: + +Command Directory +================= + +.. toctree:: + :maxdepth: 2 + :glob: + + cli/* diff --git a/docs/dev/cli.rst b/docs/dev/cli.rst index 31853174e..9545253aa 100644 --- a/docs/dev/cli.rst +++ b/docs/dev/cli.rst @@ -48,6 +48,7 @@ Then we need to register it so that `slcli table-example` will know to route to ... Which gives us + :: $ slcli table-example @@ -143,32 +144,46 @@ All commands should be documented, luckily there is a sphinx module that makes t If you were adding a summary command to `slcli account` you would find the documentation in `docs/cli/account.rst` and you would just need to add this for your command -``` -.. click:: SoftLayer.CLI.account.summary:cli - :prog: account summary - :show-nested: -``` +:: + + .. click:: SoftLayer.CLI.account.summary:cli + :prog: account summary + :show-nested: The following REGEX can take the route entry and turn it into a document entry. -``` -s/^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$/.. click:: $3\n :prog: $1 $2\n :show-nested:\n/ -``` +:: + + s/^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$/.. click:: $3\n :prog: $1 $2\n :show-nested:\n/ + + +Find:: + + ^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$ -FIND: -``` -^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$ -``` -REPLACE: -``` -.. click:: $3 - :prog: $1 $2 - :show-nested: +REPLACE:: -``` + .. click:: $3 + :prog: $1 $2 + :show-nested: I tried to get sphinx-click to auto document the ENTIRE slcli, but the results were all on one page, and required a few changes to sphinx-click itself to work. This is due to the fact that most commands in SLCLI use the function name "cli", and some hacks would have to be put inplace to use the path name instead. + + +Architecture +------------ + +*SLCLI* is the base command, and it starts at *SoftLayer\CLI\core.py*. Commands are loaded from the *SoftLayer\CLI\routes.py* file. How Click figures this out is defined by the *CommandLoader* class in core.py, which is an example of a `MultiCommand `_. + +There are a few examples of commands that are three levels deep, that use a bit more graceful command loader. + +- *SoftLayer\CLI\virt\capacity\__init__.py* +- *SoftLayer\CLI\virt\placementgroup\__init__.py* +- *SoftLayer\CLI\object_storage\credential\__init__.py* + +These commands are not directly listed in the routes file, because the autoloader doesn't have the ability to parse multiple commands like that. For now it was easier to make the rare thrid level commands have their own special loader than re-write the base command loader to be able to look deeper into the project for commands. + diff --git a/docs/index.rst b/docs/index.rst index 1b3c2b390..1d801d53d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,7 @@ in order to manage SoftLayer services. config_file api/* cli + cli_directory Contributing From d4de69f5f2c1a9730ab7cde2177157bee82c423b Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 29 Jan 2020 16:05:52 -0600 Subject: [PATCH 0020/1385] v5.8.5 --- CHANGELOG.md | 20 +++++++++++++++++++- SoftLayer/consts.py | 2 +- docs/cli/block.rst | 13 +++++++++++++ setup.py | 2 +- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ff0afc50..444c459cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,25 @@ # Change Log -## [5.8.5] - 2019-12-20 +## [5.8.5] - 2012-01-29 +https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 + +- #1195 Fixed an issue with `slcli vs dns-sync --ptr`. Added `slcli hw dns-sync` +- #1199 Fix File Storage failback and failover. +- #1198 Fix issue where the summary command fails due to None being provided as the datacenter name. +- #1208 Added The following commands: + - `slcli block volume-limits` + - `slcli file volume-limits` +- #1209 Add testing/CI for python 3.8. +- #1212 Fix vs detail erroring on servers pending cancellation. +- #1210 support subnet ACL management through cli + + `slcli block subnets-list` + + `slcli block subnets-assign` + + `slcli block subnets-remove` +- #1215 Added documentation for all SLCLI commands. + + +## [5.8.4] - 2019-12-20 https://github.com/softlayer/softlayer-python/compare/v5.8.3...v5.8.4 - #1199 Fix block storage failback and failover. diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 89bcf5187..8bb1585fd 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.4' +VERSION = 'v5.8.5' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 5ca8140b7..851d7d84c 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -110,3 +110,16 @@ Block Commands .. click:: SoftLayer.CLI.block.limit:cli :prog: block volume-limits :show-nested: + + +.. click:: SoftLayer.CLI.block.subnets.list:cli + :prog: block subnets-list + :show-nested: + +.. click:: SoftLayer.CLI.block.subnets.assign:cli + :prog: block subnets-assign + :show-nested: + +.. click:: SoftLayer.CLI.block.subnets.remove:cli + :prog: block subnets-remove + :show-nested: diff --git a/setup.py b/setup.py index 65b1d5c44..b88b324c0 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.4', + version='5.8.5', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From d205feed7d3022960dabbcb439b33e76cd39dc26 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 17:54:39 -0400 Subject: [PATCH 0021/1385] Added get lbaas by address method --- SoftLayer/managers/load_balancer.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index ea67e469c..0e93114a8 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -100,7 +100,20 @@ def get_lbaas_uuid_id(self, identifier): this_lb = self.lbaas.getLoadBalancer(identifier, mask="mask[id,uuid]") else: this_lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") - return this_lb['uuid'], this_lb['id'] + return this_lb.get('uuid'), this_lb.get('id') + + def get_lbaas_by_address(self, address): + """Gets a LBaaS by address. + + :param address: Address of the LBaaS instance + """ + this_lb = {} + this_lbs = self.lbaas.getAllObjects() + for lbaas in this_lbs: + if lbaas.get('address') == address: + this_lb = lbaas + break + return this_lb def delete_lb_member(self, identifier, member_id): """Removes a member from a LBaaS instance From e05c70c6e6012ebdf25c42ea3a6aa5e55a1c61b3 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 18:00:57 -0400 Subject: [PATCH 0022/1385] Added load balancer detail by name --- SoftLayer/CLI/loadbal/detail.py | 12 ++++++++++-- SoftLayer/utils.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index eb832d594..55d6a01f3 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -3,6 +3,7 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer import utils @@ -13,8 +14,15 @@ def cli(env, identifier): """Get Load Balancer as a Service details.""" mgr = SoftLayer.LoadBalancerManager(env.client) - _, lbid = mgr.get_lbaas_uuid_id(identifier) - this_lb = mgr.get_lb(lbid) + + if utils.valid_domain(identifier): + lbaas = mgr.get_lbaas_by_address(identifier) + if not lbaas: + raise exceptions.CLIAbort("{} address not found".format(identifier)) + this_lb = mgr.get_lb(lbaas.get('id')) + else: + _, lbid = mgr.get_lbaas_uuid_id(identifier) + this_lb = mgr.get_lb(lbid) if this_lb.get('previousErrorText'): print(this_lb.get('previousErrorText')) table = lbaas_table(this_lb) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 74ad84b96..99c37e8db 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -13,6 +13,16 @@ UUID_RE = re.compile(r'^[0-9a-f\-]{36}$', re.I) KNOWN_OPERATIONS = ['<=', '>=', '<', '>', '~', '!~', '*=', '^=', '$=', '_='] +DOMAIN_RE = re.compile(r'[-a-zA-Z0-9.]{1,40}\.') + + +def valid_domain(domain_name): + """Return whether or not given value is a valid domain. + + :param domain_name: domain string to validate. + + """ + return DOMAIN_RE.match(domain_name) def lookup(dic, key, *keys): From 768d14d5295c8cd1ac9231a07753ca8a8ff8df32 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 18:26:18 -0400 Subject: [PATCH 0023/1385] Added cli loadlal detail by address tests --- .../fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- tests/CLI/modules/loadbal_tests.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 1047a7ce8..4136ebb47 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -1,6 +1,6 @@ getObject = { 'accountId': 1234, - 'address': '01-307608-ams01.clb.appdomain.cloud', + 'address': 'test-01-307608-ams01.clb.appdomain.cloud ', 'createDate': '2019-08-12T07:49:43-06:00', 'id': 1111111, 'isPublic': 0, diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index b87dab6e9..4b011edbe 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -7,6 +7,7 @@ import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError +from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer.fixtures import SoftLayer_Product_Package @@ -216,6 +217,16 @@ def test_lb_detail(self): result = self.run_command(['lb', 'detail', '1111111']) self.assert_no_fail(result) + def test_lb_detail_by_address(self): + address = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('address') + result = self.run_command(['lb', 'detail', address]) + self.assert_no_fail(result) + + def test_lb_detail_address_not_found(self): + address = 'test-01-ams01.clb.appdomain.cloud' + result = self.run_command(['lb', 'detail', address]) + self.assertIsInstance(result.exception, CLIAbort) + def test_order(self): result = self.run_command(['loadbal', 'order', '--name', 'test', '--datacenter', 'par01', '--label', 'labeltest', '--subnet', '759282']) From 6066024266c6b069dfb542e8af6689f4ffaff683 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 18:53:07 -0400 Subject: [PATCH 0024/1385] Increased coverage for loadbal manager by adding test --- SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- tests/managers/loadbal_tests.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 4136ebb47..aacf8e61c 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -1,6 +1,6 @@ getObject = { 'accountId': 1234, - 'address': 'test-01-307608-ams01.clb.appdomain.cloud ', + 'address': 'test-01-307608-ams01.clb.appdomain.cloud', 'createDate': '2019-08-12T07:49:43-06:00', 'id': 1111111, 'isPublic': 0, diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index cf93ed9a6..673d295c9 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -9,6 +9,7 @@ """ import SoftLayer from SoftLayer import testing +from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer class LoadBalancerTests(testing.TestCase): @@ -176,3 +177,9 @@ def test_cancel_lbaas(self): uuid = 'aa-bb-cc' self.lb_mgr.cancel_lbaas(uuid) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'cancelLoadBalancer', args=(uuid,)) + + def test_get_lbaas_by_address(self): + address = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('address') + load_bal = self.lb_mgr.get_lbaas_by_address(address) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + self.assertIsNotNone(load_bal) From 1b6f6a2814cd80480781e628678657a474455f88 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 19:00:11 -0400 Subject: [PATCH 0025/1385] fixed tox issues --- tests/managers/loadbal_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index 673d295c9..4ac34e620 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -8,8 +8,8 @@ them directly to the API. """ import SoftLayer +from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer import testing -from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer class LoadBalancerTests(testing.TestCase): @@ -156,7 +156,7 @@ def test_order_lbaas(self): 'description': desc, 'location': datacenter, 'packageId': package[0]['id'], - 'useHourlyPricing': True, # Required since LBaaS is an hourly service + 'useHourlyPricing': True, # Required since LBaaS is an hourly service 'prices': [{'id': package[0]['itemPrices'][0]['id']}], 'protocolConfigurations': protocols, 'subnets': [{'id': subnet_id}], From 52a71a352d32a5ffc95474f7ed901012ea100204 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 11 Feb 2020 15:46:16 -0400 Subject: [PATCH 0026/1385] add note about using multiple colon symbols --- docs/cli/hardware.rst | 1 + docs/cli/vs.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index c11402bd3..ea7910a06 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -43,6 +43,7 @@ Provides some basic functionality to order a server. `slcli order` has a more fu When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. +**Note :** Using multiple colon symbols can cause an error. .. click:: SoftLayer.CLI.hardware.list:cli :prog: hw list diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index afa0f8b2d..374986797 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -203,6 +203,8 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: vs edit :show-nested: +**Note :** Using multiple colon symbols can cause an error. + .. click:: SoftLayer.CLI.virt.list:cli :prog: vs list :show-nested: From 5ebacfd09def8af7a228226780b5d0b5c9d6e660 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 11 Feb 2020 16:37:20 -0400 Subject: [PATCH 0027/1385] #1221 Added version checker --- SoftLayer/CLI/core.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 3a552df79..f962e8df8 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -5,12 +5,14 @@ :license: MIT, see LICENSE for more details. """ +import json import logging import os import sys import time import traceback import types +import urllib3 import click @@ -69,6 +71,26 @@ def get_command(self, ctx, name): return module +def get_latest_version(): + """Gets the latest version of the Softlayer library.""" + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + http = urllib3.PoolManager() + try: + str_result = http.request('GET', 'https://pypi.python.org/pypi/softlayer/json') + dic_result = json.loads(str_result.data.decode('utf-8')) + latest = dic_result['info']['version'] + except Exception: + latest = '%(version)s' + return latest + + +def get_version_message(): + """Gets current and latest release versions message.""" + message = 'Current: %(prog)s %(version)s \n' + latest = get_latest_version() + return message + 'Latest: %(prog)s ' + latest + + @click.group(help="SoftLayer Command-line Client", epilog="""To use most commands your SoftLayer username and api_key need to be configured. The easiest way to do that is to @@ -103,7 +125,8 @@ def get_command(self, ctx, name): is_flag=True, required=False, help="Use demo data instead of actually making API calls") -@click.version_option(prog_name="slcli (SoftLayer Command-line)") +@click.version_option(prog_name="slcli (SoftLayer Command-line)", + message=get_version_message()) @environment.pass_env def cli(env, format='table', From 04015ae95dd399ab9ad710db4909db02f182bddd Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 12 Feb 2020 16:09:13 -0600 Subject: [PATCH 0028/1385] removed todo message --- SoftLayer/CLI/user/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/user/permissions.py b/SoftLayer/CLI/user/permissions.py index b1851acb2..bf77c41ae 100644 --- a/SoftLayer/CLI/user/permissions.py +++ b/SoftLayer/CLI/user/permissions.py @@ -11,7 +11,7 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """User Permissions. TODO change to list all permissions, and which users have them""" + """User Permissions.""" mgr = SoftLayer.UserManager(env.client) user_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'username') From e4e319e201a2ecca5fc0c664f603a193f226edca Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 12 Feb 2020 18:53:24 -0400 Subject: [PATCH 0029/1385] fix the Christopher comment code review --- docs/cli/hardware.rst | 11 +++++++++-- docs/cli/vs.rst | 2 -- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index ea7910a06..3cd899d4a 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -41,9 +41,16 @@ Provides some basic functionality to order a server. `slcli order` has a more fu :prog: hw edit :show-nested: -When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. +**Note :** Using multiple ' **:** ' can cause an error. + + $ slcli hw edit 123456 --tag "cloud:service:db2whoc, cloud:svcplan:flex, cloud:svcenv:prod, cloud:bmixenv:fra" + + TransportError(0): ('Connection aborted.', -**Note :** Using multiple colon symbols can cause an error. + RemoteDisconnected('Remote end closed connection without response',)) + + +When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. .. click:: SoftLayer.CLI.hardware.list:cli :prog: hw list diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 374986797..afa0f8b2d 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -203,8 +203,6 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: vs edit :show-nested: -**Note :** Using multiple colon symbols can cause an error. - .. click:: SoftLayer.CLI.virt.list:cli :prog: vs list :show-nested: From 264c674c91026856e48daef9f0c2d19103f2b926 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 12 Feb 2020 19:38:55 -0400 Subject: [PATCH 0030/1385] #1221 changes requested and unit tests --- SoftLayer/CLI/core.py | 28 +++++++++++++++------------- tests/CLI/core_tests.py | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index f962e8df8..fe86f714e 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -5,17 +5,16 @@ :license: MIT, see LICENSE for more details. """ -import json import logging import os import sys import time import traceback import types -import urllib3 import click +import requests import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions @@ -33,6 +32,7 @@ 3: logging.DEBUG } +PROG_NAME = "slcli (SoftLayer Command-line)" VALID_FORMATS = ['table', 'raw', 'json', 'jsonraw'] DEFAULT_FORMAT = 'raw' if sys.stdout.isatty(): @@ -73,22 +73,24 @@ def get_command(self, ctx, name): def get_latest_version(): """Gets the latest version of the Softlayer library.""" - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - http = urllib3.PoolManager() try: - str_result = http.request('GET', 'https://pypi.python.org/pypi/softlayer/json') - dic_result = json.loads(str_result.data.decode('utf-8')) - latest = dic_result['info']['version'] + result = requests.get('https://pypi.org/pypi/SoftLayer/json') + json_result = result.json() + latest = 'v{}'.format(json_result['info']['version']) except Exception: - latest = '%(version)s' + latest = "Unable to get version from pypi." return latest -def get_version_message(): +def get_version_message(ctx, param, value): """Gets current and latest release versions message.""" - message = 'Current: %(prog)s %(version)s \n' + if not value or ctx.resilient_parsing: + return + current = SoftLayer.consts.VERSION latest = get_latest_version() - return message + 'Latest: %(prog)s ' + latest + click.secho("Current: {prog} {current}\nLatest: {prog} {latest}".format( + prog=PROG_NAME, current=current, latest=latest)) + ctx.exit() @click.group(help="SoftLayer Command-line Client", @@ -125,8 +127,8 @@ def get_version_message(): is_flag=True, required=False, help="Use demo data instead of actually making API calls") -@click.version_option(prog_name="slcli (SoftLayer Command-line)", - message=get_version_message()) +@click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=get_version_message, + help="Show version information.") @environment.pass_env def cli(env, format='table', diff --git a/tests/CLI/core_tests.py b/tests/CLI/core_tests.py index 321f78b1e..f230a3513 100644 --- a/tests/CLI/core_tests.py +++ b/tests/CLI/core_tests.py @@ -10,6 +10,7 @@ import click import mock +from requests.models import Response import SoftLayer from SoftLayer.CLI import core from SoftLayer.CLI import environment @@ -52,6 +53,28 @@ def test_diagnostics(self): self.assertIn('"python_version"', result.output) self.assertIn('"library_location"', result.output) + @mock.patch('requests.get') + def test_get_latest_version(self, request_get): + response = Response() + response.status_code = 200 + response.json = mock.MagicMock(return_value={"info": {"version": "1.1.1"}}) + request_get.return_value = response + version = core.get_latest_version() + self.assertIn('1.1.1', version) + + @mock.patch('requests.get') + def test_unable_get_latest_version(self, request_get): + request_get.side_effect = Exception + version = core.get_latest_version() + self.assertIn('Unable', version) + + @mock.patch('SoftLayer.CLI.core.get_latest_version') + def test_get_version_message(self, get_latest_version_mock): + get_latest_version_mock.return_value = '1.1.1' + env = environment.Environment() + result = self.run_command(['--version'], env=env) + self.assert_no_fail(result) + class CoreMainTests(testing.TestCase): From 332f30023e7cd2cc30af3efad060f3cb43976517 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 13 Feb 2020 16:09:28 -0400 Subject: [PATCH 0031/1385] #1222 changing slcli lb list to display the name instead of the address --- SoftLayer/CLI/loadbal/list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/loadbal/list.py b/SoftLayer/CLI/loadbal/list.py index 4eb2f5731..02ce5bb7c 100644 --- a/SoftLayer/CLI/loadbal/list.py +++ b/SoftLayer/CLI/loadbal/list.py @@ -30,17 +30,17 @@ def location_sort(location): def generate_lbaas_table(lbaas): """Takes a list of SoftLayer_Network_LBaaS_LoadBalancer and makes a table""" table = formatting.Table([ - 'Id', 'Location', 'Address', 'Description', 'Public', 'Create Date', 'Members', 'Listeners' + 'Id', 'Location', 'Name', 'Description', 'Public', 'Create Date', 'Members', 'Listeners' ], title="IBM Cloud LoadBalancer") - table.align['Address'] = 'l' + table.align['Name'] = 'l' table.align['Description'] = 'l' table.align['Location'] = 'l' for this_lb in sorted(lbaas, key=location_sort): table.add_row([ this_lb.get('id'), utils.lookup(this_lb, 'datacenter', 'longName'), - this_lb.get('address'), + this_lb.get('name'), this_lb.get('description'), 'Yes' if this_lb.get('isPublic', 1) == 1 else 'No', utils.clean_time(this_lb.get('createDate')), From 5a2a73677bcd36fb130e8e74375f6996744d6d13 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 13 Feb 2020 16:14:09 -0600 Subject: [PATCH 0032/1385] updated tavis.yml for Ubuntu bionic and pypy3.5 -> pypy3. specifically using 3.5 seems to be broken on travis servers, so the latest 3.6 should be fine --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e40cb2a51..35c987a63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ # https://docs.travis-ci.com/user/languages/python/#python-37-and-higher -dist: xenial +dist: bionic language: python sudo: false matrix: @@ -12,7 +12,7 @@ matrix: env: TOX_ENV=py37 - python: "3.8" env: TOX_ENV=py38 - - python: "pypy3.5" + - python: "pypy3" env: TOX_ENV=pypy3 - python: "3.6" env: TOX_ENV=analysis From ee748de19551cbbffb30e3d88ea47b09566fa4e8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 14 Feb 2020 12:36:46 -0400 Subject: [PATCH 0033/1385] #1222 Added logic to lookup LBaaS by name instead of the address --- SoftLayer/CLI/loadbal/detail.py | 12 ++----- .../SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- SoftLayer/managers/load_balancer.py | 34 ++++++++++--------- SoftLayer/utils.py | 12 +------ tests/CLI/modules/loadbal_tests.py | 15 ++++---- tests/managers/loadbal_tests.py | 15 ++++++-- 6 files changed, 41 insertions(+), 49 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 55d6a01f3..eb832d594 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -3,7 +3,6 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer import utils @@ -14,15 +13,8 @@ def cli(env, identifier): """Get Load Balancer as a Service details.""" mgr = SoftLayer.LoadBalancerManager(env.client) - - if utils.valid_domain(identifier): - lbaas = mgr.get_lbaas_by_address(identifier) - if not lbaas: - raise exceptions.CLIAbort("{} address not found".format(identifier)) - this_lb = mgr.get_lb(lbaas.get('id')) - else: - _, lbid = mgr.get_lbaas_uuid_id(identifier) - this_lb = mgr.get_lb(lbid) + _, lbid = mgr.get_lbaas_uuid_id(identifier) + this_lb = mgr.get_lb(lbid) if this_lb.get('previousErrorText'): print(this_lb.get('previousErrorText')) table = lbaas_table(this_lb) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index aacf8e61c..94220cdea 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -6,7 +6,7 @@ 'isPublic': 0, 'locationId': 265592, 'modifyDate': '2019-08-13T16:26:06-06:00', - 'name': 'dcabero-01', + 'name': 'test-01', 'operatingStatus': 'ONLINE', 'provisioningStatus': 'ACTIVE', 'type': 0, diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 0e93114a8..62d2a3075 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -92,28 +92,30 @@ def update_lb_health_monitors(self, uuid, checks): def get_lbaas_uuid_id(self, identifier): """Gets a LBaaS uuid, id. Since sometimes you need one or the other. - :param identifier: either the LB Id, or UUID, this function will return both. + :param identifier: either the LB Id, UUID or Name, this function will return UUI and LB Id. :return (uuid, id): """ - # int objects don't have a len property. - if not isinstance(identifier, int) and len(identifier) == 36: - this_lb = self.lbaas.getLoadBalancer(identifier, mask="mask[id,uuid]") + mask = "mask[id,uuid]" + if isinstance(identifier, int): + this_lb = self.lbaas.getObject(id=identifier, mask=mask) + elif len(identifier) == 36 and utils.UUID_RE.match(identifier): + this_lb = self.lbaas.getLoadBalancer(identifier, mask=mask) else: - this_lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") + this_lb = self.get_lbaas_by_name(identifier, mask=mask) + return this_lb.get('uuid'), this_lb.get('id') - def get_lbaas_by_address(self, address): - """Gets a LBaaS by address. + def get_lbaas_by_name(self, name, mask=None): + """Gets a LBaaS by name. - :param address: Address of the LBaaS instance + :param name: Name of the LBaaS instance + :param mask: + :returns: SoftLayer_Network_LBaaS_LoadBalancer or an empty dictionary if the name is not found. """ - this_lb = {} - this_lbs = self.lbaas.getAllObjects() - for lbaas in this_lbs: - if lbaas.get('address') == address: - this_lb = lbaas - break - return this_lb + object_filter = {'name': {'operation': name}} + this_lbs = self.lbaas.getAllObjects(filter=object_filter, mask=mask) + + return this_lbs[0] if this_lbs else {} def delete_lb_member(self, identifier, member_id): """Removes a member from a LBaaS instance @@ -210,7 +212,7 @@ def order_lbaas(self, datacenter, name, desc, protocols, subnet_id, public=False 'description': desc, 'location': datacenter, 'packageId': package.get('id'), - 'useHourlyPricing': True, # Required since LBaaS is an hourly service + 'useHourlyPricing': True, # Required since LBaaS is an hourly service 'prices': [{'id': price_id} for price_id in prices], 'protocolConfigurations': protocols, 'subnets': [{'id': subnet_id}], diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 99c37e8db..0234bf72d 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -11,18 +11,8 @@ # pylint: disable=no-member, invalid-name -UUID_RE = re.compile(r'^[0-9a-f\-]{36}$', re.I) +UUID_RE = re.compile(r'^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$', re.I) KNOWN_OPERATIONS = ['<=', '>=', '<', '>', '~', '!~', '*=', '^=', '$=', '_='] -DOMAIN_RE = re.compile(r'[-a-zA-Z0-9.]{1,40}\.') - - -def valid_domain(domain_name): - """Return whether or not given value is a valid domain. - - :param domain_name: domain string to validate. - - """ - return DOMAIN_RE.match(domain_name) def lookup(dic, key, *keys): diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 4b011edbe..84866d3f5 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -7,7 +7,6 @@ import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError -from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer.fixtures import SoftLayer_Product_Package @@ -217,15 +216,15 @@ def test_lb_detail(self): result = self.run_command(['lb', 'detail', '1111111']) self.assert_no_fail(result) - def test_lb_detail_by_address(self): - address = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('address') - result = self.run_command(['lb', 'detail', address]) + def test_lb_detail_by_name(self): + name = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('name') + result = self.run_command(['lb', 'detail', name]) self.assert_no_fail(result) - def test_lb_detail_address_not_found(self): - address = 'test-01-ams01.clb.appdomain.cloud' - result = self.run_command(['lb', 'detail', address]) - self.assertIsInstance(result.exception, CLIAbort) + def test_lb_detail_uuid(self): + uuid = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('uuid') + result = self.run_command(['lb', 'detail', uuid]) + self.assert_no_fail(result) def test_order(self): result = self.run_command(['loadbal', 'order', '--name', 'test', '--datacenter', 'par01', '--label', diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index 4ac34e620..38031204d 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -79,6 +79,15 @@ def test_get_lbaas_uuid_id_id(self): self.assertEqual(lb_uuid, uuid) self.assertEqual(lb_id, my_id) + def test_get_lbaas_uuid_id_name(self): + uuid = '1a1aa111-4474-4e16-9f02-4de959229b85' + my_id = 1111111 + name = 'test-01' + lb_uuid, lb_id = self.lb_mgr.get_lbaas_uuid_id(name) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + self.assertEqual(lb_uuid, uuid) + self.assertEqual(lb_id, my_id) + def test_delete_lb_member(self): uuid = 'aa-bb-cc' member_id = 'dd-ee-ff' @@ -178,8 +187,8 @@ def test_cancel_lbaas(self): self.lb_mgr.cancel_lbaas(uuid) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'cancelLoadBalancer', args=(uuid,)) - def test_get_lbaas_by_address(self): - address = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('address') - load_bal = self.lb_mgr.get_lbaas_by_address(address) + def test_get_lbaas_by_name(self): + name = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('name') + load_bal = self.lb_mgr.get_lbaas_by_name(name) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') self.assertIsNotNone(load_bal) From f4756a04e7d54e3813c738954130731ef40ffc39 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 15:37:26 -0600 Subject: [PATCH 0034/1385] add dep. dupe ordering support --- SoftLayer/CLI/block/duplicate.py | 11 +++++++++-- SoftLayer/CLI/file/duplicate.py | 12 ++++++++++-- SoftLayer/managers/block.py | 8 +++++++- SoftLayer/managers/file.py | 8 +++++++- SoftLayer/managers/storage_utils.py | 8 +++++++- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py index ec728f87c..83b8bd9bc 100644 --- a/SoftLayer/CLI/block/duplicate.py +++ b/SoftLayer/CLI/block/duplicate.py @@ -54,9 +54,15 @@ type=click.Choice(['hourly', 'monthly']), default='monthly', help="Optional parameter for Billing rate (default to monthly)") +@click.option('--dependent-duplicate', + type=click.BOOL, + default=False, + help='Whether or not this duplicate will be a dependent duplicate ' + 'of the origin volume (default to false)') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, - duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing): + duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, + dependent_duplicate): """Order a duplicate block storage volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) @@ -75,7 +81,8 @@ def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops=duplicate_iops, duplicate_tier_level=duplicate_tier, duplicate_snapshot_size=duplicate_snapshot_size, - hourly_billing_flag=hourly_billing_flag + hourly_billing_flag=hourly_billing_flag, + dependent_duplicate=dependent_duplicate ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/CLI/file/duplicate.py b/SoftLayer/CLI/file/duplicate.py index a3b4c801a..b722a564d 100644 --- a/SoftLayer/CLI/file/duplicate.py +++ b/SoftLayer/CLI/file/duplicate.py @@ -52,9 +52,16 @@ type=click.Choice(['hourly', 'monthly']), default='monthly', help="Optional parameter for Billing rate (default to monthly)") +@click.option('--dependent-duplicate', + type=click.BOOL, + default=False, + help='Whether or not this duplicate will be a dependent duplicate' + 'of the origin volume (default to false)') + @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, - duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing): + duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, + dependent_duplicate): """Order a duplicate file storage volume.""" file_manager = SoftLayer.FileStorageManager(env.client) @@ -73,7 +80,8 @@ def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops=duplicate_iops, duplicate_tier_level=duplicate_tier, duplicate_snapshot_size=duplicate_snapshot_size, - hourly_billing_flag=hourly_billing_flag + hourly_billing_flag=hourly_billing_flag, + dependent_duplicate=dependent_duplicate ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 11b998935..90bceeb70 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -310,7 +310,8 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, duplicate_size=None, duplicate_iops=None, duplicate_tier_level=None, duplicate_snapshot_size=None, - hourly_billing_flag=False): + hourly_billing_flag=False, + dependent_duplicate=False): """Places an order for a duplicate block volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -321,6 +322,8 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :param duplicate_snapshot_size: Snapshot space size for the duplicate :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. + :param dependent_duplicate: Duplicate type, normal (False) or dependent + duplicate (True) :return: Returns a SoftLayer_Container_Product_Order_Receipt """ @@ -348,6 +351,9 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, if origin_snapshot_id is not None: order['duplicateOriginSnapshotId'] = origin_snapshot_id + if dependent_duplicate: + order['isDependentDuplicateFlag'] = 1 + return self.client.call('Product_Order', 'placeOrder', order) def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None): diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 95cf85c88..ef8a7c758 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -259,7 +259,8 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, duplicate_size=None, duplicate_iops=None, duplicate_tier_level=None, duplicate_snapshot_size=None, - hourly_billing_flag=False): + hourly_billing_flag=False, + dependent_duplicate=False): """Places an order for a duplicate file volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -270,6 +271,8 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :param duplicate_snapshot_size: Snapshot space size for the duplicate :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. + :param dependent_duplicate: Duplicate type, normal (False) or dependent + duplicate (True) :return: Returns a SoftLayer_Container_Product_Order_Receipt """ @@ -289,6 +292,9 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, if origin_snapshot_id is not None: order['duplicateOriginSnapshotId'] = origin_snapshot_id + if dependent_duplicate: + order['isDependentDuplicateFlag'] = 1 + return self.client.call('Product_Order', 'placeOrder', order) def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None): diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 80ec60368..d3d377a11 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -788,7 +788,8 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, def prepare_duplicate_order_object(manager, origin_volume, iops, tier, duplicate_size, duplicate_snapshot_size, - volume_type, hourly_billing_flag=False): + volume_type, hourly_billing_flag=False, + dependent_duplicate=False): """Prepare the duplicate order to submit to SoftLayer_Product::placeOrder() :param manager: The File or Block manager calling this function @@ -799,6 +800,8 @@ def prepare_duplicate_order_object(manager, origin_volume, iops, tier, :param duplicate_snapshot_size: The size for the duplicate snapshot space :param volume_type: The type of the origin volume ('file' or 'block') :param hourly_billing_flag: Billing type, monthly (False) or hourly (True) + :param dependent_duplicate: Duplicate type, normal (False) or dependent + duplicate (True) :return: Returns the order object to be passed to the placeOrder() method of the Product_Order service """ @@ -903,6 +906,9 @@ def prepare_duplicate_order_object(manager, origin_volume, iops, tier, if volume_is_performance: duplicate_order['iops'] = iops + if dependent_duplicate: + duplicate_order['isDependentDuplicateFlag'] = 1 + return duplicate_order From 3be7a9d1375b578ce734bb6254258f3f5bf6a6c4 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 15:37:58 -0600 Subject: [PATCH 0035/1385] add myself to CONTRIBUTORS --- CONTRIBUTORS | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 4de814b58..5b475dc73 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -6,6 +6,7 @@ chechuironman Christopher Gallo David Ibarra Hans Kristian Moen +Ian Sutton Jake Williams Jason Johnson Kevin Landreth From faa6cd5b4508ef838eacac466fac0315a1445a9f Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:06:04 -0600 Subject: [PATCH 0036/1385] fix tests --- tests/CLI/modules/block_tests.py | 3 ++- tests/CLI/modules/file_tests.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f42a864ef..e59288981 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -642,7 +642,8 @@ def test_duplicate_order_hourly_billing(self, order_mock): duplicate_size=250, duplicate_iops=None, duplicate_tier_level=2, duplicate_snapshot_size=20, - hourly_billing_flag=True) + hourly_billing_flag=True, + dependent_duplicate=False) self.assert_no_fail(result) self.assertEqual(result.output, 'Order #24602 placed successfully!\n' diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 0fc4ffc06..e1d8628cb 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -629,7 +629,8 @@ def test_duplicate_order_hourly_billing(self, order_mock): duplicate_size=250, duplicate_iops=None, duplicate_tier_level=2, duplicate_snapshot_size=20, - hourly_billing_flag=True) + hourly_billing_flag=True, + dependent_duplicate=False) self.assert_no_fail(result) self.assertEqual(result.output, 'Order #24602 placed successfully!\n' From 2c5026c3fead6e2c9e24c023e49c31b83db765e9 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:29:24 -0600 Subject: [PATCH 0037/1385] more tests and analysis checker fixes --- SoftLayer/CLI/block/duplicate.py | 2 +- SoftLayer/CLI/file/duplicate.py | 3 +- SoftLayer/managers/storage_utils.py | 2 +- tests/managers/block_tests.py | 47 +++++++++++++++++++++++++++++ tests/managers/file_tests.py | 45 +++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py index 83b8bd9bc..df041bff9 100644 --- a/SoftLayer/CLI/block/duplicate.py +++ b/SoftLayer/CLI/block/duplicate.py @@ -58,7 +58,7 @@ type=click.BOOL, default=False, help='Whether or not this duplicate will be a dependent duplicate ' - 'of the origin volume (default to false)') + 'of the origin volume (default to false)') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, diff --git a/SoftLayer/CLI/file/duplicate.py b/SoftLayer/CLI/file/duplicate.py index b722a564d..ad14faf70 100644 --- a/SoftLayer/CLI/file/duplicate.py +++ b/SoftLayer/CLI/file/duplicate.py @@ -56,8 +56,7 @@ type=click.BOOL, default=False, help='Whether or not this duplicate will be a dependent duplicate' - 'of the origin volume (default to false)') - + 'of the origin volume (default to false)') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index d3d377a11..b49e0eb45 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -800,7 +800,7 @@ def prepare_duplicate_order_object(manager, origin_volume, iops, tier, :param duplicate_snapshot_size: The size for the duplicate snapshot space :param volume_type: The type of the origin volume ('file' or 'block') :param hourly_billing_flag: Billing type, monthly (False) or hourly (True) - :param dependent_duplicate: Duplicate type, normal (False) or dependent + :param dependent_duplicate: Duplicate type, normal (False) or dependent duplicate (True) :return: Returns the order object to be passed to the placeOrder() method of the Product_Order service diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index acb0dc6f6..9a81ca374 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -846,6 +846,53 @@ def test_order_block_duplicate_performance(self): 'useHourlyPricing': False },)) + def test_order_block_duplicate_depdupe(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.block.order_duplicate_volume( + 102, + origin_snapshot_id=470, + duplicate_size=1000, + duplicate_iops=2000, + duplicate_tier_level=None, + duplicate_snapshot_size=10, + dependent_duplicate=True + ) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 190113}, + {'id': 190173}, + {'id': 191193} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'osFormatType': {'keyName': 'LINUX'}, + 'duplicateOriginSnapshotId': 470, + 'iops': 2000, + 'useHourlyPricing': False, + 'isDependentDuplicateFlag': 1 + },)) + + def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index d6ca66c68..9a8224b3a 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -742,6 +742,51 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): 'useHourlyPricing': False },)) + def test_order_file_duplicate_depdupe(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.file.order_duplicate_volume( + 102, + origin_snapshot_id=470, + duplicate_size=1000, + duplicate_iops=None, + duplicate_tier_level=4, + duplicate_snapshot_size=10, + dependent_duplicate=True + ) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 194763}, + {'id': 194703}, + {'id': 194943} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'duplicateOriginSnapshotId': 470, + 'useHourlyPricing': False, + 'isDependentDuplicateFlag': 1 + },)) + + def test_order_file_duplicate_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] From 8b6bdfe618157d37451ab5c247a0e3963edaac98 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:33:20 -0600 Subject: [PATCH 0038/1385] fix test analysis checks --- tests/managers/block_tests.py | 1 - tests/managers/file_tests.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 9a81ca374..e78622105 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -892,7 +892,6 @@ def test_order_block_duplicate_depdupe(self): 'isDependentDuplicateFlag': 1 },)) - def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 9a8224b3a..69c9c60ed 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -786,7 +786,6 @@ def test_order_file_duplicate_depdupe(self): 'isDependentDuplicateFlag': 1 },)) - def test_order_file_duplicate_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] From e424ce15eaf24d327800d13e4003578b7e392577 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:52:49 -0600 Subject: [PATCH 0039/1385] fix minor changes requested --- SoftLayer/CLI/block/duplicate.py | 3 ++- SoftLayer/CLI/file/duplicate.py | 3 ++- SoftLayer/managers/file.py | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py index df041bff9..ff9ae961a 100644 --- a/SoftLayer/CLI/block/duplicate.py +++ b/SoftLayer/CLI/block/duplicate.py @@ -57,8 +57,9 @@ @click.option('--dependent-duplicate', type=click.BOOL, default=False, + show_default=True, help='Whether or not this duplicate will be a dependent duplicate ' - 'of the origin volume (default to false)') + 'of the origin volume.') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, diff --git a/SoftLayer/CLI/file/duplicate.py b/SoftLayer/CLI/file/duplicate.py index ad14faf70..13e3dcfc5 100644 --- a/SoftLayer/CLI/file/duplicate.py +++ b/SoftLayer/CLI/file/duplicate.py @@ -55,8 +55,9 @@ @click.option('--dependent-duplicate', type=click.BOOL, default=False, + show_default=True, help='Whether or not this duplicate will be a dependent duplicate' - 'of the origin volume (default to false)') + 'of the origin volume.') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index ef8a7c758..0a5214e67 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -271,8 +271,7 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :param duplicate_snapshot_size: Snapshot space size for the duplicate :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. - :param dependent_duplicate: Duplicate type, normal (False) or dependent - duplicate (True) + :param dependent_duplicate: Duplicate type, normal (False) or dependent duplicate (True) :return: Returns a SoftLayer_Container_Product_Order_Receipt """ From 7a651b05d6d96080846e4576734ebd69eeef6dd5 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:54:16 -0600 Subject: [PATCH 0040/1385] minor line length issue --- SoftLayer/managers/block.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 90bceeb70..25e6839f2 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -322,8 +322,7 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :param duplicate_snapshot_size: Snapshot space size for the duplicate :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. - :param dependent_duplicate: Duplicate type, normal (False) or dependent - duplicate (True) + :param dependent_duplicate: Duplicate type, normal (False) or dependent duplicate (True) :return: Returns a SoftLayer_Container_Product_Order_Receipt """ From c1477f86654a241dac6ef4502b3e24a15764af11 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 17 Feb 2020 19:08:52 -0400 Subject: [PATCH 0041/1385] #1222 check if the identifier is really just a number --- SoftLayer/managers/load_balancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 62d2a3075..72b13ab46 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -96,7 +96,7 @@ def get_lbaas_uuid_id(self, identifier): :return (uuid, id): """ mask = "mask[id,uuid]" - if isinstance(identifier, int): + if isinstance(identifier, int) or identifier.isdigit(): this_lb = self.lbaas.getObject(id=identifier, mask=mask) elif len(identifier) == 36 and utils.UUID_RE.match(identifier): this_lb = self.lbaas.getLoadBalancer(identifier, mask=mask) From fd7b0cf2fa0e51ae22270ac9987ca2813ce01d26 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 17 Feb 2020 19:48:42 -0400 Subject: [PATCH 0042/1385] #1222 raise an exception if not found find a lbaas instance --- SoftLayer/managers/load_balancer.py | 7 +++++-- tests/managers/loadbal_tests.py | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 72b13ab46..3ffa09594 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import exceptions from SoftLayer.managers import ordering from SoftLayer import utils @@ -110,12 +111,14 @@ def get_lbaas_by_name(self, name, mask=None): :param name: Name of the LBaaS instance :param mask: - :returns: SoftLayer_Network_LBaaS_LoadBalancer or an empty dictionary if the name is not found. + :returns: SoftLayer_Network_LBaaS_LoadBalancer. """ object_filter = {'name': {'operation': name}} this_lbs = self.lbaas.getAllObjects(filter=object_filter, mask=mask) + if not this_lbs: + raise exceptions.SoftLayerError("Unable to find LBaaS with name: {}".format(name)) - return this_lbs[0] if this_lbs else {} + return this_lbs[0] def delete_lb_member(self, identifier, member_id): """Removes a member from a LBaaS instance diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index 38031204d..d8058edcc 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -8,6 +8,7 @@ them directly to the API. """ import SoftLayer +from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer import testing @@ -192,3 +193,9 @@ def test_get_lbaas_by_name(self): load_bal = self.lb_mgr.get_lbaas_by_name(name) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') self.assertIsNotNone(load_bal) + + def test_get_lbaas_by_name_fails(self): + load_bal_mock = self.set_mock('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + load_bal_mock.return_value = [] + name = 'test' + self.assertRaises(exceptions.SoftLayerError, self.lb_mgr.get_lbaas_by_name, name) From 109ea5ae3fa3438e3da443b7ae86f915e610685d Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Thu, 20 Feb 2020 15:10:47 -0600 Subject: [PATCH 0043/1385] bring in convert/refresh funcs --- SoftLayer/CLI/block/convert.py | 17 +++++++++++++++++ SoftLayer/CLI/block/refresh.py | 18 ++++++++++++++++++ SoftLayer/CLI/file/convert.py | 17 +++++++++++++++++ SoftLayer/CLI/file/refresh.py | 18 ++++++++++++++++++ SoftLayer/CLI/routes.py | 4 ++++ SoftLayer/managers/block.py | 17 +++++++++++++++++ SoftLayer/managers/file.py | 17 +++++++++++++++++ docs/cli/block.rst | 7 +++++++ 8 files changed, 115 insertions(+) create mode 100644 SoftLayer/CLI/block/convert.py create mode 100644 SoftLayer/CLI/block/refresh.py create mode 100644 SoftLayer/CLI/file/convert.py create mode 100644 SoftLayer/CLI/file/refresh.py diff --git a/SoftLayer/CLI/block/convert.py b/SoftLayer/CLI/block/convert.py new file mode 100644 index 000000000..795d3d27c --- /dev/null +++ b/SoftLayer/CLI/block/convert.py @@ -0,0 +1,17 @@ +"""Convert a dependent duplicate volume to an indepdent volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('volume_id') +@environment.pass_env +def cli(env, volume_id): + """Convert a dependent duplicate volume to an indepdent volume.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + resp = block_manager.convert_dep_dupe(volume_id) + + click.echo(resp) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py new file mode 100644 index 000000000..66a2f3c22 --- /dev/null +++ b/SoftLayer/CLI/block/refresh.py @@ -0,0 +1,18 @@ +"""Refresh a dependent duplicate volume with a snapshot from its parent.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('volume_id') +@click.argument('snapshot_id') +@environment.pass_env +def cli(env, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + resp = block_manager.refresh_dep_dupe(volume_id, snapshot_id) + + click.echo(resp) diff --git a/SoftLayer/CLI/file/convert.py b/SoftLayer/CLI/file/convert.py new file mode 100644 index 000000000..7c01d8c53 --- /dev/null +++ b/SoftLayer/CLI/file/convert.py @@ -0,0 +1,17 @@ +"""Convert a dependent duplicate volume to an indepdent volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('volume_id') +@environment.pass_env +def cli(env, volume_id): + """Convert a dependent duplicate volume to an indepdent volume.""" + file_manager = SoftLayer.FileStorageManager(env.client) + resp = file_manager.convert_dep_dupe(volume_id) + + click.echo(resp) diff --git a/SoftLayer/CLI/file/refresh.py b/SoftLayer/CLI/file/refresh.py new file mode 100644 index 000000000..c566dac91 --- /dev/null +++ b/SoftLayer/CLI/file/refresh.py @@ -0,0 +1,18 @@ +"""Refresh a dependent duplicate volume with a snapshot from its parent.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('volume_id') +@click.argument('snapshot_id') +@environment.pass_env +def cli(env, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent.""" + file_manager = SoftLayer.FileStorageManager(env.client) + resp = file_manager.refresh_dep_dupe(volume_id, snapshot_id) + + click.echo(resp) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 27bfb0b23..b908be4f5 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -106,6 +106,8 @@ ('block:volume-order', 'SoftLayer.CLI.block.order:cli'), ('block:volume-set-lun-id', 'SoftLayer.CLI.block.lun:cli'), ('block:volume-limits', 'SoftLayer.CLI.block.limit:cli'), + ('block:volume-refresh', 'SoftLayer.CLI.block.refresh:cli'), + ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), @@ -137,6 +139,8 @@ ('file:volume-modify', 'SoftLayer.CLI.file.modify:cli'), ('file:volume-order', 'SoftLayer.CLI.file.order:cli'), ('file:volume-limits', 'SoftLayer.CLI.file.limit:cli'), + ('file:volume-refresh', 'SoftLayer.CLI.file.refresh:cli'), + ('file:volume-convert', 'SoftLayer.CLI.file.convert:cli'), ('firewall', 'SoftLayer.CLI.firewall'), ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'), diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 25e6839f2..8b379513f 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -616,3 +616,20 @@ def create_or_update_lun_id(self, volume_id, lun_id): """ return self.client.call('Network_Storage', 'createOrUpdateLunId', lun_id, id=volume_id) + + def refresh_dep_dupe(self, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent. + + :param integer volume_id: The id of the volume + :param integer snapshot_id: The id of the snapshot + """ + return self.client.call('Network_Storage', 'refreshDependentDuplicate', + snapshot_id, id=volume_id) + + def convert_dep_dupe(self, volume_id): + """Convert a dependent duplicate volume to an indepdent volume. + + :param integer volume_id: The id of the volume. + """ + return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', + id=volume_id) diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 0a5214e67..2876daf98 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -527,3 +527,20 @@ def failback_from_replicant(self, volume_id): """ return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) + + def refresh_dep_dupe(self, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent. + + :param integer volume_id: The id of the volume + :param integer snapshot_id: The id of the snapshot + """ + return self.client.call('Network_Storage', 'refreshDependentDuplicate', + snapshot_id, id=volume_id) + + def convert_dep_dupe(self, volume_id): + """Convert a dependent duplicate volume to an indepdent volume. + + :param integer volume_id: The id of the volume. + """ + return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', + id=volume_id) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 851d7d84c..860872ce7 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -111,6 +111,13 @@ Block Commands :prog: block volume-limits :show-nested: +.. click:: SoftLayer.CLI.block.refresh:cli + :prog block volume-refresh + :show-nested: + +.. click:: SoftLayer.CLI.block.convert:cli + :prog block volume-convert + :show-nested: .. click:: SoftLayer.CLI.block.subnets.list:cli :prog: block subnets-list From 4007d758b43dbf7c1562200bd69210c9a690e236 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 20 Feb 2020 18:02:41 -0400 Subject: [PATCH 0044/1385] Fix the block and file storage detail. --- SoftLayer/CLI/block/detail.py | 15 ++++++++++- SoftLayer/CLI/file/detail.py | 15 ++++++++++- tests/CLI/modules/block_tests.py | 43 ++++++++++++++++++++++++++++++++ tests/CLI/modules/file_tests.py | 43 ++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 6b1b7288c..73e93298f 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -5,16 +5,29 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer import utils +def get_block_volume_id(volume_id, block_manager): + storage_list = block_manager.list_block_volumes() + for storage in storage_list: + if volume_id == storage['username']: + volume_id = storage['id'] + break + + return volume_id + + @click.command() @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): """Display details for a specified volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) - block_volume = block_manager.get_block_volume_details(volume_id) + volume_id = get_block_volume_id(volume_id, block_manager) + block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') + block_volume = block_manager.get_block_volume_details(block_volume_id) block_volume = utils.NestedDict(block_volume) table = formatting.KeyValueTable(['Name', 'Value']) diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index 02bb5c17a..25af366f2 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -5,16 +5,29 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer import utils +def get_file_volume_id(volume_id, file_manager): + storage_list = file_manager.list_file_volumes() + for storage in storage_list: + if volume_id == storage['username']: + volume_id = storage['id'] + break + + return volume_id + + @click.command() @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): """Display details for a specified volume.""" file_manager = SoftLayer.FileStorageManager(env.client) - file_volume = file_manager.get_file_volume_details(volume_id) + volume_id = get_file_volume_id(volume_id, file_manager) + file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') + file_volume = file_manager.get_file_volume_details(file_volume_id) file_volume = utils.NestedDict(file_volume) table = formatting.KeyValueTable(['Name', 'Value']) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index e59288981..42e574c48 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -98,6 +98,49 @@ def test_volume_detail(self): ] }, json.loads(result.output)) + def test_volume_detail_name_identifier(self): + result = self.run_command(['block', 'volume-detail', 'username']) + + self.assert_no_fail(result) + isinstance(json.loads(result.output)['IOPs'], float) + self.assertEqual({ + 'Username': 'username', + 'LUN Id': '2', + 'Endurance Tier': 'READHEAVY_TIER', + 'IOPs': 1000, + 'Snapshot Capacity (GB)': '10', + 'Snapshot Used (Bytes)': 1024, + 'Capacity (GB)': '20GB', + 'Target IP': '10.1.2.3', + 'Data Center': 'dal05', + 'Type': 'ENDURANCE', + 'ID': 100, + '# of Active Transactions': '1', + 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', + 'Replicant Count': '1', + 'Replication Status': 'Replicant Volume Provisioning ' + 'has completed.', + 'Replicant Volumes': [[ + {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'}, + {'Replicant ID': 'Target IP', '1784': '10.3.174.79'}, + {'Replicant ID': 'Data Center', '1784': 'wdc01'}, + {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'}, + ], [ + {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'}, + {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, + {'Replicant ID': 'Data Center', '1785': 'dal01'}, + {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, + ]], + 'Original Volume Properties': [ + {'Property': 'Original Volume Size', + 'Value': '20'}, + {'Property': 'Original Volume Name', + 'Value': 'test-original-volume-name'}, + {'Property': 'Original Snapshot Name', + 'Value': 'test-original-snapshot-name'} + ] + }, json.loads(result.output)) + def test_volume_list(self): result = self.run_command(['block', 'volume-list']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index e1d8628cb..e5e3ca556 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -163,6 +163,49 @@ def test_volume_detail(self): ] }, json.loads(result.output)) + def test_volume_detail_name_identifier(self): + result = self.run_command(['file', 'volume-detail', 'user']) + + self.assert_no_fail(result) + self.assertEqual({ + 'Username': 'username', + 'Used Space': '0B', + 'Endurance Tier': 'READHEAVY_TIER', + 'IOPs': 1000, + 'Mount Address': '127.0.0.1:/TEST', + 'Snapshot Capacity (GB)': '10', + 'Snapshot Used (Bytes)': 1024, + 'Capacity (GB)': '20GB', + 'Target IP': '10.1.2.3', + 'Data Center': 'dal05', + 'Type': 'ENDURANCE', + 'ID': 100, + '# of Active Transactions': '1', + 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', + 'Replicant Count': '1', + 'Replication Status': 'Replicant Volume Provisioning ' + 'has completed.', + 'Replicant Volumes': [[ + {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'}, + {'Replicant ID': 'Target IP', '1784': '10.3.174.79'}, + {'Replicant ID': 'Data Center', '1784': 'wdc01'}, + {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'}, + ], [ + {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'}, + {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, + {'Replicant ID': 'Data Center', '1785': 'dal01'}, + {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, + ]], + 'Original Volume Properties': [ + {'Property': 'Original Volume Size', + 'Value': '20'}, + {'Property': 'Original Volume Name', + 'Value': 'test-original-volume-name'}, + {'Property': 'Original Snapshot Name', + 'Value': 'test-original-snapshot-name'} + ] + }, json.loads(result.output)) + def test_volume_order_performance_iops_not_given(self): result = self.run_command(['file', 'volume-order', '--storage-type=performance', '--size=20', From b07e0ecd2ebb109ef3babb3df4b19edfb6706a3c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 20 Feb 2020 18:18:33 -0400 Subject: [PATCH 0045/1385] Add docstring for block and file new method. --- SoftLayer/CLI/block/detail.py | 6 ++++++ SoftLayer/CLI/file/detail.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 73e93298f..02ce0c82f 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -10,6 +10,12 @@ def get_block_volume_id(volume_id, block_manager): + """Returns the volume id. + + :param volume_id: ID of volume. + :param block_manager: Block Storage Manager. + :return: Returns the volume id. + """ storage_list = block_manager.list_block_volumes() for storage in storage_list: if volume_id == storage['username']: diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index 25af366f2..ad0393916 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -10,6 +10,12 @@ def get_file_volume_id(volume_id, file_manager): + """Returns the volume id. + + :param volume_id: ID of volume. + :param block_manager: Block Storage Manager. + :return: Returns the volume id. + """ storage_list = file_manager.list_file_volumes() for storage in storage_list: if volume_id == storage['username']: From dab368eb153110bc0eace325f4a2c701d5fd02a2 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:01:42 -0600 Subject: [PATCH 0046/1385] #1233 refactored the file/block managers to reduce duplicated code --- .../fixtures/SoftLayer_Location_Datacenter.py | 12 + SoftLayer/managers/block.py | 451 +----------------- SoftLayer/managers/file.py | 381 +-------------- SoftLayer/managers/storage.py | 415 ++++++++++++++++ SoftLayer/managers/storage_utils.py | 17 +- tests/managers/block_tests.py | 125 ++--- tests/managers/file_tests.py | 106 ++-- 7 files changed, 585 insertions(+), 922 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Location_Datacenter.py create mode 100644 SoftLayer/managers/storage.py diff --git a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py new file mode 100644 index 000000000..510fa18e7 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py @@ -0,0 +1,12 @@ +getDatacenters = [ + { + "id": 1441195, + "longName": "Dallas 10", + "name": "dal10" + }, + { + "id": 449494, + "longName": "Dallas 9", + "name": "dal09" + } +] \ No newline at end of file diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 11b998935..4ff3faa86 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -7,32 +7,27 @@ """ from SoftLayer import exceptions from SoftLayer.managers import storage_utils +from SoftLayer.managers.storage import StorageManager + from SoftLayer import utils # pylint: disable=too-many-public-methods -class BlockStorageManager(utils.IdentifierMixin, object): +class BlockStorageManager(StorageManager): """Manages SoftLayer Block Storage volumes. See product information here: http://www.softlayer.com/block-storage - - :param SoftLayer.API.BaseClient client: the client instance """ - def __init__(self, client): - self.configuration = {} - self.client = client - def list_block_volume_limit(self): """Returns a list of block volume count limit. :return: Returns a list of block volume count limit. """ - return self.client.call('Network_Storage', 'getVolumeCountLimits') + return self.get_volume_count_limits() - def list_block_volumes(self, datacenter=None, username=None, - storage_type=None, **kwargs): + def list_block_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): """Returns a list of block volumes. :param datacenter: Datacenter short name (e.g.: dal09) @@ -84,35 +79,8 @@ def get_block_volume_details(self, volume_id, **kwargs): :param kwargs: :return: Returns details about the specified volume. """ + return self.get_volume_details(volume_id, **kwargs) - if 'mask' not in kwargs: - items = [ - 'id', - 'username', - 'password', - 'capacityGb', - 'snapshotCapacityGb', - 'parentVolume.snapshotSizeBytes', - 'storageType.keyName', - 'serviceResource.datacenter[name]', - 'serviceResourceBackendIpAddress', - 'storageTierLevel', - 'provisionedIops', - 'lunId', - 'originalVolumeName', - 'originalSnapshotName', - 'originalVolumeSize', - 'activeTransactionCount', - 'activeTransactions.transactionStatus[friendlyName]', - 'replicationPartnerCount', - 'replicationStatus', - 'replicationPartners[id,username,' - 'serviceResourceBackendIpAddress,' - 'serviceResource[datacenter[name]],' - 'replicationSchedule[type[keyname]]]', - ] - kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) def get_block_volume_access_list(self, volume_id, **kwargs): """Returns a list of authorized hosts for a specified volume. @@ -121,17 +89,7 @@ def get_block_volume_access_list(self, volume_id, **kwargs): :param kwargs: :return: Returns a list of authorized hosts for a specified volume. """ - if 'mask' not in kwargs: - items = [ - 'id', - 'allowedVirtualGuests[allowedHost[credential, sourceSubnet]]', - 'allowedHardware[allowedHost[credential]]', - 'allowedSubnets[allowedHost[credential]]', - 'allowedIpAddresses[allowedHost[credential]]', - ] - kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', - id=volume_id, **kwargs) + return self.get_volume_access_list(volume_id, **kwargs) def get_block_volume_snapshot_list(self, volume_id, **kwargs): """Returns a list of snapshots for the specified volume. @@ -140,73 +98,8 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs): :param kwargs: :return: Returns a list of snapshots for the specified volume. """ - if 'mask' not in kwargs: - items = [ - 'id', - 'notes', - 'snapshotSizeBytes', - 'storageType[keyName]', - 'snapshotCreationTimestamp', - 'intervalSchedule', - 'hourlySchedule', - 'dailySchedule', - 'weeklySchedule' - ] + return self.get_volume_snapshot_list(volume_id, **kwargs) - kwargs['mask'] = ','.join(items) - - return self.client.call('Network_Storage', 'getSnapshots', - id=volume_id, **kwargs) - - def authorize_host_to_volume(self, volume_id, - hardware_ids=None, - virtual_guest_ids=None, - ip_address_ids=None, - **kwargs): - """Authorizes hosts to Block Storage Volumes - - :param volume_id: The Block volume to authorize hosts to - :param hardware_ids: A List of SoftLayer_Hardware ids - :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids - :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids - :return: Returns an array of - SoftLayer_Network_Storage_Allowed_Host objects - which now have access to the given Block volume - """ - host_templates = [] - storage_utils.populate_host_templates(host_templates, - hardware_ids, - virtual_guest_ids, - ip_address_ids, - None) - - return self.client.call('Network_Storage', 'allowAccessFromHostList', - host_templates, id=volume_id, **kwargs) - - def deauthorize_host_to_volume(self, volume_id, - hardware_ids=None, - virtual_guest_ids=None, - ip_address_ids=None, - **kwargs): - """Revokes authorization of hosts to Block Storage Volumes - - :param volume_id: The Block volume to deauthorize hosts to - :param hardware_ids: A List of SoftLayer_Hardware ids - :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids - :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids - :return: Returns an array of - SoftLayer_Network_Storage_Allowed_Host objects - which have access to the given Block volume - """ - host_templates = [] - storage_utils.populate_host_templates(host_templates, - hardware_ids, - virtual_guest_ids, - ip_address_ids, - None) - - return self.client.call('Network_Storage', 'removeAccessFromHostList', - host_templates, id=volume_id, **kwargs) def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. @@ -217,10 +110,7 @@ def assign_subnets_to_acl(self, access_id, subnet_ids): :param list subnet_ids: The ids of the subnets to be assigned :return: Returns int array of assigned subnet ids """ - return self.client.call('Network_Storage_Allowed_Host', - 'assignSubnetsToAcl', - subnet_ids, - id=access_id) + return self.client.call('Network_Storage_Allowed_Host', 'assignSubnetsToAcl', subnet_ids, id=access_id) def remove_subnets_from_acl(self, access_id, subnet_ids): """Removes subnet records from ACL for the access host. @@ -231,10 +121,7 @@ def remove_subnets_from_acl(self, access_id, subnet_ids): :param list subnet_ids: The ids of the subnets to be removed :return: Returns int array of removed subnet ids """ - return self.client.call('Network_Storage_Allowed_Host', - 'removeSubnetsFromAcl', - subnet_ids, - id=access_id) + return self.client.call('Network_Storage_Allowed_Host', 'removeSubnetsFromAcl', subnet_ids, id=access_id) def get_subnets_in_acl(self, access_id): """Returns a list of subnet records for the access host. @@ -244,148 +131,7 @@ def get_subnets_in_acl(self, access_id): :param integer access_id: id of the access host :return: Returns an array of SoftLayer_Network_Subnet objects """ - return self.client.call('Network_Storage_Allowed_Host', - 'getSubnetsInAcl', - id=access_id) - - 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. - - :param volume_id: The ID of the primary volume to be replicated - :param snapshot_schedule: The primary volume's snapshot - schedule to use for replication - :param location: The location for the ordered replicant volume - :param tier: The tier (IOPS per GB) of the primary volume - :param os_type: The OS type of the primary volume - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - block_mask = 'billingItem[activeChildren,hourlyFlag],'\ - 'storageTierLevel,osType,staasVersion,'\ - 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ - 'intervalSchedule,hourlySchedule,dailySchedule,'\ - 'weeklySchedule,storageType[keyName],provisionedIops' - block_volume = self.get_block_volume_details(volume_id, - mask=block_mask) - - if os_type is None: - if isinstance(utils.lookup(block_volume, 'osType', 'keyName'), - str): - os_type = block_volume['osType']['keyName'] - else: - raise exceptions.SoftLayerError( - "Cannot find primary volume's os-type " - "automatically; must specify manually") - - order = storage_utils.prepare_replicant_order_object( - self, snapshot_schedule, location, tier, block_volume, 'block' - ) - - order['osFormatType'] = {'keyName': os_type} - - return self.client.call('Product_Order', 'placeOrder', order) - - def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, - duplicate_size=None, duplicate_iops=None, - duplicate_tier_level=None, - duplicate_snapshot_size=None, - hourly_billing_flag=False): - """Places an order for a duplicate block volume. - - :param origin_volume_id: The ID of the origin volume to be duplicated - :param origin_snapshot_id: Origin snapshot ID to use for duplication - :param duplicate_size: Size/capacity for the duplicate volume - :param duplicate_iops: The IOPS per GB for the duplicate volume - :param duplicate_tier_level: Tier level for the duplicate volume - :param duplicate_snapshot_size: Snapshot space size for the duplicate - :param hourly_billing_flag: Billing type, monthly (False) - or hourly (True), default to monthly. - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ - 'storageType[keyName],capacityGb,originalVolumeSize,'\ - 'provisionedIops,storageTierLevel,osType[keyName],'\ - 'staasVersion,hasEncryptionAtRest' - origin_volume = self.get_block_volume_details(origin_volume_id, - mask=block_mask) - - if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), str): - os_type = origin_volume['osType']['keyName'] - else: - raise exceptions.SoftLayerError( - "Cannot find origin volume's os-type") - - order = storage_utils.prepare_duplicate_order_object( - self, origin_volume, duplicate_iops, duplicate_tier_level, - duplicate_size, duplicate_snapshot_size, 'block', - hourly_billing_flag - ) - - order['osFormatType'] = {'keyName': os_type} - - if origin_snapshot_id is not None: - order['duplicateOriginSnapshotId'] = origin_snapshot_id - - return self.client.call('Product_Order', 'placeOrder', order) - - def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None): - """Places an order for modifying an existing block volume. - - :param volume_id: The ID of the volume to be modified - :param new_size: The new size/capacity for the volume - :param new_iops: The new IOPS for the volume - :param new_tier_level: The new tier level for the volume - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - mask_items = [ - 'id', - 'billingItem', - 'storageType[keyName]', - 'capacityGb', - 'provisionedIops', - 'storageTierLevel', - 'staasVersion', - 'hasEncryptionAtRest', - ] - block_mask = ','.join(mask_items) - volume = self.get_block_volume_details(volume_id, mask=block_mask) - - order = storage_utils.prepare_modify_order_object( - self, volume, new_iops, new_tier_level, new_size - ) - - return self.client.call('Product_Order', 'placeOrder', order) - - def delete_snapshot(self, snapshot_id): - """Deletes the specified snapshot object. - - :param snapshot_id: The ID of the snapshot object to delete. - """ - return self.client.call('Network_Storage', 'deleteObject', - id=snapshot_id) + return self.client.call('Network_Storage_Allowed_Host', 'getSubnetsInAcl', id=access_id) def order_block_volume(self, storage_type, location, size, os_type, iops=None, tier_level=None, snapshot_size=None, @@ -415,182 +161,14 @@ def order_block_volume(self, storage_type, location, size, os_type, return self.client.call('Product_Order', 'placeOrder', order) - def create_snapshot(self, volume_id, notes='', **kwargs): - """Creates a snapshot on the given block volume. - - :param integer volume_id: The id of the volume - :param string notes: The notes or "name" to assign the snapshot - :return: Returns the id of the new snapshot - """ - - return self.client.call('Network_Storage', 'createSnapshot', - notes, id=volume_id, **kwargs) - - def order_snapshot_space(self, volume_id, capacity, tier, - upgrade, **kwargs): - """Orders snapshot space for the given block volume. - - :param integer volume_id: The id of the volume - :param integer capacity: The capacity to order, in GB - :param float tier: The tier level of the block volume, in IOPS per GB - :param boolean upgrade: Flag to indicate if this order is an upgrade - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - block_mask = 'id,billingItem[location,hourlyFlag],'\ - 'storageType[keyName],storageTierLevel,provisionedIops,'\ - 'staasVersion,hasEncryptionAtRest' - block_volume = self.get_block_volume_details(volume_id, - mask=block_mask, - **kwargs) - - order = storage_utils.prepare_snapshot_order_object( - self, block_volume, capacity, tier, upgrade) - - return self.client.call('Product_Order', 'placeOrder', order) - - def cancel_snapshot_space(self, volume_id, - reason='No longer needed', - immediate=False): - """Cancels snapshot space for a given volume. - - :param integer volume_id: The volume ID - :param string reason: The reason for cancellation - :param boolean immediate_flag: Cancel immediately or on anniversary date - """ - - block_volume = self.get_block_volume_details( - volume_id, - mask='mask[id,billingItem[activeChildren,hourlyFlag]]') - - if 'activeChildren' not in block_volume['billingItem']: - raise exceptions.SoftLayerError( - 'No snapshot space found to cancel') - - children_array = block_volume['billingItem']['activeChildren'] - billing_item_id = None - - for child in children_array: - if child['categoryCode'] == 'storage_snapshot_space': - billing_item_id = child['id'] - break - - if not billing_item_id: - raise exceptions.SoftLayerError( - 'No snapshot space found to cancel') - - if utils.lookup(block_volume, 'billingItem', 'hourlyFlag'): - immediate = True - - return self.client['Billing_Item'].cancelItem( - immediate, - True, - reason, - id=billing_item_id) - - def enable_snapshots(self, volume_id, schedule_type, retention_count, - minute, hour, day_of_week, **kwargs): - """Enables snapshots for a specific block volume at a given schedule - - :param integer volume_id: The id of the volume - :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' - :param integer retention_count: Number of snapshots to be kept - :param integer minute: Minute when to take snapshot - :param integer hour: Hour when to take snapshot - :param string day_of_week: Day when to take snapshot - :return: Returns whether successfully scheduled or not - """ - - return self.client.call('Network_Storage', 'enableSnapshots', - schedule_type, - retention_count, - minute, - hour, - day_of_week, - id=volume_id, - **kwargs) - - def disable_snapshots(self, volume_id, schedule_type): - """Disables snapshots for a specific block volume at a given schedule - - :param integer volume_id: The id of the volume - :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' - :return: Returns whether successfully disabled or not - """ - - return self.client.call('Network_Storage', 'disableSnapshots', - schedule_type, id=volume_id) - - def list_volume_schedules(self, volume_id): - """Lists schedules for a given volume - - :param integer volume_id: The id of the volume - :return: Returns list of schedules assigned to a given volume - """ - volume_detail = self.client.call( - 'Network_Storage', - 'getObject', - id=volume_id, - mask='schedules[type,properties[type]]') - - return utils.lookup(volume_detail, 'schedules') - - def restore_from_snapshot(self, volume_id, snapshot_id): - """Restores a specific volume from a snapshot - - :param integer volume_id: The id of the volume - :param integer snapshot_id: The id of the restore point - :return: Returns whether succesfully restored or not - """ - - return self.client.call('Network_Storage', 'restoreFromSnapshot', - snapshot_id, id=volume_id) - - def cancel_block_volume(self, volume_id, - reason='No longer needed', - immediate=False): + def cancel_block_volume(self, volume_id, reason='No longer needed', immediate=False): """Cancels the given block storage volume. :param integer volume_id: The volume ID :param string reason: The reason for cancellation :param boolean immediate_flag: Cancel immediately or on anniversary date """ - block_volume = self.get_block_volume_details( - volume_id, - mask='mask[id,billingItem[id,hourlyFlag]]') - - if 'billingItem' not in block_volume: - raise exceptions.SoftLayerError("Block Storage was already cancelled") - - billing_item_id = block_volume['billingItem']['id'] - - if utils.lookup(block_volume, 'billingItem', 'hourlyFlag'): - immediate = True - - return self.client['Billing_Item'].cancelItem( - immediate, - True, - reason, - id=billing_item_id) - - def failover_to_replicant(self, volume_id, replicant_id): - """Failover to a volume replicant. - - :param integer volume_id: The id of the volume - :param integer replicant_id: ID of replicant to failover to - :return: Returns whether failover was successful or not - """ - - return self.client.call('Network_Storage', 'failoverToReplicant', - replicant_id, id=volume_id) - - def failback_from_replicant(self, volume_id): - """Failback from a volume replicant. - - :param integer volume_id: The id of the volume - :return: Returns whether failback was successful or not - """ - - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) + return self.cancel_volume(volume_id, reason, immediate) def set_credential_password(self, access_id, password): """Sets the password for an access host @@ -609,5 +187,4 @@ def create_or_update_lun_id(self, volume_id, lun_id): :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) + return self.client.call('Network_Storage', 'createOrUpdateLunId', lun_id, id=volume_id) diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 95cf85c88..367ce559a 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -7,27 +7,23 @@ """ from SoftLayer import exceptions from SoftLayer.managers import storage_utils +from SoftLayer.managers.storage import StorageManager from SoftLayer import utils # pylint: disable=too-many-public-methods -class FileStorageManager(utils.IdentifierMixin, object): +class FileStorageManager(StorageManager): """Manages file Storage volumes.""" - def __init__(self, client): - self.configuration = {} - self.client = client - def list_file_volume_limit(self): - """Returns a list of file volume count limit. + """Returns a list of block volume count limit. - :return: Returns a list of file volume count limit. + :return: Returns a list of block volume count limit. """ - return self.client.call('Network_Storage', 'getVolumeCountLimits') + return self.get_volume_count_limits() - def list_file_volumes(self, datacenter=None, username=None, - storage_type=None, **kwargs): + def list_file_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): """Returns a list of file volumes. :param datacenter: Datacenter short name (e.g.: dal09) @@ -109,8 +105,7 @@ def get_file_volume_details(self, volume_id, **kwargs): 'replicationSchedule[type[keyname]]]', ] kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', - id=volume_id, **kwargs) + return self.get_volume_details(volume_id, **kwargs) def get_file_volume_access_list(self, volume_id, **kwargs): """Returns a list of authorized hosts for a specified volume. @@ -119,17 +114,7 @@ def get_file_volume_access_list(self, volume_id, **kwargs): :param kwargs: :return: Returns a list of authorized hosts for a specified volume. """ - if 'mask' not in kwargs: - items = [ - 'id', - 'allowedVirtualGuests[allowedHost[credential, sourceSubnet]]', - 'allowedHardware[allowedHost[credential]]', - 'allowedSubnets[allowedHost[credential]]', - 'allowedIpAddresses[allowedHost[credential]]', - ] - kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', - id=volume_id, **kwargs) + return self.get_volume_access_list(volume_id, **kwargs) def get_file_volume_snapshot_list(self, volume_id, **kwargs): """Returns a list of snapshots for the specified volume. @@ -138,195 +123,8 @@ def get_file_volume_snapshot_list(self, volume_id, **kwargs): :param kwargs: :return: Returns a list of snapshots for the specified volume. """ - if 'mask' not in kwargs: - items = [ - 'id', - 'notes', - 'snapshotSizeBytes', - 'storageType[keyName]', - 'snapshotCreationTimestamp', - 'intervalSchedule', - 'hourlySchedule', - 'dailySchedule', - 'weeklySchedule' - ] - kwargs['mask'] = ','.join(items) - - return self.client.call('Network_Storage', 'getSnapshots', - id=volume_id, **kwargs) - - def authorize_host_to_volume(self, volume_id, - hardware_ids=None, - virtual_guest_ids=None, - ip_address_ids=None, - subnet_ids=None, - **kwargs): - """Authorizes hosts to File Storage Volumes - - :param volume_id: The File volume to authorize hosts to - :param hardware_ids: A List of SoftLayer_Hardware ids - :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids - :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids - :param subnet_ids: A List of SoftLayer_Network_Subnet ids - :return: Returns an array of - SoftLayer_Network_Storage_Allowed_Host objects - which now have access to the given File volume - """ - host_templates = [] - storage_utils.populate_host_templates(host_templates, - hardware_ids, - virtual_guest_ids, - ip_address_ids, - subnet_ids) - - return self.client.call('Network_Storage', 'allowAccessFromHostList', - host_templates, id=volume_id, **kwargs) - - def deauthorize_host_to_volume(self, volume_id, - hardware_ids=None, - virtual_guest_ids=None, - ip_address_ids=None, - subnet_ids=None, - **kwargs): - """Revokes authorization of hosts to File Storage Volumes - - :param volume_id: The File volume to deauthorize hosts to - :param hardware_ids: A List of SoftLayer_Hardware ids - :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids - :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids - :param subnet_ids: A List of SoftLayer_Network_Subnet ids - :return: Returns an array of - SoftLayer_Network_Storage_Allowed_Host objects - which have access to the given File volume - """ - host_templates = [] - storage_utils.populate_host_templates(host_templates, - hardware_ids, - virtual_guest_ids, - ip_address_ids, - subnet_ids) - - return self.client.call('Network_Storage', 'removeAccessFromHostList', - host_templates, id=volume_id, **kwargs) - - def order_replicant_volume(self, volume_id, snapshot_schedule, - location, tier=None): - """Places an order for a replicant file volume. - - :param volume_id: The ID of the primary volume to be replicated - :param snapshot_schedule: The primary volume's snapshot - schedule to use for replication - :param location: The location for the ordered replicant volume - :param tier: The tier (IOPS per GB) of the primary volume - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - file_mask = 'billingItem[activeChildren,hourlyFlag],'\ - 'storageTierLevel,osType,staasVersion,'\ - 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ - 'intervalSchedule,hourlySchedule,dailySchedule,'\ - 'weeklySchedule,storageType[keyName],provisionedIops' - file_volume = self.get_file_volume_details(volume_id, - mask=file_mask) - - order = storage_utils.prepare_replicant_order_object( - self, snapshot_schedule, location, tier, file_volume, 'file' - ) - - 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 order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, - duplicate_size=None, duplicate_iops=None, - duplicate_tier_level=None, - duplicate_snapshot_size=None, - hourly_billing_flag=False): - """Places an order for a duplicate file volume. - - :param origin_volume_id: The ID of the origin volume to be duplicated - :param origin_snapshot_id: Origin snapshot ID to use for duplication - :param duplicate_size: Size/capacity for the duplicate volume - :param duplicate_iops: The IOPS per GB for the duplicate volume - :param duplicate_tier_level: Tier level for the duplicate volume - :param duplicate_snapshot_size: Snapshot space size for the duplicate - :param hourly_billing_flag: Billing type, monthly (False) - or hourly (True), default to monthly. - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ + return self.get_volume_snapshot_list(volume_id, **kwargs) - file_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ - 'storageType[keyName],capacityGb,originalVolumeSize,'\ - 'provisionedIops,storageTierLevel,'\ - 'staasVersion,hasEncryptionAtRest' - origin_volume = self.get_file_volume_details(origin_volume_id, - mask=file_mask) - - order = storage_utils.prepare_duplicate_order_object( - self, origin_volume, duplicate_iops, duplicate_tier_level, - duplicate_size, duplicate_snapshot_size, 'file', - hourly_billing_flag - ) - - if origin_snapshot_id is not None: - order['duplicateOriginSnapshotId'] = origin_snapshot_id - - return self.client.call('Product_Order', 'placeOrder', order) - - def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None): - """Places an order for modifying an existing file volume. - - :param volume_id: The ID of the volume to be modified - :param new_size: The new size/capacity for the volume - :param new_iops: The new IOPS for the volume - :param new_tier_level: The new tier level for the volume - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - mask_items = [ - 'id', - 'billingItem', - 'storageType[keyName]', - 'capacityGb', - 'provisionedIops', - 'storageTierLevel', - 'staasVersion', - 'hasEncryptionAtRest', - ] - file_mask = ','.join(mask_items) - volume = self.get_file_volume_details(volume_id, mask=file_mask) - - order = storage_utils.prepare_modify_order_object( - self, volume, new_iops, new_tier_level, new_size - ) - - return self.client.call('Product_Order', 'placeOrder', order) - - def delete_snapshot(self, snapshot_id): - """Deletes the specified snapshot object. - - :param snapshot_id: The ID of the snapshot object to delete. - """ - return self.client.call('Network_Storage', 'deleteObject', - id=snapshot_id) def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, @@ -353,132 +151,6 @@ def order_file_volume(self, storage_type, location, size, return self.client.call('Product_Order', 'placeOrder', order) - def create_snapshot(self, volume_id, notes='', **kwargs): - """Creates a snapshot on the given file volume. - - :param integer volume_id: The id of the volume - :param string notes: The notes or "name" to assign the snapshot - :return: Returns the id of the new snapshot - """ - - return self.client.call('Network_Storage', 'createSnapshot', - notes, id=volume_id, **kwargs) - - def enable_snapshots(self, volume_id, schedule_type, retention_count, minute, hour, day_of_week, **kwargs): - """Enables snapshots for a specific file volume at a given schedule - - :param integer volume_id: The id of the volume - :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' - :param integer retention_count: The number of snapshots to attempt to retain in this schedule - :param integer minute: The minute of the hour at which HOURLY, DAILY, and WEEKLY snapshots should be taken - :param integer hour: The hour of the day at which DAILY and WEEKLY snapshots should be taken - :param string|integer day_of_week: The day of the week on which WEEKLY snapshots should be taken, - either as a string ('SUNDAY') or integer ('0' is Sunday) - :return: Returns whether successfully scheduled or not - """ - - return self.client.call('Network_Storage', 'enableSnapshots', - schedule_type, - retention_count, - minute, - hour, - day_of_week, - id=volume_id, - **kwargs) - - def disable_snapshots(self, volume_id, schedule_type): - """Disables snapshots for a specific file volume at a given schedule - - :param integer volume_id: The id of the volume - :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' - :return: Returns whether successfully disabled or not - """ - - return self.client.call('Network_Storage', 'disableSnapshots', schedule_type, id=volume_id) - - def list_volume_schedules(self, volume_id): - """Lists schedules for a given volume - - :param integer volume_id: The id of the volume - :return: Returns list of schedules assigned to a given volume - """ - volume_detail = self.client.call( - 'Network_Storage', - 'getObject', - id=volume_id, - mask='schedules[type,properties[type]]') - - return utils.lookup(volume_detail, 'schedules') - - def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): - """Orders snapshot space for the given file volume. - - :param integer volume_id: The ID of the volume - :param integer capacity: The capacity to order, in GB - :param float tier: The tier level of the file volume, in IOPS per GB - :param boolean upgrade: Flag to indicate if this order is an upgrade - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - file_mask = 'id,billingItem[location,hourlyFlag],'\ - 'storageType[keyName],storageTierLevel,provisionedIops,'\ - 'staasVersion,hasEncryptionAtRest' - file_volume = self.get_file_volume_details(volume_id, - mask=file_mask, - **kwargs) - - order = storage_utils.prepare_snapshot_order_object( - self, file_volume, capacity, tier, upgrade) - - return self.client.call('Product_Order', 'placeOrder', order) - - def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate=False): - """Cancels snapshot space for a given volume. - - :param integer volume_id: The volume ID - :param string reason: The reason for cancellation - :param boolean immediate: Cancel immediately or on anniversary date - """ - - file_volume = self.get_file_volume_details( - volume_id, - mask='mask[id,billingItem[activeChildren,hourlyFlag]]') - - if 'activeChildren' not in file_volume['billingItem']: - raise exceptions.SoftLayerError( - 'No snapshot space found to cancel') - - children_array = file_volume['billingItem']['activeChildren'] - billing_item_id = None - - for child in children_array: - if child['categoryCode'] == 'storage_snapshot_space': - billing_item_id = child['id'] - break - - if not billing_item_id: - raise exceptions.SoftLayerError( - 'No snapshot space found to cancel') - - if utils.lookup(file_volume, 'billingItem', 'hourlyFlag'): - immediate = True - - return self.client['Billing_Item'].cancelItem( - immediate, - True, - reason, - id=billing_item_id) - - def restore_from_snapshot(self, volume_id, snapshot_id): - """Restores a specific volume from a snapshot - - :param integer volume_id: The ID of the volume - :param integer snapshot_id: The id of the restore point - :return: Returns whether successfully restored or not - """ - - return self.client.call('Network_Storage', 'restoreFromSnapshot', - snapshot_id, id=volume_id) - def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=False): """Cancels the given file storage volume. @@ -486,39 +158,6 @@ def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=Fal :param string reason: The reason for cancellation :param boolean immediate: Cancel immediately or on anniversary date """ - file_volume = self.get_file_volume_details( - volume_id, - mask='mask[id,billingItem[id,hourlyFlag]]') - - if 'billingItem' not in file_volume: - raise exceptions.SoftLayerError('The volume has already been canceled') - billing_item_id = file_volume['billingItem']['id'] - - if utils.lookup(file_volume, 'billingItem', 'hourlyFlag'): - immediate = True - - return self.client['Billing_Item'].cancelItem( - immediate, - True, - reason, - id=billing_item_id) - - def failover_to_replicant(self, volume_id, replicant_id): - """Failover to a volume replicant. - - :param integer volume_id: The ID of the volume - :param integer replicant_id: ID of replicant to failover to - :return: Returns whether failover was successful or not - """ + return self.cancel_volume(volume_id, reason, immediate) - return self.client.call('Network_Storage', 'failoverToReplicant', - replicant_id, id=volume_id) - - def failback_from_replicant(self, volume_id): - """Failback from a volume replicant. - - :param integer volume_id: The ID of the volume - :return: Returns whether failback was successful or not - """ - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py new file mode 100644 index 000000000..dca50a531 --- /dev/null +++ b/SoftLayer/managers/storage.py @@ -0,0 +1,415 @@ +""" + SoftLayer.storage + ~~~~~~~~~~~~~~~ + Network Storage Manager + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer import exceptions +from SoftLayer.managers import storage_utils +from SoftLayer import utils + +# pylint: disable=too-many-public-methods + + +class StorageManager(utils.IdentifierMixin, object): + """"Base class for File and Block storage managers + + Any shared code between File and Block should ideally go here. + + :param SoftLayer.API.BaseClient client: the client instance + """ + + def __init__(self, client): + self.configuration = {} + self.client = client + + def get_volume_count_limits(self): + """Returns a list of block volume count limit. + + :return: Returns a list of block volume count limit. + """ + return self.client.call('Network_Storage', 'getVolumeCountLimits') + + def get_volume_details(self, volume_id, **kwargs): + """Returns details about the specified volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Returns details about the specified volume. + """ + + if 'mask' not in kwargs: + items = [ + 'id', + 'username', + 'password', + 'capacityGb', + 'snapshotCapacityGb', + 'parentVolume.snapshotSizeBytes', + 'storageType.keyName', + 'serviceResource.datacenter[name]', + 'serviceResourceBackendIpAddress', + 'storageTierLevel', + 'provisionedIops', + 'lunId', + 'originalVolumeName', + 'originalSnapshotName', + 'originalVolumeSize', + 'activeTransactionCount', + 'activeTransactions.transactionStatus[friendlyName]', + 'replicationPartnerCount', + 'replicationStatus', + 'replicationPartners[id,username,' + 'serviceResourceBackendIpAddress,' + 'serviceResource[datacenter[name]],' + 'replicationSchedule[type[keyname]]]', + ] + kwargs['mask'] = ','.join(items) + return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) + + def get_volume_access_list(self, volume_id, **kwargs): + """Returns a list of authorized hosts for a specified volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Returns a list of authorized hosts for a specified volume. + """ + if 'mask' not in kwargs: + items = [ + 'id', + 'allowedVirtualGuests[allowedHost[credential, sourceSubnet]]', + 'allowedHardware[allowedHost[credential]]', + 'allowedSubnets[allowedHost[credential]]', + 'allowedIpAddresses[allowedHost[credential]]', + ] + kwargs['mask'] = ','.join(items) + return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) + + def get_volume_snapshot_list(self, volume_id, **kwargs): + """Returns a list of snapshots for the specified volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Returns a list of snapshots for the specified volume. + """ + if 'mask' not in kwargs: + items = [ + 'id', + 'notes', + 'snapshotSizeBytes', + 'storageType[keyName]', + 'snapshotCreationTimestamp', + 'intervalSchedule', + 'hourlySchedule', + 'dailySchedule', + 'weeklySchedule' + ] + + kwargs['mask'] = ','.join(items) + + return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs) + + def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, + ip_address_ids=None, subnet_ids=None): + """Authorizes hosts to Storage Volumes + + :param volume_id: The File volume to authorize hosts to + :param hardware_ids: A List of SoftLayer_Hardware ids + :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids + :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids + :param subnet_ids: A List of SoftLayer_Network_Subnet ids. Only use with File volumes. + :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects + which now have access to the given volume + """ + host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids, + ip_address_ids, subnet_ids) + + return self.client.call('Network_Storage', 'allowAccessFromHostList', host_templates, id=volume_id) + + def deauthorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, + ip_address_ids=None, subnet_ids=None): + """Revokes authorization of hosts to File Storage Volumes + + :param volume_id: The File volume to deauthorize hosts to + :param hardware_ids: A List of SoftLayer_Hardware ids + :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids + :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids + :param subnet_ids: A List of SoftLayer_Network_Subnet ids. Only use with File volumes + :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects + which have access to the given File volume + """ + host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids, + ip_address_ids, subnet_ids) + + return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id) + + 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 volume. + + :param volume_id: The ID of the primary volume to be replicated + :param snapshot_schedule: The primary volume's snapshot + schedule to use for replication + :param location: The location for the ordered replicant volume + :param tier: The tier (IOPS per GB) of the primary volume + :param os_type: The OS type of the primary volume + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + + block_mask = 'billingItem[activeChildren,hourlyFlag],'\ + 'storageTierLevel,osType,staasVersion,'\ + 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ + 'intervalSchedule,hourlySchedule,dailySchedule,'\ + 'weeklySchedule,storageType[keyName],provisionedIops' + block_volume = self.get_volume_details(volume_id, mask=block_mask) + + storage_class = storage_utils.block_or_file(block_volume['storageType']['keyName']) + + order = storage_utils.prepare_replicant_order_object( + self, snapshot_schedule, location, tier, block_volume, storage_class + ) + + if storage_class == 'block': + if os_type is None: + if isinstance(utils.lookup(block_volume, 'osType', 'keyName'), str): + os_type = block_volume['osType']['keyName'] + else: + raise exceptions.SoftLayerError( + "Cannot find primary volume's os-type " + "automatically; must specify manually") + order['osFormatType'] = {'keyName': os_type} + + return self.client.call('Product_Order', 'placeOrder', order) + + def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, + duplicate_size=None, duplicate_iops=None, duplicate_tier_level=None, + duplicate_snapshot_size=None, hourly_billing_flag=False): + """Places an order for a duplicate volume. + + :param origin_volume_id: The ID of the origin volume to be duplicated + :param origin_snapshot_id: Origin snapshot ID to use for duplication + :param duplicate_size: Size/capacity for the duplicate volume + :param duplicate_iops: The IOPS per GB for the duplicate volume + :param duplicate_tier_level: Tier level for the duplicate volume + :param duplicate_snapshot_size: Snapshot space size for the duplicate + :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + + block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ + 'storageType[keyName],capacityGb,originalVolumeSize,'\ + 'provisionedIops,storageTierLevel,osType[keyName],'\ + 'staasVersion,hasEncryptionAtRest' + origin_volume = self.get_volume_details(origin_volume_id, mask=block_mask) + storage_class = storage_utils.block_or_file(origin_volume['storageType']['keyName']) + + + order = storage_utils.prepare_duplicate_order_object( + self, origin_volume, duplicate_iops, duplicate_tier_level, + duplicate_size, duplicate_snapshot_size, storage_class, hourly_billing_flag + ) + + if storage_class == 'block': + if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), str): + os_type = origin_volume['osType']['keyName'] + else: + raise exceptions.SoftLayerError("Cannot find origin volume's os-type") + + order['osFormatType'] = {'keyName': os_type} + + if origin_snapshot_id is not None: + order['duplicateOriginSnapshotId'] = origin_snapshot_id + + return self.client.call('Product_Order', 'placeOrder', order) + + + def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None): + """Places an order for modifying an existing block volume. + + :param volume_id: The ID of the volume to be modified + :param new_size: The new size/capacity for the volume + :param new_iops: The new IOPS for the volume + :param new_tier_level: The new tier level for the volume + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + + mask_items = [ + 'id', + 'billingItem', + 'storageType[keyName]', + 'capacityGb', + 'provisionedIops', + 'storageTierLevel', + 'staasVersion', + 'hasEncryptionAtRest', + ] + block_mask = ','.join(mask_items) + volume = self.get_volume_details(volume_id, mask=block_mask) + + order = storage_utils.prepare_modify_order_object( + self, volume, new_iops, new_tier_level, new_size + ) + + return self.client.call('Product_Order', 'placeOrder', order) + + + def delete_snapshot(self, snapshot_id): + """Deletes the specified snapshot object. + + :param snapshot_id: The ID of the snapshot object to delete. + """ + return self.client.call('Network_Storage', 'deleteObject', id=snapshot_id) + + def create_snapshot(self, volume_id, notes='', **kwargs): + """Creates a snapshot on the given block volume. + + :param integer volume_id: The id of the volume + :param string notes: The notes or "name" to assign the snapshot + :return: Returns the id of the new snapshot + """ + return self.client.call('Network_Storage', 'createSnapshot', notes, id=volume_id, **kwargs) + + def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): + """Orders snapshot space for the given block volume. + + :param integer volume_id: The id of the volume + :param integer capacity: The capacity to order, in GB + :param float tier: The tier level of the block volume, in IOPS per GB + :param boolean upgrade: Flag to indicate if this order is an upgrade + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + object_mask = 'id,billingItem[location,hourlyFlag],'\ + 'storageType[keyName],storageTierLevel,provisionedIops,'\ + 'staasVersion,hasEncryptionAtRest' + volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs) + + order = storage_utils.prepare_snapshot_order_object(self, volume, capacity, tier, upgrade) + + return self.client.call('Product_Order', 'placeOrder', order) + + def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate=False): + """Cancels snapshot space for a given volume. + + :param integer volume_id: The volume ID + :param string reason: The reason for cancellation + :param boolean immediate_flag: Cancel immediately or on anniversary date + """ + + object_mask = 'mask[id,billingItem[activeChildren,hourlyFlag]]' + volume = self.get_volume_details(volume_id, mask=object_mask) + + if 'activeChildren' not in volume['billingItem']: + raise exceptions.SoftLayerError('No snapshot space found to cancel') + + children_array = volume['billingItem']['activeChildren'] + billing_item_id = None + + for child in children_array: + if child['categoryCode'] == 'storage_snapshot_space': + billing_item_id = child['id'] + break + + if not billing_item_id: + raise exceptions.SoftLayerError('No snapshot space found to cancel') + + if utils.lookup(volume, 'billingItem', 'hourlyFlag'): + immediate = True + + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) + + def enable_snapshots(self, volume_id, schedule_type, retention_count, minute, hour, day_of_week, **kwargs): + """Enables snapshots for a specific block volume at a given schedule + + :param integer volume_id: The id of the volume + :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' + :param integer retention_count: Number of snapshots to be kept + :param integer minute: Minute when to take snapshot + :param integer hour: Hour when to take snapshot + :param string day_of_week: Day when to take snapshot + :return: Returns whether successfully scheduled or not + """ + return self.client.call('Network_Storage', 'enableSnapshots', schedule_type, retention_count, + minute, hour, day_of_week, id=volume_id, **kwargs) + + def disable_snapshots(self, volume_id, schedule_type): + """Disables snapshots for a specific block volume at a given schedule + + :param integer volume_id: The id of the volume + :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' + :return: Returns whether successfully disabled or not + """ + return self.client.call('Network_Storage', 'disableSnapshots', schedule_type, id=volume_id) + + def list_volume_schedules(self, volume_id): + """Lists schedules for a given volume + + :param integer volume_id: The id of the volume + :return: Returns list of schedules assigned to a given volume + """ + object_mask = 'schedules[type,properties[type]]' + volume_detail = self.client.call('Network_Storage', 'getObject', id=volume_id, mask=object_mask) + + return utils.lookup(volume_detail, 'schedules') + + def restore_from_snapshot(self, volume_id, snapshot_id): + """Restores a specific volume from a snapshot + + :param integer volume_id: The id of the volume + :param integer snapshot_id: The id of the restore point + :return: Returns whether succesfully restored or not + """ + return self.client.call('Network_Storage', 'restoreFromSnapshot', snapshot_id, id=volume_id) + + def failover_to_replicant(self, volume_id, replicant_id): + """Failover to a volume replicant. + + :param integer volume_id: The id of the volume + :param integer replicant_id: ID of replicant to failover to + :return: Returns whether failover was successful or not + """ + return self.client.call('Network_Storage', 'failoverToReplicant', replicant_id, id=volume_id) + + def failback_from_replicant(self, volume_id): + """Failback from a volume replicant. + + :param integer volume_id: The id of the volume + :return: Returns whether failback was successful or not + """ + + return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) + + def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): + """Cancels the given block storage volume. + + :param integer volume_id: The volume ID + :param string reason: The reason for cancellation + :param boolean immediate_flag: Cancel immediately or on anniversary date + """ + object_mask = 'mask[id,billingItem[id,hourlyFlag]]' + volume = self.get_volume_details(volume_id, mask=object_mask) + + if 'billingItem' not in volume: + raise exceptions.SoftLayerError("Block Storage was already cancelled") + + billing_item_id = volume['billingItem']['id'] + + if utils.lookup(volume, 'billingItem', 'hourlyFlag'): + immediate = True + + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 80ec60368..97a8c14e2 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -19,19 +19,19 @@ } -def populate_host_templates(host_templates, - hardware_ids=None, +def populate_host_templates(hardware_ids=None, virtual_guest_ids=None, ip_address_ids=None, subnet_ids=None): - """Populate the given host_templates array with the IDs provided + """Returns a populated array with the IDs provided - :param host_templates: The array to which host templates will be added :param hardware_ids: A List of SoftLayer_Hardware ids :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids :param subnet_ids: A List of SoftLayer_Network_Subnet ids + :return: array of objects formatted for allowAccessFromHostList """ + host_templates = [] if hardware_ids is not None: for hardware_id in hardware_ids: host_templates.append({ @@ -59,6 +59,7 @@ def populate_host_templates(host_templates, 'objectType': 'SoftLayer_Network_Subnet', 'id': subnet_id }) + return host_templates def get_package(manager, category_code): @@ -984,6 +985,13 @@ def prepare_modify_order_object(manager, volume, new_iops, new_tier, new_size): return modify_order +def block_or_file(storage_type_keyname): + """returns either 'block' or 'file' + + :param storage_type_keyname: the Network_Storage['storageType']['keyName'] + :returns: 'block' or 'file' + """ + return 'block' if 'BLOCK_STORAGE' in storage_type_keyname else 'file' def _has_category(categories, category_code): return any( @@ -1014,3 +1022,4 @@ def _find_price_id(prices, category, restriction_type=None, restriction_value=No continue return {'id': price['id']} + diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index acb0dc6f6..400ba0e19 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -8,7 +8,11 @@ import copy import SoftLayer from SoftLayer import exceptions -from SoftLayer import fixtures +from SoftLayer.fixtures import SoftLayer_Account +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Network_Storage +from SoftLayer.fixtures import SoftLayer_Network_Storage_Allowed_Host from SoftLayer import testing @@ -83,7 +87,7 @@ def test_cancel_block_volume_billing_item_found(self): def test_get_block_volume_details(self): result = self.block.get_block_volume_details(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result) + self.assertEqual(SoftLayer_Network_Storage.getObject, result) expected_mask = 'id,' \ 'username,' \ @@ -119,7 +123,7 @@ def test_get_block_volume_details(self): def test_list_block_volumes(self): result = self.block.list_block_volumes() - self.assertEqual(fixtures.SoftLayer_Account.getIscsiNetworkStorage, + self.assertEqual(SoftLayer_Account.getIscsiNetworkStorage, result) expected_filter = { @@ -157,7 +161,7 @@ def test_list_block_volumes_with_additional_filters(self): storage_type="Endurance", username="username") - self.assertEqual(fixtures.SoftLayer_Account.getIscsiNetworkStorage, + self.assertEqual(SoftLayer_Account.getIscsiNetworkStorage, result) expected_filter = { @@ -197,7 +201,7 @@ def test_list_block_volumes_with_additional_filters(self): def test_get_block_volume_access_list(self): result = self.block.get_block_volume_access_list(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result) + self.assertEqual(SoftLayer_Network_Storage.getObject, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -207,7 +211,7 @@ def test_get_block_volume_access_list(self): def test_get_block_volume_snapshot_list(self): result = self.block.get_block_volume_snapshot_list(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getSnapshots, + self.assertEqual(SoftLayer_Network_Storage.getSnapshots, result) self.assert_called_with( @@ -218,7 +222,7 @@ def test_get_block_volume_snapshot_list(self): def test_delete_snapshot(self): result = self.block.delete_snapshot(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.deleteObject, + self.assertEqual(SoftLayer_Network_Storage.deleteObject, result) self.assert_called_with( @@ -331,7 +335,7 @@ def test_replicant_failover(self): result = self.block.failover_to_replicant(1234, 5678) self.assertEqual( - fixtures.SoftLayer_Network_Storage.failoverToReplicant, result) + SoftLayer_Network_Storage.failoverToReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failoverToReplicant', @@ -343,7 +347,7 @@ def test_replicant_failback(self): result = self.block.failback_from_replicant(1234) self.assertEqual( - fixtures.SoftLayer_Network_Storage.failbackFromReplicant, result) + SoftLayer_Network_Storage.failbackFromReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failbackFromReplicant', @@ -373,9 +377,9 @@ def test_order_block_volume_performance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -389,7 +393,7 @@ def test_order_block_volume_performance(self): service_offering='storage_as_a_service' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -418,9 +422,9 @@ def test_order_block_volume_endurance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -433,7 +437,7 @@ def test_order_block_volume_endurance(self): service_offering='storage_as_a_service' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -463,7 +467,7 @@ def test_authorize_host_to_volume(self): virtual_guest_ids=[200], ip_address_ids=[300]) - self.assertEqual(fixtures.SoftLayer_Network_Storage. + self.assertEqual(SoftLayer_Network_Storage. allowAccessFromHostList, result) self.assert_called_with( @@ -478,7 +482,7 @@ def test_deauthorize_host_to_volume(self): virtual_guest_ids=[200], ip_address_ids=[300]) - self.assertEqual(fixtures.SoftLayer_Network_Storage. + self.assertEqual(SoftLayer_Network_Storage. removeAccessFromHostList, result) self.assert_called_with( @@ -491,7 +495,7 @@ def test_assign_subnets_to_acl(self): 12345, subnet_ids=[12345678]) - self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + self.assertEqual(SoftLayer_Network_Storage_Allowed_Host. assignSubnetsToAcl, result) self.assert_called_with( @@ -504,7 +508,7 @@ def test_remove_subnets_from_acl(self): 12345, subnet_ids=[12345678]) - self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + self.assertEqual(SoftLayer_Network_Storage_Allowed_Host. removeSubnetsFromAcl, result) self.assert_called_with( @@ -515,7 +519,7 @@ def test_remove_subnets_from_acl(self): def test_get_subnets_in_acl(self): result = self.block.get_subnets_in_acl(12345) - self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + self.assertEqual(SoftLayer_Network_Storage_Allowed_Host. getSubnetsInAcl, result) self.assert_called_with( @@ -526,7 +530,7 @@ def test_get_subnets_in_acl(self): def test_create_snapshot(self): result = self.block.create_snapshot(123, 'hello world') - self.assertEqual(fixtures.SoftLayer_Network_Storage.createSnapshot, + self.assertEqual(SoftLayer_Network_Storage.createSnapshot, result) self.assert_called_with( @@ -538,7 +542,7 @@ def test_snapshot_restore(self): result = self.block.restore_from_snapshot(12345678, 87654321) self.assertEqual( - fixtures.SoftLayer_Network_Storage.restoreFromSnapshot, + SoftLayer_Network_Storage.restoreFromSnapshot, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -549,7 +553,7 @@ def test_enable_snapshots(self): result = self.block.enable_snapshots(12345678, 'WEEKLY', 10, 47, 16, 'FRIDAY') - self.assertEqual(fixtures.SoftLayer_Network_Storage.enableSnapshots, + self.assertEqual(SoftLayer_Network_Storage.enableSnapshots, result) self.assert_called_with( @@ -560,7 +564,7 @@ def test_enable_snapshots(self): def test_disable_snapshots(self): result = self.block.disable_snapshots(12345678, 'HOURLY') - self.assertEqual(fixtures.SoftLayer_Network_Storage.disableSnapshots, + self.assertEqual(SoftLayer_Network_Storage.disableSnapshots, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -571,7 +575,7 @@ def test_list_volume_schedules(self): result = self.block.list_volume_schedules(12345678) self.assertEqual( - fixtures.SoftLayer_Network_Storage.listVolumeSchedules, + SoftLayer_Network_Storage.listVolumeSchedules, result) expected_mask = 'schedules[type,properties[type]]' @@ -585,16 +589,16 @@ def test_list_volume_schedules(self): def test_order_block_snapshot_space_upgrade(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_snapshot_space(102, 20, None, True) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -615,15 +619,15 @@ def test_order_block_snapshot_space_upgrade(self): def test_order_block_snapshot_space(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_snapshot_space(102, 10, None, False) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -643,7 +647,10 @@ def test_order_block_snapshot_space(self): ) def test_order_block_replicant_os_type_not_found(self): - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_package = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock_package.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) del mock_volume['osType'] mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -665,9 +672,9 @@ def test_order_block_replicant_performance_os_type_given(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -679,7 +686,7 @@ def test_order_block_replicant_performance_os_type_given(self): os_type='XEN' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -712,15 +719,15 @@ def test_order_block_replicant_endurance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_replicant_volume(102, 'WEEKLY', 'dal09') - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -749,9 +756,9 @@ def test_order_block_replicant_endurance(self): def test_order_block_duplicate_origin_os_type_not_found(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) del mock_volume['osType'] mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -767,9 +774,9 @@ def test_order_block_duplicate_origin_os_type_not_found(self): def test_order_block_duplicate_performance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -778,7 +785,7 @@ def test_order_block_duplicate_performance_no_duplicate_snapshot(self): 102, duplicate_snapshot_size=0) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -804,9 +811,9 @@ def test_order_block_duplicate_performance_no_duplicate_snapshot(self): def test_order_block_duplicate_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -820,7 +827,7 @@ def test_order_block_duplicate_performance(self): duplicate_snapshot_size=10 ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -848,9 +855,9 @@ def test_order_block_duplicate_performance(self): def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -858,7 +865,7 @@ def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): 102, duplicate_snapshot_size=0) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -883,9 +890,9 @@ def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): def test_order_block_duplicate_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -898,7 +905,7 @@ def test_order_block_duplicate_endurance(self): duplicate_snapshot_size=10 ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -925,16 +932,16 @@ def test_order_block_duplicate_endurance(self): def test_order_block_modified_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_modified_volume(102, new_size=1000, new_iops=2000, new_tier_level=None) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', 'placeOrder', @@ -948,15 +955,15 @@ def test_order_block_modified_performance(self): def test_order_block_modified_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_modified_volume(102, new_size=1000, new_iops=None, new_tier_level=4) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', 'placeOrder', @@ -976,4 +983,4 @@ def test_setCredentialPassword(self): def test_list_block_volume_limit(self): result = self.block.list_block_volume_limit() - self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits, result) + self.assertEqual(SoftLayer_Network_Storage.getVolumeCountLimits, result) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index d6ca66c68..640c2d9f8 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -8,7 +8,11 @@ import copy import SoftLayer from SoftLayer import exceptions -from SoftLayer import fixtures +from SoftLayer.fixtures import SoftLayer_Account +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Network_Storage +from SoftLayer.fixtures import SoftLayer_Network_Storage_Allowed_Host from SoftLayer import testing @@ -49,7 +53,7 @@ def test_authorize_host_to_volume(self): ip_address_ids=[300], subnet_ids=[400]) - self.assertEqual(fixtures.SoftLayer_Network_Storage. + self.assertEqual(SoftLayer_Network_Storage. allowAccessFromHostList, result) self.assert_called_with( @@ -65,7 +69,7 @@ def test_deauthorize_host_to_volume(self): ip_address_ids=[300], subnet_ids=[400]) - self.assertEqual(fixtures.SoftLayer_Network_Storage. + self.assertEqual(SoftLayer_Network_Storage. removeAccessFromHostList, result) self.assert_called_with( @@ -84,7 +88,7 @@ def test_enable_snapshots(self): result = self.file.enable_snapshots(12345678, 'WEEKLY', 10, 47, 16, 'FRIDAY') - self.assertEqual(fixtures.SoftLayer_Network_Storage.enableSnapshots, + self.assertEqual(SoftLayer_Network_Storage.enableSnapshots, result) self.assert_called_with( @@ -95,7 +99,7 @@ def test_enable_snapshots(self): def test_disable_snapshots(self): result = self.file.disable_snapshots(12345678, 'HOURLY') - self.assertEqual(fixtures.SoftLayer_Network_Storage.disableSnapshots, + self.assertEqual(SoftLayer_Network_Storage.disableSnapshots, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -106,7 +110,7 @@ def test_snapshot_restore(self): result = self.file.restore_from_snapshot(12345678, 87654321) self.assertEqual( - fixtures.SoftLayer_Network_Storage.restoreFromSnapshot, + SoftLayer_Network_Storage.restoreFromSnapshot, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -116,7 +120,7 @@ def test_snapshot_restore(self): def test_get_file_volume_details(self): result = self.file.get_file_volume_details(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result) + self.assertEqual(SoftLayer_Network_Storage.getObject, result) expected_mask = 'id,'\ 'username,'\ @@ -153,7 +157,7 @@ def test_get_file_volume_details(self): def test_get_file_volume_snapshot_list(self): result = self.file.get_file_volume_snapshot_list(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getSnapshots, + self.assertEqual(SoftLayer_Network_Storage.getSnapshots, result) self.assert_called_with( @@ -164,7 +168,7 @@ def test_get_file_volume_snapshot_list(self): def test_create_snapshot(self): result = self.file.create_snapshot(123, 'hello world') - self.assertEqual(fixtures.SoftLayer_Network_Storage.createSnapshot, + self.assertEqual(SoftLayer_Network_Storage.createSnapshot, result) self.assert_called_with( @@ -277,7 +281,7 @@ def test_replicant_failover(self): result = self.file.failover_to_replicant(1234, 5678) self.assertEqual( - fixtures.SoftLayer_Network_Storage.failoverToReplicant, result) + SoftLayer_Network_Storage.failoverToReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failoverToReplicant', @@ -289,7 +293,7 @@ def test_replicant_failback(self): result = self.file.failback_from_replicant(1234) self.assertEqual( - fixtures.SoftLayer_Network_Storage.failbackFromReplicant, result) + SoftLayer_Network_Storage.failbackFromReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failbackFromReplicant', @@ -317,7 +321,7 @@ def test_get_replication_locations(self): def test_delete_snapshot(self): result = self.file.delete_snapshot(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.deleteObject, + self.assertEqual(SoftLayer_Network_Storage.deleteObject, result) self.assert_called_with( @@ -328,7 +332,7 @@ def test_delete_snapshot(self): def test_list_file_volumes(self): result = self.file.list_file_volumes() - self.assertEqual(fixtures.SoftLayer_Account.getNasNetworkStorage, + self.assertEqual(SoftLayer_Account.getNasNetworkStorage, result) expected_filter = { @@ -366,7 +370,7 @@ def test_list_file_volumes_with_additional_filters(self): storage_type="Endurance", username="username") - self.assertEqual(fixtures.SoftLayer_Account.getNasNetworkStorage, + self.assertEqual(SoftLayer_Account.getNasNetworkStorage, result) expected_filter = { @@ -408,9 +412,9 @@ def test_order_file_volume_performance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -423,7 +427,7 @@ def test_order_file_volume_performance(self): service_offering='storage_as_a_service' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -451,9 +455,9 @@ def test_order_file_volume_endurance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -466,7 +470,7 @@ def test_order_file_volume_endurance(self): service_offering='storage_as_a_service' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -490,16 +494,16 @@ def test_order_file_volume_endurance(self): def test_order_file_snapshot_space_upgrade(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_snapshot_space(102, 20, None, True) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -520,16 +524,16 @@ def test_order_file_snapshot_space_upgrade(self): def test_order_file_snapshot_space(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_snapshot_space(102, 10, None, False) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -553,16 +557,16 @@ def test_order_file_replicant_performance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_replicant_volume(102, 'WEEKLY', 'dal09') - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -594,16 +598,16 @@ def test_order_file_replicant_endurance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_replicant_volume(102, 'WEEKLY', 'dal09') - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -631,9 +635,9 @@ def test_order_file_replicant_endurance(self): def test_order_file_duplicate_performance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -642,7 +646,7 @@ def test_order_file_duplicate_performance_no_duplicate_snapshot(self): 102, duplicate_snapshot_size=0) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -667,8 +671,8 @@ def test_order_file_duplicate_performance_no_duplicate_snapshot(self): def test_order_file_duplicate_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -682,7 +686,7 @@ def test_order_file_duplicate_performance(self): duplicate_snapshot_size=10 ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -709,9 +713,9 @@ def test_order_file_duplicate_performance(self): def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -720,7 +724,7 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): 102, duplicate_snapshot_size=0) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -744,9 +748,9 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): def test_order_file_duplicate_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -760,7 +764,7 @@ def test_order_file_duplicate_endurance(self): duplicate_snapshot_size=10 ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -786,16 +790,16 @@ def test_order_file_duplicate_endurance(self): def test_order_file_modified_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_modified_volume(102, new_size=1000, new_iops=2000, new_tier_level=None) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', 'placeOrder', @@ -809,16 +813,16 @@ def test_order_file_modified_performance(self): def test_order_file_modified_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_modified_volume(102, new_size=1000, new_iops=None, new_tier_level=4) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', 'placeOrder', @@ -831,4 +835,4 @@ def test_order_file_modified_endurance(self): def test_list_file_volume_limit(self): result = self.file.list_file_volume_limit() - self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits, result) + self.assertEqual(SoftLayer_Network_Storage.getVolumeCountLimits, result) From ef96801cc6c55bc2253ce27b6968b49350f67861 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:19:06 -0600 Subject: [PATCH 0047/1385] style and tox fixes --- .../fixtures/SoftLayer_Location_Datacenter.py | 2 +- SoftLayer/managers/block.py | 6 +----- SoftLayer/managers/file.py | 6 +----- SoftLayer/managers/storage.py | 13 +++++-------- SoftLayer/managers/storage_utils.py | 3 ++- tests/managers/block_tests.py | 4 ++-- tests/managers/file_tests.py | 5 ++--- tests/managers/storage_utils_tests.py | 16 +++------------- 8 files changed, 17 insertions(+), 38 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py index 510fa18e7..e9aa9b48e 100644 --- a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py +++ b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py @@ -9,4 +9,4 @@ "longName": "Dallas 9", "name": "dal09" } -] \ No newline at end of file +] diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 4ff3faa86..45091c002 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -5,10 +5,8 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions -from SoftLayer.managers import storage_utils from SoftLayer.managers.storage import StorageManager - +from SoftLayer.managers import storage_utils from SoftLayer import utils # pylint: disable=too-many-public-methods @@ -81,7 +79,6 @@ def get_block_volume_details(self, volume_id, **kwargs): """ return self.get_volume_details(volume_id, **kwargs) - def get_block_volume_access_list(self, volume_id, **kwargs): """Returns a list of authorized hosts for a specified volume. @@ -100,7 +97,6 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs): """ return self.get_volume_snapshot_list(volume_id, **kwargs) - def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 367ce559a..b594209d4 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -5,9 +5,8 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions -from SoftLayer.managers import storage_utils from SoftLayer.managers.storage import StorageManager +from SoftLayer.managers import storage_utils from SoftLayer import utils # pylint: disable=too-many-public-methods @@ -125,7 +124,6 @@ def get_file_volume_snapshot_list(self, volume_id, **kwargs): """ return self.get_volume_snapshot_list(volume_id, **kwargs) - def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, service_offering='storage_as_a_service', @@ -159,5 +157,3 @@ def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=Fal :param boolean immediate: Cancel immediately or on anniversary date """ return self.cancel_volume(volume_id, reason, immediate) - - diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index dca50a531..7d4f74562 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -139,8 +139,8 @@ def deauthorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects which have access to the given File volume """ - host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids, - ip_address_ids, subnet_ids) + host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids, + ip_address_ids, subnet_ids) return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id) @@ -182,8 +182,8 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No storage_class = storage_utils.block_or_file(block_volume['storageType']['keyName']) order = storage_utils.prepare_replicant_order_object( - self, snapshot_schedule, location, tier, block_volume, storage_class - ) + self, snapshot_schedule, location, tier, block_volume, storage_class + ) if storage_class == 'block': if os_type is None: @@ -219,7 +219,6 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, origin_volume = self.get_volume_details(origin_volume_id, mask=block_mask) storage_class = storage_utils.block_or_file(origin_volume['storageType']['keyName']) - order = storage_utils.prepare_duplicate_order_object( self, origin_volume, duplicate_iops, duplicate_tier_level, duplicate_size, duplicate_snapshot_size, storage_class, hourly_billing_flag @@ -238,7 +237,6 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, return self.client.call('Product_Order', 'placeOrder', order) - def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None): """Places an order for modifying an existing block volume. @@ -268,7 +266,6 @@ def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tie return self.client.call('Product_Order', 'placeOrder', order) - def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. @@ -297,7 +294,7 @@ def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): object_mask = 'id,billingItem[location,hourlyFlag],'\ 'storageType[keyName],storageTierLevel,provisionedIops,'\ 'staasVersion,hasEncryptionAtRest' - volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs) + volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs) order = storage_utils.prepare_snapshot_order_object(self, volume, capacity, tier, upgrade) diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 97a8c14e2..021fc713f 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -985,6 +985,7 @@ def prepare_modify_order_object(manager, volume, new_iops, new_tier, new_size): return modify_order + def block_or_file(storage_type_keyname): """returns either 'block' or 'file' @@ -993,6 +994,7 @@ def block_or_file(storage_type_keyname): """ return 'block' if 'BLOCK_STORAGE' in storage_type_keyname else 'file' + def _has_category(categories, category_code): return any( True @@ -1022,4 +1024,3 @@ def _find_price_id(prices, category, restriction_type=None, restriction_value=No continue return {'id': price['id']} - diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 400ba0e19..633cd21c0 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -9,10 +9,10 @@ import SoftLayer from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Account -from SoftLayer.fixtures import SoftLayer_Product_Package -from SoftLayer.fixtures import SoftLayer_Product_Order from SoftLayer.fixtures import SoftLayer_Network_Storage from SoftLayer.fixtures import SoftLayer_Network_Storage_Allowed_Host +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 640c2d9f8..8a2db96df 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -9,10 +9,9 @@ import SoftLayer from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Account -from SoftLayer.fixtures import SoftLayer_Product_Package -from SoftLayer.fixtures import SoftLayer_Product_Order from SoftLayer.fixtures import SoftLayer_Network_Storage -from SoftLayer.fixtures import SoftLayer_Network_Storage_Allowed_Host +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing diff --git a/tests/managers/storage_utils_tests.py b/tests/managers/storage_utils_tests.py index f6934edaf..976f3749a 100644 --- a/tests/managers/storage_utils_tests.py +++ b/tests/managers/storage_utils_tests.py @@ -23,30 +23,20 @@ def set_up(self): # Tests for populate_host_templates() # --------------------------------------------------------------------- def test_populate_host_templates_no_ids_given(self): - host_templates = [] - - storage_utils.populate_host_templates(host_templates) - + host_templates = storage_utils.populate_host_templates() self.assertEqual([], host_templates) def test_populate_host_templates_empty_arrays_given(self): - host_templates = [] - - storage_utils.populate_host_templates( - host_templates, + host_templates = storage_utils.populate_host_templates( hardware_ids=[], virtual_guest_ids=[], ip_address_ids=[], subnet_ids=[] ) - self.assertEqual([], host_templates) def test_populate_host_templates(self): - host_templates = [] - - storage_utils.populate_host_templates( - host_templates, + host_templates = storage_utils.populate_host_templates( hardware_ids=[1111], virtual_guest_ids=[2222], ip_address_ids=[3333], From 1c812ded1233e9fa8230da510ad913442d4a2606 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:29:28 -0600 Subject: [PATCH 0048/1385] resolved merge conflicts with master --- SoftLayer/managers/storage.py | 9 ++++++--- tests/managers/block_tests.py | 6 +++--- tests/managers/file_tests.py | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 7d4f74562..c9768730a 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -197,9 +197,9 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No return self.client.call('Product_Order', 'placeOrder', order) - def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, - duplicate_size=None, duplicate_iops=None, duplicate_tier_level=None, - duplicate_snapshot_size=None, hourly_billing_flag=False): + def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, duplicate_size=None, + duplicate_iops=None, duplicate_tier_level=None, duplicate_snapshot_size=None, + hourly_billing_flag=False, dependent_duplicate=False): """Places an order for a duplicate volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -234,6 +234,9 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, if origin_snapshot_id is not None: order['duplicateOriginSnapshotId'] = origin_snapshot_id + if dependent_duplicate: + # if isDependentDuplicateFlag is set to ANYTHING, it is considered dependent. + order['isDependentDuplicateFlag'] = 1 return self.client.call('Product_Order', 'placeOrder', order) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 911a5f766..30a767aaf 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -855,9 +855,9 @@ def test_order_block_duplicate_performance(self): def test_order_block_duplicate_depdupe(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -872,7 +872,7 @@ def test_order_block_duplicate_depdupe(self): dependent_duplicate=True ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 2011ff327..bdfe9aafd 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -747,9 +747,9 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): def test_order_file_duplicate_depdupe(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -764,7 +764,7 @@ def test_order_file_duplicate_depdupe(self): dependent_duplicate=True ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', From 63219ac10af22c0f6f6eded39a3683487983cf06 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:51:07 -0600 Subject: [PATCH 0049/1385] Merged in #1232 and added it to the refactor --- SoftLayer/CLI/block/detail.py | 17 ---------- SoftLayer/CLI/file/detail.py | 17 ---------- SoftLayer/managers/block.py | 7 ++++ SoftLayer/managers/file.py | 7 ++++ SoftLayer/managers/storage.py | 4 +++ tests/CLI/modules/block_tests.py | 56 +++++++++----------------------- tests/CLI/modules/file_tests.py | 56 +++++++++----------------------- 7 files changed, 50 insertions(+), 114 deletions(-) diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 02ce0c82f..2e7b115e7 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -9,29 +9,12 @@ from SoftLayer import utils -def get_block_volume_id(volume_id, block_manager): - """Returns the volume id. - - :param volume_id: ID of volume. - :param block_manager: Block Storage Manager. - :return: Returns the volume id. - """ - storage_list = block_manager.list_block_volumes() - for storage in storage_list: - if volume_id == storage['username']: - volume_id = storage['id'] - break - - return volume_id - - @click.command() @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): """Display details for a specified volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) - volume_id = get_block_volume_id(volume_id, block_manager) block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') block_volume = block_manager.get_block_volume_details(block_volume_id) block_volume = utils.NestedDict(block_volume) diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index ad0393916..cea86e351 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -9,29 +9,12 @@ from SoftLayer import utils -def get_file_volume_id(volume_id, file_manager): - """Returns the volume id. - - :param volume_id: ID of volume. - :param block_manager: Block Storage Manager. - :return: Returns the volume id. - """ - storage_list = file_manager.list_file_volumes() - for storage in storage_list: - if volume_id == storage['username']: - volume_id = storage['id'] - break - - return volume_id - - @click.command() @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): """Display details for a specified volume.""" file_manager = SoftLayer.FileStorageManager(env.client) - volume_id = get_file_volume_id(volume_id, file_manager) file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') file_volume = file_manager.get_file_volume_details(file_volume_id) file_volume = utils.NestedDict(file_volume) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 45091c002..eec22a4bb 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -184,3 +184,10 @@ def create_or_update_lun_id(self, volume_id, lun_id): :return: a SoftLayer_Network_Storage_Property object """ return self.client.call('Network_Storage', 'createOrUpdateLunId', lun_id, id=volume_id) + + def _get_ids_from_username(self, username): + object_mask = "mask[id]" + results = self.list_block_volumes(username=username, mask=object_mask) + if results: + return [result['id'] for result in results] + diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index b594209d4..458ffa9aa 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -157,3 +157,10 @@ def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=Fal :param boolean immediate: Cancel immediately or on anniversary date """ return self.cancel_volume(volume_id, reason, immediate) + + def _get_ids_from_username(self, username): + object_mask = "mask[id]" + results = self.list_file_volumes(username=username, mask=object_mask) + if results: + return [result['id'] for result in results] + diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index c9768730a..7c927362c 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -23,6 +23,10 @@ class StorageManager(utils.IdentifierMixin, object): def __init__(self, client): self.configuration = {} self.client = client + self.resolvers = [self._get_ids_from_username] + + def _get_ids_from_username(self, username): + raise exceptions.SoftLayerError("Not Implemented.") def get_volume_count_limits(self): """Returns a list of block volume count limit. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 42e574c48..9e5774400 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -60,6 +60,7 @@ def test_volume_detail(self): self.assert_no_fail(result) isinstance(json.loads(result.output)['IOPs'], float) + self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=1234) self.assertEqual({ 'Username': 'username', 'LUN Id': '2', @@ -99,47 +100,22 @@ def test_volume_detail(self): }, json.loads(result.output)) def test_volume_detail_name_identifier(self): - result = self.run_command(['block', 'volume-detail', 'username']) - + result = self.run_command(['block', 'volume-detail', 'SL-12345']) + expected_filter = { + 'iscsiNetworkStorage': { + 'serviceResource': { + 'type': { + 'type': {'operation': '!~ ISCSI'} + } + }, + 'storageType': { + 'keyName': {'operation': '*= BLOCK_STORAGE'} + }, + 'username': {'operation': '_= SL-12345'}}} + + self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage', filter=expected_filter) + self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=100) self.assert_no_fail(result) - isinstance(json.loads(result.output)['IOPs'], float) - self.assertEqual({ - 'Username': 'username', - 'LUN Id': '2', - 'Endurance Tier': 'READHEAVY_TIER', - 'IOPs': 1000, - 'Snapshot Capacity (GB)': '10', - 'Snapshot Used (Bytes)': 1024, - 'Capacity (GB)': '20GB', - 'Target IP': '10.1.2.3', - 'Data Center': 'dal05', - 'Type': 'ENDURANCE', - 'ID': 100, - '# of Active Transactions': '1', - 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', - 'Replicant Count': '1', - 'Replication Status': 'Replicant Volume Provisioning ' - 'has completed.', - 'Replicant Volumes': [[ - {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'}, - {'Replicant ID': 'Target IP', '1784': '10.3.174.79'}, - {'Replicant ID': 'Data Center', '1784': 'wdc01'}, - {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'}, - ], [ - {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'}, - {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, - {'Replicant ID': 'Data Center', '1785': 'dal01'}, - {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, - ]], - 'Original Volume Properties': [ - {'Property': 'Original Volume Size', - 'Value': '20'}, - {'Property': 'Original Volume Name', - 'Value': 'test-original-volume-name'}, - {'Property': 'Original Snapshot Name', - 'Value': 'test-original-snapshot-name'} - ] - }, json.loads(result.output)) def test_volume_list(self): result = self.run_command(['block', 'volume-list']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index e5e3ca556..b37fb0c3f 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -164,47 +164,23 @@ def test_volume_detail(self): }, json.loads(result.output)) def test_volume_detail_name_identifier(self): - result = self.run_command(['file', 'volume-detail', 'user']) - + result = self.run_command(['file', 'volume-detail', 'SL-12345']) + expected_filter = { + 'nasNetworkStorage': { + 'serviceResource': { + 'type': { + 'type': {'operation': '!~ NAS'} + } + }, + 'storageType': { + 'keyName': {'operation': '*= FILE_STORAGE'} + }, + 'username': {'operation': '_= SL-12345'}}} + + self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage', filter=expected_filter) + self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=1) self.assert_no_fail(result) - self.assertEqual({ - 'Username': 'username', - 'Used Space': '0B', - 'Endurance Tier': 'READHEAVY_TIER', - 'IOPs': 1000, - 'Mount Address': '127.0.0.1:/TEST', - 'Snapshot Capacity (GB)': '10', - 'Snapshot Used (Bytes)': 1024, - 'Capacity (GB)': '20GB', - 'Target IP': '10.1.2.3', - 'Data Center': 'dal05', - 'Type': 'ENDURANCE', - 'ID': 100, - '# of Active Transactions': '1', - 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', - 'Replicant Count': '1', - 'Replication Status': 'Replicant Volume Provisioning ' - 'has completed.', - 'Replicant Volumes': [[ - {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'}, - {'Replicant ID': 'Target IP', '1784': '10.3.174.79'}, - {'Replicant ID': 'Data Center', '1784': 'wdc01'}, - {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'}, - ], [ - {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'}, - {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, - {'Replicant ID': 'Data Center', '1785': 'dal01'}, - {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, - ]], - 'Original Volume Properties': [ - {'Property': 'Original Volume Size', - 'Value': '20'}, - {'Property': 'Original Volume Name', - 'Value': 'test-original-volume-name'}, - {'Property': 'Original Snapshot Name', - 'Value': 'test-original-snapshot-name'} - ] - }, json.loads(result.output)) + def test_volume_order_performance_iops_not_given(self): result = self.run_command(['file', 'volume-order', From 77dc9368c0b13d0478a9e252e698ef83dcf5a3d1 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:57:12 -0600 Subject: [PATCH 0050/1385] tox and style fixes --- SoftLayer/managers/block.py | 2 +- SoftLayer/managers/file.py | 2 +- SoftLayer/managers/storage.py | 3 --- tests/CLI/modules/block_tests.py | 4 ++-- tests/CLI/modules/file_tests.py | 5 ++--- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index eec22a4bb..4d129d07c 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -190,4 +190,4 @@ def _get_ids_from_username(self, username): results = self.list_block_volumes(username=username, mask=object_mask) if results: return [result['id'] for result in results] - + return [] diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 458ffa9aa..ce1b951c8 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -163,4 +163,4 @@ def _get_ids_from_username(self, username): results = self.list_file_volumes(username=username, mask=object_mask) if results: return [result['id'] for result in results] - + return [] diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 7c927362c..2d45bd0db 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -25,9 +25,6 @@ def __init__(self, client): self.client = client self.resolvers = [self._get_ids_from_username] - def _get_ids_from_username(self, username): - raise exceptions.SoftLayerError("Not Implemented.") - def get_volume_count_limits(self): """Returns a list of block volume count limit. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 9e5774400..f8408dcdd 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -107,10 +107,10 @@ def test_volume_detail_name_identifier(self): 'type': { 'type': {'operation': '!~ ISCSI'} } - }, + }, 'storageType': { 'keyName': {'operation': '*= BLOCK_STORAGE'} - }, + }, 'username': {'operation': '_= SL-12345'}}} self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage', filter=expected_filter) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index b37fb0c3f..20e065940 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -171,17 +171,16 @@ def test_volume_detail_name_identifier(self): 'type': { 'type': {'operation': '!~ NAS'} } - }, + }, 'storageType': { 'keyName': {'operation': '*= FILE_STORAGE'} - }, + }, 'username': {'operation': '_= SL-12345'}}} self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage', filter=expected_filter) self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=1) self.assert_no_fail(result) - def test_volume_order_performance_iops_not_given(self): result = self.run_command(['file', 'volume-order', '--storage-type=performance', '--size=20', From c64c058d9488c35f4d95e1ad9b5628d496334c70 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 27 Feb 2020 14:31:41 -0600 Subject: [PATCH 0051/1385] a few minor unit test fixes --- tests/CLI/modules/block_tests.py | 4 +++- tests/managers/block_tests.py | 12 ++++++++++++ tests/managers/file_tests.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f8408dcdd..0dd7eac57 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -111,7 +111,9 @@ def test_volume_detail_name_identifier(self): 'storageType': { 'keyName': {'operation': '*= BLOCK_STORAGE'} }, - 'username': {'operation': '_= SL-12345'}}} + 'username': {'operation': '_= SL-12345'} + } + } self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage', filter=expected_filter) self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=100) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 30a767aaf..7febbfcf3 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1030,3 +1030,15 @@ def test_setCredentialPassword(self): def test_list_block_volume_limit(self): result = self.block.list_block_volume_limit() self.assertEqual(SoftLayer_Network_Storage.getVolumeCountLimits, result) + + def test_get_ids_from_username(self): + result = self.block._get_ids_from_username("test") + self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') + self.assertEqual([100], result) + + def test_get_ids_from_username_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getIscsiNetworkStorage') + mock.return_value = [] + result = self.block._get_ids_from_username("test") + self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') + self.assertEqual([], result) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index bdfe9aafd..dbd181228 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -879,3 +879,15 @@ def test_order_file_modified_endurance(self): def test_list_file_volume_limit(self): result = self.file.list_file_volume_limit() self.assertEqual(SoftLayer_Network_Storage.getVolumeCountLimits, result) + + def test_get_ids_from_username(self): + result = self.file._get_ids_from_username("test") + self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage') + self.assertEqual([1], result) + + def test_get_ids_from_username_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getNasNetworkStorage') + mock.return_value = [] + result = self.file._get_ids_from_username("test") + self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage') + self.assertEqual([], result) From 030bb5a91bede9df4e9ebdeca90c8e11b33243c7 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Fri, 28 Feb 2020 09:09:03 -0600 Subject: [PATCH 0052/1385] minor --- SoftLayer/managers/block.py | 6 ++---- docs/cli/file.rst | 8 ++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 8b379513f..d3c1918fe 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -623,13 +623,11 @@ def refresh_dep_dupe(self, volume_id, snapshot_id): :param integer volume_id: The id of the volume :param integer snapshot_id: The id of the snapshot """ - return self.client.call('Network_Storage', 'refreshDependentDuplicate', - snapshot_id, id=volume_id) + return self.client.call('Network_Storage', 'refreshDependentDuplicate', snapshot_id, id=volume_id) def convert_dep_dupe(self, volume_id): """Convert a dependent duplicate volume to an indepdent volume. :param integer volume_id: The id of the volume. """ - return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', - id=volume_id) + return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', id=volume_id) diff --git a/docs/cli/file.rst b/docs/cli/file.rst index ad01b0337..13cf92a61 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -99,6 +99,14 @@ File Commands :prog: file volume-limits :show-nested: +.. click:: SoftLayer.CLI.file.refresh:cli + :prog file volume-refresh + :show-nested: + +.. click:: SoftLayer.CLI.file.convert:cli + :prog file volume-convert + :show-nested: + .. click:: SoftLayer.CLI.file.snapshot.schedule_list:cli :prog: file snapshot-schedule-list :show-nested: From 73b1315e2d4f732fb0ee0cc526aedca6526a0f81 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Fri, 28 Feb 2020 10:58:50 -0600 Subject: [PATCH 0053/1385] UTs, and merge in coalesced storage branch --- .../fixtures/SoftLayer_Network_Storage.py | 9 +++++++++ SoftLayer/managers/storage.py | 15 ++++++++++++++ tests/CLI/modules/block_tests.py | 10 ++++++++++ tests/CLI/modules/file_tests.py | 10 ++++++++++ tests/managers/block_tests.py | 20 +++++++++++++++++++ tests/managers/file_tests.py | 20 +++++++++++++++++++ 6 files changed, 84 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 3c8d335e9..611e88005 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -157,6 +157,7 @@ 'storageTierLevel': 'READHEAVY_TIER', 'storageType': {'keyName': 'ENDURANCE_STORAGE'}, 'username': 'username', + 'dependentDuplicate': 1, } getSnapshots = [{ @@ -232,3 +233,11 @@ 'maximumAvailableCount': 300, 'provisionedCount': 100 } + +refreshDependentDuplicate = { + 'dependentDuplicate': 1 +} + +convertCloneDependentToIndependent = { + 'dependentDuplicate': 1 +} diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 2d45bd0db..a17322361 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -414,3 +414,18 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): immediate = True return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) + + def refresh_dep_dupe(self, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent. + + :param integer volume_id: The id of the volume + :param integer snapshot_id: The id of the snapshot + """ + return self.client.call('Network_Storage', 'refreshDependentDuplicate', snapshot_id, id=volume_id) + + def convert_dep_dupe(self, volume_id): + """Convert a dependent duplicate volume to an indepdent volume. + + :param integer volume_id: The id of the volume. + """ + return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', id=volume_id) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 0dd7eac57..b39face10 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -716,3 +716,13 @@ def test_volume_limit(self, list_mock): result = self.run_command(['block', 'volume-limits']) self.assert_no_fail(result) + + def test_dep_dupe_refresh(self): + result = self.run_command(['block', 'volume-refresh', '102', '103']) + + self.assert_no_fail(result) + + def test_dep_dupe_convert(self): + result = self.run_command(['block', 'volume-convert', '102']) + + self.assert_no_fail(result) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 20e065940..1d64f54ae 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -695,3 +695,13 @@ def test_volume_limit(self, list_mock): }] result = self.run_command(['file', 'volume-limits']) self.assert_no_fail(result) + + def test_dep_dupe_refresh(self): + result = self.run_command(['file', 'volume-refresh', '102', '103']) + + self.assert_no_fail(result) + + def test_dep_dupe_convert(self): + result = self.run_command(['file', 'volume-convert', '102']) + + self.assert_no_fail(result) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 7febbfcf3..c9731a04d 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1042,3 +1042,23 @@ def test_get_ids_from_username_empty(self): result = self.block._get_ids_from_username("test") self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') self.assertEqual([], result) + + def test_refresh_block_depdupe(self): + result = self.block.refresh_dep_dupe(123, snapshot_id=321) + self.assertEqual(SoftLayer_Network_Storage.refreshDependentDuplicate, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'refreshDependentDuplicate', + identifier=123 + ) + + def test_convert_block_depdupe(self): + result = self.block.convert_dep_dupe(123) + self.assertEqual(SoftLayer_Network_Storage.convertCloneDependentToIndependent, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'convertCloneDependentToIndependent', + identifier=123 + ) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index dbd181228..6df2b8721 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -891,3 +891,23 @@ def test_get_ids_from_username_empty(self): result = self.file._get_ids_from_username("test") self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage') self.assertEqual([], result) + + def test_refresh_file_depdupe(self): + result = self.file.refresh_dep_dupe(123, snapshot_id=321) + self.assertEqual(SoftLayer_Network_Storage.refreshDependentDuplicate, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'refreshDependentDuplicate', + identifier=123 + ) + + def test_convert_file_depdupe(self): + result = self.file.convert_dep_dupe(123) + self.assertEqual(SoftLayer_Network_Storage.convertCloneDependentToIndependent, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'convertCloneDependentToIndependent', + identifier=123 + ) From 833700ccbfc932f8c554bdf94399a1589d967d92 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Fri, 28 Feb 2020 16:17:13 -0600 Subject: [PATCH 0054/1385] spelling --- SoftLayer/CLI/block/convert.py | 4 ++-- SoftLayer/CLI/file/convert.py | 4 ++-- SoftLayer/managers/storage.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/block/convert.py b/SoftLayer/CLI/block/convert.py index 795d3d27c..a48d8926c 100644 --- a/SoftLayer/CLI/block/convert.py +++ b/SoftLayer/CLI/block/convert.py @@ -1,4 +1,4 @@ -"""Convert a dependent duplicate volume to an indepdent volume.""" +"""Convert a dependent duplicate volume to an independent volume.""" # :license: MIT, see LICENSE for more details. import click @@ -10,7 +10,7 @@ @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): - """Convert a dependent duplicate volume to an indepdent volume.""" + """Convert a dependent duplicate volume to an independent volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) resp = block_manager.convert_dep_dupe(volume_id) diff --git a/SoftLayer/CLI/file/convert.py b/SoftLayer/CLI/file/convert.py index 7c01d8c53..8558ef009 100644 --- a/SoftLayer/CLI/file/convert.py +++ b/SoftLayer/CLI/file/convert.py @@ -1,4 +1,4 @@ -"""Convert a dependent duplicate volume to an indepdent volume.""" +"""Convert a dependent duplicate volume to an independent volume.""" # :license: MIT, see LICENSE for more details. import click @@ -10,7 +10,7 @@ @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): - """Convert a dependent duplicate volume to an indepdent volume.""" + """Convert a dependent duplicate volume to an independent volume.""" file_manager = SoftLayer.FileStorageManager(env.client) resp = file_manager.convert_dep_dupe(volume_id) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index a17322361..9a8015d58 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -424,7 +424,7 @@ def refresh_dep_dupe(self, volume_id, snapshot_id): return self.client.call('Network_Storage', 'refreshDependentDuplicate', snapshot_id, id=volume_id) def convert_dep_dupe(self, volume_id): - """Convert a dependent duplicate volume to an indepdent volume. + """Convert a dependent duplicate volume to an independent volume. :param integer volume_id: The id of the volume. """ From fc787a0c077e12a57325b21df9c7c02c0b255090 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 9 Mar 2020 15:20:59 -0500 Subject: [PATCH 0055/1385] #801 added support for json filters, and json parsing for parameters --- SoftLayer/CLI/call_api.py | 59 +++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index 6e16a2a77..c1f3f3191 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -1,5 +1,6 @@ """Call arbitrary API endpoints.""" import click +import json from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions @@ -27,8 +28,7 @@ def _build_filters(_filters): if len(top_parts) == 2: break else: - raise exceptions.CLIAbort('Failed to find valid operation for: %s' - % _filter) + raise exceptions.CLIAbort('Failed to find valid operation for: %s' % _filter) key, value = top_parts current = root @@ -67,26 +67,59 @@ def _build_python_example(args, kwargs): return call_str +def _validate_filter(ctx, param, value): + """Validates a JSON style object filter""" + _filter = None + + if value: + try: + _filter = json.loads(value) + if not isinstance(_filter, dict): + raise exceptions.CLIAbort("\"{}\" should be a JSON object, but is a {} instead.". + format(_filter, type(_filter))) + except json.JSONDecodeError as e: + raise exceptions.CLIAbort("\"{}\" is not valid JSON. {}".format(value, e)) + + return _filter + +def _validate_parameters(ctx, param, value): + """Checks if value is a JSON string, and converts it to a datastructure if that is true""" + + validated_values = [] + for i, parameter in enumerate(value): + if isinstance(parameter, str): + # looks like a JSON string... + if '{' in parameter or '[' in parameter: + try: + parameter = json.loads(parameter) + except json.JSONDecodeError as e: + click.secho("{} looked like json, but wasn't valid, passing to API as is. {}".format(parameter, e), + fg='red') + validated_values.append(parameter) + return validated_values @click.command('call', short_help="Call arbitrary API endpoints.") @click.argument('service') @click.argument('method') -@click.argument('parameters', nargs=-1) +@click.argument('parameters', nargs=-1, callback=_validate_parameters) @click.option('--id', '_id', help="Init parameter") @helpers.multi_option('--filter', '-f', '_filters', - help="Object filters. This should be of the form: " - "'property=value' or 'nested.property=value'. Complex " - "filters like betweenDate are not currently supported.") + help="Object filters. This should be of the form: 'property=value' or 'nested.property=value'." + "Complex filters should use --json-filter.") @click.option('--mask', help="String-based object mask") @click.option('--limit', type=click.INT, help="Result limit") @click.option('--offset', type=click.INT, help="Result offset") @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") +@click.option('--json-filter', callback=_validate_filter, + help="A JSON string to be passed in as the object filter to the API call." + "Remember to use double quotes (\") for variable names. Can NOT be used with --filter.") @environment.pass_env def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, - output_python=False): + output_python=False, json_filter=None): """Call arbitrary API endpoints with the given SERVICE and METHOD. + For parameters that require a datatype, use a JSON string for that parameter. Example:: slcli call-api Account getObject @@ -100,12 +133,22 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, --mask=id,hostname,datacenter.name,maxCpu slcli call-api Account getVirtualGuests \\ -f 'virtualGuests.datacenter.name IN dal05,sng01' + slcli call-api Account getVirtualGuests \\ + --json-filter '{"virtualGuests":{"hostname": {"operation": "^= test"}}}' --limit=10 + slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ + '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' """ + if _filters and json_filter: + raise exceptions.CLIAbort("--filter and --json-filter cannot be used together.") + + object_filter = _build_filters(_filters) + if json_filter: + object_filter.update(json_filter) args = [service, method] + list(parameters) kwargs = { 'id': _id, - 'filter': _build_filters(_filters), + 'filter': object_filter, 'mask': mask, 'limit': limit, 'offset': offset, From f87671be97213be9769c882fc990eed71a83ed53 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 9 Mar 2020 18:50:05 -0400 Subject: [PATCH 0056/1385] Fix order place bare metal capacity restriction. --- SoftLayer/managers/ordering.py | 7 ++++++- tests/managers/ordering_tests.py | 33 ++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e86679f61..7e4e81db5 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -351,7 +351,7 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): keynames in the given package """ - mask = 'id, capacity, itemCategory, keyName, prices[categories]' + mask = 'id, description, capacity, itemCategory, keyName, prices[categories]' items = self.list_items(package_keyname, mask=mask) item_capacity = self.get_item_capacity(items, item_keynames) @@ -422,6 +422,11 @@ def get_item_capacity(self, items, item_keynames): if "TIER" in item["keyName"]: item_capacity = item['capacity'] break + if "INTEL" in item["keyName"]: + item_split = item['description'].split("(") + item_core = item_split[1].split(" ") + item_capacity = item_core[0] + break return item_capacity def get_preset_prices(self, preset): diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 045517cd8..c8a3870ae 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -309,7 +309,7 @@ def test_get_price_id_list(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -327,7 +327,7 @@ def test_get_price_id_list_no_core(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], None) - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -342,7 +342,7 @@ def test_get_price_id_list_item_not_found(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.get_price_id_list, 'PACKAGE_KEYNAME', ['ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual("Item ITEM2 does not exist for package PACKAGE_KEYNAME", str(exc)) @@ -357,7 +357,7 @@ def test_get_price_id_list_gpu_items_with_two_categories(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM1'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price2['id'], price1['id']], prices) @@ -608,7 +608,7 @@ def test_location_group_id_none(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -626,7 +626,7 @@ def test_location_groud_id_empty(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -673,11 +673,10 @@ def test_issues1067(self): { 'id': 10453, 'itemCategory': {'categoryCode': 'server'}, + "description": "Dual Intel Xeon Silver 4110 (16 Cores, 2.10 GHz)", 'keyName': 'INTEL_INTEL_XEON_4110_2_10', 'prices': [ { - 'capacityRestrictionMaximum': '2', - 'capacityRestrictionMinimum': '2', 'capacityRestrictionType': 'PROCESSOR', 'categories': [{'categoryCode': 'os'}], 'id': 201161, @@ -744,3 +743,21 @@ def test_get_item_capacity_storage(self): item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) self.assertEqual(1, int(item_capacity)) + + def test_get_item_capacity_intel(self): + + items = [{ + "capacity": "1", + "id": 6131, + "description": "Dual Intel Xeon E5-2690 v3 (24 Cores, 2.60 GHz)", + "keyName": "INTEL_XEON_2690_2_60", + }, + { + "capacity": "1", + "id": 10201, + "keyName": "GUEST_CORE_1_DEDICATED", + }] + + item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) + + self.assertEqual(24, int(item_capacity)) From bc787e4b7d351632091860111f73d42175d886ac Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 9 Mar 2020 19:06:04 -0400 Subject: [PATCH 0057/1385] Fix tox analysis 122 > 120 characters. --- tests/managers/ordering_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index c8a3870ae..39caa72f4 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -357,8 +357,8 @@ def test_get_price_id_list_gpu_items_with_two_categories(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM1'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' - 'prices[categories]') + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, ' + 'keyName, ' 'prices[categories]') self.assertEqual([price2['id'], price1['id']], prices) def test_generate_no_complex_type(self): From fb10a3586c3232fe35dcae9208b280526382236b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 11 Mar 2020 15:25:29 -0500 Subject: [PATCH 0058/1385] tox fixes for advanced filters --- SoftLayer/CLI/call_api.py | 27 +++++++++------ docs/cli/commands.rst | 8 +++++ tests/CLI/modules/call_api_tests.py | 54 +++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index c1f3f3191..7eccfd37f 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -1,7 +1,8 @@ """Call arbitrary API endpoints.""" -import click import json +import click + from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting @@ -28,7 +29,7 @@ def _build_filters(_filters): if len(top_parts) == 2: break else: - raise exceptions.CLIAbort('Failed to find valid operation for: %s' % _filter) + raise exceptions.CLIAbort('Failed to find valid operation for: %s' % _filter) key, value = top_parts current = root @@ -67,37 +68,40 @@ def _build_python_example(args, kwargs): return call_str -def _validate_filter(ctx, param, value): + +def _validate_filter(ctx, param, value): # pylint: disable=unused-argument """Validates a JSON style object filter""" _filter = None - + # print("VALUE: {}".format(value)) if value: try: _filter = json.loads(value) if not isinstance(_filter, dict): raise exceptions.CLIAbort("\"{}\" should be a JSON object, but is a {} instead.". format(_filter, type(_filter))) - except json.JSONDecodeError as e: - raise exceptions.CLIAbort("\"{}\" is not valid JSON. {}".format(value, e)) + except json.JSONDecodeError as error: + raise exceptions.CLIAbort("\"{}\" is not valid JSON. {}".format(value, error)) return _filter -def _validate_parameters(ctx, param, value): + +def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument """Checks if value is a JSON string, and converts it to a datastructure if that is true""" validated_values = [] - for i, parameter in enumerate(value): + for parameter in value: if isinstance(parameter, str): # looks like a JSON string... if '{' in parameter or '[' in parameter: try: parameter = json.loads(parameter) - except json.JSONDecodeError as e: - click.secho("{} looked like json, but wasn't valid, passing to API as is. {}".format(parameter, e), - fg='red') + except json.JSONDecodeError as error: + click.secho("{} looked like json, but was invalid, passing to API as is. {}". + format(parameter, error), fg='red') validated_values.append(parameter) return validated_values + @click.command('call', short_help="Call arbitrary API endpoints.") @click.argument('service') @click.argument('method') @@ -138,6 +142,7 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' """ + if _filters and json_filter: raise exceptions.CLIAbort("--filter and --json-filter cannot be used together.") diff --git a/docs/cli/commands.rst b/docs/cli/commands.rst index a577adcdb..c1c5bd3fb 100644 --- a/docs/cli/commands.rst +++ b/docs/cli/commands.rst @@ -3,6 +3,14 @@ Call API ======== +This function allows you to easily call any API. The format is + +`slcli call-api SoftLayer_Service method param1 param2 --id=1234 --mask="mask[id,name]"` + +Parameters should be in the order they are presented on sldn.softlayer.com. +Any complex parameters (those that link to other datatypes) should be presented as JSON strings. They need to be enclosed in single quotes (`'`), and variables and strings enclosed in double quotes (`"`). + +For example: `{"hostname":"test",ssh_keys:[{"id":1234}]}` .. click:: SoftLayer.CLI.call_api:cli :prog: call-api diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index d99c59d35..8d3f19ab2 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -158,8 +158,7 @@ def test_object_table(self): 'None': None, 'Bool': True} - result = self.run_command(['call-api', 'Service', 'method'], - fmt='table') + result = self.run_command(['call-api', 'Service', 'method'], fmt='table') self.assert_no_fail(result) # NOTE(kmcdonald): Order is not guaranteed @@ -179,8 +178,7 @@ def test_object_nested(self): result = self.run_command(['call-api', 'Service', 'method']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - {'this': {'is': [{'pretty': 'nested'}]}}) + self.assertEqual(json.loads(result.output), {'this': {'is': [{'pretty': 'nested'}]}}) def test_list(self): mock = self.set_mock('SoftLayer_Service', 'method') @@ -208,8 +206,7 @@ def test_list_table(self): 'None': None, 'Bool': True}] - result = self.run_command(['call-api', 'Service', 'method'], - fmt='table') + result = self.run_command(['call-api', 'Service', 'method'], fmt='table') self.assert_no_fail(result) self.assertEqual(result.output, @@ -224,12 +221,10 @@ def test_parameters(self): mock = self.set_mock('SoftLayer_Service', 'method') mock.return_value = {} - result = self.run_command(['call-api', 'Service', 'method', - 'arg1', '1234']) + result = self.run_command(['call-api', 'Service', 'method', 'arg1', '1234']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Service', 'method', - args=('arg1', '1234')) + self.assert_called_with('SoftLayer_Service', 'method', args=('arg1', '1234')) def test_fixture_not_implemented(self): service = 'SoftLayer_Test' @@ -264,3 +259,42 @@ def test_fixture_exception(self): self.assertIsInstance(result.exception, SoftLayerAPIError) output = '%s::%s fixture is not implemented' % (call_service, call_method) self.assertIn(output, result.exception.faultString) + + def test_json_filter_validation(self): + json_filter = '{"test":"something"}' + result = call_api._validate_filter(None, None, json_filter) + self.assertEqual(result['test'], 'something') + + # Valid JSON, but we expect objects, not simple types + with pytest.raises(exceptions.CLIAbort): + call_api._validate_filter(None, None, '"test"') + + # Invalid JSON + with pytest.raises(exceptions.CLIAbort): + call_api._validate_filter(None, None, 'test') + + # Empty Request + result = call_api._validate_filter(None, None, None) + self.assertEqual(None, result) + + def test_json_parameters_validation(self): + json_params = ('{"test":"something"}', 'String', 1234, '[{"a":"b"}]', '{funky non [ Json') + result = call_api._validate_parameters(None, None, json_params) + self.assertEqual(result[0], {"test": "something"}) + self.assertEqual(result[1], "String") + self.assertEqual(result[2], 1234) + self.assertEqual(result[3], [{"a": "b"}]) + self.assertEqual(result[4], "{funky non [ Json") + + def test_filter_with_filter(self): + result = self.run_command(['call-api', 'Account', 'getObject', '--filter=nested.property=5432', + '--json-filter={"test":"something"}']) + self.assertEqual(2, result.exit_code) + self.assertEqual(result.exception.message, "--filter and --json-filter cannot be used together.") + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_json_filter(self): + pass + result = self.run_command(['call-api', 'Account', 'getObject', '--json-filter={"test":"something"}']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getObject', filter={"test": "something"}) From 66d85aa47bab47f4e8436a2e18381ff0355fa54d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 11 Mar 2020 15:37:49 -0500 Subject: [PATCH 0059/1385] removed debug message --- SoftLayer/CLI/call_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index 7eccfd37f..c25724076 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -72,7 +72,6 @@ def _build_python_example(args, kwargs): def _validate_filter(ctx, param, value): # pylint: disable=unused-argument """Validates a JSON style object filter""" _filter = None - # print("VALUE: {}".format(value)) if value: try: _filter = json.loads(value) From 8d2901ddf39bab78f86e2b11bee7ee9652169ff5 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 13 Mar 2020 18:29:50 -0400 Subject: [PATCH 0060/1385] add image datacenter sub command --- SoftLayer/CLI/image/datacenter.py | 30 +++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/image.py | 54 +++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 SoftLayer/CLI/image/datacenter.py diff --git a/SoftLayer/CLI/image/datacenter.py b/SoftLayer/CLI/image/datacenter.py new file mode 100644 index 000000000..44232e358 --- /dev/null +++ b/SoftLayer/CLI/image/datacenter.py @@ -0,0 +1,30 @@ +"""Edit details of an image.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--add/--remove', + default=False, + help="To add or remove Datacenter") +@click.argument('locations', nargs=-1) +@environment.pass_env +def cli(env, identifier, add, locations): + """Add/Remove datacenter of an image.""" + + image_mgr = SoftLayer.ImageManager(env.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'Image template') + + if add: + result = image_mgr.add_locations(image_id, locations) + else: + result = image_mgr.remove_locations(image_id, locations) + + env.fout(result) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b908be4f5..f6edee475 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -163,6 +163,7 @@ ('image:list', 'SoftLayer.CLI.image.list:cli'), ('image:import', 'SoftLayer.CLI.image.import:cli'), ('image:export', 'SoftLayer.CLI.image.export:cli'), + ('image:datacenter', 'SoftLayer.CLI.image.datacenter:cli'), ('ipsec', 'SoftLayer.CLI.vpn.ipsec'), ('ipsec:configure', 'SoftLayer.CLI.vpn.ipsec.configure:cli'), diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 34efb36bf..66efaba74 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -7,6 +7,7 @@ """ from SoftLayer import utils +from SoftLayer.CLI import exceptions IMAGE_MASK = ('id,accountId,name,globalIdentifier,blockDevices,parentId,' 'createDate,transaction') @@ -187,3 +188,56 @@ def export_image_to_uri(self, image_id, uri, ibm_api_key=None): }, id=image_id) else: return self.vgbdtg.copyToExternalSource({'uri': uri}, id=image_id) + + def add_locations(self, image_id, location_names): + """Add available locations to an archive image template. + + :param int image_id: The ID of the image + :param location_names: Locations for the Image. + """ + locations = self.get_locations_id_list(image_id, location_names) + locations_ids = [{'id': location_id} for location_id in locations] + return self.vgbdtg.addLocations(locations_ids, id=image_id) + + def remove_locations(self, image_id, location_names): + """Remove available locations from an archive image template. + + :param int image_id: The ID of the image + :param location_names: Locations for the Image. + """ + locations = self.get_locations_id_list(image_id, location_names) + locations_ids = [{'id': location_id} for location_id in locations] + return self.vgbdtg.removeLocations(locations_ids, id=image_id) + + def get_storage_locations(self, image_id): + """Get available locations for public image storage. + + :param int image_id: The ID of the image + """ + return self.vgbdtg.getStorageLocations(id=image_id) + + def get_locations_id_list(self, image_id, location_names): + """Converts a list of location names to a list of location IDs. + + :param int image_id: The ID of the image. + :param list location_names: A list of location names strings. + :returns: A list of locations IDs associated with the given location + keynames in the image id. + """ + locations = self.get_storage_locations(image_id) + locations_ids = [] + matching_location = {} + + for location_name in location_names: + try: + for location in locations: + if location_name == location.get('name'): + matching_location = location + break + except IndexError: + raise exceptions.SoftLayerError( + "Location {} does not exist for available locations for image {}".format(location_name, + image_id)) + locations_ids.append(matching_location.get('id')) + + return locations_ids From f571572729b888ce2d5a320d59c9ab752970b44d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 16 Mar 2020 15:57:26 -0500 Subject: [PATCH 0061/1385] updated docblock for call-api --- SoftLayer/CLI/call_api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index c25724076..cbce4eccb 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -115,8 +115,9 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") @click.option('--json-filter', callback=_validate_filter, - help="A JSON string to be passed in as the object filter to the API call." - "Remember to use double quotes (\") for variable names. Can NOT be used with --filter.") + help="A JSON string to be passed in as the object filter to the API call. " + "Remember to use double quotes (\") for variable names. Can NOT be used with --filter. " + "Dont use whitespace outside of strings, or the slcli might have trouble parsing it.") @environment.pass_env def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, output_python=False, json_filter=None): @@ -137,7 +138,7 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, slcli call-api Account getVirtualGuests \\ -f 'virtualGuests.datacenter.name IN dal05,sng01' slcli call-api Account getVirtualGuests \\ - --json-filter '{"virtualGuests":{"hostname": {"operation": "^= test"}}}' --limit=10 + --json-filter '{"virtualGuests":{"hostname":{"operation":"^= test"}}}' --limit=10 slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' """ From cd8ce76db6eb69868686952d287334b1fd77d045 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 17 Mar 2020 16:05:09 -0500 Subject: [PATCH 0062/1385] #1243 refactored unit tests to use python unittest over testtools, because testtools was breaking in the latest version --- SoftLayer/CLI/deprecated.py | 14 -------------- SoftLayer/testing/__init__.py | 25 +++++++++++++++++-------- tests/CLI/deprecated_tests.py | 33 --------------------------------- 3 files changed, 17 insertions(+), 55 deletions(-) delete mode 100644 SoftLayer/CLI/deprecated.py delete mode 100644 tests/CLI/deprecated_tests.py diff --git a/SoftLayer/CLI/deprecated.py b/SoftLayer/CLI/deprecated.py deleted file mode 100644 index 0609a9246..000000000 --- a/SoftLayer/CLI/deprecated.py +++ /dev/null @@ -1,14 +0,0 @@ -""" - SoftLayer.CLI.deprecated - ~~~~~~~~~~~~~~~~~~~~~~~~ - Handles usage of the deprecated command name, 'sl'. - :license: MIT, see LICENSE for more details. -""" -import sys - - -def main(): - """Main function for the deprecated 'sl' command.""" - print("ERROR: Use the 'slcli' command instead.", file=sys.stderr) - print("> slcli %s" % ' '.join(sys.argv[1:]), file=sys.stderr) - sys.exit(-1) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index d02e60f0e..9c8b81c47 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -8,10 +8,10 @@ # pylint: disable=invalid-name import logging import os.path +import unittest from click import testing import mock -import testtools import SoftLayer from SoftLayer.CLI import core @@ -68,8 +68,7 @@ def _record_call(self, call): 'offset']: details.append('%s=%r' % (prop, getattr(call, prop))) - logging.info('%s::%s called; %s', - call.service, call.method, '; '.join(details)) + logging.info('%s::%s called; %s', call.service, call.method, '; '.join(details)) def _mock_key(service, method): @@ -77,7 +76,7 @@ def _mock_key(service, method): return '%s::%s' % (service, method) -class TestCase(testtools.TestCase): +class TestCase(unittest.TestCase): """Testcase class with PEP-8 compatible method names.""" @classmethod @@ -100,7 +99,7 @@ def tear_down(self): """Aliased from tearDown.""" def setUp(self): # NOQA - testtools.TestCase.setUp(self) + unittest.TestCase.setUp(self) self.mocks.clear() @@ -114,7 +113,7 @@ def setUp(self): # NOQA self.set_up() def tearDown(self): # NOQA - testtools.TestCase.tearDown(self) + super(TestCase, self).tearDown() self.tear_down() self.mocks.clear() @@ -141,8 +140,7 @@ def assert_called_with(self, service, method, **props): if self.calls(service, method, **props): return - raise AssertionError('%s::%s was not called with given properties: %s' - % (service, method, props)) + raise AssertionError('%s::%s was not called with given properties: %s' % (service, method, props)) def assert_no_fail(self, result): """Fail when a failing click result has an error""" @@ -170,6 +168,17 @@ def run_command(self, args=None, env=None, fixtures=True, fmt='json', stdin=None runner = testing.CliRunner() return runner.invoke(core.cli, args=args, input=stdin, obj=env or self.env) + def assertRaises(self, exception, function_callable, *args, **kwds): # pylint: disable=arguments-differ + """Converts testtools.assertRaises to unittest.assertRaises calls. + + testtools==2.4.0 require unittest2, which breaks pytest>=5.4.1 on skipTest. + But switching to just using unittest breaks assertRaises because the format is slightly different. + This basically just reformats the call so I don't have to re-write a bunch of tests. + """ + with super(TestCase, self).assertRaises(exception) as cm: + function_callable(*args, **kwds) + return cm.exception + def call_has_props(call, props): """Check if a call has matching properties of a given props dictionary.""" diff --git a/tests/CLI/deprecated_tests.py b/tests/CLI/deprecated_tests.py deleted file mode 100644 index f28025f36..000000000 --- a/tests/CLI/deprecated_tests.py +++ /dev/null @@ -1,33 +0,0 @@ -""" - SoftLayer.tests.CLI.deprecated_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -import io - -import mock - -from SoftLayer.CLI import deprecated -from SoftLayer import testing - - -class EnvironmentTests(testing.TestCase): - - def test_main(self): - - with mock.patch('sys.stderr', new=io.StringIO()) as fake_out: - ex = self.assertRaises(SystemExit, deprecated.main) - self.assertEqual(ex.code, -1) - - self.assertIn("ERROR: Use the 'slcli' command instead.", - fake_out.getvalue()) - - def test_with_args(self): - with mock.patch('sys.stderr', new=io.StringIO()) as fake_out: - with mock.patch('sys.argv', new=['sl', 'module', 'subcommand']): - ex = self.assertRaises(SystemExit, deprecated.main) - self.assertEqual(ex.code, -1) - - self.assertIn("ERROR: Use the 'slcli' command instead.", - fake_out.getvalue()) From 9a4dc533cedbb79cb8ffcbf3ecd3226119631975 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 17 Mar 2020 18:20:14 -0400 Subject: [PATCH 0063/1385] Adding image test --- SoftLayer/CLI/image/datacenter.py | 3 +- ...rtual_Guest_Block_Device_Template_Group.py | 18 ++++-- tests/CLI/modules/image_tests.py | 56 +++++++++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 tests/CLI/modules/image_tests.py diff --git a/SoftLayer/CLI/image/datacenter.py b/SoftLayer/CLI/image/datacenter.py index 44232e358..66d1f0ed0 100644 --- a/SoftLayer/CLI/image/datacenter.py +++ b/SoftLayer/CLI/image/datacenter.py @@ -5,7 +5,6 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import helpers @@ -20,7 +19,7 @@ def cli(env, identifier, add, locations): """Add/Remove datacenter of an image.""" image_mgr = SoftLayer.ImageManager(env.client) - image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'Image template') + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') if add: result = image_mgr.add_locations(image_id, locations) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py index 785ed3b05..82956c0a2 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py @@ -7,6 +7,11 @@ 'name': 'test_image', 'parentId': '', 'publicFlag': True, + 'children': [{ + 'datacenter': { + 'name': 'ams01' + } + }], }, { 'accountId': 1234, 'blockDevices': [], @@ -16,6 +21,11 @@ 'name': 'test_image2', 'parentId': '', 'publicFlag': True, + 'children': [{ + 'datacenter': { + 'name': 'ams01' + } + }], }] getObject = IMAGES[0] @@ -23,17 +33,17 @@ deleteObject = {} editObject = True setTags = True -createFromExternalSource = [{ +createFromExternalSource = { 'createDate': '2013-12-05T21:53:03-06:00', 'globalIdentifier': '0B5DEAF4-643D-46CA-A695-CECBE8832C9D', 'id': 100, 'name': 'test_image', -}] -createFromIcos = [{ +} +createFromIcos = { 'createDate': '2013-12-05T21:53:03-06:00', 'globalIdentifier': '0B5DEAF4-643D-46CA-A695-CECBE8832C9D', 'id': 100, 'name': 'test_image', -}] +} copyToExternalSource = True copyToIcos = True diff --git a/tests/CLI/modules/image_tests.py b/tests/CLI/modules/image_tests.py new file mode 100644 index 000000000..e100d5eab --- /dev/null +++ b/tests/CLI/modules/image_tests.py @@ -0,0 +1,56 @@ +""" + SoftLayer.tests.CLI.modules.image_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import json +import os.path + +import mock + +from SoftLayer.CLI import exceptions +from SoftLayer import testing + + +class ImageTests(testing.TestCase): + + def test_detail(self): + result = self.run_command(['image', 'detail', '100']) + self.assert_no_fail(result) + + def test_delete(self): + result = self.run_command(['image', 'delete', '100']) + self.assert_no_fail(result) + + def test_edit_note(self): + result = self.run_command(['image', 'edit', '100', '--note=test']) + self.assert_no_fail(result) + + def test_edit_name(self): + result = self.run_command(['image', 'edit', '100', '--name=test']) + self.assert_no_fail(result) + + def test_edit_tag(self): + result = self.run_command(['image', 'edit', '100', '--tag=test']) + self.assert_no_fail(result) + + def test_import(self): + result = self.run_command(['image', 'import', '100', 'swift://test']) + self.assert_no_fail(result) + + def test_export(self): + result = self.run_command(['image', 'export', '100', 'swift://test']) + self.assert_no_fail(result) + + def test_list(self): + result = self.run_command(['image', 'list']) + self.assert_no_fail(result) + + def test_datacenter_add(self): + result = self.run_command(['image', 'datacenter', '100', '--add', 'test']) + self.assert_no_fail(result) + + # def test_datacenter_remove(self): + # result = self.run_command(['image', 'datacenter', '--remove', 'test']) + # self.assert_no_fail(result) From 8c61232f38c1412b21f2c47dc416e32cc5138da8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Mar 2020 15:55:52 -0400 Subject: [PATCH 0064/1385] add tests for image manager and cli --- SoftLayer/CLI/image/datacenter.py | 2 +- ...rtual_Guest_Block_Device_Template_Group.py | 6 ++++ SoftLayer/managers/image.py | 19 ++++++------ tests/CLI/modules/image_tests.py | 17 +++++----- tests/managers/image_tests.py | 31 +++++++++++++++++++ 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/SoftLayer/CLI/image/datacenter.py b/SoftLayer/CLI/image/datacenter.py index 66d1f0ed0..fd59ce271 100644 --- a/SoftLayer/CLI/image/datacenter.py +++ b/SoftLayer/CLI/image/datacenter.py @@ -13,7 +13,7 @@ @click.option('--add/--remove', default=False, help="To add or remove Datacenter") -@click.argument('locations', nargs=-1) +@click.argument('locations', nargs=-1, required=True) @environment.pass_env def cli(env, identifier, add, locations): """Add/Remove datacenter of an image.""" diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py index 82956c0a2..e2e7f0e45 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py @@ -47,3 +47,9 @@ } copyToExternalSource = True copyToIcos = True +addLocations = True +removeLocations = True +getStorageLocations = [ + {'id': 265592, 'longName': 'Amsterdam 1', 'name': 'ams01', 'statusId': 2}, + {'id': 814994, 'longName': 'Amsterdam 3', 'name': 'ams03', 'statusId': 2}, +] diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 66efaba74..8a62a625b 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -6,8 +6,8 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import exceptions from SoftLayer import utils -from SoftLayer.CLI import exceptions IMAGE_MASK = ('id,accountId,name,globalIdentifier,blockDevices,parentId,' 'createDate,transaction') @@ -227,17 +227,18 @@ def get_locations_id_list(self, image_id, location_names): locations = self.get_storage_locations(image_id) locations_ids = [] matching_location = {} + output_error = "Location {} does not exist for available locations for image {}" for location_name in location_names: - try: - for location in locations: - if location_name == location.get('name'): - matching_location = location - break - except IndexError: + for location in locations: + if location_name == location.get('name'): + matching_location = location + break + if matching_location.get('id') is None: raise exceptions.SoftLayerError( - "Location {} does not exist for available locations for image {}".format(location_name, - image_id)) + output_error.format(location_name, image_id) + ) + locations_ids.append(matching_location.get('id')) return locations_ids diff --git a/tests/CLI/modules/image_tests.py b/tests/CLI/modules/image_tests.py index e100d5eab..9305cc4ba 100644 --- a/tests/CLI/modules/image_tests.py +++ b/tests/CLI/modules/image_tests.py @@ -4,12 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import json -import os.path -import mock - -from SoftLayer.CLI import exceptions from SoftLayer import testing @@ -48,9 +43,13 @@ def test_list(self): self.assert_no_fail(result) def test_datacenter_add(self): - result = self.run_command(['image', 'datacenter', '100', '--add', 'test']) + result = self.run_command(['image', 'datacenter', '100', '--add', 'ams01']) + self.assert_no_fail(result) + + def test_datacenter_remove(self): + result = self.run_command(['image', 'datacenter', '100', '--remove', 'ams01']) self.assert_no_fail(result) - # def test_datacenter_remove(self): - # result = self.run_command(['image', 'datacenter', '--remove', 'test']) - # self.assert_no_fail(result) + def test_datacenter_remove_fails(self): + result = self.run_command(['image', 'datacenter', '100', '--remove']) + self.assertEqual(2, result.exit_code) diff --git a/tests/managers/image_tests.py b/tests/managers/image_tests.py index b36deea75..6f88689b4 100644 --- a/tests/managers/image_tests.py +++ b/tests/managers/image_tests.py @@ -6,6 +6,7 @@ """ import SoftLayer +from SoftLayer import exceptions from SoftLayer import testing IMAGE_SERVICE = 'SoftLayer_Virtual_Guest_Block_Device_Template_Group' @@ -192,3 +193,33 @@ def test_export_image_cos(self): 'copyToIcos', args=({'uri': 'cos://someuri', 'ibmApiKey': 'someApiKey'},), identifier=1234) + + def test_add_locations_image(self): + locations = ['ams01'] + self.image.add_locations(100, locations) + + self.assert_called_with(IMAGE_SERVICE, 'addLocations', identifier=100) + + def test_add_locations_fail(self): + locations = ['test'] + self.assertRaises( + exceptions.SoftLayerError, + self.image.add_locations, + 100, + locations + ) + + def test_remove_locations_image(self): + locations = ['ams01'] + self.image.remove_locations(100, locations) + + self.assert_called_with(IMAGE_SERVICE, 'removeLocations', identifier=100) + + def test_get_locations_id_fails(self): + locations = ['test'] + self.assertRaises( + exceptions.SoftLayerError, + self.image.get_locations_id_list, + 100, + locations + ) From 953ce15f2a3b2ffdc0e27d95afbe4f8cda2cdbfd Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 23 Mar 2020 17:18:30 -0400 Subject: [PATCH 0065/1385] set default behavior to add datacenters --- SoftLayer/CLI/image/datacenter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/image/datacenter.py b/SoftLayer/CLI/image/datacenter.py index fd59ce271..8d59c6dbe 100644 --- a/SoftLayer/CLI/image/datacenter.py +++ b/SoftLayer/CLI/image/datacenter.py @@ -10,8 +10,7 @@ @click.command() @click.argument('identifier') -@click.option('--add/--remove', - default=False, +@click.option('--add/--remove', default=True, help="To add or remove Datacenter") @click.argument('locations', nargs=-1, required=True) @environment.pass_env From 212472ffc4667f535755bea2d0e69a276f038888 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 23 Mar 2020 18:36:44 -0400 Subject: [PATCH 0066/1385] improve gather list of locations --- SoftLayer/managers/image.py | 23 +++++++++-------------- tests/managers/image_tests.py | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 8a62a625b..d30b05305 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -195,9 +195,8 @@ def add_locations(self, image_id, location_names): :param int image_id: The ID of the image :param location_names: Locations for the Image. """ - locations = self.get_locations_id_list(image_id, location_names) - locations_ids = [{'id': location_id} for location_id in locations] - return self.vgbdtg.addLocations(locations_ids, id=image_id) + locations = self.get_locations_list(image_id, location_names) + return self.vgbdtg.addLocations(locations, id=image_id) def remove_locations(self, image_id, location_names): """Remove available locations from an archive image template. @@ -205,9 +204,8 @@ def remove_locations(self, image_id, location_names): :param int image_id: The ID of the image :param location_names: Locations for the Image. """ - locations = self.get_locations_id_list(image_id, location_names) - locations_ids = [{'id': location_id} for location_id in locations] - return self.vgbdtg.removeLocations(locations_ids, id=image_id) + locations = self.get_locations_list(image_id, location_names) + return self.vgbdtg.removeLocations(locations, id=image_id) def get_storage_locations(self, image_id): """Get available locations for public image storage. @@ -216,13 +214,12 @@ def get_storage_locations(self, image_id): """ return self.vgbdtg.getStorageLocations(id=image_id) - def get_locations_id_list(self, image_id, location_names): - """Converts a list of location names to a list of location IDs. + def get_locations_list(self, image_id, location_names): + """Converts a list of location names to a list of locations. :param int image_id: The ID of the image. :param list location_names: A list of location names strings. - :returns: A list of locations IDs associated with the given location - keynames in the image id. + :returns: A list of locations associated with the given location names in the image. """ locations = self.get_storage_locations(image_id) locations_ids = [] @@ -235,10 +232,8 @@ def get_locations_id_list(self, image_id, location_names): matching_location = location break if matching_location.get('id') is None: - raise exceptions.SoftLayerError( - output_error.format(location_name, image_id) - ) + raise exceptions.SoftLayerError(output_error.format(location_name, image_id)) - locations_ids.append(matching_location.get('id')) + locations_ids.append(matching_location) return locations_ids diff --git a/tests/managers/image_tests.py b/tests/managers/image_tests.py index 6f88689b4..e8d585aca 100644 --- a/tests/managers/image_tests.py +++ b/tests/managers/image_tests.py @@ -219,7 +219,7 @@ def test_get_locations_id_fails(self): locations = ['test'] self.assertRaises( exceptions.SoftLayerError, - self.image.get_locations_id_list, + self.image.get_locations_list, 100, locations ) From c1f55e73366da4d6f029cccd96cd282cce422faf Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 24 Mar 2020 09:15:53 -0400 Subject: [PATCH 0067/1385] fix the issue 887 --- SoftLayer/CLI/ticket/__init__.py | 8 ++- SoftLayer/CLI/ticket/create.py | 2 +- SoftLayer/CLI/ticket/detail.py | 5 +- tests/CLI/modules/ticket_tests.py | 107 +++++------------------------- 4 files changed, 25 insertions(+), 97 deletions(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index 9886aa686..b08663322 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -1,10 +1,10 @@ """Support tickets.""" import click +import re from SoftLayer.CLI import formatting - TEMPLATE_MSG = "***** SoftLayer Ticket Content ******" # https://softlayer.github.io/reference/services/SoftLayer_Ticket_Priority/getPriorities/ @@ -17,7 +17,7 @@ ] -def get_ticket_results(mgr, ticket_id, update_count=1): +def get_ticket_results(mgr, ticket_id, is_json, update_count=1): """Get output about a ticket. :param integer id: the ticket ID @@ -64,6 +64,8 @@ def get_ticket_results(mgr, ticket_id, update_count=1): # NOTE(kmcdonald): Windows new-line characters need to be stripped out wrapped_entry += click.wrap_text(update['entry'].replace('\r', '')) + if is_json: + if '\n' in wrapped_entry: + wrapped_entry = re.sub(r"(? Date: Tue, 24 Mar 2020 09:50:45 -0400 Subject: [PATCH 0068/1385] fix the coverage test --- tests/CLI/modules/ticket_tests.py | 97 ++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 46d957f81..dcfa44968 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -1,13 +1,15 @@ """ SoftLayer.tests.CLI.modules.ticket_tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :license: MIT, see LICENSE for more details. """ import json import mock from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import ticket +from SoftLayer.managers import TicketManager from SoftLayer import testing @@ -40,8 +42,8 @@ def test_detail(self): 'status': 'Closed', 'title': 'Cloud Instance Cancellation - 08/01/13', 'update 1': 'a bot says something', - 'update 2': 'By John Smith user says something', - 'update 3': 'By emp1 (Employee) employee says something', + 'update 2': 'By John Smith\nuser says something', + 'update 3': 'By emp1 (Employee)\nemployee says something', } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), expected) @@ -209,6 +211,95 @@ def test_ticket_upload_no_name(self): "data": b"ticket attached data"},), identifier=1) + def test_ticket_upload(self): + result = self.run_command(['ticket', 'upload', '1', + '--path=tests/resources/attachment_upload', + '--name=a_file_name']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Ticket', + 'addAttachedFile', + args=({"filename": "a_file_name", + "data": b"ticket attached data"},), + identifier=1) + + def test_init_ticket_results(self): + ticket_mgr = TicketManager(self.client) + ticket_table = ticket.get_ticket_results(ticket_mgr, 100) + self.assert_called_with('SoftLayer_Ticket', 'getObject', identifier=100) + self.assertIsInstance(ticket_table, formatting.KeyValueTable) + + ticket_object = ticket_table.to_python() + self.assertEqual('No Priority', ticket_object['priority']) + self.assertEqual(100, ticket_object['id']) + + def test_init_ticket_results_asigned_user(self): + mock = self.set_mock('SoftLayer_Ticket', 'getObject') + mock.return_value = { + "serviceProviderResourceId": "CS12345", + "id": 100, + "title": "Simple Title", + "priority": 1, + "assignedUser": { + "firstName": "Test", + "lastName": "User" + }, + "status": { + "name": "Closed" + }, + "createDate": "2013-08-01T14:14:04-07:00", + "lastEditDate": "2013-08-01T14:16:47-07:00", + "updates": [{'entry': 'a bot says something'}] + } + + ticket_mgr = TicketManager(self.client) + ticket_table = ticket.get_ticket_results(ticket_mgr, 100) + self.assert_called_with('SoftLayer_Ticket', 'getObject', identifier=100) + self.assertIsInstance(ticket_table, formatting.KeyValueTable) + + ticket_object = ticket_table.to_python() + self.assertEqual('Severity 1 - Critical Impact / Service Down', ticket_object['priority']) + self.assertEqual('Test User', ticket_object['user']) + + def test_ticket_summary(self): + mock = self.set_mock('SoftLayer_Account', 'getObject') + mock.return_value = { + 'openTicketCount': 1, + 'closedTicketCount': 2, + 'openBillingTicketCount': 3, + 'openOtherTicketCount': 4, + 'openSalesTicketCount': 5, + 'openSupportTicketCount': 6, + 'openAccountingTicketCount': 7 + } + expected = [ + {'Status': 'Open', + 'count': [ + {'Type': 'Accounting', 'count': 7}, + {'Type': 'Billing', 'count': 3}, + {'Type': 'Sales', 'count': 5}, + {'Type': 'Support', 'count': 6}, + {'Type': 'Other', 'count': 4}, + {'Type': 'Total', 'count': 1}]}, + {'Status': 'Closed', 'count': 2} + ] + result = self.run_command(['ticket', 'summary']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getObject') + self.assertEqual(expected, json.loads(result.output)) + + def test_ticket_update(self): + result = self.run_command(['ticket', 'update', '100', '--body=Testing']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing'},), identifier=100) + + @mock.patch('click.edit') + def test_ticket_update_no_body(self, edit_mock): + edit_mock.return_value = 'Testing1' + result = self.run_command(['ticket', 'update', '100']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) + def test_ticket_json(self): result = self.run_command(['--format=json', 'ticket', 'detail', '1']) expected = {'Case_Number': 'CS123456', From 59b921f41a10ed0ec78e391094e07d06efede89d Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 24 Mar 2020 10:11:08 -0400 Subject: [PATCH 0069/1385] fix the coverage test --- tests/CLI/modules/ticket_tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index dcfa44968..a9e305c19 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -42,8 +42,8 @@ def test_detail(self): 'status': 'Closed', 'title': 'Cloud Instance Cancellation - 08/01/13', 'update 1': 'a bot says something', - 'update 2': 'By John Smith\nuser says something', - 'update 3': 'By emp1 (Employee)\nemployee says something', + 'update 2': 'By John Smith user says something', + 'update 3': 'By emp1 (Employee) employee says something', } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), expected) @@ -225,7 +225,7 @@ def test_ticket_upload(self): def test_init_ticket_results(self): ticket_mgr = TicketManager(self.client) - ticket_table = ticket.get_ticket_results(ticket_mgr, 100) + ticket_table = ticket.get_ticket_results(ticket_mgr, False,100) self.assert_called_with('SoftLayer_Ticket', 'getObject', identifier=100) self.assertIsInstance(ticket_table, formatting.KeyValueTable) @@ -253,7 +253,7 @@ def test_init_ticket_results_asigned_user(self): } ticket_mgr = TicketManager(self.client) - ticket_table = ticket.get_ticket_results(ticket_mgr, 100) + ticket_table = ticket.get_ticket_results(ticket_mgr, False, 100) self.assert_called_with('SoftLayer_Ticket', 'getObject', identifier=100) self.assertIsInstance(ticket_table, formatting.KeyValueTable) From fbda3640cdd3524816c776e9a068539ac3c1b099 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 11:33:23 -0400 Subject: [PATCH 0070/1385] Feature vs storage details. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/storage.py | 53 ++++++++ SoftLayer/fixtures/SoftLayer_Account.py | 16 +++ .../SoftLayer_Network_Storage_Iscsi.py | 23 ++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 68 ++++++++++ SoftLayer/managers/vs.py | 28 ++++ tests/CLI/modules/vs/vs_tests.py | 6 + tests/managers/vs/vs_tests.py | 127 ++++++++++++++++++ 8 files changed, 322 insertions(+) create mode 100644 SoftLayer/CLI/virt/storage.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f6edee475..abeae28fe 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -36,6 +36,7 @@ ('virtual:ready', 'SoftLayer.CLI.virt.ready:cli'), ('virtual:reboot', 'SoftLayer.CLI.virt.power:reboot'), ('virtual:reload', 'SoftLayer.CLI.virt.reload:cli'), + ('virtual:storage', 'SoftLayer.CLI.virt.storage:cli'), ('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'), ('virtual:usage', 'SoftLayer.CLI.virt.usage:cli'), ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py new file mode 100644 index 000000000..c88991a2c --- /dev/null +++ b/SoftLayer/CLI/virt/storage.py @@ -0,0 +1,53 @@ +"""Get storage details for a virtual server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get storage details for a virtual server. + """ + vsi = SoftLayer.VSManager(env.client) + vsi_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') + iscsi_storage_data = vsi.get_storage_details(vsi_id, "ISCSI") + nas_storage_data = vsi.get_storage_details(vsi_id, "NAS") + storage_credentials = vsi.get_storage_credentials(vsi_id) + portable_storage = vsi.get_portable_storage(vsi_id) + + table_credentials = formatting.Table(['Username', 'Password', 'IQN'], title="Block Storage Details \n iSCSI") + if storage_credentials: + table_credentials.add_row([storage_credentials['credential']['username'], + storage_credentials['credential']['password'], + storage_credentials['name']]) + + table_iscsi = formatting.Table(['LUN name', 'capacity', 'Target address', 'Location', 'Notes']) + for iscsi in iscsi_storage_data: + table_iscsi.add_row([iscsi['username'], iscsi['capacityGb'], + iscsi['serviceResourceBackendIpAddress'], + iscsi['allowedVirtualGuests'][0]['datacenter']['longName'], + iscsi.get('notes', None)]) + + table_portable = formatting.Table(['Description', 'Capacity'], title="Portable Storage") + for portable in portable_storage: + table_portable.add_row([portable.get('description', None), portable.get('capacity', None)]) + + table_nas = formatting.Table(['Volume name', 'capacity', 'Host Name', 'Location', 'Notes'], + title="File Storage Details") + for nas in nas_storage_data: + table_nas.add_row([nas['username'], nas['capacityGb'], + nas['serviceResourceBackendIpAddress'], + nas['allowedVirtualGuests'][0]['datacenter']['longName'], + nas.get('notes', None)]) + + env.fout(table_credentials) + env.fout(table_iscsi) + env.fout(table_portable) + env.fout(table_nas) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index ddb2a4354..378890edb 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -852,3 +852,19 @@ } } ] + +getPortableStorageVolumes = [ + { + "capacity": 200, + "createDate": "2018-10-06T04:27:59-06:00", + "description": "Disk 2", + "id": 11111, + "modifyDate": "", + "name": "Disk 2", + "parentId": "", + "storageRepositoryId": 22222, + "typeId": 241, + "units": "GB", + "uuid": "fd477feb-bf32-408e-882f-02540gghgh111" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py new file mode 100644 index 000000000..f6683df8c --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py @@ -0,0 +1,23 @@ +getObject = { + "id": 11111, + "allowedVirtualGuests": [ + { + "id": 22222, + "allowedHost": { + "accountId": 12345, + "id": 18311111, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableId": 6222222, + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "12345", + "createDate": "2020-03-20T13:35:47-06:00", + "id": 1522222, + "nasCredentialTypeId": 2, + "password": "SjFDCpHrmKewos", + "username": "SL02SU322222-V62922222" + } + } + } + ] +} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 270ecf2ad..0eb728b03 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -670,3 +670,71 @@ } } ] + +getAttachedNetworkStorages = [ + { + "accountId": 11111, + "capacityGb": 20, + "createDate": "2018-04-05T05:15:49-06:00", + "id": 22222, + "nasType": "NAS", + "serviceProviderId": 1, + "storageTypeId": "13", + "username": "SL02SEV311111_11", + "allowedVirtualGuests": [ + { + "id": 12345, + "datacenter": { + "id": 449506, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + } + } + ], + "serviceResourceBackendIpAddress": "fsn-fra0201a-fz.service.softlayer.com", + "serviceResourceName": "Storage Type 02 File Aggregate stfm-fra0201a" + }, + { + "accountId": 11111, + "capacityGb": 12000, + "createDate": "2018-01-28T04:57:30-06:00", + "id": 3777111, + "nasType": "ISCSI", + "notes": "BlockStorage12T", + "password": "", + "serviceProviderId": 1, + "storageTypeId": "7", + "username": "SL02SEL32222-9", + "allowedVirtualGuests": [ + { + "id": 629222, + "datacenter": { + "id": 449506, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + } + } + ], + "serviceResourceBackendIpAddress": "10.31.95.152", + "serviceResourceName": "Storage Type 02 Block Aggregate stbm-fra0201a" + } +] + +getAllowedHost = { + "accountId": 11111, + "credentialId": 22222, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableId": 6291111, + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "11111", + "createDate": "2020-03-20T13:35:47-06:00", + "id": 44444, + "nasCredentialTypeId": 2, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } +} diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index b1391bc3a..30298ef9d 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -51,6 +51,7 @@ def __init__(self, client, ordering_manager=None): self.account = client['Account'] self.guest = client['Virtual_Guest'] self.package_svc = client['Product_Package'] + self.storage_iscsi = client['SoftLayer_Network_Storage_Iscsi'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) @@ -1123,3 +1124,30 @@ def _get_price_id_for_upgrade(self, package_items, option, value, public=True): return price['id'] else: return price['id'] + + def get_storage_details(self, instance_id, nas_type): + """Returns the virtual server attached network storage. + + :param int instance_id: Id of the virtual server + :param nas_type: storage type. + """ + nas_type = nas_type + mask = 'mask[id,username,capacityGb,notes,serviceResourceBackendIpAddress,' \ + 'allowedVirtualGuests[id,datacenter]]' + return self.guest.getAttachedNetworkStorages(nas_type, mask=mask, id=instance_id) + + def get_storage_credentials(self, instance_id): + """Returns the virtual server storage credentials. + + :param int instance_id: Id of the virtual server + """ + mask = 'mask[credential]' + return self.guest.getAllowedHost(mask=mask, id=instance_id) + + def get_portable_storage(self, instance_id): + """Returns the virtual server storage credentials. + + :param int instance_id: Id of the virtual server + """ + object_filter = {"portableStorageVolumes": {"blockDevices": {"guest": {"id": {"operation": instance_id}}}}} + return self.account.getPortableStorageVolumes(filter=object_filter) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 2c79f42bf..f22a15d2e 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -740,3 +740,9 @@ def test_bandwidth_vs_quite(self): self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + + def test_vs_storage(self): + result = self.run_command( + ['vs', 'storage', '100']) + + self.assert_no_fail(result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 47fcaf20d..f55430697 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -925,3 +925,130 @@ def test_get_bandwidth_allocation_with_allotment(self): result = self.vs.get_bandwidth_allocation(1234) self.assertEqual(2000, int(result['allotment']['amount'])) + + def test_get_storage_iscsi_details(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAttachedNetworkStorages') + mock.return_value = [ + { + "accountId": 11111, + "capacityGb": 12000, + "id": 3777123, + "nasType": "ISCSI", + "username": "SL02SEL31111-9", + } + ] + + result = self.vs.get_storage_details(1234, 'ISCSI') + + self.assertEqual([{ + "accountId": 11111, + "capacityGb": 12000, + "id": 3777123, + "nasType": "ISCSI", + "username": "SL02SEL31111-9", + }], result) + + def test_get_storage_iscsi_empty_details(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAttachedNetworkStorages') + mock.return_value = [] + + result = self.vs.get_storage_details(1234, 'ISCSI') + + self.assertEqual([], result) + + def test_get_storage_nas_details(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAttachedNetworkStorages') + mock.return_value = [ + { + "accountId": 11111, + "capacityGb": 12000, + "id": 3777111, + "nasType": "NAS", + "username": "SL02SEL32222-9", + } + ] + + result = self.vs.get_storage_details(1234, 'NAS') + + self.assertEqual([{ + "accountId": 11111, + "capacityGb": 12000, + "id": 3777111, + "nasType": "NAS", + "username": "SL02SEL32222-9", + }], result) + + def test_get_storage_nas_empty_details(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAttachedNetworkStorages') + mock.return_value = [] + + result = self.vs.get_storage_details(1234, 'NAS') + + self.assertEqual([], result) + + def test_get_storage_credentials(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAllowedHost') + mock.return_value = { + "accountId": 11111, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "11111", + "id": 44444, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } + } + + result = self.vs.get_storage_credentials(1234) + + self.assertEqual({ + "accountId": 11111, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "11111", + "id": 44444, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } + }, result) + + def test_get_none_storage_credentials(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAllowedHost') + mock.return_value = None + + result = self.vs.get_storage_credentials(1234) + + self.assertEqual(None, result) + + def test_get_portable_storage(self): + result = self.vs.get_portable_storage(1234) + self.assert_called_with('SoftLayer_Account', + 'getPortableStorageVolumes') + + self.assertEqual([ + { + "capacity": 200, + "createDate": "2018-10-06T04:27:59-06:00", + "description": "Disk 2", + "id": 11111, + "modifyDate": "", + "name": "Disk 2", + "parentId": "", + "storageRepositoryId": 22222, + "typeId": 241, + "units": "GB", + "uuid": "fd477feb-bf32-408e-882f-02540gghgh111" + } + ], result) + + def test_get_portable_storage_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getPortableStorageVolumes') + mock.return_value = [] + + result = self.vs.get_portable_storage(1234) + + self.assertEqual([], result) From 4543e6cbdcd6c41d99721bbdcc93bb4afab029e7 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 14:39:52 -0400 Subject: [PATCH 0071/1385] Feature hardware storage details. --- SoftLayer/CLI/hardware/storage.py | 47 +++++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Hardware_Server.py | 68 +++++++++++++ SoftLayer/managers/hardware.py | 21 +++- tests/CLI/modules/server_tests.py | 6 ++ tests/managers/hardware_tests.py | 99 ++++++++++++++++++- 6 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/hardware/storage.py diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py new file mode 100644 index 000000000..1646aafb6 --- /dev/null +++ b/SoftLayer/CLI/hardware/storage.py @@ -0,0 +1,47 @@ +"""Get storage details for a hardware server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get storage details for a hardware server. + """ + hardware = SoftLayer.HardwareManager(env.client) + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') + iscsi_storage_data = hardware.get_storage_details(hardware_id, "ISCSI") + nas_storage_data = hardware.get_storage_details(hardware_id, "NAS") + storage_credentials = hardware.get_storage_credentials(hardware_id) + + table_credentials = formatting.Table(['Username', 'Password', 'IQN'], title="Block Storage Details \n iSCSI") + if storage_credentials: + table_credentials.add_row([storage_credentials['credential']['username'], + storage_credentials['credential']['password'], + storage_credentials['name']]) + + table_iscsi = formatting.Table(['LUN name', 'capacity', 'Target address', 'Location', 'Notes']) + for iscsi in iscsi_storage_data: + table_iscsi.add_row([iscsi['username'], iscsi['capacityGb'], + iscsi['serviceResourceBackendIpAddress'], + iscsi['allowedHardware'][0]['datacenter']['longName'], + iscsi.get('notes', None)]) + + table_nas = formatting.Table(['Volume name', 'capacity', 'Host Name', 'Location', 'Notes'], + title="File Storage Details") + for nas in nas_storage_data: + table_nas.add_row([nas['username'], nas['capacityGb'], + nas['serviceResourceBackendIpAddress'], + nas['allowedHardware'][0]['datacenter']['longName'], + nas.get('notes', None)]) + + env.fout(table_credentials) + env.fout(table_iscsi) + env.fout(table_nas) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index abeae28fe..e18190d56 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -246,6 +246,7 @@ ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'), ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), + ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 47e9a1bcb..920b91bf1 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -149,3 +149,71 @@ ] getMetricTrackingObjectId = 1000 + +getAttachedNetworkStorages = [ + { + "accountId": 11111, + "capacityGb": 20, + "createDate": "2018-04-05T05:15:49-06:00", + "id": 22222, + "nasType": "NAS", + "serviceProviderId": 1, + "storageTypeId": "13", + "username": "SL02SEV311111_11", + "allowedHardware": [ + { + "id": 12345, + "datacenter": { + "id": 449506, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + } + } + ], + "serviceResourceBackendIpAddress": "fsn-fra0201a-fz.service.softlayer.com", + "serviceResourceName": "Storage Type 02 File Aggregate stfm-fra0201a" + }, + { + "accountId": 11111, + "capacityGb": 12000, + "createDate": "2018-01-28T04:57:30-06:00", + "id": 3777111, + "nasType": "ISCSI", + "notes": "BlockStorage12T", + "password": "", + "serviceProviderId": 1, + "storageTypeId": "7", + "username": "SL02SEL32222-9", + "allowedHardware": [ + { + "id": 629222, + "datacenter": { + "id": 449506, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + } + } + ], + "serviceResourceBackendIpAddress": "10.31.95.152", + "serviceResourceName": "Storage Type 02 Block Aggregate stbm-fra0201a" + } +] + +getAllowedHost = { + "accountId": 11111, + "credentialId": 22222, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableId": 6291111, + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "11111", + "createDate": "2020-03-20T13:35:47-06:00", + "id": 44444, + "nasCredentialTypeId": 2, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } +} diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fa4ee42a8..472208880 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -10,9 +10,9 @@ import time import SoftLayer +from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.managers import ordering -from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -702,6 +702,25 @@ def get_bandwidth_allocation(self, instance_id): return {'allotment': allotment.get('allocation'), 'usage': usage} return {'allotment': allotment, 'usage': usage} + def get_storage_details(self, instance_id, nas_type): + """Returns the hardware server attached network storage. + + :param int instance_id: Id of the hardware server + :param nas_type: storage type. + """ + nas_type = nas_type + mask = 'mask[id,username,capacityGb,notes,serviceResourceBackendIpAddress,' \ + 'allowedHardware[id,datacenter]]' + return self.hardware.getAttachedNetworkStorages(nas_type, mask=mask, id=instance_id) + + def get_storage_credentials(self, instance_id): + """Returns the hardware server storage credentials. + + :param int instance_id: Id of the hardware server + """ + mask = 'mask[credential]' + return self.hardware.getAllowedHost(mask=mask, id=instance_id) + def _get_extra_price_id(items, key_name, hourly, location): """Returns a price id attached to item with the given key_name.""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 14f8e9201..758bd6371 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -826,3 +826,9 @@ def test_dns_sync_misc_exception(self, confirm_mock): result = self.run_command(['hw', 'dns-sync', '-a', '1000']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_hardware_storage(self): + result = self.run_command( + ['hw', 'storage', '100']) + + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 5710dd0ae..b3afe269f 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -9,7 +9,6 @@ import mock import SoftLayer - from SoftLayer import fixtures from SoftLayer import managers from SoftLayer import testing @@ -467,6 +466,104 @@ def test_get_bandwidth_allocation_no_allotment(self): self.assertEqual(None, result['allotment']) + def test_get_storage_iscsi_details(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') + mock.return_value = [ + { + "accountId": 11111, + "capacityGb": 12000, + "id": 3777123, + "nasType": "ISCSI", + "username": "SL02SEL31111-9", + } + ] + + result = self.hardware.get_storage_details(1234, 'ISCSI') + + self.assertEqual([{ + "accountId": 11111, + "capacityGb": 12000, + "id": 3777123, + "nasType": "ISCSI", + "username": "SL02SEL31111-9", + }], result) + + def test_get_storage_iscsi_empty_details(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') + mock.return_value = [] + + result = self.hardware.get_storage_details(1234, 'ISCSI') + + self.assertEqual([], result) + + def test_get_storage_nas_details(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') + mock.return_value = [ + { + "accountId": 11111, + "capacityGb": 12000, + "id": 3777111, + "nasType": "NAS", + "username": "SL02SEL32222-9", + } + ] + + result = self.hardware.get_storage_details(1234, 'NAS') + + self.assertEqual([{ + "accountId": 11111, + "capacityGb": 12000, + "id": 3777111, + "nasType": "NAS", + "username": "SL02SEL32222-9", + }], result) + + def test_get_storage_nas_empty_details(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') + mock.return_value = [] + + result = self.hardware.get_storage_details(1234, 'NAS') + + self.assertEqual([], result) + + def test_get_storage_credentials(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAllowedHost') + mock.return_value = { + "accountId": 11111, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableName": "HARDWARE", + "credential": { + "accountId": "11111", + "id": 44444, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } + } + + result = self.hardware.get_storage_credentials(1234) + + self.assertEqual({ + "accountId": 11111, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableName": "HARDWARE", + "credential": { + "accountId": "11111", + "id": 44444, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } + }, result) + + def test_get_none_storage_credentials(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAllowedHost') + mock.return_value = None + + result = self.hardware.get_storage_credentials(1234) + + self.assertEqual(None, result) + class HardwareHelperTests(testing.TestCase): def test_get_extra_price_id_no_items(self): From 84cf1b6e5039cbeedb355e46151391428ec8643c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 26 Mar 2020 16:08:55 -0500 Subject: [PATCH 0072/1385] version to 5.8.6 --- CHANGELOG.md | 13 +++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 444c459cb..971b16ba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log +## [5.8.6] - 2012-03-26 +https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 + +- #1222 Get load balancer (LBaaS) by name +- #1221 Added version checker +- #1227 Updated unit test suite for TravisCI to run properly +- #1225 Add note about using multiple colon symbols not working when setting tags. +- #1228 Support ordering [Dependent Duplicate Volumes](https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-dependentduplicate) +- #1233 Refactored File/Block managers to reduce duplicated code. +- #1231 Added Refresh functions for Dependent Duplicate Volumes +- #801 Added support for JSON styled parameters and object filters +- #1234 Added ability to change which datacenters an image template was stored in + ## [5.8.5] - 2012-01-29 https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 8bb1585fd..041f3b013 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.5' +VERSION = 'v5.8.6' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index b88b324c0..a7c4eb61e 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.5', + version='5.8.6', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From a6818155c1a46956f44261ca7948eb64745193c7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 26 Mar 2020 16:18:23 -0500 Subject: [PATCH 0073/1385] updated release dates to proper year --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 971b16ba1..e3c10eda1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## [5.8.6] - 2012-03-26 +## [5.8.6] - 2020-03-26 https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 - #1222 Get load balancer (LBaaS) by name @@ -14,7 +14,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 - #1234 Added ability to change which datacenters an image template was stored in -## [5.8.5] - 2012-01-29 +## [5.8.5] - 2020-01-29 https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 - #1195 Fixed an issue with `slcli vs dns-sync --ptr`. Added `slcli hw dns-sync` From cd2bd8e1ea08c18e1a9f288fb3147a9b0cc27bed Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 17:42:17 -0400 Subject: [PATCH 0074/1385] Add local disks information for vs and hard drives information for hardware server. --- SoftLayer/CLI/hardware/storage.py | 11 +++ SoftLayer/CLI/virt/storage.py | 20 +++++ .../fixtures/SoftLayer_Hardware_Server.py | 25 ++++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 29 +++++++ SoftLayer/managers/hardware.py | 7 ++ SoftLayer/managers/vs.py | 10 ++- tests/managers/hardware_tests.py | 62 ++++++++++++++ tests/managers/vs/vs_tests.py | 80 +++++++++++++++++++ 8 files changed, 243 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py index 1646aafb6..69232922c 100644 --- a/SoftLayer/CLI/hardware/storage.py +++ b/SoftLayer/CLI/hardware/storage.py @@ -20,6 +20,7 @@ def cli(env, identifier): iscsi_storage_data = hardware.get_storage_details(hardware_id, "ISCSI") nas_storage_data = hardware.get_storage_details(hardware_id, "NAS") storage_credentials = hardware.get_storage_credentials(hardware_id) + hard_drives = hardware.get_hard_drives(hardware_id) table_credentials = formatting.Table(['Username', 'Password', 'IQN'], title="Block Storage Details \n iSCSI") if storage_credentials: @@ -42,6 +43,16 @@ def cli(env, identifier): nas['allowedHardware'][0]['datacenter']['longName'], nas.get('notes', None)]) + table_hard_drives = formatting.Table(['Type', 'Name', 'Capacity', 'Serial #'], title="Other storage details") + for drives in hard_drives: + table_hard_drives.add_row([drives['hardwareComponentModel']['hardwareGenericComponentModel'] + ['hardwareComponentType']['type'], drives['hardwareComponentModel'] + ['manufacturer'] + " " + drives['hardwareComponentModel']['name'], + str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) + + " " + str(drives['hardwareComponentModel']['hardwareGenericComponentModel'] + ['units']), drives['serialNumber']]) + env.fout(table_credentials) env.fout(table_iscsi) env.fout(table_nas) + env.fout(table_hard_drives) diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py index c88991a2c..1fdd1a78d 100644 --- a/SoftLayer/CLI/virt/storage.py +++ b/SoftLayer/CLI/virt/storage.py @@ -21,6 +21,7 @@ def cli(env, identifier): nas_storage_data = vsi.get_storage_details(vsi_id, "NAS") storage_credentials = vsi.get_storage_credentials(vsi_id) portable_storage = vsi.get_portable_storage(vsi_id) + local_disks = vsi.get_local_disks(vsi_id) table_credentials = formatting.Table(['Username', 'Password', 'IQN'], title="Block Storage Details \n iSCSI") if storage_credentials: @@ -47,7 +48,26 @@ def cli(env, identifier): nas['allowedVirtualGuests'][0]['datacenter']['longName'], nas.get('notes', None)]) + table_local_disks = formatting.Table(['Type', 'Name', 'Capacity'], title="Other storage details") + for disks in local_disks: + if 'diskImage' in disks: + table_local_disks.add_row([get_local_type(disks), disks['mountType'], + str(disks['diskImage']['capacity']) + " " + str(disks['diskImage']['units'])]) + env.fout(table_credentials) env.fout(table_iscsi) env.fout(table_portable) env.fout(table_nas) + env.fout(table_local_disks) + + +def get_local_type(disks): + """Returns the virtual server local disk type. + + :param disks: virtual serve local disks. + """ + disk_type = 'System' + if 'SWAP' in disks['diskImage']['description']: + disk_type = 'Swap' + + return disk_type diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 920b91bf1..e90288753 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -217,3 +217,28 @@ "username": "SL02SU11111-V62941551" } } + +getHardDrives = [ + { + "id": 11111, + "serialNumber": "z1w4sdf", + "serviceProviderId": 1, + "hardwareComponentModel": { + "capacity": "1000", + "description": "SATAIII:2000:8300:Constellation", + "id": 111, + "manufacturer": "Seagate", + "name": "Constellation ES", + "hardwareGenericComponentModel": { + "capacity": "1000", + "units": "GB", + "hardwareComponentType": { + "id": 1, + "keyName": "HARD_DRIVE", + "type": "Hard Drive", + "typeParentId": 5 + } + } + } + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 0eb728b03..c42963c8e 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -738,3 +738,32 @@ "username": "SL02SU11111-V62941551" } } + +getBlockDevices = [ + { + "createDate": "2018-10-06T04:27:35-06:00", + "device": "0", + "id": 11111, + "mountType": "Disk", + "diskImage": { + "capacity": 100, + "description": "adns.vmware.com", + "id": 72222, + "name": "adns.vmware.com", + "units": "GB", + } + }, + { + "device": "1", + "id": 22222, + "mountType": "Disk", + "statusId": 1, + "diskImage": { + "capacity": 2, + "description": "6211111-SWAP", + "id": 33333, + "name": "6211111-SWAP", + "units": "GB", + } + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 472208880..ca19a51f3 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -721,6 +721,13 @@ def get_storage_credentials(self, instance_id): mask = 'mask[credential]' return self.hardware.getAllowedHost(mask=mask, id=instance_id) + def get_hard_drives(self, instance_id): + """Returns the hardware server hard drives. + + :param int instance_id: Id of the hardware server + """ + return self.hardware.getHardDrives(id=instance_id) + def _get_extra_price_id(items, key_name, hourly, location): """Returns a price id attached to item with the given key_name.""" diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 30298ef9d..ade6aa68d 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1145,9 +1145,17 @@ def get_storage_credentials(self, instance_id): return self.guest.getAllowedHost(mask=mask, id=instance_id) def get_portable_storage(self, instance_id): - """Returns the virtual server storage credentials. + """Returns the virtual server portable storage. :param int instance_id: Id of the virtual server """ object_filter = {"portableStorageVolumes": {"blockDevices": {"guest": {"id": {"operation": instance_id}}}}} return self.account.getPortableStorageVolumes(filter=object_filter) + + def get_local_disks(self, instance_id): + """Returns the virtual server local disks. + + :param int instance_id: Id of the virtual server + """ + mask = 'mask[diskImage]' + return self.guest.getBlockDevices(mask=mask, id=instance_id) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index b3afe269f..9ac63c224 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -564,6 +564,68 @@ def test_get_none_storage_credentials(self): self.assertEqual(None, result) + def test_get_hard_drives(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') + mock.return_value = [ + { + "id": 11111, + "serialNumber": "z1w4sdf", + "serviceProviderId": 1, + "hardwareComponentModel": { + "capacity": "1000", + "description": "SATAIII:2000:8300:Constellation", + "id": 111, + "manufacturer": "Seagate", + "name": "Constellation ES", + "hardwareGenericComponentModel": { + "capacity": "1000", + "units": "GB", + "hardwareComponentType": { + "id": 1, + "keyName": "HARD_DRIVE", + "type": "Hard Drive", + "typeParentId": 5 + } + } + } + } + ] + + result = self.hardware.get_hard_drives(1234) + + self.assertEqual([ + { + "id": 11111, + "serialNumber": "z1w4sdf", + "serviceProviderId": 1, + "hardwareComponentModel": { + "capacity": "1000", + "description": "SATAIII:2000:8300:Constellation", + "id": 111, + "manufacturer": "Seagate", + "name": "Constellation ES", + "hardwareGenericComponentModel": { + "capacity": "1000", + "units": "GB", + "hardwareComponentType": { + "id": 1, + "keyName": "HARD_DRIVE", + "type": "Hard Drive", + "typeParentId": 5 + } + } + } + } + ], result) + + def test_get_hard_drive_empty(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') + mock.return_value = [] + + result = self.hardware.get_hard_drives(1234) + + self.assertEqual([], result) + class HardwareHelperTests(testing.TestCase): def test_get_extra_price_id_no_items(self): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index f55430697..40fb3063f 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1052,3 +1052,83 @@ def test_get_portable_storage_empty(self): result = self.vs.get_portable_storage(1234) self.assertEqual([], result) + + def test_get_local_disks_system(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [ + { + "createDate": "2018-10-06T04:27:35-06:00", + "device": "0", + "id": 11111, + "mountType": "Disk", + "diskImage": { + "capacity": 100, + "description": "adns.vmware.com", + "id": 72222, + "name": "adns.vmware.com", + "units": "GB", + } + } + ] + + result = self.vs.get_local_disks(1234) + + self.assertEqual([ + { + "createDate": "2018-10-06T04:27:35-06:00", + "device": "0", + "id": 11111, + "mountType": "Disk", + "diskImage": { + "capacity": 100, + "description": "adns.vmware.com", + "id": 72222, + "name": "adns.vmware.com", + "units": "GB", + } + } + ], result) + + def test_get_local_disks_empty(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [] + + result = self.vs.get_local_disks(1234) + + self.assertEqual([], result) + + def test_get_local_disks_swap(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [ + { + "device": "1", + "id": 22222, + "mountType": "Disk", + "statusId": 1, + "diskImage": { + "capacity": 2, + "description": "6211111-SWAP", + "id": 33333, + "name": "6211111-SWAP", + "units": "GB", + } + } + ] + + result = self.vs.get_local_disks(1234) + + self.assertEqual([ + { + "device": "1", + "id": 22222, + "mountType": "Disk", + "statusId": 1, + "diskImage": { + "capacity": 2, + "description": "6211111-SWAP", + "id": 33333, + "name": "6211111-SWAP", + "units": "GB", + } + } + ], result) From 047620a223eca84b4a2e52329d860c6a3c593454 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 26 Mar 2020 16:52:52 -0500 Subject: [PATCH 0075/1385] v5.8.7 release --- CHANGELOG.md | 7 ++++--- SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3c10eda1..2d271cd86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [5.8.6] - 2020-03-26 -https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 +## [5.8.7] - 2020-03-26 +https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.7 - #1222 Get load balancer (LBaaS) by name - #1221 Added version checker @@ -13,6 +13,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 - #801 Added support for JSON styled parameters and object filters - #1234 Added ability to change which datacenters an image template was stored in +## [5.8.6] - Skipped ## [5.8.5] - 2020-01-29 https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 @@ -33,7 +34,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 ## [5.8.4] - 2019-12-20 -https://github.com/softlayer/softlayer-python/compare/v5.8.3...v5.8.4 +https://github.com/softlayer/softlayer-python/compare/v5.8.3...5vhttps://pypi.org/help/#file-name-reuse.8.4 - #1199 Fix block storage failback and failover. - #1202 Order a virtual server private. diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 041f3b013..e651d91ca 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.6' +VERSION = 'v5.8.7' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index a7c4eb61e..d8e9f566f 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.6', + version='5.8.7', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 1ac8bcb9df823ae97ad38755b033673283034dca Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 26 Mar 2020 16:53:50 -0500 Subject: [PATCH 0076/1385] v5.8.7 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d271cd86..965e9967f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 ## [5.8.4] - 2019-12-20 -https://github.com/softlayer/softlayer-python/compare/v5.8.3...5vhttps://pypi.org/help/#file-name-reuse.8.4 +https://github.com/softlayer/softlayer-python/compare/v5.8.3...v5.8.4 - #1199 Fix block storage failback and failover. - #1202 Order a virtual server private. From 644cbc0601782490613a4d168011a48de8058ec1 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 18:07:16 -0400 Subject: [PATCH 0077/1385] Fix tox analysis. --- SoftLayer/CLI/hardware/storage.py | 4 ++-- SoftLayer/CLI/virt/storage.py | 4 ++-- SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py | 2 +- SoftLayer/managers/hardware.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py index 69232922c..2a4501298 100644 --- a/SoftLayer/CLI/hardware/storage.py +++ b/SoftLayer/CLI/hardware/storage.py @@ -13,8 +13,8 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Get storage details for a hardware server. - """ + """Get storage details for a hardware server.""" + hardware = SoftLayer.HardwareManager(env.client) hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') iscsi_storage_data = hardware.get_storage_details(hardware_id, "ISCSI") diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py index 1fdd1a78d..8d1b65854 100644 --- a/SoftLayer/CLI/virt/storage.py +++ b/SoftLayer/CLI/virt/storage.py @@ -13,8 +13,8 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Get storage details for a virtual server. - """ + """Get storage details for a virtual server.""" + vsi = SoftLayer.VSManager(env.client) vsi_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') iscsi_storage_data = vsi.get_storage_details(vsi_id, "ISCSI") diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py index f6683df8c..04c228476 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py @@ -20,4 +20,4 @@ } } ] -} \ No newline at end of file +} diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ca19a51f3..2ac20b2b2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -10,9 +10,9 @@ import time import SoftLayer -from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.managers import ordering +from SoftLayer import utils LOGGER = logging.getLogger(__name__) From a7fe50241121ba4cf8b1ae68113979157505fc09 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 18:22:26 -0400 Subject: [PATCH 0078/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 1 - SoftLayer/managers/vs.py | 1 - 2 files changed, 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 2ac20b2b2..99ab45b9c 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -708,7 +708,6 @@ def get_storage_details(self, instance_id, nas_type): :param int instance_id: Id of the hardware server :param nas_type: storage type. """ - nas_type = nas_type mask = 'mask[id,username,capacityGb,notes,serviceResourceBackendIpAddress,' \ 'allowedHardware[id,datacenter]]' return self.hardware.getAttachedNetworkStorages(nas_type, mask=mask, id=instance_id) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index ade6aa68d..e63d7a80f 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1131,7 +1131,6 @@ def get_storage_details(self, instance_id, nas_type): :param int instance_id: Id of the virtual server :param nas_type: storage type. """ - nas_type = nas_type mask = 'mask[id,username,capacityGb,notes,serviceResourceBackendIpAddress,' \ 'allowedVirtualGuests[id,datacenter]]' return self.guest.getAttachedNetworkStorages(nas_type, mask=mask, id=instance_id) From cbe7c98a3927e9b598ad51dcf64680aaad40718d Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 31 Mar 2020 20:09:33 -0400 Subject: [PATCH 0079/1385] implement the new feature hw billing and vs billing --- SoftLayer/CLI/hardware/billing.py | 38 +++++++++++++++++++++ SoftLayer/CLI/virt/billing.py | 38 +++++++++++++++++++++ tests/CLI/modules/server_tests.py | 55 +++++++++++++++++++++---------- tests/CLI/modules/vs/vs_tests.py | 52 +++++++++++++++++++---------- 4 files changed, 147 insertions(+), 36 deletions(-) create mode 100644 SoftLayer/CLI/hardware/billing.py create mode 100644 SoftLayer/CLI/virt/billing.py diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py new file mode 100644 index 000000000..2131f999c --- /dev/null +++ b/SoftLayer/CLI/hardware/billing.py @@ -0,0 +1,38 @@ +"""Get billing for a hardware device.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get billing for a hardware device.""" + hardware = SoftLayer.HardwareManager(env.client) + + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') + result = hardware.get_hardware(hardware_id) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['hardwareId', identifier]) + + table.add_row(['BillingIttem', utils.lookup(result, 'billingItem', 'id')]) + table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) + table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) + table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) + + price_table = formatting.Table(['Item', 'Recurring Price']) + for item in utils.lookup(result, 'billingItem', 'children') or []: + price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) + + table.add_row(['prices', price_table]) + env.fout(table) diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py new file mode 100644 index 000000000..500692d26 --- /dev/null +++ b/SoftLayer/CLI/virt/billing.py @@ -0,0 +1,38 @@ +"""Get billing for a virtual device.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get billing for a virtual device.""" + virtual = SoftLayer.VSManager(env.client) + + virtual_id = helpers.resolve_id(virtual.resolve_ids, identifier, 'virtual') + result = virtual.get_instance(virtual_id) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['VirtuallId', identifier]) + + table.add_row(['BillingIttem', utils.lookup(result, 'billingItem', 'id')]) + table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) + table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) + table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) + + price_table = formatting.Table(['Recurring Price']) + for item in utils.lookup(result, 'billingItem', 'children') or []: + price_table.add_row([item['nextInvoiceTotalRecurringAmount']]) + + table.add_row(['prices', price_table]) + env.fout(table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 14f8e9201..16af3a235 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -659,19 +659,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -714,12 +714,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) @@ -826,3 +826,22 @@ def test_dns_sync_misc_exception(self, confirm_mock): result = self.run_command(['hw', 'dns-sync', '-a', '1000']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_billing(self): + result = self.run_command(['hw', 'billing', '123456']) + billing_json = { + "hardwareId": "123456", + "BillingIttem": 6327, + "recurringFee": 1.54, + "Total": 16.08, + "provisionDate": None, + "prices": [ + { + "Item": "test", + "Recurring Price": 1 + } + ] + } + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), billing_json) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 2c79f42bf..e82bf4e87 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -320,19 +320,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -375,12 +375,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) @@ -740,3 +740,19 @@ def test_bandwidth_vs_quite(self): self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + + def test_billing(self): + result = self.run_command(['vs', 'billing', '123456']) + vir_billing = { + "BillingIttem": 6327, + "Total": 1.54, + "VirtuallId": "123456", + "prices": [{"Recurring Price": 1}, + {"Recurring Price": 1}, + {"Recurring Price": 1}, + {"Recurring Price": 1}, + {"Recurring Price": 1}], + "provisionDate": None, + "recurringFee": None} + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), vir_billing) From 0d69281a756f257935ad187f977e28d3ec9ec911 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 1 Apr 2020 08:34:03 -0400 Subject: [PATCH 0080/1385] add the routes file --- SoftLayer/CLI/routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f6edee475..b68fd8e4a 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -20,6 +20,7 @@ ('virtual', 'SoftLayer.CLI.virt'), ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), + ('virtual:billing', 'SoftLayer.CLI.virt.billing:cli'), ('virtual:cancel', 'SoftLayer.CLI.virt.cancel:cli'), ('virtual:capture', 'SoftLayer.CLI.virt.capture:cli'), ('virtual:create', 'SoftLayer.CLI.virt.create:cli'), @@ -163,7 +164,6 @@ ('image:list', 'SoftLayer.CLI.image.list:cli'), ('image:import', 'SoftLayer.CLI.image.import:cli'), ('image:export', 'SoftLayer.CLI.image.export:cli'), - ('image:datacenter', 'SoftLayer.CLI.image.datacenter:cli'), ('ipsec', 'SoftLayer.CLI.vpn.ipsec'), ('ipsec:configure', 'SoftLayer.CLI.vpn.ipsec.configure:cli'), @@ -231,6 +231,7 @@ ('hardware:create', 'SoftLayer.CLI.hardware.create:cli'), ('hardware:create-options', 'SoftLayer.CLI.hardware.create_options:cli'), ('hardware:detail', 'SoftLayer.CLI.hardware.detail:cli'), + ('hardware:billing', 'SoftLayer.CLI.hardware.billing:cli'), ('hardware:edit', 'SoftLayer.CLI.hardware.edit:cli'), ('hardware:list', 'SoftLayer.CLI.hardware.list:cli'), ('hardware:power-cycle', 'SoftLayer.CLI.hardware.power:power_cycle'), From 845760df58f39ca10232bd85902b83db8d1ed158 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Thu, 2 Apr 2020 15:40:29 -0400 Subject: [PATCH 0081/1385] fix Christopher code review --- SoftLayer/CLI/hardware/billing.py | 4 ++-- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/billing.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index 2131f999c..f1b1a8501 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -23,9 +23,9 @@ def cli(env, identifier): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['hardwareId', identifier]) + table.add_row(['Id', identifier]) - table.add_row(['BillingIttem', utils.lookup(result, 'billingItem', 'id')]) + table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b68fd8e4a..d7e6334c8 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -164,6 +164,7 @@ ('image:list', 'SoftLayer.CLI.image.list:cli'), ('image:import', 'SoftLayer.CLI.image.import:cli'), ('image:export', 'SoftLayer.CLI.image.export:cli'), + ('image:datacenter', 'SoftLayer.CLI.image.datacenter:cli'), ('ipsec', 'SoftLayer.CLI.vpn.ipsec'), ('ipsec:configure', 'SoftLayer.CLI.vpn.ipsec.configure:cli'), diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py index 500692d26..872f708a0 100644 --- a/SoftLayer/CLI/virt/billing.py +++ b/SoftLayer/CLI/virt/billing.py @@ -23,9 +23,9 @@ def cli(env, identifier): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['VirtuallId', identifier]) + table.add_row(['Id', identifier]) - table.add_row(['BillingIttem', utils.lookup(result, 'billingItem', 'id')]) + table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) From ff42108a6a8841353c0839da51ea5c9bab1c376c Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Thu, 2 Apr 2020 16:12:12 -0400 Subject: [PATCH 0082/1385] fix Christopher code review --- SoftLayer/CLI/hardware/billing.py | 6 +++--- SoftLayer/CLI/virt/billing.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index f1b1a8501..03b32e7f6 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -26,13 +26,13 @@ def cli(env, identifier): table.add_row(['Id', identifier]) table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) - table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) + table.add_row(['Recurring Fee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) - table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) + table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) price_table = formatting.Table(['Item', 'Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) + price_table.add_row([item['Description'], item['NextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) env.fout(table) diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py index 872f708a0..7312de8d8 100644 --- a/SoftLayer/CLI/virt/billing.py +++ b/SoftLayer/CLI/virt/billing.py @@ -26,9 +26,9 @@ def cli(env, identifier): table.add_row(['Id', identifier]) table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) - table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) + table.add_row(['Recurring Fee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) - table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) + table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) price_table = formatting.Table(['Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: From 1091825383df7419ca3989c55d8ae59b7a1c2b7b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 2 Apr 2020 19:03:42 -0400 Subject: [PATCH 0083/1385] Add local disk information for vs and hardware detail. --- SoftLayer/CLI/hardware/detail.py | 11 +++++++ SoftLayer/CLI/hardware/storage.py | 13 ++++---- SoftLayer/CLI/virt/detail.py | 20 +++++++++++++ tests/CLI/modules/server_tests.py | 33 ++++++++++++++++++++ tests/CLI/modules/vs/vs_tests.py | 50 +++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index 89f0cb0ee..d48a7e5ed 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -27,6 +27,7 @@ def cli(env, identifier, passwords, price): hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') result = hardware.get_hardware(hardware_id) result = utils.NestedDict(result) + hard_drives = hardware.get_hard_drives(hardware_id) operating_system = utils.lookup(result, 'operatingSystem', 'softwareLicense', 'softwareDescription') or {} memory = formatting.gb(result.get('memoryCapacity', 0)) @@ -34,6 +35,15 @@ def cli(env, identifier, passwords, price): if utils.lookup(result, 'billingItem') != []: owner = utils.lookup(result, 'billingItem', 'orderItem', 'order', 'userRecord', 'username') + table_hard_drives = formatting.Table(['Name', 'Capacity', 'Serial #']) + for drives in hard_drives: + name = drives['hardwareComponentModel']['manufacturer'] + " " + drives['hardwareComponentModel']['name'] + capacity = str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) + " " + str( + drives['hardwareComponentModel']['hardwareGenericComponentModel']['units']) + serial = drives['serialNumber'] + + table_hard_drives.add_row([name, capacity, serial]) + table.add_row(['id', result['id']]) table.add_row(['guid', result['globalIdentifier'] or formatting.blank()]) table.add_row(['hostname', result['hostname']]) @@ -43,6 +53,7 @@ def cli(env, identifier, passwords, price): table.add_row(['datacenter', result['datacenter']['name'] or formatting.blank()]) table.add_row(['cores', result['processorPhysicalCoreAmount']]) table.add_row(['memory', memory]) + table.add_row(['drives', table_hard_drives]) table.add_row(['public_ip', result['primaryIpAddress'] or formatting.blank()]) table.add_row(['private_ip', result['primaryBackendIpAddress'] or formatting.blank()]) table.add_row(['ipmi_ip', result['networkManagementIpAddress'] or formatting.blank()]) diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py index 2a4501298..46cb30be8 100644 --- a/SoftLayer/CLI/hardware/storage.py +++ b/SoftLayer/CLI/hardware/storage.py @@ -45,12 +45,13 @@ def cli(env, identifier): table_hard_drives = formatting.Table(['Type', 'Name', 'Capacity', 'Serial #'], title="Other storage details") for drives in hard_drives: - table_hard_drives.add_row([drives['hardwareComponentModel']['hardwareGenericComponentModel'] - ['hardwareComponentType']['type'], drives['hardwareComponentModel'] - ['manufacturer'] + " " + drives['hardwareComponentModel']['name'], - str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) - + " " + str(drives['hardwareComponentModel']['hardwareGenericComponentModel'] - ['units']), drives['serialNumber']]) + type = drives['hardwareComponentModel']['hardwareGenericComponentModel']['hardwareComponentType']['type'] + name = drives['hardwareComponentModel']['manufacturer'] + " " + drives['hardwareComponentModel']['name'] + capacity = str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) + " " + str( + drives['hardwareComponentModel']['hardwareGenericComponentModel']['units']) + serial = drives['serialNumber'] + + table_hard_drives.add_row([type, name, capacity, serial]) env.fout(table_credentials) env.fout(table_iscsi) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 131117df2..d17e489f2 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -32,6 +32,13 @@ def cli(env, identifier, passwords=False, price=False): vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') result = vsi.get_instance(vs_id) result = utils.NestedDict(result) + local_disks = vsi.get_local_disks(vs_id) + + table_local_disks = formatting.Table(['Type', 'Name', 'Capacity']) + for disks in local_disks: + if 'diskImage' in disks: + table_local_disks.add_row([get_local_type(disks), disks['mountType'], + str(disks['diskImage']['capacity']) + " " + str(disks['diskImage']['units'])]) table.add_row(['id', result['id']]) table.add_row(['guid', result['globalIdentifier']]) @@ -57,6 +64,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['os_version', operating_system.get('version', '-')]) table.add_row(['cores', result['maxCpu']]) table.add_row(['memory', formatting.mb_to_gb(result['maxMemory'])]) + table.add_row(['drives', table_local_disks]) table.add_row(['public_ip', result.get('primaryIpAddress', '-')]) table.add_row(['private_ip', result.get('primaryBackendIpAddress', '-')]) table.add_row(['private_only', result['privateNetworkOnlyFlag']]) @@ -192,3 +200,15 @@ def _get_security_table(result): return secgroup_table else: return None + + +def get_local_type(disks): + """Returns the virtual server local disk type. + + :param disks: virtual serve local disks. + """ + disk_type = 'System' + if 'SWAP' in disks['diskImage']['description']: + disk_type = 'Swap' + + return disk_type diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 758bd6371..65feef1e7 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -139,6 +139,39 @@ def test_detail_empty_allotment(self): '-', ) + def test_detail_drives(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') + mock.return_value = [ + { + "id": 11111, + "serialNumber": "z1w4sdf", + "hardwareComponentModel": { + "capacity": "1000", + "description": "SATAIII:2000:8300:Constellation", + "id": 111, + "manufacturer": "Seagate", + "name": "Constellation ES", + "hardwareGenericComponentModel": { + "capacity": "1000", + "units": "GB", + "hardwareComponentType": { + "id": 1, + "keyName": "HARD_DRIVE", + "type": "Hard Drive", + "typeParentId": 5 + } + } + } + } + ] + result = self.run_command(['server', 'detail', '100']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output['drives'][0]['Capacity'], '1000 GB') + self.assertEqual(output['drives'][0]['Name'], 'Seagate Constellation ES') + self.assertEqual(output['drives'][0]['Serial #'], 'z1w4sdf') + def test_list_servers(self): result = self.run_command(['server', 'list', '--tag=openstack']) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index f22a15d2e..5c1f03f1b 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -216,6 +216,56 @@ def test_detail_vs_empty_allotment(self): '-', ) + def test_detail_drives_system(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [ + { + "createDate": "2018-10-06T04:27:35-06:00", + "device": "0", + "id": 11111, + "mountType": "Disk", + "diskImage": { + "capacity": 100, + "description": "adns.vmware.com", + "id": 72222, + "name": "adns.vmware.com", + "units": "GB", + } + } + ] + result = self.run_command(['vs', 'detail', '100']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output['drives'][0]['Capacity'], '100 GB') + self.assertEqual(output['drives'][0]['Name'], 'Disk') + self.assertEqual(output['drives'][0]['Type'], 'System') + + def test_detail_drives_swap(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [ + { + "device": "1", + "id": 22222, + "mountType": "Disk", + "statusId": 1, + "diskImage": { + "capacity": 2, + "description": "6211111-SWAP", + "id": 33333, + "name": "6211111-SWAP", + "units": "GB", + } + } + ] + result = self.run_command(['vs', 'detail', '100']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output['drives'][0]['Capacity'], '2 GB') + self.assertEqual(output['drives'][0]['Name'], 'Disk') + self.assertEqual(output['drives'][0]['Type'], 'Swap') + def test_detail_vs_dedicated_host_not_found(self): ex = SoftLayerAPIError('SoftLayer_Exception', 'Not found') mock = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getObject') From 187c0ca8541c59473e7d2129a0c93a7dafb7c1d2 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 27 Mar 2020 10:06:12 -0400 Subject: [PATCH 0084/1385] add user vpn manual config flag --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/user/vpn_manual.py | 28 ++++++++++++++++++++++++++++ SoftLayer/managers/user.py | 9 +++++++++ tests/CLI/modules/user_tests.py | 18 +++++++++++++++++- 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/user/vpn_manual.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f6edee475..6d976c3f4 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -304,6 +304,7 @@ ('user:edit-details', 'SoftLayer.CLI.user.edit_details:cli'), ('user:create', 'SoftLayer.CLI.user.create:cli'), ('user:delete', 'SoftLayer.CLI.user.delete:cli'), + ('user:vpn-manual', 'SoftLayer.CLI.user.vpn_manual:cli'), ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), diff --git a/SoftLayer/CLI/user/vpn_manual.py b/SoftLayer/CLI/user/vpn_manual.py new file mode 100644 index 000000000..fb8ac78fc --- /dev/null +++ b/SoftLayer/CLI/user/vpn_manual.py @@ -0,0 +1,28 @@ +"""List Users.""" +# :license: MIT, see LICENSE for more details. + + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('user') +@click.option('--enable/--disable', default=True, + help="Whether enable or disable vpnManualConfig flag.") +@environment.pass_env +def cli(env, user, enable): + """Enable or disable user vpn subnets manual config""" + mgr = SoftLayer.UserManager(env.client) + user_id = helpers.resolve_id(mgr.resolve_ids, user, 'username') + + result = mgr.vpn_manual(user_id, enable) + message = "{} vpn manual config {}".format(user, 'enable' if enable else 'disable') + + if result: + click.secho(message, fg='green') + else: + click.secho("Failed to update {}".format(user), fg='red') diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 247071381..df2d7b454 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -267,6 +267,15 @@ def add_api_authentication_key(self, user_id): """ return self.user_service.addApiAuthenticationKey(id=user_id) + def vpn_manual(self, user_id, value): + """Set vpnManualConfig flag + + :param int user_id: User to edit + :param bool value: Value for vpnManualConfig flag + """ + user_object = {'vpnManualConfig': value} + return self.edit_user(user_id, user_object) + def _keyname_search(haystack, needle): for item in haystack: diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 79554fc85..d69e36a27 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -14,7 +14,6 @@ class UserCLITests(testing.TestCase): - """User list tests""" def test_user_list(self): @@ -153,6 +152,7 @@ def test_edit_perms_from_user(self): self.assert_called_with('SoftLayer_User_Customer', 'addBulkPortalPermission', identifier=11100) """User create tests""" + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user(self, confirm_mock): confirm_mock.return_value = True @@ -228,6 +228,7 @@ def test_create_user_from_user(self, confirm_mock): self.assert_called_with('SoftLayer_User_Customer', 'getObject', identifier=1234) """User edit-details tests""" + @mock.patch('SoftLayer.CLI.user.edit_details.click') def test_edit_details(self, click): result = self.run_command(['user', 'edit-details', '1234', '-t', '{"firstName":"Supermand"}']) @@ -252,6 +253,7 @@ def test_edit_details_bad_json(self): self.assertEqual(result.exit_code, 2) """User delete tests""" + @mock.patch('SoftLayer.CLI.user.delete.click') def test_delete(self, click): result = self.run_command(['user', 'delete', '12345']) @@ -269,3 +271,17 @@ def test_delete_failure(self, click): self.assert_no_fail(result) self.assert_called_with('SoftLayer_User_Customer', 'editObject', args=({'userStatusId': 1021},), identifier=12345) + + """User vpn manual config tests""" + + @mock.patch('SoftLayer.CLI.user.vpn_manual.click') + def test_vpn_manual(self, click): + result = self.run_command(['user', 'vpn-manual', '12345', '--enable']) + click.secho.assert_called_with('12345 vpn manual config enable', fg='green') + self.assert_no_fail(result) + + def test_vpn_manual_fail(self): + mock = self.set_mock('SoftLayer_User_Customer', 'editObject') + mock.return_value = False + result = self.run_command(['user', 'vpn-manual', '12345', '--enable']) + self.assert_no_fail(result) From 4d297f2a665552d736425edd87c889359223c07f Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 3 Apr 2020 18:42:46 -0400 Subject: [PATCH 0085/1385] 1239 add vpn subnet access to a user --- SoftLayer/CLI/routes.py | 3 +- SoftLayer/CLI/user/vpn_subnet.py | 30 +++++++++++ ...SoftLayer_Network_Service_Vpn_Overrides.py | 2 + SoftLayer/fixtures/SoftLayer_User_Customer.py | 9 +++- SoftLayer/managers/user.py | 52 +++++++++++++++++-- tests/CLI/modules/user_tests.py | 20 +++++++ tests/managers/user_tests.py | 20 ++++++- 7 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 SoftLayer/CLI/user/vpn_subnet.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Service_Vpn_Overrides.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 6d976c3f4..4a326130b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -191,11 +191,9 @@ ('loadbal:order-options', 'SoftLayer.CLI.loadbal.order:order_options'), ('loadbal:cancel', 'SoftLayer.CLI.loadbal.order:cancel'), - ('loadbal:ns-detail', 'SoftLayer.CLI.loadbal.ns_detail:cli'), ('loadbal:ns-list', 'SoftLayer.CLI.loadbal.ns_list:cli'), - ('metadata', 'SoftLayer.CLI.metadata:cli'), ('nas', 'SoftLayer.CLI.nas'), @@ -305,6 +303,7 @@ ('user:create', 'SoftLayer.CLI.user.create:cli'), ('user:delete', 'SoftLayer.CLI.user.delete:cli'), ('user:vpn-manual', 'SoftLayer.CLI.user.vpn_manual:cli'), + ('user:vpn-subnet', 'SoftLayer.CLI.user.vpn_subnet:cli'), ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), diff --git a/SoftLayer/CLI/user/vpn_subnet.py b/SoftLayer/CLI/user/vpn_subnet.py new file mode 100644 index 000000000..0ea12284d --- /dev/null +++ b/SoftLayer/CLI/user/vpn_subnet.py @@ -0,0 +1,30 @@ +"""List Users.""" +# :license: MIT, see LICENSE for more details. + + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command() +@click.option('--add/--remove', default=True, + help="Add or remove access to subnets.") +@click.argument('user', nargs=1, required=True) +@click.argument('subnet', nargs=-1, required=True) +@environment.pass_env +def cli(env, user, add, subnet): + """Add or remove subnets access for a user.""" + mgr = SoftLayer.UserManager(env.client) + user_id = helpers.resolve_id(mgr.resolve_ids, user, 'username') + if add: + result = mgr.vpn_subnet_add(user_id, subnet) + else: + result = mgr.vpn_subnet_remove(user_id, subnet) + + if result: + click.secho("%s updated successfully" % (user), fg='green') + else: + click.secho("Failed to update %s" % (user), fg='red') diff --git a/SoftLayer/fixtures/SoftLayer_Network_Service_Vpn_Overrides.py b/SoftLayer/fixtures/SoftLayer_Network_Service_Vpn_Overrides.py new file mode 100644 index 000000000..a0c5caec2 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Service_Vpn_Overrides.py @@ -0,0 +1,2 @@ +createObjects = True +deleteObjects = True diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer.py b/SoftLayer/fixtures/SoftLayer_User_Customer.py index 4b30ba326..42c8f84cc 100644 --- a/SoftLayer/fixtures/SoftLayer_User_Customer.py +++ b/SoftLayer/fixtures/SoftLayer_User_Customer.py @@ -63,7 +63,6 @@ 'name': 'Add/Upgrade Storage (StorageLayer)'} ] - getLoginAttempts = [ { "createDate": "2017-10-03T09:28:33-06:00", @@ -74,8 +73,16 @@ } ] +getOverrides = [ + { + 'id': 3661234, + 'subnetId': 1234 + } +] + addBulkPortalPermission = True removeBulkPortalPermission = True createObject = getObject editObject = True addApiAuthenticationKey = True +updateVpnUser = True diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index df2d7b454..a299d6209 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -34,6 +34,7 @@ class UserManager(utils.IdentifierMixin, object): def __init__(self, client): self.client = client self.user_service = self.client['SoftLayer_User_Customer'] + self.override_service = self.client['Network_Service_Vpn_Overrides'] self.account_service = self.client['SoftLayer_Account'] self.resolvers = [self._get_id_from_username] self.all_permissions = None @@ -268,14 +269,59 @@ def add_api_authentication_key(self, user_id): return self.user_service.addApiAuthenticationKey(id=user_id) def vpn_manual(self, user_id, value): - """Set vpnManualConfig flag + """Enable or disable the manual config of subnets. - :param int user_id: User to edit - :param bool value: Value for vpnManualConfig flag + :param int user_id: User to edit. + :param bool value: Value for vpnManualConfig flag. """ user_object = {'vpnManualConfig': value} return self.edit_user(user_id, user_object) + def vpn_subnet_add(self, user_id, subnet_ids): + """Add subnets for a user. + + :param int user_id: User to edit. + :param list subnet_ids: list of subnet Ids. + """ + overrides = [{"userId": user_id, "subnetId": subnet_id} for subnet_id in subnet_ids] + return_value = self.override_service.createObjects(overrides) + return return_value and self.user_service.updateVpnUser(id=user_id) + + def vpn_subnet_remove(self, user_id, subnet_ids): + """Remove subnets for a user. + + :param int user_id: User to edit. + :param list subnet_ids: list of subnet Ids. + """ + overrides = self.get_overrides_list(user_id, subnet_ids) + return_value = self.override_service.deleteObjects(overrides) + return return_value and self.user_service.updateVpnUser(id=user_id) + + def get_overrides_list(self, user_id, subnet_ids): + """Converts a list of subnets to a list of overrides. + + :param int user_id: The ID of the user. + :param list subnet_ids: A list of subnets. + :returns: A list of overrides associated with the given subnets. + """ + + overrides_list = [] + matching_overrides = {} + output_error = "Subnet {} does not exist in the subnets assigned for user {}" + _mask = 'mask[id,subnetId]' + overrides = self.user_service.getOverrides(id=user_id, mask=_mask) + for subnet in subnet_ids: + for override in overrides: + if int(subnet) == override.get('subnetId'): + matching_overrides = override + break + if matching_overrides.get('subnetId') is None: + raise exceptions.SoftLayerError(output_error.format(subnet, user_id)) + + overrides_list.append(matching_overrides) + + return overrides_list + def _keyname_search(haystack, needle): for item in haystack: diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index d69e36a27..6f58c14a0 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -285,3 +285,23 @@ def test_vpn_manual_fail(self): mock.return_value = False result = self.run_command(['user', 'vpn-manual', '12345', '--enable']) self.assert_no_fail(result) + + """User vpn subnet tests""" + + @mock.patch('SoftLayer.CLI.user.vpn_subnet.click') + def test_vpn_subnet_add(self, click): + result = self.run_command(['user', 'vpn-subnet', '12345', '--add', '1234']) + click.secho.assert_called_with('12345 updated successfully', fg='green') + self.assert_no_fail(result) + + def test_vpn_subnet_add_fail(self): + mock = self.set_mock('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects') + mock.return_value = False + result = self.run_command(['user', 'vpn-subnet', '12345', '--add', '1234']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.user.vpn_subnet.click') + def test_vpn_subnet_remove(self, click): + result = self.run_command(['user', 'vpn-subnet', '12345', '--remove', '1234']) + click.secho.assert_called_with('12345 updated successfully', fg='green') + self.assert_no_fail(result) diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 66443de04..79b41e26f 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -9,7 +9,6 @@ from SoftLayer import exceptions from SoftLayer import testing - real_datetime_class = datetime.datetime @@ -18,6 +17,7 @@ def mock_datetime(target, datetime_module): https://solidgeargroup.com/mocking-the-time """ + class DatetimeSubclassMeta(type): @classmethod def __instancecheck__(mcs, obj): @@ -106,7 +106,6 @@ def test_get_logins_default(self): def test_get_events_default(self): target = datetime.datetime(2018, 5, 15) with mock_datetime(target, datetime): - self.manager.get_events(1234) expected_filter = { 'userId': { @@ -221,3 +220,20 @@ def test_create_user_handle_paas_exception(self): self.assertEqual(ex.args[0], "Your request for a new user was received, but it needs to be processed by " "the Platform Services API first. Barring any errors on the Platform Services " "side, your new user should be created shortly.") + + # def test_list_user_filter(self): + # test_filter = {'id': {'operation': 1234}} + # self.manager.list_users(objectfilter=test_filter) + # self.assert_called_with('SoftLayer_Account', 'getUsers', filter=test_filter) + + def test_vpn_manual(self): + self.manager.vpn_manual(1234, True) + self.assert_called_with('SoftLayer_User_Customer', 'editObject', identifier=1234) + + def test_vpn_subnet_add(self): + self.manager.vpn_subnet_add(1234, [1234]) + self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects') + + def test_vpn_subnet_remove(self): + self.manager.vpn_subnet_remove(1234, [1234]) + self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects') From 0a7ff724221e092b1a2c7516c642f11df2cc1a11 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 6 Apr 2020 17:15:59 -0500 Subject: [PATCH 0086/1385] #1247 added account billing-items/item-details/cancel-item commands --- SoftLayer/CLI/account/billing_items.py | 60 +++++++++++++ SoftLayer/CLI/account/cancel_item.py | 18 ++++ SoftLayer/CLI/account/item_detail.py | 52 +++++++++++ SoftLayer/CLI/formatting.py | 1 + SoftLayer/CLI/routes.py | 3 + SoftLayer/fixtures/SoftLayer_Account.py | 90 +++++++++++++++++++- SoftLayer/fixtures/SoftLayer_Billing_Item.py | 64 ++++++++++++++ SoftLayer/managers/account.py | 58 +++++++++++++ SoftLayer/utils.py | 36 +++++++- tests/CLI/modules/account_tests.py | 18 ++++ tests/managers/account_tests.py | 40 +++++++++ 11 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/account/billing_items.py create mode 100644 SoftLayer/CLI/account/cancel_item.py create mode 100644 SoftLayer/CLI/account/item_detail.py diff --git a/SoftLayer/CLI/account/billing_items.py b/SoftLayer/CLI/account/billing_items.py new file mode 100644 index 000000000..32bc6c271 --- /dev/null +++ b/SoftLayer/CLI/account/billing_items.py @@ -0,0 +1,60 @@ +"""Lists all active billing items on this account. See https://cloud.ibm.com/billing/billing-items""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """Lists billing items with some other useful information. + + Similiar to https://cloud.ibm.com/billing/billing-items + """ + + manager = AccountManager(env.client) + items = manager.get_account_billing_items() + table = item_table(items) + + env.fout(table) + + +def item_table(items): + """Formats a table for billing items""" + table = formatting.Table([ + "Id", + "Create Date", + "Cost", + "Category Code", + "Ordered By", + "Description", + "Notes" + ], title="Billing Items") + table.align['Description'] = 'l' + table.align['Category Code'] = 'l' + for item in items: + description = item.get('description') + fqdn = "{}.{}".format(item.get('hostName', ''), item.get('domainName', '')) + if fqdn != ".": + description = fqdn + user = utils.lookup(item, 'orderItem', 'order', 'userRecord') + ordered_by = "IBM" + create_date = utils.clean_time(item.get('createDate'), in_format='%Y-%m-%d', out_format='%Y-%m-%d') + if user: + # ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + ordered_by = user.get('displayName') + + table.add_row([ + item.get('id'), + create_date, + item.get('nextInvoiceTotalRecurringAmount'), + item.get('categoryCode'), + ordered_by, + utils.trim_to(description, 50), + utils.trim_to(item.get('notes', 'None'), 40), + ]) + return table diff --git a/SoftLayer/CLI/account/cancel_item.py b/SoftLayer/CLI/account/cancel_item.py new file mode 100644 index 000000000..de0fa446b --- /dev/null +++ b/SoftLayer/CLI/account/cancel_item.py @@ -0,0 +1,18 @@ +"""Cancels a billing item.""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.account import AccountManager as AccountManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancels a billing item.""" + + manager = AccountManager(env.client) + item = manager.cancel_item(identifier) + + env.fout(item) diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py new file mode 100644 index 000000000..7a2c53df3 --- /dev/null +++ b/SoftLayer/CLI/account/item_detail.py @@ -0,0 +1,52 @@ +"""Gets some details about a specific billing item.""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Gets detailed information about a billing item.""" + manager = AccountManager(env.client) + item = manager.get_billing_item(identifier) + env.fout(item_table(item)) + + +def item_table(item): + """Formats a table for billing items""" + + date_format = '%Y-%m-%d' + table = formatting.KeyValueTable(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) + table.add_row(['createDate', utils.clean_time(item.get('createDate'), date_format, date_format)]) + table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) + table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) + table.add_row(['description', item.get('description')]) + fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) + if fqdn != ".": + table.add_row(['FQDN', fqdn]) + + if item.get('hourlyFlag', False): + table.add_row(['hourlyRecurringFee', item.get('hourlyRecurringFee')]) + table.add_row(['hoursUsed', item.get('hoursUsed')]) + table.add_row(['currentHourlyCharge', item.get('currentHourlyCharge')]) + else: + table.add_row(['recurringFee', item.get('recurringFee')]) + + ordered_by = "IBM" + user = utils.lookup(item, 'orderItem', 'order', 'userRecord') + if user: + ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + table.add_row(['Ordered By', ordered_by]) + table.add_row(['Notes', item.get('notes')]) + table.add_row(['Location', utils.lookup(item, 'location', 'name')]) + if item.get('children'): + for child in item.get('children'): + table.add_row([child.get('categoryCode'), child.get('description')]) + + return table diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b591f814f..16e4a8d85 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -62,6 +62,7 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 # responds to .separator if hasattr(data, 'separator'): + print("THERE IS A SEPARATOR |{}|".format(data.separator)) output = [format_output(d, fmt=fmt) for d in data if d] return str(SequentialOutput(data.separator, output)) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f6edee475..b8fd294a1 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -17,6 +17,9 @@ ('account:events', 'SoftLayer.CLI.account.events:cli'), ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), ('account:summary', 'SoftLayer.CLI.account.summary:cli'), + ('account:billing-items', 'SoftLayer.CLI.account.billing_items:cli'), + ('account:item-detail', 'SoftLayer.CLI.account.item_detail:cli'), + ('account:cancel-item', 'SoftLayer.CLI.account.cancel_item:cli'), ('virtual', 'SoftLayer.CLI.virt'), ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index ddb2a4354..9524c61cb 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -417,7 +417,7 @@ getClosedTickets = [ticket for ticket in getTickets if ticket['statusId'] == 1002] -getCurrentUser = {'id': 12345, +getCurrentUser = {'id': 12345, 'username': 'testAccount', 'apiAuthenticationKeys': [{'authenticationKey': 'A' * 64}]} getCdnAccounts = [ @@ -852,3 +852,91 @@ } } ] + +getAllTopLevelBillingItems = [ + { + "allowCancellationFlag": 1, + "cancellationDate": "None", + "categoryCode": "server", + "createDate": "2015-05-28T09:53:41-06:00", + "cycleStartDate": "2020-04-03T23:12:04-06:00", + "description": "Dual E5-2690 v3 (12 Cores, 2.60 GHz)", + "domainName": "sl-netbase.com", + "hostName": "testsangeles101", + "id": 53891943, + "lastBillDate": "2020-04-03T23:12:04-06:00", + "modifyDate": "2020-04-03T23:12:07-06:00", + "nextBillDate": "2020-05-03T23:00:00-06:00", + "orderItemId": 68626055, + "parentId": "None", + "recurringFee": "1000", + "recurringFeeTaxRate": "0", + "recurringMonths": 1, + "hourlyFlag": False, + "location": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, + "nextInvoiceTotalRecurringAmount": 0, + "orderItem": { + "id": 68626055, + "order": { + "id": 4544893, + "userRecord": { + "displayName": "TEst", + "email": "test@us.ibm.com", + "id": 167758, + "userStatus": { + "id": 1001, + "keyName": "CANCEL_PENDING", + "name": "Cancel Pending" + } + } + } + }, + "resourceTableId": 544444 + }, + { + "allowCancellationFlag": 1, + "cancellationDate": "None", + "categoryCode": "server", + "createDate": "2015-05-28T09:56:44-06:00", + "cycleStartDate": "2020-04-03T23:12:05-06:00", + "description": "Dual E5-2690 v3 (12 Cores, 2.60 GHz)", + "domainName": "sl-netbase.com", + "hostName": "testsangeles101", + "id": 53892197, + "lastBillDate": "2020-04-03T23:12:05-06:00", + "modifyDate": "2020-04-03T23:12:07-06:00", + "nextBillDate": "2020-05-03T23:00:00-06:00", + "orderItemId": 68626801, + "recurringFee": "22220", + "hourlyFlag": False, + "location": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, + "nextInvoiceTotalRecurringAmount": 0, + "orderItem": { + "id": 68626801, + "order": { + "id": 4545911, + "userRecord": { + "displayName": "Test", + "email": "test@us.ibm.com", + "id": 167758, + "userStatus": { + "id": 1001, + "keyName": "ACTIVE", + "name": "Active" + } + } + } + }, + "resourceTableId": 777777 + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Item.py b/SoftLayer/fixtures/SoftLayer_Billing_Item.py index 6bcf84493..a35e51c6b 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Item.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Item.py @@ -1,3 +1,67 @@ cancelService = True cancelServiceOnAnniversaryDate = True cancelItem = True + +getObject = { + "allowCancellationFlag": 1, + "cancellationDate": "None", + "categoryCode": "server", + "createDate": "2015-05-28T10:36:38-06:00", + "cycleStartDate": "2020-04-03T23:12:05-06:00", + "description": "Dual E5-2690 v3 (12 Cores, 2.60 GHz)", + "domainName": "sl-test.com", + "hostName": "testsangeles101", + "id": 53897671, + "lastBillDate": "2020-04-03T23:12:05-06:00", + "modifyDate": "2020-04-03T23:12:07-06:00", + "nextBillDate": "2020-05-03T23:00:00-06:00", + "orderItemId": 68634907, + "parentId": "None", + "recurringFee": "1000", + "recurringMonths": 1, + "children": [ + { + "allowCancellationFlag": 1, + "associatedBillingItemId": "53897671", + "cancellationDate": "None", + "categoryCode": "second_processor", + "createDate": "2015-05-28T10:36:38-06:00", + "cycleStartDate": "2020-04-03T23:12:05-06:00", + "description": "E5-2690 v3 (12 Cores, 2.60 GHz)", + "id": 53897673, + "lastBillDate": "2020-04-03T23:12:05-06:00", + "modifyDate": "2020-04-03T23:12:07-06:00", + "nextBillDate": "2020-05-03T23:00:00-06:00", + "oneTimeFee": "0", + "oneTimeFeeTaxRate": "0", + "orderItemId": 68634909, + "parentId": 53897671, + "recurringFee": "1000", + "setupFeeTaxRate": "0" + }, + ], + "hourlyFlag": False, + "location": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, + "nextInvoiceTotalRecurringAmount": 0, + "orderItem": { + "id": 68634907, + "order": { + "id": 4546175, + "userRecord": { + "displayName": "Tester", + "email": "test@us.ibm.com", + "id": 167758, + "userStatus": { + "keyName": "ACTIVE", + "name": "Active" + } + } + } + }, + "resourceTableId": "None" +} diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 1f7d4871d..4269bc7a5 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -139,3 +139,61 @@ def get_billing_items(self, identifier): iter=True, limit=100 ) + + def get_account_billing_items(self, mask=None): + """Gets all the topLevelBillingItems currently active on the account + + :param string mask: Object Mask + :return: Billing_Item + """ + + if mask is None: + mask = """mask[ + orderItem[id,order[id,userRecord[id,email,displayName,userStatus]]], + nextInvoiceTotalRecurringAmount, + location, hourlyFlag + ]""" + + object_filter = { + "allTopLevelBillingItems": { + "cancellationDate": { + "operation": "is null" + }, + "createDate": utils.query_filter_orderby() + } + } + + return self.client.call('Account', 'getAllTopLevelBillingItems', + mask=mask, filter=object_filter, iter=True, limit=100) + + def get_billing_item(self, identifier, mask=None): + """Gets details about a billing item + + :param int identifier Billing_Item id + :param string mask: Object mask to use. + :return: Billing_Item + """ + + if mask is None: + mask = """mask[ + orderItem[id,order[id,userRecord[id,email,displayName,userStatus]]], + nextInvoiceTotalRecurringAmount, + location, hourlyFlag, children + ]""" + + return self.client.call('Billing_Item', 'getObject', id=identifier, mask=mask) + + def cancel_item(self, identifier, reason="No longer needed", note=None): + """Cancels a specific billing item with a reason + + :param int identifier: Billing_Item id + :param string reason: A cancellation reason + :param string note: Custom note to set when cancelling. Defaults to information about who canceled the item. + :return: bool + """ + + if note is None: + user = self.client.call('Account', 'getCurrentUser', mask="mask[id,displayName,email,username]") + note = "Cancelled by {} with the SLCLI".format(user.get('username')) + + return self.client.call('Billing_Item', 'cancelItem', False, True, reason, note, id=identifier) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 0234bf72d..cc6d7bd4f 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -121,6 +121,21 @@ def query_filter_date(start, end): } +def query_filter_orderby(sort="ASC"): + """Returns an object filter operation for sorting + + :param string sort: either ASC or DESC + """ + _filter = { + "operation": "orderBy", + "options": [{ + "name": "sort", + "value": [sort] + }] + } + return _filter + + def format_event_log_date(date_string, utc): """Gets a date in the format that the SoftLayer_EventLog object likes. @@ -305,7 +320,12 @@ def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H: clean = datetime.datetime.strptime(sltime, in_format) return clean.strftime(out_format) # The %z option only exists with py3.6+ - except ValueError: + except ValueError as e: + # Just ignore data that in_format didn't process. + ulr = len(e.args[0].partition('unconverted data remains: ')[2]) + if ulr: + clean = datetime.datetime.strptime(sltime[:-ulr], in_format) + return clean.strftime(out_format) return sltime @@ -334,3 +354,17 @@ def days_to_datetime(days): date -= datetime.timedelta(days=days) return date + + +def trim_to(string, length=80, tail="..."): + """Returns a string that is length long. tail added if trimmed + + :param string string: String you want to trim + :param int length: max length for the string + :param string tail: appended to strings that were trimmed. + """ + + if len(string) > length: + return string[:length] + tail + else: + return string diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index c495546c8..e231bb2be 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -93,3 +93,21 @@ def test_account_summary(self): result = self.run_command(['account', 'summary']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getObject') + + # slcli account billing-items + def test_account_billing_items(self): + result = self.run_command(['account', 'billing-items']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getAllTopLevelBillingItems') + + # slcli account item-detail + def test_account_get_billing_item_detail(self): + result = self.run_command(['account', 'item-detail', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier='12345') + + # slcli account cancel-item + def test_account_cancel_item(self): + result = self.run_command(['account', 'cancel-item', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier='12345') diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 7efc42acd..513aa44ff 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -52,3 +52,43 @@ def test_get_invoices_closed(self): def test_get_billing_items(self): self.manager.get_billing_items(12345) self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems') + + def test_get_account_billing_items(self): + self.manager.get_account_billing_items() + object_filter = { + "allTopLevelBillingItems": { + "cancellationDate": { + "operation": "is null" + }, + "createDate": { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['ASC'] + }] + } + } + } + + self.assert_called_with('SoftLayer_Account', 'getAllTopLevelBillingItems', + offset=0, limit=100, filter=object_filter) + self.manager.get_account_billing_items(mask="id") + self.assert_called_with('SoftLayer_Account', 'getAllTopLevelBillingItems', mask="mask[id]") + + def test_get_billing_item(self): + self.manager.get_billing_item(12345) + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=12345) + self.manager.get_billing_item(12345, mask="id") + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=12345, mask="mask[id]") + + def test_cancel_item(self): + self.manager.cancel_item(12345) + reason = "No longer needed" + note = "Cancelled by testAccount with the SLCLI" + self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', + args=(False, True, reason, note), identifier=12345) + reason = "TEST" + note = "note test" + self.manager.cancel_item(12345, reason, note) + self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', + args=(False, True, reason, note), identifier=12345) From 2b0ffd9101f4aa771aba88b187ac5a8512a28739 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 7 Apr 2020 16:26:08 -0400 Subject: [PATCH 0087/1385] add command docs/cli --- docs/cli/hardware.rst | 4 ++++ docs/cli/vs.rst | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 3cd899d4a..4fd245e22 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -36,6 +36,10 @@ Provides some basic functionality to order a server. `slcli order` has a more fu :prog: hw detail :show-nested: +.. click:: SoftLayer.CLI.hardware.billing:cli + :prog: hw billing + :show-nested: + .. click:: SoftLayer.CLI.hardware.edit:cli :prog: hw edit diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index afa0f8b2d..75db769a8 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -245,6 +245,10 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: vs usage :show-nested: +.. click:: SoftLayer.CLI.virt.billing:cli + :prog: vs billing + :show-nested: + From 620ccc8638b1d006f6668163828f03c628d38133 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Apr 2020 16:54:11 -0500 Subject: [PATCH 0088/1385] #1247 added docCheck.py to make sure new commands get documented --- SoftLayer/CLI/routes.py | 1 + docCheck.py | 94 +++++++++++++++++++++++++++++++++++++++++ docs/cli/account.rst | 12 ++++++ docs/cli/block.rst | 8 ++++ docs/cli/file.rst | 8 ++++ docs/cli/hardware.rst | 40 +++++++++--------- docs/cli/image.rst | 4 ++ docs/cli/ipsec.rst | 54 +++++++++++++++++++++++ docs/cli/loadbal.rst | 4 +- docs/cli/nas.rst | 12 ++++++ docs/cli/vs.rst | 46 ++++++++++++-------- tox.ini | 6 ++- 12 files changed, 249 insertions(+), 40 deletions(-) create mode 100644 docCheck.py create mode 100644 docs/cli/nas.rst diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b8fd294a1..5d7d89f40 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -226,6 +226,7 @@ ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), ('rwhois:show', 'SoftLayer.CLI.rwhois.show:cli'), + ('rwhois:sho1w', 'SoftLayer.CLI.rwhois.show1:cli'), ('hardware', 'SoftLayer.CLI.hardware'), ('hardware:bandwidth', 'SoftLayer.CLI.hardware.bandwidth:cli'), diff --git a/docCheck.py b/docCheck.py new file mode 100644 index 000000000..f6b11ba36 --- /dev/null +++ b/docCheck.py @@ -0,0 +1,94 @@ +"""Makes sure all routes have documentation""" +import SoftLayer +from SoftLayer.CLI import routes +from pprint import pprint as pp +import glob +import logging +import os +import sys +import re + +class Checker(): + + def __init__(self): + pass + + def getDocFiles(self, path=None): + files = [] + if path is None: + path = ".{seper}docs{seper}cli".format(seper=os.path.sep) + for file in glob.glob(path + '/*', recursive=True): + if os.path.isdir(file): + files = files + self.getDocFiles(file) + else: + files.append(file) + return files + + def readDocs(self, path=None): + files = self.getDocFiles(path) + commands = {} + click_regex = re.compile(r"\.\. click:: ([a-zA-Z0-9_\.:]*)") + prog_regex = re.compile(r"\W*:prog: (.*)") + + for file in files: + click_line = '' + prog_line = '' + with open(file, 'r') as f: + for line in f: + click_match = re.match(click_regex, line) + prog_match = False + if click_match: + click_line = click_match.group(1) + + # Prog line should always be directly after click line. + prog_match = re.match(prog_regex, f.readline()) + if prog_match: + prog_line = prog_match.group(1).replace(" ", ":") + commands[prog_line] = click_line + click_line = '' + prog_line = '' + # pp(commands) + return commands + + def checkCommand(self, command, documented_commands): + """Sees if a command is documented + + :param tuple command: like the entry in the routes file ('command:action', 'SoftLayer.CLI.module.function') + :param documented_commands: dictionary of commands found to be auto-documented. + """ + + # These commands use a slightly different loader. + ignored = [ + 'virtual:capacity', + 'virtual:placementgroup', + 'object-storage:credential' + ] + if command[0] in ignored: + return True + if documented_commands.get(command[0], False) == command[1]: + return True + return False + + + def main(self, debug=0): + existing_commands = routes.ALL_ROUTES + documented_commands = self.readDocs() + # pp(documented_commands) + exitCode = 0 + for command in existing_commands: + if (command[1].find(":") == -1): # Header commands in the routes file, dont need documentaiton. + continue + else: + if self.checkCommand(command, documented_commands): + if debug: + print("{} is documented".format(command[0])) + + else: + print("===> {} {} IS UNDOCUMENTED <===".format(command[0], command[1])) + exitCode = 1 + sys.exit(exitCode) + + +if __name__ == "__main__": + main = Checker() + main.main() diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 9b3ad6954..c34f37d7d 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -22,4 +22,16 @@ Account Commands .. click:: SoftLayer.CLI.account.invoice_detail:cli :prog: account invoice-detail + :show-nested: + +.. click:: SoftLayer.CLI.account.billing_items:cli + :prog: account billing-items + :show-nested: + +.. click:: SoftLayer.CLI.account.item_detail:cli + :prog: account item-detail + :show-nested: + +.. click:: SoftLayer.CLI.account.cancel_item:cli + :prog: account cancel-item :show-nested: \ No newline at end of file diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 860872ce7..8b31d5a99 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -130,3 +130,11 @@ Block Commands .. click:: SoftLayer.CLI.block.subnets.remove:cli :prog: block subnets-remove :show-nested: + +.. click:: SoftLayer.CLI.block.refresh:cli + :prog: block volume-refresh + :show-nested: + +.. click:: SoftLayer.CLI.block.convert:cli + :prog: block volume-convert + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 13cf92a61..52dad83a4 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -110,3 +110,11 @@ File Commands .. click:: SoftLayer.CLI.file.snapshot.schedule_list:cli :prog: file snapshot-schedule-list :show-nested: + +.. click:: SoftLayer.CLI.file.refresh:cli + :prog: file volume-refresh + :show-nested: + +.. click:: SoftLayer.CLI.file.convert:cli + :prog: file volume-convert + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 3cd899d4a..e99413c90 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -5,40 +5,40 @@ Interacting with Hardware .. click:: SoftLayer.CLI.hardware.bandwidth:cli - :prog: hw bandwidth + :prog: hardware bandwidth :show-nested: .. click:: SoftLayer.CLI.hardware.cancel_reasons:cli - :prog: hw cancel-reasons + :prog: hardware cancel-reasons :show-nested: .. click:: SoftLayer.CLI.hardware.cancel:cli - :prog: hw cancel + :prog: hardware cancel :show-nested: .. click:: SoftLayer.CLI.hardware.create_options:cli - :prog: hw create-options + :prog: hardware create-options :show-nested: .. click:: SoftLayer.CLI.hardware.create:cli - :prog: hw create + :prog: hardware create :show-nested: Provides some basic functionality to order a server. `slcli order` has a more full featured method of ordering servers. This command only supports the FAST_PROVISION type. .. click:: SoftLayer.CLI.hardware.credentials:cli - :prog: hw credentials + :prog: hardware credentials :show-nested: .. click:: SoftLayer.CLI.hardware.detail:cli - :prog: hw detail + :prog: hardware detail :show-nested: .. click:: SoftLayer.CLI.hardware.edit:cli - :prog: hw edit + :prog: hardware edit :show-nested: **Note :** Using multiple ' **:** ' can cause an error. @@ -53,55 +53,55 @@ Provides some basic functionality to order a server. `slcli order` has a more fu When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. .. click:: SoftLayer.CLI.hardware.list:cli - :prog: hw list + :prog: hardware list :show-nested: .. click:: SoftLayer.CLI.hardware.power:power_cycle - :prog: hw power-cycle + :prog: hardware power-cycle :show-nested: .. click:: SoftLayer.CLI.hardware.power:power_off - :prog: hw power-off + :prog: hardware power-off :show-nested: .. click:: SoftLayer.CLI.hardware.power:power_on - :prog: hw power-on + :prog: hardware power-on :show-nested: .. click:: SoftLayer.CLI.hardware.power:reboot - :prog: hw reboot + :prog: hardware reboot :show-nested: .. click:: SoftLayer.CLI.hardware.reload:cli - :prog: hw reload + :prog: hardware reload :show-nested: .. click:: SoftLayer.CLI.hardware.power:rescue - :prog: hw rescue + :prog: hardware rescue .. click:: SoftLayer.CLI.hardware.reflash_firmware:cli - :prog: hw reflash-firmware + :prog: hardware reflash-firmware :show-nested: Reflash here means the current version of the firmware running on your server will be re-flashed onto the selected hardware. This does require a reboot. See `slcli hw update-firmware` if you want the newest version. .. click:: SoftLayer.CLI.hardware.update_firmware:cli - :prog: hw update-firmware + :prog: hardware update-firmware :show-nested: This function updates the firmware of a server. If already at the latest version, no software is installed. .. click:: SoftLayer.CLI.hardware.toggle_ipmi:cli - :prog: hw toggle-ipmi + :prog: hardware toggle-ipmi :show-nested: .. click:: SoftLayer.CLI.hardware.ready:cli - :prog: hw ready + :prog: hardware ready :show-nested: .. click:: SoftLayer.CLI.hardware.dns:cli - :prog: hw dns-sync + :prog: hardware dns-sync :show-nested: diff --git a/docs/cli/image.rst b/docs/cli/image.rst index 771abd16c..93ba13321 100644 --- a/docs/cli/image.rst +++ b/docs/cli/image.rst @@ -26,3 +26,7 @@ Disk Image Commands .. click:: SoftLayer.CLI.image.export:cli :prog: image export :show-nested: + +.. click:: SoftLayer.CLI.image.datacenter:cli + :prog: image datacenter + :show-nested: diff --git a/docs/cli/ipsec.rst b/docs/cli/ipsec.rst index 2786a5ed0..cc1ed5b11 100644 --- a/docs/cli/ipsec.rst +++ b/docs/cli/ipsec.rst @@ -14,6 +14,12 @@ To see more information about the IPSEC tunnel context module and API internacti ipsec list ---------- + + +.. click:: SoftLayer.CLI.vpn.ipsec.list:cli + :prog: ipsec list + :show-nested: + A list of all IPSEC tunnel contexts associated with the current user's account can be retrieved via the ``ipsec list`` command. This provides a brief overview of all tunnel contexts and can be used to retrieve an individual context's identifier, which all other CLI commands require. :: @@ -28,6 +34,12 @@ A list of all IPSEC tunnel contexts associated with the current user's account c ipsec detail ------------ + +.. click:: SoftLayer.CLI.vpn.ipsec.detail:cli + :prog: ipsec detail + :show-nested: + + More detailed information can be retrieved for an individual context using the ``ipsec detail`` command. Using the detail command, information about associated internal subnets, remote subnets, static subnets, service subnets and address translations may also be retrieved using multiple instances of the ``-i|--include`` option. :: @@ -91,6 +103,12 @@ More detailed information can be retrieved for an individual context using the ` ipsec update ------------ + +.. click:: SoftLayer.CLI.vpn.ipsec.update:cli + :prog: ipsec update + :show-nested: + + Most values listed in the tunnel context detail printout can be modified using the ``ipsec update`` command. The following is given when executing with the ``-h|--help`` option and highlights all properties that may be modified. :: @@ -134,6 +152,12 @@ Most values listed in the tunnel context detail printout can be modified using t ipsec configure --------------- + +.. click:: SoftLayer.CLI.vpn.ipsec.configure:cli + :prog: ipsec configure + :show-nested: + + A request to configure SoftLayer network devices for a given tunnel context can be issued using the ``ipsec configure`` command. .. note:: @@ -144,6 +168,12 @@ A request to configure SoftLayer network devices for a given tunnel context can ipsec subnet-add ---------------- + +.. click:: SoftLayer.CLI.vpn.ipsec.subnet.add:cli + :prog: ipsec subnet-add + :show-nested: + + Internal, remote and service subnets can be associated to an IPSEC tunnel context using the ``ipsec subnet-add`` command. Additionally, remote subnets can be created using this same command, which will then be associated to the targeted tunnel context. .. note:: @@ -167,6 +197,13 @@ The following is an example of creating and associating a remote subnet to a tun ipsec subnet-remove ------------------- + +.. click:: SoftLayer.CLI.vpn.ipsec.subnet.remove:cli + :prog: ipsec subnet-remove + :show-nested: + + + Internal, remote and service subnets can be disassociated from an IPSEC tunnel context via the ``ipsec subnet-remove`` command. .. note:: @@ -183,6 +220,12 @@ The following is an example of disassociating an internal subnet from a tunnel c ipsec translation-add --------------------- + +.. click:: SoftLayer.CLI.vpn.ipsec.translation.add:cli + :prog: ipsec translation-add + :show-nested: + + Address translation entries can be added to a tunnel context to provide NAT functionality from a statically routed subnet associated with the tunnel context to a remote subnet. This action is performed with the ``ipsec translation-add`` command. .. note:: @@ -199,6 +242,12 @@ The following is an example of adding a new address translation entry. ipsec translation-remove ------------------------ + +.. click:: SoftLayer.CLI.vpn.ipsec.translation.remove:cli + :prog: ipsec translation-remove + :show-nested: + + Address translation entries can be removed using the ``ipsec translation-remove`` command. The following is an example of removing an address translation entry. @@ -211,6 +260,11 @@ The following is an example of removing an address translation entry. ipsec translation-update ------------------------ + +.. click:: SoftLayer.CLI.vpn.ipsec.translation.update:cli + :prog: ipsec translation-update + :show-nested: + Address translation entries may also be modified using the ``ipsec translation-update`` command. The following is an example of updating an existing address translation entry. diff --git a/docs/cli/loadbal.rst b/docs/cli/loadbal.rst index cec4200fd..a4116b877 100644 --- a/docs/cli/loadbal.rst +++ b/docs/cli/loadbal.rst @@ -23,7 +23,7 @@ LBaaS Commands :prog: loadbal member-add :show-nested: .. click:: SoftLayer.CLI.loadbal.members:remove - :prog: loadbal member-remove + :prog: loadbal member-del :show-nested: .. click:: SoftLayer.CLI.loadbal.pools:add :prog: loadbal pool-add @@ -32,7 +32,7 @@ LBaaS Commands :prog: loadbal pool-edit :show-nested: .. click:: SoftLayer.CLI.loadbal.pools:delete - :prog: loadbal pool-delete + :prog: loadbal pool-del :show-nested: .. click:: SoftLayer.CLI.loadbal.pools:l7pool_add :prog: loadbal l7pool-add diff --git a/docs/cli/nas.rst b/docs/cli/nas.rst new file mode 100644 index 000000000..024744919 --- /dev/null +++ b/docs/cli/nas.rst @@ -0,0 +1,12 @@ +.. _cli_nas: + +NAS Commands +============ + +.. click:: SoftLayer.CLI.nas.list:cli + :prog: nas list + :show-nested: + +.. click:: SoftLayer.CLI.nas.credentials:cli + :prog: nas credentials + :show-nested: \ No newline at end of file diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index afa0f8b2d..96bb5a9d2 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -173,79 +173,91 @@ username is 'root' and password is 'ABCDEFGH'. .. click:: SoftLayer.CLI.virt.bandwidth:cli - :prog: vs bandwidth + :prog: virtual bandwidth :show-nested: If no timezone is specified, IMS local time (CST) will be assumed, which might not match your user's selected timezone. .. click:: SoftLayer.CLI.virt.cancel:cli - :prog: vs cancel + :prog: virtual cancel :show-nested: .. click:: SoftLayer.CLI.virt.capture:cli - :prog: vs capture + :prog: virtual capture :show-nested: .. click:: SoftLayer.CLI.virt.create:cli - :prog: vs create + :prog: virtual create :show-nested: .. click:: SoftLayer.CLI.virt.create_options:cli - :prog: vs create-options + :prog: virtual create-options :show-nested: .. click:: SoftLayer.CLI.virt.dns:cli - :prog: vs dns-sync + :prog: virtual dns-sync :show-nested: .. click:: SoftLayer.CLI.virt.edit:cli - :prog: vs edit + :prog: virtual edit :show-nested: .. click:: SoftLayer.CLI.virt.list:cli - :prog: vs list + :prog: virtual list :show-nested: .. click:: SoftLayer.CLI.virt.power:pause - :prog: vs pause + :prog: virtual pause :show-nested: .. click:: SoftLayer.CLI.virt.power:power_on - :prog: vs power-on + :prog: virtual power-on :show-nested: .. click:: SoftLayer.CLI.virt.power:power_off - :prog: vs power-off + :prog: virtual power-off :show-nested: .. click:: SoftLayer.CLI.virt.power:resume - :prog: vs resume + :prog: virtual resume :show-nested: .. click:: SoftLayer.CLI.virt.power:rescue - :prog: vs rescue + :prog: virtual rescue :show-nested: .. click:: SoftLayer.CLI.virt.power:reboot - :prog: vs reboot + :prog: virtual reboot :show-nested: .. click:: SoftLayer.CLI.virt.ready:cli - :prog: vs ready + :prog: virtual ready :show-nested: .. click:: SoftLayer.CLI.virt.upgrade:cli - :prog: vs upgrade + :prog: virtual upgrade :show-nested: .. click:: SoftLayer.CLI.virt.usage:cli - :prog: vs usage + :prog: virtual usage :show-nested: +.. click:: SoftLayer.CLI.virt.detail:cli + :prog: virtual detail + :show-nested: + + +.. click:: SoftLayer.CLI.virt.reload:cli + :prog: virtual reload + :show-nested: + +.. click:: SoftLayer.CLI.virt.credentials:cli + :prog: virtual credentials + :show-nested: Reserved Capacity diff --git a/tox.ini b/tox.ini index 22af57229..c9b3b5e72 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38,pypy3,analysis,coverage +envlist = py35,py36,py37,py38,pypy3,analysis,coverage,docs [flake8] @@ -57,3 +57,7 @@ commands = --min-similarity-lines=50 \ --max-line-length=120 \ -r n + +[testenv:docs] +commands = + python ./docCheck.py \ No newline at end of file From 0759c7d426fbfe212e230598264c2d6ace82af12 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Apr 2020 16:55:43 -0500 Subject: [PATCH 0089/1385] removed a bad route --- SoftLayer/CLI/routes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5d7d89f40..b8fd294a1 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -226,7 +226,6 @@ ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), ('rwhois:show', 'SoftLayer.CLI.rwhois.show:cli'), - ('rwhois:sho1w', 'SoftLayer.CLI.rwhois.show1:cli'), ('hardware', 'SoftLayer.CLI.hardware'), ('hardware:bandwidth', 'SoftLayer.CLI.hardware.bandwidth:cli'), From 5970edbf1ea8bc1b73c4d2655eb789ff11d8373b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Apr 2020 17:14:34 -0500 Subject: [PATCH 0090/1385] added a github workflow to see how it works --- .github/workflows/documentation.yml | 27 +++++++++++++++++++++++++++ tools/test-requirements.txt | 1 + 2 files changed, 28 insertions(+) create mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 000000000..424eb7c26 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,27 @@ +name: softlayer + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Documentation Checks + run: | + python docCheck.py diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index 3080abf43..d417e04c1 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -1,4 +1,5 @@ tox +coveralls pytest pytest-cov mock From 5eced1617c8c2557632435796ece74965b8f6ce7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Apr 2020 17:27:39 -0500 Subject: [PATCH 0091/1385] removed debug test --- SoftLayer/CLI/formatting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 16e4a8d85..b591f814f 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -62,7 +62,6 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 # responds to .separator if hasattr(data, 'separator'): - print("THERE IS A SEPARATOR |{}|".format(data.separator)) output = [format_output(d, fmt=fmt) for d in data if d] return str(SequentialOutput(data.separator, output)) From 0a325133b4cedc332a8da786a225b7e1275938cc Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 8 Apr 2020 12:05:23 -0400 Subject: [PATCH 0092/1385] #1239 fix cli user docs --- SoftLayer/CLI/user/vpn_manual.py | 4 ++-- SoftLayer/CLI/user/vpn_subnet.py | 2 +- docs/cli/users.rst | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/user/vpn_manual.py b/SoftLayer/CLI/user/vpn_manual.py index fb8ac78fc..35e92f758 100644 --- a/SoftLayer/CLI/user/vpn_manual.py +++ b/SoftLayer/CLI/user/vpn_manual.py @@ -1,4 +1,4 @@ -"""List Users.""" +"""Enable or Disable vpn subnets manual config for a user.""" # :license: MIT, see LICENSE for more details. @@ -12,7 +12,7 @@ @click.command() @click.argument('user') @click.option('--enable/--disable', default=True, - help="Whether enable or disable vpnManualConfig flag.") + help="Enable or disable vpn subnets manual config.") @environment.pass_env def cli(env, user, enable): """Enable or disable user vpn subnets manual config""" diff --git a/SoftLayer/CLI/user/vpn_subnet.py b/SoftLayer/CLI/user/vpn_subnet.py index 0ea12284d..627d58542 100644 --- a/SoftLayer/CLI/user/vpn_subnet.py +++ b/SoftLayer/CLI/user/vpn_subnet.py @@ -1,4 +1,4 @@ -"""List Users.""" +"""Add or remove specific subnets access for a user.""" # :license: MIT, see LICENSE for more details. diff --git a/docs/cli/users.rst b/docs/cli/users.rst index 5058d0652..21ff1b7b8 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -32,4 +32,12 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli :prog: user delete :show-nested: +.. click:: SoftLayer.CLI.user.vpn-manual:cli + :prog: user vpn-manual + :show-nested: + +.. click:: SoftLayer.CLI.user.vpn-subnet:cli + :prog: user vpn-subnet + :show-nested: + From ce8b5f1c713b246d9d63db386d8cec232e4b07ec Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 8 Apr 2020 12:33:04 -0400 Subject: [PATCH 0093/1385] #1239 improve methods add remove subnet from user --- SoftLayer/managers/user.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index a299d6209..5875d76a8 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -285,7 +285,10 @@ def vpn_subnet_add(self, user_id, subnet_ids): """ overrides = [{"userId": user_id, "subnetId": subnet_id} for subnet_id in subnet_ids] return_value = self.override_service.createObjects(overrides) - return return_value and self.user_service.updateVpnUser(id=user_id) + update_success = self.user_service.updateVpnUser(id=user_id) + if not update_success: + raise exceptions.SoftLayerAPIError("Overrides created, but unable to update VPN user") + return return_value def vpn_subnet_remove(self, user_id, subnet_ids): """Remove subnets for a user. @@ -295,7 +298,10 @@ def vpn_subnet_remove(self, user_id, subnet_ids): """ overrides = self.get_overrides_list(user_id, subnet_ids) return_value = self.override_service.deleteObjects(overrides) - return return_value and self.user_service.updateVpnUser(id=user_id) + update_success = self.user_service.updateVpnUser(id=user_id) + if not update_success: + raise exceptions.SoftLayerAPIError("Overrides deleted, but unable to update VPN user") + return return_value def get_overrides_list(self, user_id, subnet_ids): """Converts a list of subnets to a list of overrides. From 423eda1495e63e7420d43483caf663428839830c Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 8 Apr 2020 16:00:56 -0400 Subject: [PATCH 0094/1385] #1239 fix user manager subnet tests --- tests/managers/user_tests.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 79b41e26f..b75a5a772 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -221,19 +221,28 @@ def test_create_user_handle_paas_exception(self): "the Platform Services API first. Barring any errors on the Platform Services " "side, your new user should be created shortly.") - # def test_list_user_filter(self): - # test_filter = {'id': {'operation': 1234}} - # self.manager.list_users(objectfilter=test_filter) - # self.assert_called_with('SoftLayer_Account', 'getUsers', filter=test_filter) - def test_vpn_manual(self): - self.manager.vpn_manual(1234, True) - self.assert_called_with('SoftLayer_User_Customer', 'editObject', identifier=1234) + user_id = 1234 + self.manager.vpn_manual(user_id, True) + self.assert_called_with('SoftLayer_User_Customer', 'editObject', identifier=user_id) def test_vpn_subnet_add(self): - self.manager.vpn_subnet_add(1234, [1234]) - self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects') + user_id = 1234 + subnet_id = 1234 + expected_args = ( + [{"userId": user_id, "subnetId": subnet_id}], + ) + self.manager.vpn_subnet_add(user_id, [subnet_id]) + self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects', args=expected_args) + self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) def test_vpn_subnet_remove(self): - self.manager.vpn_subnet_remove(1234, [1234]) - self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects') + user_id = 1234 + subnet_id = 1234 + overrides = [{'id': 3661234, 'subnetId': subnet_id}] + expected_args = ( + overrides, + ) + self.manager.vpn_subnet_remove(user_id, [subnet_id]) + self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects', args=expected_args) + self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) From bfa4731c38a625153bd76ebdbfca740fdc6b0394 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 8 Apr 2020 16:02:10 -0400 Subject: [PATCH 0095/1385] Add docs for vs and hardware storage. --- docs/cli/hardware.rst | 4 ++++ docs/cli/vs.rst | 3 +++ 2 files changed, 7 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 3cd899d4a..fdedad4f6 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -105,3 +105,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.dns:cli :prog: hw dns-sync :show-nested: + +.. click:: SoftLayer.CLI.hardware.storage:cli + :prog: hw storage + :show-nested: diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index afa0f8b2d..70b5631c5 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -245,6 +245,9 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: vs usage :show-nested: +.. click:: SoftLayer.CLI.virt.storage:cli + :prog: vs storage + :show-nested: From 788b7a0957c47c4e22329db9337bcac89ff34240 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 8 Apr 2020 16:15:13 -0400 Subject: [PATCH 0096/1385] Fix tox analysis. --- SoftLayer/CLI/hardware/storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py index 46cb30be8..9af2f387c 100644 --- a/SoftLayer/CLI/hardware/storage.py +++ b/SoftLayer/CLI/hardware/storage.py @@ -45,13 +45,13 @@ def cli(env, identifier): table_hard_drives = formatting.Table(['Type', 'Name', 'Capacity', 'Serial #'], title="Other storage details") for drives in hard_drives: - type = drives['hardwareComponentModel']['hardwareGenericComponentModel']['hardwareComponentType']['type'] + type_drive = drives['hardwareComponentModel']['hardwareGenericComponentModel']['hardwareComponentType']['type'] name = drives['hardwareComponentModel']['manufacturer'] + " " + drives['hardwareComponentModel']['name'] capacity = str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) + " " + str( drives['hardwareComponentModel']['hardwareGenericComponentModel']['units']) serial = drives['serialNumber'] - table_hard_drives.add_row([type, name, capacity, serial]) + table_hard_drives.add_row([type_drive, name, capacity, serial]) env.fout(table_credentials) env.fout(table_iscsi) From 16ad4e0972a7c5cc25778d4ee19366dd5a112bee Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 8 Apr 2020 16:15:44 -0400 Subject: [PATCH 0097/1385] fix the problems with tox --- SoftLayer/CLI/hardware/billing.py | 2 +- tests/CLI/modules/server_tests.py | 21 +++++++++------------ tests/CLI/modules/vs/vs_tests.py | 23 +++++++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index 03b32e7f6..55c68c485 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -32,7 +32,7 @@ def cli(env, identifier): price_table = formatting.Table(['Item', 'Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['Description'], item['NextInvoiceTotalRecurringAmount']]) + price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) env.fout(table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 16af3a235..588ed4d1e 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -830,18 +830,15 @@ def test_dns_sync_misc_exception(self, confirm_mock): def test_billing(self): result = self.run_command(['hw', 'billing', '123456']) billing_json = { - "hardwareId": "123456", - "BillingIttem": 6327, - "recurringFee": 1.54, - "Total": 16.08, - "provisionDate": None, - "prices": [ - { - "Item": "test", - "Recurring Price": 1 - } - ] + 'Billing Item Id': 6327, + 'Id': '123456', + 'Provision Date': None, + 'Recurring Fee': 1.54, + 'Total': 16.08, + 'prices': [{ + 'Item': 'test', + 'Recurring Price': 1 + }] } - self.assert_no_fail(result) self.assertEqual(json.loads(result.output), billing_json) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index e82bf4e87..9fbe699c2 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -744,15 +744,18 @@ def test_bandwidth_vs_quite(self): def test_billing(self): result = self.run_command(['vs', 'billing', '123456']) vir_billing = { - "BillingIttem": 6327, - "Total": 1.54, - "VirtuallId": "123456", - "prices": [{"Recurring Price": 1}, - {"Recurring Price": 1}, - {"Recurring Price": 1}, - {"Recurring Price": 1}, - {"Recurring Price": 1}], - "provisionDate": None, - "recurringFee": None} + 'Billing Item Id': 6327, + 'Id': '123456', + 'Provision Date': None, + 'Recurring Fee': None, + 'Total': 1.54, + 'prices': [ + {'Recurring Price': 1}, + {'Recurring Price': 1}, + {'Recurring Price': 1}, + {'Recurring Price': 1}, + {'Recurring Price': 1} + ] + } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), vir_billing) From f5fd7298eab6d1f2b70b4c7f16c4d4104987a290 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 8 Apr 2020 16:42:40 -0400 Subject: [PATCH 0098/1385] fix the Christopher code review --- SoftLayer/CLI/ticket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index b08663322..ebeee3bb3 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -66,6 +66,6 @@ def get_ticket_results(mgr, ticket_id, is_json, update_count=1): wrapped_entry += click.wrap_text(update['entry'].replace('\r', '')) if is_json: if '\n' in wrapped_entry: - wrapped_entry = re.sub(r"(? Date: Mon, 13 Apr 2020 16:00:54 -0400 Subject: [PATCH 0099/1385] fix the travis error --- SoftLayer/CLI/ticket/__init__.py | 7 +++---- tests/CLI/modules/ticket_tests.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index ebeee3bb3..a8ffae6e0 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -17,7 +17,7 @@ ] -def get_ticket_results(mgr, ticket_id, is_json, update_count=1): +def get_ticket_results(mgr, ticket_id, is_json=False, update_count=1): """Get output about a ticket. :param integer id: the ticket ID @@ -64,8 +64,7 @@ def get_ticket_results(mgr, ticket_id, is_json, update_count=1): # NOTE(kmcdonald): Windows new-line characters need to be stripped out wrapped_entry += click.wrap_text(update['entry'].replace('\r', '')) - if is_json: - if '\n' in wrapped_entry: - wrapped_entry = re.sub(r"(? Date: Mon, 13 Apr 2020 16:39:56 -0400 Subject: [PATCH 0100/1385] fix the travis error --- SoftLayer/CLI/ticket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index a8ffae6e0..81c248322 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -17,7 +17,7 @@ ] -def get_ticket_results(mgr, ticket_id, is_json=False, update_count=1): +def get_ticket_results(mgr, ticket_id, is_json = False, update_count=1): """Get output about a ticket. :param integer id: the ticket ID From 47b03f304c9873ab0e94d0453175f9be8d658210 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 14 Apr 2020 11:22:40 -0400 Subject: [PATCH 0101/1385] fix the doCheck.py --- docs/cli/hardware.rst | 4 ++-- docs/cli/users.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index cd97c9d0d..0cce23042 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -37,7 +37,7 @@ Provides some basic functionality to order a server. `slcli order` has a more fu :show-nested: .. click:: SoftLayer.CLI.hardware.billing:cli - :prog: hw billing + :prog: hardware billing :show-nested: @@ -111,5 +111,5 @@ This function updates the firmware of a server. If already at the latest version :show-nested: .. click:: SoftLayer.CLI.hardware.storage:cli - :prog: hw storage + :prog: hardware storage :show-nested: diff --git a/docs/cli/users.rst b/docs/cli/users.rst index 21ff1b7b8..feb94e352 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -32,11 +32,11 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli :prog: user delete :show-nested: -.. click:: SoftLayer.CLI.user.vpn-manual:cli +.. click:: SoftLayer.CLI.user.vpn_manual:cli :prog: user vpn-manual :show-nested: -.. click:: SoftLayer.CLI.user.vpn-subnet:cli +.. click:: SoftLayer.CLI.user.vpn_subnet:cli :prog: user vpn-subnet :show-nested: From a714a1129dc5236b0ddc24dc95fecfdf2d822255 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 Apr 2020 14:28:51 -0500 Subject: [PATCH 0102/1385] Adding more github action tests, removing travis CI tests --- .github/workflows/documentation.yml | 2 +- .github/workflows/tests.yml | 54 +++++++++++++++++++++++++++++ .travis.yml | 27 --------------- SoftLayer/CLI/ticket/__init__.py | 4 +-- SoftLayer/CLI/ticket/detail.py | 2 +- 5 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 424eb7c26..c713212ee 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,4 +1,4 @@ -name: softlayer +name: documentation on: push: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..d2cab7484 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,54 @@ +name: Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5,3.6,3.7,3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox + run: tox -e + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox + run: tox -e coverage + analysis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox + run: tox -e analysis \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 35c987a63..000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -# https://docs.travis-ci.com/user/languages/python/#python-37-and-higher -dist: bionic -language: python -sudo: false -matrix: - include: - - python: "3.5" - env: TOX_ENV=py35 - - python: "3.6" - env: TOX_ENV=py36 - - python: "3.7" - env: TOX_ENV=py37 - - python: "3.8" - env: TOX_ENV=py38 - - python: "pypy3" - env: TOX_ENV=pypy3 - - python: "3.6" - env: TOX_ENV=analysis - - python: "3.6" - env: TOX_ENV=coverage -install: - - pip install tox - - pip install coveralls -script: - - tox -e $TOX_ENV -after_success: - - if [[ $TOX_ENV = "coverage" ]]; then coveralls; fi diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index 81c248322..3aec9744b 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -1,7 +1,7 @@ """Support tickets.""" +import re import click -import re from SoftLayer.CLI import formatting @@ -17,7 +17,7 @@ ] -def get_ticket_results(mgr, ticket_id, is_json = False, update_count=1): +def get_ticket_results(mgr, ticket_id, is_json=False, update_count=1): """Get output about a ticket. :param integer id: the ticket ID diff --git a/SoftLayer/CLI/ticket/detail.py b/SoftLayer/CLI/ticket/detail.py index a7c59416e..fad8a644a 100644 --- a/SoftLayer/CLI/ticket/detail.py +++ b/SoftLayer/CLI/ticket/detail.py @@ -21,7 +21,7 @@ def cli(env, identifier, count): """Get details for a ticket.""" is_json = False - if (env.format == 'json'): + if env.format == 'json': is_json = True mgr = SoftLayer.TicketManager(env.client) From 49ad0562743031281816561121e15a847ffd5b5c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 Apr 2020 14:31:57 -0500 Subject: [PATCH 0103/1385] fixed a typo --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d2cab7484..7351b5c51 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: python -m pip install --upgrade pip pip install -r tools/test-requirements.txt - name: Tox - run: tox -e + run: tox -e py coverage: runs-on: ubuntu-latest steps: From 91ae1ca0823a4179b0514b2c8da773adc007c647 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 Apr 2020 14:38:48 -0500 Subject: [PATCH 0104/1385] Fixed test names --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7351b5c51..7aa408ed8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r tools/test-requirements.txt - - name: Tox + - name: Tox Test run: tox -e py coverage: runs-on: ubuntu-latest @@ -36,7 +36,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r tools/test-requirements.txt - - name: Tox + - name: Tox Coverage run: tox -e coverage analysis: runs-on: ubuntu-latest @@ -50,5 +50,5 @@ jobs: run: | python -m pip install --upgrade pip pip install -r tools/test-requirements.txt - - name: Tox + - name: Tox Analysis run: tox -e analysis \ No newline at end of file From 37eb8780a36826e8e0bd306e420991d5c5aa31f4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 Apr 2020 14:41:51 -0500 Subject: [PATCH 0105/1385] updated readme --- README.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 535b54597..b4a321866 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,10 @@ SoftLayer API Python Client =========================== -.. image:: https://travis-ci.org/softlayer/softlayer-python.svg?branch=master - :target: https://travis-ci.org/softlayer/softlayer-python +.. image:: https://github.com/softlayer/softlayer-python/workflows/Tests/badge.svg + :target: https://github.com/softlayer/softlayer-python/actions?query=workflow%3ATests + +.. image:: https://github.com/softlayer/softlayer-python/workflows/documentation/badge.svg + :target: https://github.com/softlayer/softlayer-python/actions?query=workflow%3Adocumentation .. image:: https://landscape.io/github/softlayer/softlayer-python/master/landscape.svg :target: https://landscape.io/github/softlayer/softlayer-python/master From 5fd9fb46ffde51791f5237ab92724c8913dab436 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Apr 2020 17:41:17 -0400 Subject: [PATCH 0106/1385] #1258 add control for none type value --- SoftLayer/CLI/formatting.py | 13 +++++++++---- tests/CLI/helper_tests.py | 9 +++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b591f814f..b88fe056b 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -416,11 +416,13 @@ def _format_list(result): if not result: return result - if isinstance(result[0], dict): - return _format_list_objects(result) + new_result = [item for item in result if item] + + if isinstance(new_result[0], dict): + return _format_list_objects(new_result) table = Table(['value']) - for item in result: + for item in new_result: table.add_row([iter_to_table(item)]) return table @@ -430,12 +432,15 @@ def _format_list_objects(result): all_keys = set() for item in result: - all_keys = all_keys.union(item.keys()) + if isinstance(item, dict): + all_keys = all_keys.union(item.keys()) all_keys = sorted(all_keys) table = Table(all_keys) for item in result: + if not item: + continue values = [] for key in all_keys: value = iter_to_table(item.get(key)) diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index 5c6656c21..c22278c34 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -439,11 +439,10 @@ def test_template_options(self): class TestExportToTemplate(testing.TestCase): def test_export_to_template(self): - if(sys.platform.startswith("win")): + if (sys.platform.startswith("win")): self.skipTest("Test doesn't work in Windows") # Tempfile creation is wonky on windows with tempfile.NamedTemporaryFile() as tmp: - template.export_to_template(tmp.name, { 'os': None, 'datacenter': 'ams01', @@ -487,3 +486,9 @@ def test_format_api_list_non_objects(self): self.assertIsInstance(result, formatting.Table) self.assertEqual(result.columns, ['value']) self.assertEqual(result.rows, [['a'], ['b'], ['c']]) + + def test_format_api_list_with_none_value(self): + result = formatting._format_list([{'key': [None, 'value']}, None]) + + self.assertIsInstance(result, formatting.Table) + self.assertEqual(result.columns, ['key']) From 5aaee862180da3da14d7dfbfd55370d3d5ea0138 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Mon, 27 Apr 2020 17:56:05 -0400 Subject: [PATCH 0107/1385] add the rebundant/degraded option --- SoftLayer/CLI/hardware/edit.py | 18 +++++++++++++++--- SoftLayer/managers/hardware.py | 6 +++--- tests/CLI/modules/server_tests.py | 6 ++++-- tests/managers/hardware_tests.py | 8 ++++---- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index e4aca4dcc..c47f565f0 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -22,8 +22,10 @@ help="Public port speed. -1 is best speed available") @click.option('--private-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), help="Private port speed. -1 is best speed available") +@click.option('--rebundant', is_flag=True,default=False, help="The desired state of redundancy for the interface(s)") +@click.option('--degraded', is_flag=True,default=False, help="The desired state of degraded for the interface(s)") @environment.pass_env -def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed): +def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed, rebundant, degraded): """Edit hardware details.""" if userdata and userfile: @@ -51,7 +53,17 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed raise exceptions.CLIAbort("Failed to update hardware") if public_speed is not None: - mgr.change_port_speed(hw_id, True, int(public_speed)) + if rebundant: + mgr.change_port_speed(hw_id, True, int(public_speed), 'rebundant') + if degraded: + mgr.change_port_speed(hw_id, True, int(public_speed), 'degraded') + if not rebundant and not degraded: + raise exceptions.CLIAbort("Failed to update hardwar") if private_speed is not None: - mgr.change_port_speed(hw_id, False, int(private_speed)) + if rebundant: + mgr.change_port_speed(hw_id, False, int(private_speed), 'rebundant') + if degraded: + mgr.change_port_speed(hw_id, False, int(private_speed), 'degraded') + if not rebundant and not degraded: + raise exceptions.CLIAbort("Failed to update hardware") diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 99ab45b9c..e46674bf2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -297,7 +297,7 @@ def rescue(self, hardware_id): """ return self.hardware.bootToRescueLayer(id=hardware_id) - def change_port_speed(self, hardware_id, public, speed): + def change_port_speed(self, hardware_id, public, speed, rebundant): """Allows you to change the port speed of a server's NICs. :param int hardware_id: The ID of the server @@ -319,11 +319,11 @@ def change_port_speed(self, hardware_id, public, speed): if public: return self.client.call('Hardware_Server', 'setPublicNetworkInterfaceSpeed', - speed, id=hardware_id) + [rebundant, speed], id=hardware_id) else: return self.client.call('Hardware_Server', 'setPrivateNetworkInterfaceSpeed', - speed, id=hardware_id) + [rebundant, speed], id=hardware_id) def place_order(self, **kwargs): """Places an order for a piece of hardware. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 44e2bc81b..25cb20de4 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -526,7 +526,9 @@ def test_edit(self): '--tag=dev', '--tag=green', '--public-speed=10', + '--rebundant', '--private-speed=100', + '--degraded', '100']) self.assert_no_fail(result) @@ -544,12 +546,12 @@ def test_edit(self): ) self.assert_called_with( 'SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', - args=(10,), + args=(['rebundant', 10],), identifier=100, ) self.assert_called_with( 'SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', - args=(100,), + args=(['degraded', 100],), identifier=100, ) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 9ac63c224..472e25c75 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -335,20 +335,20 @@ def test_cancel_running_transaction(self): 12345) def test_change_port_speed_public(self): - self.hardware.change_port_speed(2, True, 100) + self.hardware.change_port_speed(2, True, 100, 'degraded') self.assert_called_with('SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', identifier=2, - args=(100,)) + args=(['degraded', 100],)) def test_change_port_speed_private(self): - self.hardware.change_port_speed(2, False, 10) + self.hardware.change_port_speed(2, False, 10, 'rebundant') self.assert_called_with('SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', identifier=2, - args=(10,)) + args=(['rebundant', 10],)) def test_edit_meta(self): # Test editing user data From d8552baa9653f275f0af964bc4a42335efc5760f Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Mon, 27 Apr 2020 18:08:20 -0400 Subject: [PATCH 0108/1385] fix the tox analysis --- SoftLayer/CLI/hardware/edit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index c47f565f0..aad0b68b4 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -22,8 +22,8 @@ help="Public port speed. -1 is best speed available") @click.option('--private-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), help="Private port speed. -1 is best speed available") -@click.option('--rebundant', is_flag=True,default=False, help="The desired state of redundancy for the interface(s)") -@click.option('--degraded', is_flag=True,default=False, help="The desired state of degraded for the interface(s)") +@click.option('--rebundant', is_flag=True, default=False, help="The desired state of redundancy for the interface(s)") +@click.option('--degraded', is_flag=True, default=False, help="The desired state of degraded for the interface(s)") @environment.pass_env def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed, rebundant, degraded): """Edit hardware details.""" From 15791619c9454d8807a4ca982a7b8099a0cd1d37 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 28 Apr 2020 12:24:16 -0400 Subject: [PATCH 0109/1385] Add Account planned, unplanned and announcement events. --- SoftLayer/CLI/account/events.py | 88 ++++++++++++++++++++++++++------- SoftLayer/managers/account.py | 56 +++++++++++++++++---- tests/managers/account_tests.py | 45 ++++++++++++++++- 3 files changed, 158 insertions(+), 31 deletions(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 5cc91144d..c80614969 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -2,10 +2,10 @@ # :license: MIT, see LICENSE for more details. import click +from SoftLayer import utils from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager -from SoftLayer import utils @click.command() @@ -16,39 +16,89 @@ def cli(env, ack_all): """Summary and acknowledgement of upcoming and ongoing maintenance events""" manager = AccountManager(env.client) - events = manager.get_upcoming_events() + planned_events = manager.get_upcoming_events("PLANNED") + unplanned_events = manager.get_upcoming_events("UNPLANNED_INCIDENT") + announcement_events = manager.get_upcoming_events("ANNOUNCEMENT") + + add_ack_flag(planned_events, manager, ack_all) + env.fout(planned_event_table(planned_events)) + + add_ack_flag(unplanned_events, manager, ack_all) + env.fout(unplanned_event_table(unplanned_events)) + add_ack_flag(announcement_events, manager, ack_all) + env.fout(announcement_event_table(announcement_events)) + + +def add_ack_flag(events, manager, ack_all): if ack_all: for event in events: result = manager.ack_event(event['id']) event['acknowledgedFlag'] = result - env.fout(event_table(events)) -def event_table(events): +def planned_event_table(events): """Formats a table for events""" - table = formatting.Table([ - "Id", - "Start Date", - "End Date", - "Subject", - "Status", - "Acknowledged", - "Updates", - "Impacted Resources" - ], title="Upcoming Events") - table.align['Subject'] = 'l' - table.align['Impacted Resources'] = 'l' + planned_table = formatting.Table(['Event Data', 'Id', 'Event ID', 'Subject', 'Status', 'Items', 'Start Date', + 'End Date', 'Acknowledged', 'Updates'], title="Planned Events") + planned_table.align['Subject'] = 'l' + planned_table.align['Impacted Resources'] = 'l' for event in events: - table.add_row([ + planned_table.add_row([ + utils.clean_time(event.get('startDate')), event.get('id'), + event.get('systemTicketId'), + # Some subjects can have \r\n for some reason. + utils.clean_splitlines(event.get('subject')), + utils.lookup(event, 'statusCode', 'name'), + event.get('impactedResourceCount'), utils.clean_time(event.get('startDate')), utils.clean_time(event.get('endDate')), + event.get('acknowledgedFlag'), + event.get('updateCount'), + + ]) + return planned_table + + +def unplanned_event_table(events): + """Formats a table for events""" + unplanned_table = formatting.Table(['Id', 'Event ID', 'Subject', 'Status', 'Items', 'Start Date', + 'Last Updated', 'Acknowledged', 'Updates'], title="Unplanned Events") + unplanned_table.align['Subject'] = 'l' + unplanned_table.align['Impacted Resources'] = 'l' + for event in events: + print(event.get('modifyDate')) + unplanned_table.add_row([ + event.get('id'), + event.get('systemTicketId'), # Some subjects can have \r\n for some reason. utils.clean_splitlines(event.get('subject')), utils.lookup(event, 'statusCode', 'name'), + event.get('impactedResourceCount'), + utils.clean_time(event.get('startDate')), + utils.clean_time(event.get('modifyDate')), event.get('acknowledgedFlag'), event.get('updateCount'), - event.get('impactedResourceCount') ]) - return table + return unplanned_table + + +def announcement_event_table(events): + """Formats a table for events""" + announcement_table = formatting.Table( + ['Id', 'Event ID', 'Subject', 'Status', 'Items', 'Acknowledged', 'Updates'], title="Announcement Events") + announcement_table.align['Subject'] = 'l' + announcement_table.align['Impacted Resources'] = 'l' + for event in events: + announcement_table.add_row([ + event.get('id'), + event.get('systemTicketId'), + # Some subjects can have \r\n for some reason. + utils.clean_splitlines(event.get('subject')), + utils.lookup(event, 'statusCode', 'name'), + event.get('impactedResourceCount'), + event.get('acknowledgedFlag'), + event.get('updateCount') + ]) + return announcement_table diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 4269bc7a5..56f951c28 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -47,25 +47,61 @@ def get_summary(self): """ return self.client.call('Account', 'getObject', mask=mask) - def get_upcoming_events(self): - """Retreives a list of Notification_Occurrence_Events that have not ended yet + def get_upcoming_events(self, event_type): + """Retrieves a list of Notification_Occurrence_Events that have not ended yet + :param: String event_type: notification event type. :return: SoftLayer_Notification_Occurrence_Event """ - mask = "mask[id, subject, startDate, endDate, statusCode, acknowledgedFlag, impactedResourceCount, updateCount]" + mask = "mask[id, subject, startDate, endDate, modifyDate, statusCode, acknowledgedFlag, " \ + "impactedResourceCount, updateCount, systemTicketId, notificationOccurrenceEventType[keyName]]" + _filter = { - 'endDate': { - 'operation': '> sysdate' - }, - 'startDate': { + 'notificationOccurrenceEventType': { + 'keyName': { + 'operation': event_type + } + } + } + + self.add_event_filter(_filter, event_type) + + return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) + + @staticmethod + def add_event_filter(_filter, event_type): + """Add data to the object filter. + + :param: _filter: event filter. + :param: string event_type: event type. + """ + if event_type == 'PLANNED': + _filter['endDate'] = { + 'operation': '> sysdate - 2' + } + _filter['startDate'] = { 'operation': 'orderBy', 'options': [{ 'name': 'sort', - 'value': ['ASC'] + 'value': ['DESC'] }] } - } - return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) + + if event_type == 'UNPLANNED_INCIDENT': + _filter['modifyDate'] = { + 'operation': '> sysdate - 2' + } + + if event_type == 'ANNOUNCEMENT': + _filter['statusCode'] = { + 'keyName': { + 'operation': 'in', + 'options': [{ + 'name': 'data', + 'value': ['PUBLISHED'] + }] + } + } def ack_event(self, event_id): """Acknowledge an event. This mostly prevents it from appearing as a notification in the control portal. diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 513aa44ff..b47ec6abb 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -18,10 +18,51 @@ def test_get_summary(self): self.manager.get_summary() self.assert_called_with('SoftLayer_Account', 'getObject') - def test_get_upcoming_events(self): - self.manager.get_upcoming_events() + def test_get_planned_upcoming_events(self): + self.manager.get_upcoming_events("PLANNED") self.assert_called_with(self.SLNOE, 'getAllObjects') + def test_get_unplanned_upcoming_events(self): + self.manager.get_upcoming_events("UNPLANNED_INCIDENT") + self.assert_called_with(self.SLNOE, 'getAllObjects') + + def test_get_announcement_upcoming_events(self): + self.manager.get_upcoming_events("ANNOUNCEMENT") + self.assert_called_with(self.SLNOE, 'getAllObjects') + + def test_add_planned_event_filter(self): + event_type = 'PLANNED' + _filter = { + 'notificationOccurrenceEventType': { + 'keyName': { + 'operation': event_type + } + } + } + self.manager.add_event_filter(_filter, event_type) + + def test_add_unplanned_event_filter(self): + event_type = 'UNPLANNED_INCIDENT' + _filter = { + 'notificationOccurrenceEventType': { + 'keyName': { + 'operation': event_type + } + } + } + self.manager.add_event_filter(_filter, event_type) + + def test_add_announcement_event_filter(self): + event_type = 'ANNOUNCEMENT' + _filter = { + 'notificationOccurrenceEventType': { + 'keyName': { + 'operation': event_type + } + } + } + self.manager.add_event_filter(_filter, event_type) + def test_ack_event(self): self.manager.ack_event(12345) self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=12345) From 975435d0fcafe2cdc77acb71138132f82490357e Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 28 Apr 2020 12:50:54 -0400 Subject: [PATCH 0110/1385] fix analysis tox. --- SoftLayer/CLI/account/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index c80614969..689b5edad 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -2,10 +2,10 @@ # :license: MIT, see LICENSE for more details. import click -from SoftLayer import utils from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils @click.command() From 7203df063f2927fcce42d154ba278d57a8dae847 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 28 Apr 2020 15:01:15 -0400 Subject: [PATCH 0111/1385] fix analysis tox method docstring. --- SoftLayer/CLI/account/events.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 689b5edad..b5d8960cb 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -31,6 +31,7 @@ def cli(env, ack_all): def add_ack_flag(events, manager, ack_all): + """Add acknowledgedFlag to the event""" if ack_all: for event in events: result = manager.ack_event(event['id']) From 47574a40f1a8bb47bde475e7dea21de5deab27b2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 28 Apr 2020 16:23:44 -0500 Subject: [PATCH 0112/1385] fixing pylint 2.5.0 new errors --- SoftLayer/managers/dedicated_host.py | 15 ++++++----- SoftLayer/managers/hardware.py | 40 ++++++++++++---------------- SoftLayer/managers/metadata.py | 5 ++-- SoftLayer/managers/vs_capacity.py | 5 ++-- 4 files changed, 31 insertions(+), 34 deletions(-) diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index 86258416b..24cb3f300 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -7,8 +7,9 @@ """ import logging -import SoftLayer +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer import utils @@ -395,7 +396,7 @@ def _get_location(self, regions, datacenter): if region['location']['location']['name'] == datacenter: return region - raise SoftLayer.SoftLayerError("Could not find valid location for: '%s'" % datacenter) + raise SoftLayerError("Could not find valid location for: '%s'" % datacenter) def get_create_options(self): """Returns valid options for ordering a dedicated host.""" @@ -426,7 +427,7 @@ def _get_price(self, package): if not price.get('locationGroupId'): return price['id'] - raise SoftLayer.SoftLayerError("Could not find valid price") + raise SoftLayerError("Could not find valid price") def _get_item(self, package, flavor): """Returns the item for ordering a dedicated host.""" @@ -435,7 +436,7 @@ def _get_item(self, package, flavor): if item['keyName'] == flavor: return item - raise SoftLayer.SoftLayerError("Could not find valid item for: '%s'" % flavor) + raise SoftLayerError("Could not find valid item for: '%s'" % flavor) def _get_backend_router(self, locations, item): """Returns valid router options for ordering a dedicated host.""" @@ -495,7 +496,7 @@ def _get_backend_router(self, locations, item): routers = self.host.getAvailableRouters(host, mask=mask) return routers - raise SoftLayer.SoftLayerError("Could not find available routers") + raise SoftLayerError("Could not find available routers") def _get_default_router(self, routers, router_name=None): """Returns the default router for ordering a dedicated host.""" @@ -508,7 +509,7 @@ def _get_default_router(self, routers, router_name=None): if router['hostname'] == router_name: return router['id'] - raise SoftLayer.SoftLayerError("Could not find valid default router") + raise SoftLayerError("Could not find valid default router") def get_router_options(self, datacenter=None, flavor=None): """Returns available backend routers for the dedicated host.""" @@ -524,7 +525,7 @@ def _delete_guest(self, guest_id): msg = 'Cancelled' try: self.guest.deleteObject(id=guest_id) - except SoftLayer.SoftLayerAPIError as e: + except SoftLayerAPIError as e: msg = 'Exception: ' + e.faultString return msg diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 99ab45b9c..9d7cbf3f8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,9 +9,10 @@ import socket import time -import SoftLayer from SoftLayer.decoration import retry +from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering +from SoftLayer.managers.ticket import TicketManager from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -77,19 +78,18 @@ def cancel_hardware(self, hardware_id, reason='unneeded', comment='', immediate= # Get cancel reason reasons = self.get_cancellation_reasons() cancel_reason = reasons.get(reason, reasons['unneeded']) - ticket_mgr = SoftLayer.TicketManager(self.client) + ticket_mgr = TicketManager(self.client) mask = 'mask[id, hourlyBillingFlag, billingItem[id], openCancellationTicket[id], activeTransaction]' hw_billing = self.get_hardware(hardware_id, mask=mask) if 'activeTransaction' in hw_billing: - raise SoftLayer.SoftLayerError("Unable to cancel hardware with running transaction") + raise SoftLayerError("Unable to cancel hardware with running transaction") if 'billingItem' not in hw_billing: if utils.lookup(hw_billing, 'openCancellationTicket', 'id'): - raise SoftLayer.SoftLayerError("Ticket #%s already exists for this server" % - hw_billing['openCancellationTicket']['id']) - raise SoftLayer.SoftLayerError("Cannot locate billing for the server. " - "The server may already be cancelled.") + raise SoftLayerError("Ticket #%s already exists for this server" % + hw_billing['openCancellationTicket']['id']) + raise SoftLayerError("Cannot locate billing for the server. The server may already be cancelled.") billing_id = hw_billing['billingItem']['id'] @@ -744,7 +744,7 @@ def _get_extra_price_id(items, key_name, hourly, location): return price['id'] - raise SoftLayer.SoftLayerError( + raise SoftLayerError( "Could not find valid price for extra option, '%s'" % key_name) @@ -762,7 +762,7 @@ def _get_default_price_id(items, option, hourly, location): _matches_location(price, location)]): return price['id'] - raise SoftLayer.SoftLayerError( + raise SoftLayerError( "Could not find valid price for '%s' option" % option) @@ -792,7 +792,7 @@ def _get_bandwidth_price_id(items, return price['id'] - raise SoftLayer.SoftLayerError( + raise SoftLayerError( "Could not find valid price for bandwidth option") @@ -800,11 +800,8 @@ def _get_os_price_id(items, os, location): """Returns the price id matching.""" for item in items: - if any([utils.lookup(item, - 'itemCategory', - 'categoryCode') != 'os', - utils.lookup(item, - 'keyName') != os]): + if any([utils.lookup(item, 'itemCategory', 'categoryCode') != 'os', + utils.lookup(item, 'keyName') != os]): continue for price in item['prices']: @@ -813,17 +810,14 @@ def _get_os_price_id(items, os, location): return price['id'] - raise SoftLayer.SoftLayerError("Could not find valid price for os: '%s'" % - os) + raise SoftLayerError("Could not find valid price for os: '%s'" % os) def _get_port_speed_price_id(items, port_speed, no_public, location): """Choose a valid price id for port speed.""" for item in items: - if utils.lookup(item, - 'itemCategory', - 'categoryCode') != 'port_speed': + if utils.lookup(item, 'itemCategory', 'categoryCode') != 'port_speed': continue # Check for correct capacity and if the item matches private only @@ -838,7 +832,7 @@ def _get_port_speed_price_id(items, port_speed, no_public, location): return price['id'] - raise SoftLayer.SoftLayerError( + raise SoftLayerError( "Could not find valid price for port speed: '%s'" % port_speed) @@ -887,7 +881,7 @@ def _get_location(package, location): if region['location']['location']['name'] == location: return region - raise SoftLayer.SoftLayerError("Could not find valid location for: '%s'" % location) + raise SoftLayerError("Could not find valid location for: '%s'" % location) def _get_preset_id(package, size): @@ -896,4 +890,4 @@ def _get_preset_id(package, size): if preset['keyName'] == size or preset['id'] == size: return preset['id'] - raise SoftLayer.SoftLayerError("Could not find valid size for: '%s'" % size) + raise SoftLayerError("Could not find valid size for: '%s'" % size) diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py index cdc021515..603ff23aa 100644 --- a/SoftLayer/managers/metadata.py +++ b/SoftLayer/managers/metadata.py @@ -5,7 +5,8 @@ :license: MIT, see LICENSE for more details. """ -import SoftLayer +# import SoftLayer +from SoftLayer.API import BaseClient from SoftLayer import consts from SoftLayer import exceptions from SoftLayer import transports @@ -66,7 +67,7 @@ def __init__(self, client=None, timeout=5): timeout=timeout, endpoint_url=consts.API_PRIVATE_ENDPOINT_REST, ) - client = SoftLayer.BaseClient(transport=transport) + client = BaseClient(transport=transport) self.client = client diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index c2be6a615..453d51b7f 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -7,8 +7,9 @@ """ import logging -import SoftLayer +# import SoftLayer +from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.vs import VSManager from SoftLayer import utils @@ -144,7 +145,7 @@ def create_guest(self, capacity_id, test, guest_object): capacity_flavor = capacity['instances'][0]['billingItem']['item']['keyName'] flavor = _flavor_string(capacity_flavor, guest_object['primary_disk']) except KeyError: - raise SoftLayer.SoftLayerError("Unable to find capacity Flavor.") + raise SoftLayerError("Unable to find capacity Flavor.") guest_object['flavor'] = flavor guest_object['datacenter'] = capacity['backendRouter']['datacenter']['name'] From 2d6155aa646dc991ec8a3d7b55718677d739cf71 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 28 Apr 2020 16:25:11 -0500 Subject: [PATCH 0113/1385] removing dead imports --- SoftLayer/managers/dedicated_host.py | 1 - SoftLayer/managers/metadata.py | 1 - SoftLayer/managers/vs_capacity.py | 1 - 3 files changed, 3 deletions(-) diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index 24cb3f300..89246da28 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -5,7 +5,6 @@ :license: MIT, see License for more details. """ - import logging from SoftLayer.exceptions import SoftLayerAPIError diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py index 603ff23aa..13f350add 100644 --- a/SoftLayer/managers/metadata.py +++ b/SoftLayer/managers/metadata.py @@ -5,7 +5,6 @@ :license: MIT, see LICENSE for more details. """ -# import SoftLayer from SoftLayer.API import BaseClient from SoftLayer import consts from SoftLayer import exceptions diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 453d51b7f..3f6574f12 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -7,7 +7,6 @@ """ import logging -# import SoftLayer from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering From d078f0eca776915415b443d6e1bdfe68de8e99a3 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 29 Apr 2020 10:57:11 -0400 Subject: [PATCH 0114/1385] fix the Christopehr code review comments --- SoftLayer/CLI/hardware/edit.py | 16 ++++++++-------- SoftLayer/managers/hardware.py | 6 +++--- tests/CLI/modules/server_tests.py | 6 +++--- tests/managers/hardware_tests.py | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index aad0b68b4..dc1152c6f 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -22,10 +22,10 @@ help="Public port speed. -1 is best speed available") @click.option('--private-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), help="Private port speed. -1 is best speed available") -@click.option('--rebundant', is_flag=True, default=False, help="The desired state of redundancy for the interface(s)") +@click.option('--redundant', is_flag=True, default=False, help="The desired state of redundancy for the interface(s)") @click.option('--degraded', is_flag=True, default=False, help="The desired state of degraded for the interface(s)") @environment.pass_env -def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed, rebundant, degraded): +def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed, redundant, degraded): """Edit hardware details.""" if userdata and userfile: @@ -53,17 +53,17 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed raise exceptions.CLIAbort("Failed to update hardware") if public_speed is not None: - if rebundant: - mgr.change_port_speed(hw_id, True, int(public_speed), 'rebundant') + if redundant: + mgr.change_port_speed(hw_id, True, int(public_speed), 'redundant') if degraded: mgr.change_port_speed(hw_id, True, int(public_speed), 'degraded') - if not rebundant and not degraded: + if not redundant and not degraded: raise exceptions.CLIAbort("Failed to update hardwar") if private_speed is not None: - if rebundant: - mgr.change_port_speed(hw_id, False, int(private_speed), 'rebundant') + if redundant: + mgr.change_port_speed(hw_id, False, int(private_speed), 'redundant') if degraded: mgr.change_port_speed(hw_id, False, int(private_speed), 'degraded') - if not rebundant and not degraded: + if not redundant and not degraded: raise exceptions.CLIAbort("Failed to update hardware") diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index e46674bf2..1934fa0eb 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -297,7 +297,7 @@ def rescue(self, hardware_id): """ return self.hardware.bootToRescueLayer(id=hardware_id) - def change_port_speed(self, hardware_id, public, speed, rebundant): + def change_port_speed(self, hardware_id, public, speed, redundant=None): """Allows you to change the port speed of a server's NICs. :param int hardware_id: The ID of the server @@ -319,11 +319,11 @@ def change_port_speed(self, hardware_id, public, speed, rebundant): if public: return self.client.call('Hardware_Server', 'setPublicNetworkInterfaceSpeed', - [rebundant, speed], id=hardware_id) + [speed, redundant], id=hardware_id) else: return self.client.call('Hardware_Server', 'setPrivateNetworkInterfaceSpeed', - [rebundant, speed], id=hardware_id) + [speed, redundant], id=hardware_id) def place_order(self, **kwargs): """Places an order for a piece of hardware. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 25cb20de4..157acfcc1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -526,7 +526,7 @@ def test_edit(self): '--tag=dev', '--tag=green', '--public-speed=10', - '--rebundant', + '--redundant', '--private-speed=100', '--degraded', '100']) @@ -546,12 +546,12 @@ def test_edit(self): ) self.assert_called_with( 'SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', - args=(['rebundant', 10],), + args=([10, 'redundant'],), identifier=100, ) self.assert_called_with( 'SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', - args=(['degraded', 100],), + args=([100, 'degraded'],), identifier=100, ) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 472e25c75..06b2a334f 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -343,12 +343,12 @@ def test_change_port_speed_public(self): args=(['degraded', 100],)) def test_change_port_speed_private(self): - self.hardware.change_port_speed(2, False, 10, 'rebundant') + self.hardware.change_port_speed(2, False, 10, 'redundant') self.assert_called_with('SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', identifier=2, - args=(['rebundant', 10],)) + args=([10,'redundant'],)) def test_edit_meta(self): # Test editing user data From 89e873b530815425b2079880efe6022c693df474 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 29 Apr 2020 11:05:34 -0400 Subject: [PATCH 0115/1385] fix tox tool --- tests/managers/hardware_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 06b2a334f..f504dba94 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -340,7 +340,7 @@ def test_change_port_speed_public(self): self.assert_called_with('SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', identifier=2, - args=(['degraded', 100],)) + args=([100, 'degraded'],)) def test_change_port_speed_private(self): self.hardware.change_port_speed(2, False, 10, 'redundant') @@ -348,7 +348,7 @@ def test_change_port_speed_private(self): self.assert_called_with('SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', identifier=2, - args=([10,'redundant'],)) + args=([10, 'redundant'],)) def test_edit_meta(self): # Test editing user data @@ -374,10 +374,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): From 5462fd076377e74f85c3aa502f1de77bad86cc20 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 29 Apr 2020 18:30:46 -0400 Subject: [PATCH 0116/1385] fix the isue 1262 --- SoftLayer/managers/ordering.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 7e4e81db5..b5e659ee1 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -251,14 +251,13 @@ def list_categories(self, package_keyname, **kwargs): :param str package_keyname: The package for which to get the categories. :returns: List of categories associated with the package """ - get_kwargs = {} - get_kwargs['mask'] = kwargs.get('mask', CATEGORY_MASK) + kwargs['mask'] = kwargs.get('mask', CATEGORY_MASK) if 'filter' in kwargs: - get_kwargs['filter'] = kwargs['filter'] + kwargs['filter'] = kwargs['filter'] package = self.get_package_by_key(package_keyname, mask='id') - categories = self.package_svc.getConfiguration(id=package['id'], **get_kwargs) + categories = self.package_svc.getConfiguration(id=package['id'], **kwargs) return categories def list_items(self, package_keyname, **kwargs): @@ -268,14 +267,11 @@ def list_items(self, package_keyname, **kwargs): :returns: List of items in the package """ - get_kwargs = {} - get_kwargs['mask'] = kwargs.get('mask', ITEM_MASK) - - if 'filter' in kwargs: - get_kwargs['filter'] = kwargs['filter'] + if 'mask' not in kwargs: + kwargs['mask'] = ITEM_MASK package = self.get_package_by_key(package_keyname, mask='id') - items = self.package_svc.getItems(id=package['id'], **get_kwargs) + items = self.package_svc.getItems(id=package['id'], **kwargs) return items def list_packages(self, **kwargs): @@ -284,13 +280,12 @@ def list_packages(self, **kwargs): :returns: List of active packages. """ - get_kwargs = {} - get_kwargs['mask'] = kwargs.get('mask', PACKAGE_MASK) + kwargs['mask'] = kwargs.get('mask', PACKAGE_MASK) if 'filter' in kwargs: - get_kwargs['filter'] = kwargs['filter'] + kwargs['filter'] = kwargs['filter'] - packages = self.package_svc.getAllObjects(**get_kwargs) + packages = self.package_svc.getAllObjects(**kwargs) return [package for package in packages if package['isActive']] @@ -301,15 +296,15 @@ def list_presets(self, package_keyname, **kwargs): :returns: A list of package presets that can be used for ordering """ - get_kwargs = {} - get_kwargs['mask'] = kwargs.get('mask', PRESET_MASK) + + kwargs['mask'] = kwargs.get('mask', PRESET_MASK) if 'filter' in kwargs: - get_kwargs['filter'] = kwargs['filter'] + kwargs['filter'] = kwargs['filter'] package = self.get_package_by_key(package_keyname, mask='id') - acc_presets = self.package_svc.getAccountRestrictedActivePresets(id=package['id'], **get_kwargs) - active_presets = self.package_svc.getActivePresets(id=package['id'], **get_kwargs) + acc_presets = self.package_svc.getAccountRestrictedActivePresets(id=package['id'], **kwargs) + active_presets = self.package_svc.getActivePresets(id=package['id'], **kwargs) return active_presets + acc_presets def get_preset_by_key(self, package_keyname, preset_keyname, mask=None): From 998572c72c3e8db94a69e04f8751cd902390ba53 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 30 Apr 2020 16:32:20 -0500 Subject: [PATCH 0117/1385] #1266 added JSON encoder for bytes objects --- SoftLayer/transports.py | 12 +++++++++++- tests/transport_tests.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 297249852..afa8df88f 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +import base64 import importlib import json import logging @@ -359,7 +360,7 @@ def __call__(self, request): body['parameters'] = request.args if body: - request.payload = json.dumps(body) + request.payload = json.dumps(body, cls=ComplexEncoder) url_parts = [self.endpoint_url, request.service] if request.identifier is not None: @@ -566,3 +567,12 @@ def _format_object_mask(objectmask): not objectmask.startswith('[')): objectmask = "mask[%s]" % objectmask return objectmask + +class ComplexEncoder(json.JSONEncoder): + def default(self, obj): + # Base64 encode bytes type objects. + if isinstance(obj, bytes): + base64_bytes = base64.b64encode(obj) + return base64_bytes.decode("utf-8") + # Let the base class default method raise the TypeError + return json.JSONEncoder.default(self, obj) \ No newline at end of file diff --git a/tests/transport_tests.py b/tests/transport_tests.py index d105c3fdc..d8e8245c5 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -7,6 +7,7 @@ import io import warnings +import json import mock import pytest import requests @@ -527,6 +528,31 @@ def test_with_args(self, request): proxies=None, timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_args_bytes(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ('test', b'asdf') + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'POST', + 'http://something.com/SoftLayer_Service/getObject.json', + headers=mock.ANY, + auth=None, + data='{"parameters": ["test", "YXNkZg=="]}', + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + @mock.patch('SoftLayer.transports.requests.Session.request') def test_with_filter(self, request): request().text = '{}' @@ -674,6 +700,14 @@ def test_print_reproduceable(self): output_text = self.transport.print_reproduceable(req) self.assertIn("https://test.com", output_text) + def test_complex_encoder_bytes(self): + to_encode = { + 'test' : ['array', 0, 1, False], + 'bytes': b'ASDASDASD' + } + result = json.dumps(to_encode, cls=transports.ComplexEncoder) + self.assertEqual(result, '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}') + class TestFixtureTransport(testing.TestCase): From 8714c03edb39c70e71062d66752d7ebc534ab3d6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 30 Apr 2020 16:42:13 -0500 Subject: [PATCH 0118/1385] tox fixes --- SoftLayer/transports.py | 13 +++++++++---- tests/transport_tests.py | 3 +-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index afa8df88f..02cd7a214 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -568,11 +568,16 @@ def _format_object_mask(objectmask): objectmask = "mask[%s]" % objectmask return objectmask + class ComplexEncoder(json.JSONEncoder): - def default(self, obj): + """ComplexEncoder helps jsonencoder deal with byte strings""" + + def default(self, o): + """Encodes o as JSON""" + # Base64 encode bytes type objects. - if isinstance(obj, bytes): - base64_bytes = base64.b64encode(obj) + if isinstance(o, bytes): + base64_bytes = base64.b64encode(o) return base64_bytes.decode("utf-8") # Let the base class default method raise the TypeError - return json.JSONEncoder.default(self, obj) \ No newline at end of file + return json.JSONEncoder.default(self, o) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index d8e8245c5..8267c6f58 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -528,7 +528,6 @@ def test_with_args(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') def test_with_args_bytes(self, request): request().text = '{}' @@ -702,7 +701,7 @@ def test_print_reproduceable(self): def test_complex_encoder_bytes(self): to_encode = { - 'test' : ['array', 0, 1, False], + 'test': ['array', 0, 1, False], 'bytes': b'ASDASDASD' } result = json.dumps(to_encode, cls=transports.ComplexEncoder) From 99879fccbb7f94b71434e9365cc4891e3c3624d7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 30 Apr 2020 16:54:38 -0500 Subject: [PATCH 0119/1385] fixed unit test issues --- tests/transport_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 8267c6f58..6e6c793fd 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -705,7 +705,9 @@ def test_complex_encoder_bytes(self): 'bytes': b'ASDASDASD' } result = json.dumps(to_encode, cls=transports.ComplexEncoder) - self.assertEqual(result, '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}') + # result = '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}' + # encode doesn't always encode in the same order, so testing exact match SOMETIMES breaks. + self.assertIn("QVNEQVNEQVNE", result) class TestFixtureTransport(testing.TestCase): From 5db394e4c164ed6a50adc910cfc7ed0943570e98 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 18 May 2020 07:18:32 -0500 Subject: [PATCH 0120/1385] v5.8.8 release --- CHANGELOG.md | 17 +++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 965e9967f..626c66af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Change Log + +## [5.8.8] - 2020-05-18 +https://github.com/softlayer/softlayer-python/compare/v5.8.7...v5.8.8 + +- #1266 Fixed ticket upload with REST endpoint +- #1263 add the redundant/degraded option to hardware +- #1262 Added `iter` option for ordering manager functions +- #1264 Add Account planned, unplanned and announcement events +- #1265 fixed pylint 2.5.0 errors +- #1261 Fix AttributeError: 'NoneType' object has no attribute 'keys +- #1256 Adding more github action tests, removing travis CI tests +- #887 fix Response shows additional new lines (\n) in ticket details +- #1241 Storage feature for virtual and hardware servers +- #1242 Hardware and Virtual billing info +- #1239 VPN subnet access to a use +- #1254 added account billing-items/item-details/cancel-item commands + ## [5.8.7] - 2020-03-26 https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.7 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index e651d91ca..0ea903bf5 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.7' +VERSION = 'v5.8.8' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index d8e9f566f..6a6c9a551 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.7', + version='5.8.8', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From bd7cc6d3afea03fe11a468b8fdf9c971ab21e052 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 26 May 2020 14:18:31 -0500 Subject: [PATCH 0121/1385] #1252 added automated snap publisher --- .github/workflows/release.yml | 23 +++++++++++++++++++++++ snap/snapcraft.yaml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..397168add --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: Release + +on: + release: + types: [published] + +jobs: + release: + runs-on: ubuntu-latest + strategy: + matrix: + arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] + steps: + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v1.1.1 + with: + snapcraft_token: ${{ secrets.snapcraft_token }} + - name: Push to stable + run: | + VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` + echo Publishing $VERSION on ${{ matrix.arch }} + snapcraft release slcli $VERSION stable + diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b08438c82..474b05f1b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.8.1+git' # check versioning +version: 'git' # will be replaced by a `git describe` based version string summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From b81c031b7a874fc9e9e796cffb6e2c5d2c2b8573 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 26 May 2020 14:30:06 -0500 Subject: [PATCH 0122/1385] fixed typo in release github action --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 397168add..f16f7934c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] - steps: + steps: - name: Install Snapcraft uses: samuelmeuli/action-snapcraft@v1.1.1 with: From 81808b6f760ed52bc54b6e09ac13d2c7d7d7d1a4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 26 May 2020 14:37:18 -0500 Subject: [PATCH 0123/1385] Update release.yml --- .github/workflows/release.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f16f7934c..36ef10414 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,13 +11,13 @@ jobs: matrix: arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] steps: - - name: Install Snapcraft - uses: samuelmeuli/action-snapcraft@v1.1.1 - with: - snapcraft_token: ${{ secrets.snapcraft_token }} - - name: Push to stable - run: | - VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` - echo Publishing $VERSION on ${{ matrix.arch }} - snapcraft release slcli $VERSION stable + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v1.1.1 + with: + snapcraft_token: ${{ secrets.snapcraft_token }} + - name: Push to stable + run: | + VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` + echo Publishing $VERSION on ${{ matrix.arch }} + snapcraft release slcli $VERSION stable From 078f9f1aa623ed2f2d0d36f9dcccd062dc8e05a6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 1 Jun 2020 17:03:44 -0500 Subject: [PATCH 0124/1385] #1230 basic tag listing --- SoftLayer/CLI/core.py | 1 + SoftLayer/CLI/routes.py | 3 ++ SoftLayer/CLI/tags/__init__.py | 1 + SoftLayer/CLI/tags/list.py | 75 +++++++++++++++++++++++++++++ SoftLayer/managers/__init__.py | 2 + SoftLayer/managers/tags.py | 87 ++++++++++++++++++++++++++++++++++ 6 files changed, 169 insertions(+) create mode 100644 SoftLayer/CLI/tags/__init__.py create mode 100644 SoftLayer/CLI/tags/list.py create mode 100644 SoftLayer/managers/tags.py diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index fe86f714e..362ff72e7 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -35,6 +35,7 @@ PROG_NAME = "slcli (SoftLayer Command-line)" VALID_FORMATS = ['table', 'raw', 'json', 'jsonraw'] DEFAULT_FORMAT = 'raw' + if sys.stdout.isatty(): DEFAULT_FORMAT = 'table' diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 452ee0f9c..6c2bae945 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -290,6 +290,9 @@ ('subnet:list', 'SoftLayer.CLI.subnet.list:cli'), ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), + ('tags', 'SoftLayer.CLI.tags'), + ('tags:list', 'SoftLayer.CLI.tags.list:cli'), + ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), ('ticket:detail', 'SoftLayer.CLI.ticket.detail:cli'), diff --git a/SoftLayer/CLI/tags/__init__.py b/SoftLayer/CLI/tags/__init__.py new file mode 100644 index 000000000..e5caefa78 --- /dev/null +++ b/SoftLayer/CLI/tags/__init__.py @@ -0,0 +1 @@ +"""Manage Tags""" \ No newline at end of file diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py new file mode 100644 index 000000000..71634478a --- /dev/null +++ b/SoftLayer/CLI/tags/list.py @@ -0,0 +1,75 @@ +"""List Tags.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers.tags import TagManager +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + +# pylint: disable=unnecessary-lambda + +from pprint import pprint as pp + +@click.command() +@click.option('--detail', '-d', is_flag=True, default=False, + help="Show information about the resources using this tag.") +@environment.pass_env +def cli(env, detail): + """List Tags.""" + + tag_manager = TagManager(env.client) + + if detail: + tables = detailed_table(tag_manager) + for table in tables: + env.fout(table) + else: + table = simple_table(tag_manager) + env.fout(table) + # pp(tags.list_tags()) + + +def tag_row(tag): + return [tag.get('id'), tag.get('name'), tag.get('referenceCount',0)] + +def detailed_table(tag_manager): + """Creates a table for each tag, with details about resources using it""" + tags = tag_manager.get_attached_tags() + tables = [] + for tag in tags: + references = tag_manager.get_tag_references(tag.get('id')) + # pp(references) + new_table = formatting.Table(['Id', 'Type', 'Resource'], title=tag.get('name')) + for reference in references: + tag_type = utils.lookup(reference, 'tagType', 'keyName') + resource_id = reference.get('resourceTableId') + resource_row = get_resource_name(tag_manager, resource_id, tag_type) + new_table.add_row([resource_id, tag_type, resource_row]) + tables.append(new_table) + + return tables + +def simple_table(tag_manager): + """Just tags and how many resources on each""" + tags = tag_manager.list_tags() + table = formatting.Table(['Id', 'Tag', 'Count'], title='Tags') + for tag in tags.get('attached', []): + table.add_row(tag_row(tag)) + for tag in tags.get('unattached', []): + table.add_row(tag_row(tag)) + return table + +def get_resource_name(tag_manager, resource_id, tag_type): + """Returns a string to identify a resource""" + try: + resource = tag_manager.reference_lookup(resource_id, tag_type) + if tag_type == 'NETWORK_VLAN_FIREWALL': + resource_row = resource.get('primaryIpAddress') + else: + resource_row = resource.get('fullyQualifiedDomainName') + except SoftLayerAPIError as e: + resource_row = "{}".format(e.reason) + return resource_row \ No newline at end of file diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 5c489345d..8053ec70e 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -24,6 +24,7 @@ from SoftLayer.managers.ordering import OrderingManager from SoftLayer.managers.sshkey import SshKeyManager from SoftLayer.managers.ssl import SSLManager +from SoftLayer.managers.tags import TagManager from SoftLayer.managers.ticket import TicketManager from SoftLayer.managers.user import UserManager from SoftLayer.managers.vs import VSManager @@ -50,6 +51,7 @@ 'PlacementManager', 'SshKeyManager', 'SSLManager', + 'TagManager', 'TicketManager', 'UserManager', 'VSManager', diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py new file mode 100644 index 000000000..8e6d282bd --- /dev/null +++ b/SoftLayer/managers/tags.py @@ -0,0 +1,87 @@ +""" + SoftLayer.tags + ~~~~~~~~~~~~ + Tag Manager + + :license: MIT, see LICENSE for more details. +""" +import re + +from SoftLayer.exceptions import SoftLayerAPIError + +class TagManager(object): + """Manager for Tag functions.""" + + def __init__(self, client): + self.client = client + + def list_tags(self, mask=None): + """Returns a list of all tags for the Current User + + :param str mask: Object mask to use if you do not want the default. + """ + if mask is None: + mask = "mask[id,name,referenceCount]" + unattached = self.get_unattached_tags(mask) + attached = self.get_attached_tags(mask) + return {'attached': attached, 'unattached': unattached} + # return [unattached, attached] + + def get_unattached_tags(self, mask=None): + """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser()""" + return self.client.call('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', + mask=mask, iter=True) + + def get_attached_tags(self, mask=None): + """Calls SoftLayer_Tag::getAttachedTagsForCurrentUser()""" + return self.client.call('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', + mask=mask, iter=True) + + def get_tag_references(self, tag_id, mask=None): + if mask is None: + mask="mask[tagType]" + return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) + + def reference_lookup(self, resource_table_id, tag_type): + """Returns the SoftLayer Service for the corresponding type + + :param int resource_table_id: Tag_Reference::resourceTableId + :param string tag_type: Tag_Reference->tagType->keyName + + From SoftLayer_Tag::getAllTagTypes() + + |Type |Service | + | ----------------------------- | ------ | + |Hardware |HARDWARE| + |CCI |GUEST| + |Account Document |ACCOUNT_DOCUMENT| + |Ticket |TICKET| + |Vlan Firewall |NETWORK_VLAN_FIREWALL| + |Contract |CONTRACT| + |Image Template |IMAGE_TEMPLATE| + |Application Delivery Controller |APPLICATION_DELIVERY_CONTROLLER| + |Vlan |NETWORK_VLAN| + |Dedicated Host |DEDICATED_HOST| + """ + + if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: + raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + + if tag_type == 'APPLICATION_DELIVERY_CONTROLLER' : + service = 'Network_Application_Delivery_Controller' + elif tag_type == 'GUEST': + service = 'Virtual_Guest' + elif tag_type == 'DEDICATED_HOST': + service = 'Virtual_DedicatedHost' + else: + + tag_type = tag_type.lower() + # Sets the First letter, and any letter preceeded by a '_' to uppercase + # HARDWARE -> Hardware, NETWORK_VLAN -> Network_Vlan for example. + service = re.sub(r'(^[a-z]|\_[a-z])', lambda x: x.group().upper(), tag_type) + + # return {} + return self.client.call(service, 'getObject', id=resource_table_id) + + + From 54853e2faf31f6bbf64ef1e584f5673c6e507784 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 2 Jun 2020 09:28:40 -0500 Subject: [PATCH 0125/1385] added tag docs --- docs/cli/tags.rst | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/cli/tags.rst diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst new file mode 100644 index 000000000..5cca010f8 --- /dev/null +++ b/docs/cli/tags.rst @@ -0,0 +1,9 @@ +.. _cli_tags: + +Tag Commands +============ + + +.. click:: SoftLayer.CLI.tags.list:cli + :prog: tags list + :show-nested: From 9cc1c4ee0bc31e04f8c7f491a4dc0cda115161e8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 2 Jun 2020 11:16:30 -0500 Subject: [PATCH 0126/1385] #1230 added unit tests --- SoftLayer/fixtures/SoftLayer_Hardware.py | 71 ++++++++++++++ SoftLayer/fixtures/SoftLayer_Tag.py | 16 ++++ SoftLayer/managers/tags.py | 16 +++- tests/CLI/modules/tag_tests.py | 27 ++++++ tests/managers/tag_tests.py | 116 +++++++++++++++++++++++ 5 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Hardware.py create mode 100644 SoftLayer/fixtures/SoftLayer_Tag.py create mode 100644 tests/CLI/modules/tag_tests.py create mode 100644 tests/managers/tag_tests.py diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py new file mode 100644 index 000000000..edaa0554c --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -0,0 +1,71 @@ +getObject = { + 'id': 1000, + 'globalIdentifier': '1a2b3c-1701', + 'datacenter': {'id': 50, 'name': 'TEST00', + 'description': 'Test Data Center'}, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'nextInvoiceTotalRecurringAmount': 16.08, + 'children': [ + {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, + ], + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, + 'primaryIpAddress': '172.16.1.100', + 'hostname': 'hardware-test1', + 'domain': 'test.sftlyr.ws', + 'bareMetalInstanceFlag': True, + 'fullyQualifiedDomainName': 'hardware-test1.test.sftlyr.ws', + 'processorPhysicalCoreAmount': 2, + 'memoryCapacity': 2, + 'primaryBackendIpAddress': '10.1.0.2', + 'networkManagementIpAddress': '10.1.0.3', + 'hardwareStatus': {'status': 'ACTIVE'}, + 'primaryNetworkComponent': {'maxSpeed': 10, 'speed': 10}, + 'provisionDate': '2013-08-01 15:23:45', + 'notes': 'These are test notes.', + 'operatingSystem': { + 'softwareLicense': { + 'softwareDescription': { + 'referenceCode': 'UBUNTU_12_64', + 'name': 'Ubuntu', + 'version': 'Ubuntu 12.04 LTS', + } + }, + 'passwords': [ + {'username': 'root', 'password': 'abc123'} + ], + }, + 'remoteManagementAccounts': [ + {'username': 'root', 'password': 'abc123'} + ], + 'networkVlans': [ + { + 'networkSpace': 'PRIVATE', + 'vlanNumber': 1800, + 'id': 9653 + }, + { + 'networkSpace': 'PUBLIC', + 'vlanNumber': 3672, + 'id': 19082 + }, + ], + 'tagReferences': [ + {'tag': {'name': 'test_tag'}} + ], + 'activeTransaction': { + 'transactionStatus': { + 'name': 'TXN_NAME', + 'friendlyName': 'Friendly Transaction Name', + 'id': 6660 + } + } +} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py new file mode 100644 index 000000000..221efc06b --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -0,0 +1,16 @@ +getUnattachedTagsForCurrentUser = [{'id': 287895, 'name': 'coreos', 'referenceCount': 0}] +getAttachedTagsForCurrentUser = [{'id': 1286571, 'name': 'bs_test_instance', 'referenceCount': 5}] +getReferences = [ +{ + 'id': 73009305, + 'resourceTableId': 33488921, + 'tag': { + 'id': 1286571, + 'name': 'bs_test_instance', + }, + 'tagId': 1286571, + 'tagType': {'description': 'CCI', 'keyName': 'GUEST'}, + 'tagTypeId': 2, + 'usrRecordId': 6625205 +} +] \ No newline at end of file diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 8e6d282bd..234a5e7b6 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -25,19 +25,29 @@ def list_tags(self, mask=None): unattached = self.get_unattached_tags(mask) attached = self.get_attached_tags(mask) return {'attached': attached, 'unattached': unattached} - # return [unattached, attached] def get_unattached_tags(self, mask=None): - """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser()""" + """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser() + + :params string mask: Mask to use. + """ return self.client.call('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=mask, iter=True) def get_attached_tags(self, mask=None): - """Calls SoftLayer_Tag::getAttachedTagsForCurrentUser()""" + """Calls SoftLayer_Tag::getAttachedTagsForCurrentUser() + + :params string mask: Mask to use. + """ return self.client.call('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=mask, iter=True) def get_tag_references(self, tag_id, mask=None): + """Calls SoftLayer_Tag::getReferences(id=tag_id) + + :params int tag_id: Tag id to get references from + :params string mask: Mask to use. + """ if mask is None: mask="mask[tagType]" return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py new file mode 100644 index 000000000..1e578fd3c --- /dev/null +++ b/tests/CLI/modules/tag_tests.py @@ -0,0 +1,27 @@ +""" + SoftLayer.tests.CLI.modules.tag_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account +from SoftLayer import testing + + +class TagCLITests(testing.TestCase): + + def test_list(self): + result = self.run_command(['tags', 'list']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assertIn('coreos', result.output) + + def test_list_detail(self): + result = self.run_command(['tags', 'list', '-d']) + self.assert_no_fail(result) + self.assertIn('"vs-test1.test.sftlyr.ws', result.output) # From fixtures/virutal_guest.getObject + # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) \ No newline at end of file diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py new file mode 100644 index 000000000..ed23111ec --- /dev/null +++ b/tests/managers/tag_tests.py @@ -0,0 +1,116 @@ +""" + SoftLayer.tests.managers.tag_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import mock +import sys +import unittest + +import SoftLayer +from SoftLayer import fixtures +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers import tags +from SoftLayer import testing + + +class TagTests(testing.TestCase): + + def set_up(self): + self.tag_manager = SoftLayer.TagManager(self.client) + self.test_mask = "mask[id]" + + def test_list_tags(self): + result = self.tag_manager.list_tags() + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assertIn('attached', result.keys()) + self.assertIn('unattached', result.keys()) + + def test_list_tags_mask(self): + result = self.tag_manager.list_tags(mask=self.test_mask) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) + self.assertIn('attached', result.keys()) + self.assertIn('unattached', result.keys()) + + def test_unattached_tags(self): + result = self.tag_manager.get_unattached_tags() + self.assertEqual('coreos', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=None) + + def test_unattached_tags_mask(self): + result = self.tag_manager.get_unattached_tags(mask=self.test_mask) + self.assertEqual('coreos', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) + + def test_attached_tags(self): + result = self.tag_manager.get_attached_tags() + self.assertEqual('bs_test_instance', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=None) + + def test_attached_tags_mask(self): + result = self.tag_manager.get_attached_tags(mask=self.test_mask) + self.assertEqual('bs_test_instance', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) + + def test_get_tag_references(self): + tag_id = 1286571 + result = self.tag_manager.get_tag_references(tag_id) + self.assertEqual(tag_id, result[0].get('tagId')) + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id) + + def test_get_tag_references_mask(self): + tag_id = 1286571 + result = self.tag_manager.get_tag_references(tag_id, mask=self.test_mask) + self.assertEqual(tag_id, result[0].get('tagId')) + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id, mask=self.test_mask) + + def test_reference_lookup_hardware(self): + resource_id = 12345 + tag_type = 'HARDWARE' + + result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) + + def test_reference_lookup_hardware(self): + resource_id = 12345 + tag_type = 'HARDWARE' + + result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) + + def test_reference_lookup_guest(self): + resource_id = 12345 + tag_type = 'GUEST' + + result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=resource_id) + + def test_reference_lookup_app_delivery(self): + resource_id = 12345 + tag_type = 'APPLICATION_DELIVERY_CONTROLLER' + + result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', 'getObject', identifier=resource_id) + + def test_reference_lookup_dedicated(self): + resource_id = 12345 + tag_type = 'DEDICATED_HOST' + + result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getObject', identifier=resource_id) + + def test_reference_lookup_document(self): + resource_id = 12345 + tag_type = 'ACCOUNT_DOCUMENT' + + exception = self.assertRaises( + SoftLayerAPIError, + self.tag_manager.reference_lookup, + resource_id, + tag_type + ) + self.assertEqual(exception.faultCode, 404) + self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") \ No newline at end of file From 4840c6b776d119006f332e388e591af2e2720d87 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 2 Jun 2020 11:25:34 -0500 Subject: [PATCH 0127/1385] tox fixes --- SoftLayer/CLI/tags/list.py | 10 +++++++--- SoftLayer/fixtures/SoftLayer_Tag.py | 26 +++++++++++++------------- SoftLayer/managers/tags.py | 8 +++----- tests/CLI/modules/tag_tests.py | 4 ++-- tests/managers/tag_tests.py | 19 ++++++++++--------- 5 files changed, 35 insertions(+), 32 deletions(-) diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index 71634478a..a02f0dde1 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -13,6 +13,7 @@ from pprint import pprint as pp + @click.command() @click.option('--detail', '-d', is_flag=True, default=False, help="Show information about the resources using this tag.") @@ -21,7 +22,7 @@ def cli(env, detail): """List Tags.""" tag_manager = TagManager(env.client) - + if detail: tables = detailed_table(tag_manager) for table in tables: @@ -33,7 +34,8 @@ def cli(env, detail): def tag_row(tag): - return [tag.get('id'), tag.get('name'), tag.get('referenceCount',0)] + return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] + def detailed_table(tag_manager): """Creates a table for each tag, with details about resources using it""" @@ -52,6 +54,7 @@ def detailed_table(tag_manager): return tables + def simple_table(tag_manager): """Just tags and how many resources on each""" tags = tag_manager.list_tags() @@ -62,6 +65,7 @@ def simple_table(tag_manager): table.add_row(tag_row(tag)) return table + def get_resource_name(tag_manager, resource_id, tag_type): """Returns a string to identify a resource""" try: @@ -72,4 +76,4 @@ def get_resource_name(tag_manager, resource_id, tag_type): resource_row = resource.get('fullyQualifiedDomainName') except SoftLayerAPIError as e: resource_row = "{}".format(e.reason) - return resource_row \ No newline at end of file + return resource_row diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 221efc06b..3246870c0 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -1,16 +1,16 @@ getUnattachedTagsForCurrentUser = [{'id': 287895, 'name': 'coreos', 'referenceCount': 0}] getAttachedTagsForCurrentUser = [{'id': 1286571, 'name': 'bs_test_instance', 'referenceCount': 5}] getReferences = [ -{ - 'id': 73009305, - 'resourceTableId': 33488921, - 'tag': { - 'id': 1286571, - 'name': 'bs_test_instance', - }, - 'tagId': 1286571, - 'tagType': {'description': 'CCI', 'keyName': 'GUEST'}, - 'tagTypeId': 2, - 'usrRecordId': 6625205 -} -] \ No newline at end of file + { + 'id': 73009305, + 'resourceTableId': 33488921, + 'tag': { + 'id': 1286571, + 'name': 'bs_test_instance', + }, + 'tagId': 1286571, + 'tagType': {'description': 'CCI', 'keyName': 'GUEST'}, + 'tagTypeId': 2, + 'usrRecordId': 6625205 + } +] diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 234a5e7b6..a2b1a7251 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -9,6 +9,7 @@ from SoftLayer.exceptions import SoftLayerAPIError + class TagManager(object): """Manager for Tag functions.""" @@ -49,7 +50,7 @@ def get_tag_references(self, tag_id, mask=None): :params string mask: Mask to use. """ if mask is None: - mask="mask[tagType]" + mask = "mask[tagType]" return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) def reference_lookup(self, resource_table_id, tag_type): @@ -77,7 +78,7 @@ def reference_lookup(self, resource_table_id, tag_type): if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) - if tag_type == 'APPLICATION_DELIVERY_CONTROLLER' : + if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': service = 'Network_Application_Delivery_Controller' elif tag_type == 'GUEST': service = 'Virtual_Guest' @@ -92,6 +93,3 @@ def reference_lookup(self, resource_table_id, tag_type): # return {} return self.client.call(service, 'getObject', id=resource_table_id) - - - diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index 1e578fd3c..de0d9e07c 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -20,8 +20,8 @@ def test_list(self): def test_list_detail(self): result = self.run_command(['tags', 'list', '-d']) self.assert_no_fail(result) - self.assertIn('"vs-test1.test.sftlyr.ws', result.output) # From fixtures/virutal_guest.getObject + self.assertIn('"vs-test1.test.sftlyr.ws', result.output) # From fixtures/virutal_guest.getObject # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) - self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) \ No newline at end of file + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index ed23111ec..899d38a0e 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -69,48 +69,49 @@ def test_get_tag_references_mask(self): def test_reference_lookup_hardware(self): resource_id = 12345 - tag_type = 'HARDWARE' + tag_type = 'HARDWARE' result = self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) def test_reference_lookup_hardware(self): resource_id = 12345 - tag_type = 'HARDWARE' + tag_type = 'HARDWARE' result = self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) def test_reference_lookup_guest(self): resource_id = 12345 - tag_type = 'GUEST' + tag_type = 'GUEST' result = self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=resource_id) def test_reference_lookup_app_delivery(self): resource_id = 12345 - tag_type = 'APPLICATION_DELIVERY_CONTROLLER' + tag_type = 'APPLICATION_DELIVERY_CONTROLLER' result = self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', 'getObject', identifier=resource_id) + self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', + 'getObject', identifier=resource_id) def test_reference_lookup_dedicated(self): resource_id = 12345 - tag_type = 'DEDICATED_HOST' + tag_type = 'DEDICATED_HOST' result = self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getObject', identifier=resource_id) def test_reference_lookup_document(self): resource_id = 12345 - tag_type = 'ACCOUNT_DOCUMENT' + tag_type = 'ACCOUNT_DOCUMENT' exception = self.assertRaises( SoftLayerAPIError, self.tag_manager.reference_lookup, - resource_id, + resource_id, tag_type ) self.assertEqual(exception.faultCode, 404) - self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") \ No newline at end of file + self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") From a331898b6023e2590edb27e051574b523024b1f5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 2 Jun 2020 11:40:58 -0500 Subject: [PATCH 0128/1385] #1230 fixed tox analysis errors --- SoftLayer/CLI/tags/__init__.py | 2 +- SoftLayer/CLI/tags/list.py | 11 ++-- SoftLayer/fixtures/SoftLayer_Hardware.py | 58 ++++++++----------- .../SoftLayer_Network_Storage_Allowed_Host.py | 32 +++++----- SoftLayer/managers/tags.py | 4 +- tests/CLI/modules/block_tests.py | 2 +- tests/CLI/modules/server_tests.py | 36 ++++++------ tests/CLI/modules/tag_tests.py | 1 - tests/CLI/modules/vs/vs_tests.py | 36 ++++++------ tests/managers/hardware_tests.py | 8 +-- tests/managers/tag_tests.py | 22 ++----- tests/managers/user_tests.py | 4 +- 12 files changed, 95 insertions(+), 121 deletions(-) diff --git a/SoftLayer/CLI/tags/__init__.py b/SoftLayer/CLI/tags/__init__.py index e5caefa78..f8dd3783b 100644 --- a/SoftLayer/CLI/tags/__init__.py +++ b/SoftLayer/CLI/tags/__init__.py @@ -1 +1 @@ -"""Manage Tags""" \ No newline at end of file +"""Manage Tags""" diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index a02f0dde1..870d5faf5 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -3,16 +3,14 @@ import click -from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer.managers.tags import TagManager from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers.tags import TagManager from SoftLayer import utils # pylint: disable=unnecessary-lambda -from pprint import pprint as pp - @click.command() @click.option('--detail', '-d', is_flag=True, default=False, @@ -34,6 +32,7 @@ def cli(env, detail): def tag_row(tag): + """Format a tag table row""" return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] @@ -74,6 +73,6 @@ def get_resource_name(tag_manager, resource_id, tag_type): resource_row = resource.get('primaryIpAddress') else: resource_row = resource.get('fullyQualifiedDomainName') - except SoftLayerAPIError as e: - resource_row = "{}".format(e.reason) + except SoftLayerAPIError as exception: + resource_row = "{}".format(exception.reason) return resource_row diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index edaa0554c..cb902b556 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -1,8 +1,8 @@ getObject = { - 'id': 1000, - 'globalIdentifier': '1a2b3c-1701', - 'datacenter': {'id': 50, 'name': 'TEST00', - 'description': 'Test Data Center'}, + 'id': 1234, + 'globalIdentifier': 'xxxxc-asd', + 'datacenter': {'id': 12, 'name': 'DALLAS21', + 'description': 'Dallas 21'}, 'billingItem': { 'id': 6327, 'recurringFee': 1.54, @@ -13,59 +13,47 @@ 'orderItem': { 'order': { 'userRecord': { - 'username': 'chechu', + 'username': 'bob', } } } }, - 'primaryIpAddress': '172.16.1.100', - 'hostname': 'hardware-test1', + 'primaryIpAddress': '4.4.4.4', + 'hostname': 'testtest1', 'domain': 'test.sftlyr.ws', 'bareMetalInstanceFlag': True, - 'fullyQualifiedDomainName': 'hardware-test1.test.sftlyr.ws', - 'processorPhysicalCoreAmount': 2, - 'memoryCapacity': 2, - 'primaryBackendIpAddress': '10.1.0.2', - 'networkManagementIpAddress': '10.1.0.3', + 'fullyQualifiedDomainName': 'testtest1.test.sftlyr.ws', + 'processorPhysicalCoreAmount': 4, + 'memoryCapacity': 4, + 'primaryBackendIpAddress': '10.4.4.4', + 'networkManagementIpAddress': '10.4.4.4', 'hardwareStatus': {'status': 'ACTIVE'}, - 'primaryNetworkComponent': {'maxSpeed': 10, 'speed': 10}, - 'provisionDate': '2013-08-01 15:23:45', - 'notes': 'These are test notes.', + 'primaryNetworkComponent': {'maxSpeed': 1000, 'speed': 1000}, + 'provisionDate': '2020-08-01 15:23:45', + 'notes': 'NOTES NOTES NOTES', 'operatingSystem': { 'softwareLicense': { 'softwareDescription': { - 'referenceCode': 'UBUNTU_12_64', + 'referenceCode': 'UBUNTU_20_64', 'name': 'Ubuntu', - 'version': 'Ubuntu 12.04 LTS', + 'version': 'Ubuntu 20.04 LTS', } }, 'passwords': [ - {'username': 'root', 'password': 'abc123'} + {'username': 'root', 'password': 'xxxxxxxxxxxx'} ], }, 'remoteManagementAccounts': [ - {'username': 'root', 'password': 'abc123'} + {'username': 'root', 'password': 'zzzzzzzzzzzzzz'} ], 'networkVlans': [ { 'networkSpace': 'PRIVATE', - 'vlanNumber': 1800, - 'id': 9653 - }, - { - 'networkSpace': 'PUBLIC', - 'vlanNumber': 3672, - 'id': 19082 + 'vlanNumber': 1234, + 'id': 11111 }, ], 'tagReferences': [ - {'tag': {'name': 'test_tag'}} + {'tag': {'name': 'a tag'}} ], - 'activeTransaction': { - 'transactionStatus': { - 'name': 'TXN_NAME', - 'friendlyName': 'Friendly Transaction Name', - 'id': 6660 - } - } -} \ No newline at end of file +} diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py index 5bf8c3354..923147a58 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py @@ -32,22 +32,22 @@ getObject = TEST_ALLOWED_HOST getSubnetsInAcl = [{ - 'id': 12345678, - 'accountId': 1234, - 'networkIdentifier': '10.11.12.13', - 'cidr': '14', - 'billingRecordId': None, - 'parentId': None, - 'networkVlanId': None, - 'createDate': '2020-01-02 00:00:01', - 'modifyDate': None, - 'subnetType': 'SECONDARY_ON_VLAN', - 'restrictAllocationFlag': 0, - 'leafFlag': 1, - 'ownerId': 1, - 'ipAddressBegin': 129123, - 'ipAddressEnd': 129145, - 'purgeFlag': 0 + 'id': 12345678, + 'accountId': 1234, + 'networkIdentifier': '10.11.12.13', + 'cidr': '14', + 'billingRecordId': None, + 'parentId': None, + 'networkVlanId': None, + 'createDate': '2020-01-02 00:00:01', + 'modifyDate': None, + 'subnetType': 'SECONDARY_ON_VLAN', + 'restrictAllocationFlag': 0, + 'leafFlag': 1, + 'ownerId': 1, + 'ipAddressBegin': 129123, + 'ipAddressEnd': 129145, + 'purgeFlag': 0 }] assignSubnetsToAcl = [ diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index a2b1a7251..e22e94654 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -61,8 +61,8 @@ def reference_lookup(self, resource_table_id, tag_type): From SoftLayer_Tag::getAllTagTypes() - |Type |Service | - | ----------------------------- | ------ | + |Type |Service | + | ----------------------------- | ------ | |Hardware |HARDWARE| |CCI |GUEST| |Account Document |ACCOUNT_DOCUMENT| diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index b39face10..e5f6ba8c5 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -112,7 +112,7 @@ def test_volume_detail_name_identifier(self): 'keyName': {'operation': '*= BLOCK_STORAGE'} }, 'username': {'operation': '_= SL-12345'} - } + } } self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage', filter=expected_filter) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 157acfcc1..a7fd4e908 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -694,19 +694,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -749,12 +749,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index de0d9e07c..357364063 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -4,7 +4,6 @@ Tests for the user cli command """ -from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account from SoftLayer import testing diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index c61883385..a4bf28509 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -370,19 +370,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -425,12 +425,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index f504dba94..69e7ae52a 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -374,10 +374,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index 899d38a0e..38f198ae9 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -4,12 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock -import sys -import unittest -import SoftLayer -from SoftLayer import fixtures from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer.managers import tags from SoftLayer import testing @@ -18,7 +13,7 @@ class TagTests(testing.TestCase): def set_up(self): - self.tag_manager = SoftLayer.TagManager(self.client) + self.tag_manager = tags.TagManager(self.client) self.test_mask = "mask[id]" def test_list_tags(self): @@ -71,28 +66,21 @@ def test_reference_lookup_hardware(self): resource_id = 12345 tag_type = 'HARDWARE' - result = self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) - - def test_reference_lookup_hardware(self): - resource_id = 12345 - tag_type = 'HARDWARE' - - result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) def test_reference_lookup_guest(self): resource_id = 12345 tag_type = 'GUEST' - result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=resource_id) def test_reference_lookup_app_delivery(self): resource_id = 12345 tag_type = 'APPLICATION_DELIVERY_CONTROLLER' - result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', 'getObject', identifier=resource_id) @@ -100,7 +88,7 @@ def test_reference_lookup_dedicated(self): resource_id = 12345 tag_type = 'DEDICATED_HOST' - result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getObject', identifier=resource_id) def test_reference_lookup_document(self): diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index b75a5a772..b0ab015f9 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -231,7 +231,7 @@ def test_vpn_subnet_add(self): subnet_id = 1234 expected_args = ( [{"userId": user_id, "subnetId": subnet_id}], - ) + ) self.manager.vpn_subnet_add(user_id, [subnet_id]) self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects', args=expected_args) self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) @@ -242,7 +242,7 @@ def test_vpn_subnet_remove(self): overrides = [{'id': 3661234, 'subnetId': subnet_id}] expected_args = ( overrides, - ) + ) self.manager.vpn_subnet_remove(user_id, [subnet_id]) self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects', args=expected_args) self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) From 2bc9fa0420fc21b73affbf881b955df6232b8020 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 3 Jun 2020 15:44:47 -0400 Subject: [PATCH 0129/1385] Set tags. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/set.py | 24 ++++++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Tag.py | 2 ++ SoftLayer/managers/tags.py | 9 +++++++++ tests/CLI/modules/tag_tests.py | 20 ++++++++++++++++++++ tests/managers/tag_tests.py | 8 ++++++++ 6 files changed, 64 insertions(+) create mode 100644 SoftLayer/CLI/tags/set.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 6c2bae945..9304e2b33 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -292,6 +292,7 @@ ('tags', 'SoftLayer.CLI.tags'), ('tags:list', 'SoftLayer.CLI.tags.list:cli'), + ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/set.py b/SoftLayer/CLI/tags/set.py new file mode 100644 index 000000000..aa0879c6e --- /dev/null +++ b/SoftLayer/CLI/tags/set.py @@ -0,0 +1,24 @@ +"""Set Tags.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.tags import TagManager + + +@click.command() +@click.option('--tags', '-t', type=click.STRING, required=True, help='List of tags e.g. "tag1, tag2"') +@click.option('--key-name', '-k', type=click.STRING, required=True, help="Key name of a tag type e.g. GUEST, HARDWARE") +@click.option('--resource-id', '-r', type=click.INT, required=True, help="ID of the object being tagged") +@environment.pass_env +def cli(env, tags, key_name, resource_id): + """Set Tags.""" + + tag_manager = TagManager(env.client) + tags = tag_manager.set_tags(tags, key_name, resource_id) + + if tags: + click.secho("Set tags successfully", fg='green') + else: + click.secho("Failed to set tags", fg='red') diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 3246870c0..ca0d952ef 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -14,3 +14,5 @@ 'usrRecordId': 6625205 } ] + +setTags = True diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index e22e94654..fa19d3d3f 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -93,3 +93,12 @@ def reference_lookup(self, resource_table_id, tag_type): # return {} return self.client.call(service, 'getObject', id=resource_table_id) + + def set_tags(self, tags, key_name, resource_id): + """Calls SoftLayer_Tag::setTags() + + :param string tags: List of tags. + :param string key_name: Key name of a tag type. + :param int resource_id: ID of the object being tagged. + """ + return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id) diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index 357364063..847511e80 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -4,6 +4,8 @@ Tests for the user cli command """ +import mock + from SoftLayer import testing @@ -24,3 +26,21 @@ def test_list_detail(self): self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) + + @mock.patch('SoftLayer.CLI.tags.set.click') + def test_set_tags(self, click): + result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) + click.secho.assert_called_with('Set tags successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'setTags', + args=("tag1,tag2", "GUEST", 100),) + + @mock.patch('SoftLayer.CLI.tags.set.click') + def test_set_tags_failure(self, click): + mock = self.set_mock('SoftLayer_Tag', 'setTags') + mock.return_value = False + result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) + click.secho.assert_called_with('Failed to set tags', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'setTags', + args=("tag1,tag2", "GUEST", 100),) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index 38f198ae9..f7ab940bb 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -103,3 +103,11 @@ def test_reference_lookup_document(self): ) self.assertEqual(exception.faultCode, 404) self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") + + def test_set_tags(self): + tags = "tag1,tag2" + key_name = "GUEST" + resource_id = 100 + + self.tag_manager.set_tags(tags, key_name, resource_id) + self.assert_called_with('SoftLayer_Tag', 'setTags') From 6bdbf963341da83533cb4ad03a55344bb4613a73 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Jun 2020 19:21:31 -0400 Subject: [PATCH 0130/1385] add tags details --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/details.py | 24 ++++++++++++++++++++++++ SoftLayer/CLI/tags/list.py | 6 +++--- SoftLayer/managers/tags.py | 22 ++++++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/CLI/tags/details.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 9304e2b33..a71b62439 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -293,6 +293,7 @@ ('tags', 'SoftLayer.CLI.tags'), ('tags:list', 'SoftLayer.CLI.tags.list:cli'), ('tags:set', 'SoftLayer.CLI.tags.set:cli'), + ('tags:details', 'SoftLayer.CLI.tags.details:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/details.py b/SoftLayer/CLI/tags/details.py new file mode 100644 index 000000000..eb22bfada --- /dev/null +++ b/SoftLayer/CLI/tags/details.py @@ -0,0 +1,24 @@ +"""Details of a Tag.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI.tags.list import detailed_table +from SoftLayer.managers.tags import TagManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get details for a Tag.""" + + tag_manager = TagManager(env.client) + + if str.isdigit(identifier): + tags = [tag_manager.get_tag(identifier)] + else: + tags = tag_manager.get_tag_by_name(identifier) + table = detailed_table(tag_manager, tags) + env.fout(table) diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index 870d5faf5..e2f136581 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -22,7 +22,7 @@ def cli(env, detail): tag_manager = TagManager(env.client) if detail: - tables = detailed_table(tag_manager) + tables = detailed_table(tag_manager, tag_manager.get_attached_tags()) for table in tables: env.fout(table) else: @@ -36,9 +36,8 @@ def tag_row(tag): return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] -def detailed_table(tag_manager): +def detailed_table(tag_manager, tags): """Creates a table for each tag, with details about resources using it""" - tags = tag_manager.get_attached_tags() tables = [] for tag in tags: references = tag_manager.get_tag_references(tag.get('id')) @@ -76,3 +75,4 @@ def get_resource_name(tag_manager, resource_id, tag_type): except SoftLayerAPIError as exception: resource_row = "{}".format(exception.reason) return resource_row + diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index fa19d3d3f..e5b43a60f 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -53,6 +53,28 @@ def get_tag_references(self, tag_id, mask=None): mask = "mask[tagType]" return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) + def get_tag(self, tag_id, mask=None): + """Calls SoftLayer_Tag::getObject(id=tag_id) + + :params int tag_id: Tag id to get object from + :params string mask: Mask to use. + """ + if mask is None: + mask = "mask[id,name]" + result = self.client.call('SoftLayer_Tag', 'getObject', id=tag_id, mask=mask) + return result + + def get_tag_by_name(self, tag_name, mask=None): + """Calls SoftLayer_Tag::getTagByTagName(tag_name) + + :params string tag_name: Tag name to get object from + :params string mask: Mask to use. + """ + if mask is None: + mask = "mask[id,name]" + result = self.client.call('SoftLayer_Tag', 'getTagByTagName', tag_name, mask=mask) + return result + def reference_lookup(self, resource_table_id, tag_type): """Returns the SoftLayer Service for the corresponding type From 6be47b817f40dfa297dfb91840d784b226d91bd3 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Jun 2020 19:22:47 -0400 Subject: [PATCH 0131/1385] add tags details tests --- SoftLayer/fixtures/SoftLayer_Tag.py | 4 ++++ tests/CLI/modules/tag_tests.py | 16 ++++++++++++++-- tests/managers/tag_tests.py | 26 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index ca0d952ef..6839f7398 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -16,3 +16,7 @@ ] setTags = True + +getObject = getAttachedTagsForCurrentUser[0] + +getTagByTagName = getAttachedTagsForCurrentUser diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index 847511e80..f7eac8430 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -33,7 +33,7 @@ def test_set_tags(self, click): click.secho.assert_called_with('Set tags successfully', fg='green') self.assert_no_fail(result) self.assert_called_with('SoftLayer_Tag', 'setTags', - args=("tag1,tag2", "GUEST", 100),) + args=("tag1,tag2", "GUEST", 100), ) @mock.patch('SoftLayer.CLI.tags.set.click') def test_set_tags_failure(self, click): @@ -43,4 +43,16 @@ def test_set_tags_failure(self, click): click.secho.assert_called_with('Failed to set tags', fg='red') self.assert_no_fail(result) self.assert_called_with('SoftLayer_Tag', 'setTags', - args=("tag1,tag2", "GUEST", 100),) + args=("tag1,tag2", "GUEST", 100), ) + + def test_details_by_name(self): + tag_name = 'bs_test_instance' + result = self.run_command(['tags', 'details', tag_name]) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=(tag_name,)) + + def test_details_by_id(self): + tag_id = '1286571' + result = self.run_command(['tags', 'details', tag_id]) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index f7ab940bb..e2dba99c9 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -111,3 +111,29 @@ def test_set_tags(self): self.tag_manager.set_tags(tags, key_name, resource_id) self.assert_called_with('SoftLayer_Tag', 'setTags') + + def test_get_tag(self): + tag_id = 1286571 + result = self.tag_manager.get_tag(tag_id) + self.assertEqual(tag_id, result.get('id')) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) + + def test_get_tag_mask(self): + tag_id = 1286571 + result = self.tag_manager.get_tag(tag_id, mask=self.test_mask) + self.assertEqual(tag_id, result.get('id')) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id, mask=self.test_mask) + + def test_get_tag_by_name(self): + tag_name = 'bs_test_instance' + result = self.tag_manager.get_tag_by_name(tag_name) + args = (tag_name,) + self.assertEqual(tag_name, result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=args) + + def test_get_tag_by_name_mask(self): + tag_name = 'bs_test_instance' + result = self.tag_manager.get_tag_by_name(tag_name, mask=self.test_mask) + args = (tag_name,) + self.assertEqual(tag_name, result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', mask=self.test_mask, args=args) From 988185ab16cd656a22ca0749e93bf22d7983ec1a Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Jun 2020 19:23:25 -0400 Subject: [PATCH 0132/1385] add tags docs --- docs/cli/tags.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst index 5cca010f8..3aa9d75f9 100644 --- a/docs/cli/tags.rst +++ b/docs/cli/tags.rst @@ -7,3 +7,11 @@ Tag Commands .. click:: SoftLayer.CLI.tags.list:cli :prog: tags list :show-nested: + +.. click:: SoftLayer.CLI.tags.set:cli + :prog: tags set + :show-nested: + +.. click:: SoftLayer.CLI.tags.details:cli + :prog: tags details + :show-nested: \ No newline at end of file From da3c5e27e9919fa64792cb08a3f8191664f7c861 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 5 Jun 2020 11:57:47 -0400 Subject: [PATCH 0133/1385] delete tags --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/delete.py | 30 +++++++++++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Tag.py | 2 ++ SoftLayer/managers/tags.py | 3 +++ tests/CLI/modules/tag_tests.py | 9 +++++++++ 5 files changed, 45 insertions(+) create mode 100644 SoftLayer/CLI/tags/delete.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a71b62439..2ee597045 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -294,6 +294,7 @@ ('tags:list', 'SoftLayer.CLI.tags.list:cli'), ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('tags:details', 'SoftLayer.CLI.tags.details:cli'), + ('tags:delete', 'SoftLayer.CLI.tags.delete:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/delete.py b/SoftLayer/CLI/tags/delete.py new file mode 100644 index 000000000..5c6ccbc08 --- /dev/null +++ b/SoftLayer/CLI/tags/delete.py @@ -0,0 +1,30 @@ +"""List Tags.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers.tags import TagManager +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + +# pylint: disable=unnecessary-lambda + +from pprint import pprint as pp + + +@click.command() +@click.option('-id', required=False, show_default=False, type=int, help='identifier') +@click.option('--name', required=False, default=False, type=str, show_default=False, help='tag name') +@environment.pass_env +def cli(env, id, name): + """delete Tag.""" + + tag_manager = TagManager(env.client) + + if not name and id is not None: + tag_name = tag_manager.get_tag(id) + tag_manager.delete_tag(tag_name['name']) + if name and id is None: + tag_manager.delete_tag(name) diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 6839f7398..ec4d1163e 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -15,6 +15,8 @@ } ] +deleteTag = True + setTags = True getObject = getAttachedTagsForCurrentUser[0] diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index e5b43a60f..8752f5257 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -116,6 +116,9 @@ def reference_lookup(self, resource_table_id, tag_type): # return {} return self.client.call(service, 'getObject', id=resource_table_id) + def delete_tag(self, name): + return self.client.call('SoftLayer_Tag', 'deleteTag', name) + def set_tags(self, tags, key_name, resource_id): """Calls SoftLayer_Tag::setTags() diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index f7eac8430..ac6930d08 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -7,6 +7,7 @@ import mock from SoftLayer import testing +from SoftLayer.managers.tags import TagManager class TagCLITests(testing.TestCase): @@ -56,3 +57,11 @@ def test_details_by_id(self): result = self.run_command(['tags', 'details', tag_id]) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) + + def test_deleteTags_by_name(self): + result = self.run_command(['tags', 'delete', '--name="test"']) + self.assert_no_fail(result) + + def test_deleteTags_by_id(self): + result = self.run_command(['tags', 'delete', '-id=123456']) + self.assert_no_fail(result) From b4cff4282e79f7284955b2942bf575342f06eca9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 8 Jun 2020 16:52:43 -0500 Subject: [PATCH 0134/1385] #1230 added a taggable command, to list all things that are able to be tagged --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/delete.py | 37 +++++------ SoftLayer/CLI/tags/details.py | 9 ++- SoftLayer/CLI/tags/set.py | 6 +- SoftLayer/CLI/tags/taggable.py | 44 +++++++++++++ SoftLayer/managers/tags.py | 113 ++++++++++++++++++++++++++++----- 6 files changed, 172 insertions(+), 38 deletions(-) create mode 100644 SoftLayer/CLI/tags/taggable.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2ee597045..3c15fe772 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -295,6 +295,7 @@ ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('tags:details', 'SoftLayer.CLI.tags.details:cli'), ('tags:delete', 'SoftLayer.CLI.tags.delete:cli'), + ('tags:taggable', 'SoftLayer.CLI.tags.taggable:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/delete.py b/SoftLayer/CLI/tags/delete.py index 5c6ccbc08..f72e4687f 100644 --- a/SoftLayer/CLI/tags/delete.py +++ b/SoftLayer/CLI/tags/delete.py @@ -1,30 +1,31 @@ -"""List Tags.""" +"""Delete Tags.""" # :license: MIT, see LICENSE for more details. import click -from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer.managers.tags import TagManager from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer import utils - -# pylint: disable=unnecessary-lambda - -from pprint import pprint as pp @click.command() -@click.option('-id', required=False, show_default=False, type=int, help='identifier') -@click.option('--name', required=False, default=False, type=str, show_default=False, help='tag name') +@click.argument('identifier') +@click.option('--name', required=False, default=False, is_flag=True, show_default=False, + help='Assume identifier is a tag name. Useful if your tag name is a number.') @environment.pass_env -def cli(env, id, name): - """delete Tag.""" +def cli(env, identifier, name): + """Delete a Tag. Tag names that contain spaces need to be encased in quotes""" tag_manager = TagManager(env.client) - - if not name and id is not None: - tag_name = tag_manager.get_tag(id) - tag_manager.delete_tag(tag_name['name']) - if name and id is None: - tag_manager.delete_tag(name) + tag_name = identifier + # If the identifier is a int, and user didn't tell us it was a name. + if str.isdigit(identifier) and not name: + tag = tag_manager.get_tag(tag_id) + tag_name = tag.get('name', None) + + + result = tag_manager.delete_tag(tag_name) + if result: + click.secho("Tag {} has been removed".format(tag_name), fg='green') + else: + click.secho("Failed to remove tag {}".format(tag_name), fg='red') \ No newline at end of file diff --git a/SoftLayer/CLI/tags/details.py b/SoftLayer/CLI/tags/details.py index eb22bfada..7c397f431 100644 --- a/SoftLayer/CLI/tags/details.py +++ b/SoftLayer/CLI/tags/details.py @@ -10,13 +10,16 @@ @click.command() @click.argument('identifier') +@click.option('--name', required=False, default=False, is_flag=True, show_default=False, + help='Assume identifier is a tag name. Useful if your tag name is a number.') @environment.pass_env -def cli(env, identifier): - """Get details for a Tag.""" +def cli(env, identifier, name): + """Get details for a Tag. Identifier can be either a name or tag-id""" tag_manager = TagManager(env.client) - if str.isdigit(identifier): + # If the identifier is a int, and user didn't tell us it was a name. + if str.isdigit(identifier) and not name: tags = [tag_manager.get_tag(identifier)] else: tags = tag_manager.get_tag_by_name(identifier) diff --git a/SoftLayer/CLI/tags/set.py b/SoftLayer/CLI/tags/set.py index aa0879c6e..e30137ea5 100644 --- a/SoftLayer/CLI/tags/set.py +++ b/SoftLayer/CLI/tags/set.py @@ -8,8 +8,10 @@ @click.command() -@click.option('--tags', '-t', type=click.STRING, required=True, help='List of tags e.g. "tag1, tag2"') -@click.option('--key-name', '-k', type=click.STRING, required=True, help="Key name of a tag type e.g. GUEST, HARDWARE") +@click.option('--tags', '-t', type=click.STRING, required=True, + help='Comma seperated list of tags, enclosed in quotes. "tag1, tag2"') +@click.option('--key-name', '-k', type=click.STRING, required=True, + help="Key name of a tag type e.g. GUEST, HARDWARE. See slcli tags taggable output.") @click.option('--resource-id', '-r', type=click.INT, required=True, help="ID of the object being tagged") @environment.pass_env def cli(env, tags, key_name, resource_id): diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py new file mode 100644 index 000000000..2fd9ef2b1 --- /dev/null +++ b/SoftLayer/CLI/tags/taggable.py @@ -0,0 +1,44 @@ +"""List everything that could be tagged.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI.exceptions import ArgumentError +from SoftLayer.CLI import formatting +from SoftLayer.managers.tags import TagManager +from SoftLayer.CLI import environment + +from pprint import pprint as pp +@click.command() +@environment.pass_env +def cli(env): + """List everything that could be tagged.""" + + tag_manager = TagManager(env.client) + tag_types = tag_manager.get_all_tag_types() + for tag_type in tag_types: + title = "{} ({})".format(tag_type['description'], tag_type['keyName']) + table = formatting.Table(['Id', 'Name'], title=title) + resources = tag_manager.taggable_by_type(tag_type['keyName']) + for resource in resources: + table.add_row([ + resource['resource']['id'], + get_resource_name(resource['resource'], tag_type['keyName']) + ]) + env.fout(table) + + +def get_resource_name(resource, tag_type): + """Returns a string that names a resource""" + if tag_type == 'NETWORK_VLAN_FIREWALL': + return resource.get('primaryIpAddress') + elif tag_type == 'NETWORK_VLAN': + return "{} ({})".format(resource.get('vlanNumber'), resource.get('name')) + elif tag_type == 'IMAGE_TEMPLATE' or tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + return resource.get('name') + elif tag_type == 'TICKET': + return resource.get('subjet') + elif tag_type == 'NETWORK_SUBNET': + return resource.get('networkIdentifier') + else: + return resource.get('fullyQualifiedDomainName') \ No newline at end of file diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 8752f5257..19fd077ec 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -96,9 +96,75 @@ def reference_lookup(self, resource_table_id, tag_type): |Vlan |NETWORK_VLAN| |Dedicated Host |DEDICATED_HOST| """ + service = self.type_to_service(tag_type) + if service is None: + raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + # return {} + return self.client.call(service, 'getObject', id=resource_table_id) + + def delete_tag(self, name): + """Calls SoftLayer_Tag::deleteTag + + :param string name: tag name to delete + """ + return self.client.call('SoftLayer_Tag', 'deleteTag', name) + + def set_tags(self, tags, key_name, resource_id): + """Calls SoftLayer_Tag::setTags() + + :param string tags: List of tags. + :param string key_name: Key name of a tag type. + :param int resource_id: ID of the object being tagged. + """ + return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id) + def get_all_tag_types(self): + """Calls SoftLayer_Tag::getAllTagTypes()""" + types = self.client.call('SoftLayer_Tag', 'getAllTagTypes') + useable_types = [] + for tag_type in types: + service = self.type_to_service(tag_type['keyName']) + # Mostly just to remove the types that are not user taggable. + if service is not None: + temp_type = tag_type + temp_type['service'] = service + useable_types.append(temp_type) + return useable_types + + def taggable_by_type(self, tag_type): + """Returns a list of resources that can be tagged, that are of the given type + + :param string tag_type: Key name of a tag type. See SoftLayer_Tag::getAllTagTypes + """ + service = self.type_to_service(tag_type) + search_term = "_objectType:SoftLayer_{}".format(service) + if tag_type == 'TICKET': + search_term = "{} status.name: open".format(search_term) + elif tag_type == 'IMAGE_TEMPLATE': + mask = "mask[id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,imageType]" + resources = self.client.call('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups', + mask=mask, iter=True) + to_return = [] + # Fake search result output + for resource in resources: + to_return.append({'resourceType':service, 'resource':resource}) + return to_return + elif tag_type == 'NETWORK_SUBNET': + resources = self.client.call('SoftLayer_Account', 'getSubnets', iter=True) + to_return = [] + # Fake search result output + for resource in resources: + to_return.append({'resourceType':service, 'resource':resource}) + return to_return + resources = self.client.call('SoftLayer_Search', 'advancedSearch', search_term, iter=True) + return resources + + @staticmethod + def type_to_service(tag_type): + """Returns the SoftLayer service for the given tag_type""" + service = None if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: - raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + return None if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': service = 'Network_Application_Delivery_Controller' @@ -106,24 +172,41 @@ def reference_lookup(self, resource_table_id, tag_type): service = 'Virtual_Guest' elif tag_type == 'DEDICATED_HOST': service = 'Virtual_DedicatedHost' + elif tag_type == 'IMAGE_TEMPLATE': + service = 'Virtual_Guest_Block_Device_Template_Group' else: tag_type = tag_type.lower() # Sets the First letter, and any letter preceeded by a '_' to uppercase # HARDWARE -> Hardware, NETWORK_VLAN -> Network_Vlan for example. service = re.sub(r'(^[a-z]|\_[a-z])', lambda x: x.group().upper(), tag_type) + return service + + # @staticmethod + # def type_to_datatype(tag_type): + # """Returns the SoftLayer datatye for the given tag_type""" + # datatye = None + # if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: + # return None + + # if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + # datatye = 'adcLoadBalancers' + # elif tag_type == 'GUEST': + # datatye = 'virtualGuests' + # elif tag_type == 'DEDICATED_HOST': + # datatye = 'dedicatedHosts' + # elif tag_type == 'HARDWARE': + # datatye = 'hardware' + # elif tag_type == 'TICKET': + # datatye = 'openTickets' + # elif tag_type == 'NETWORK_SUBNET': + # datatye = 'subnets' + # elif tag_type == 'NETWORK_VLAN': + # datatye = 'networkVlans' + # elif tag_type == 'NETWORK_VLAN_FIREWALL': + # datatye = 'networkVlans' + # elif tag_type == 'IMAGE_TEMPLATE': + # datatye = 'blockDeviceTemplateGroups' + + # return datatye - # return {} - return self.client.call(service, 'getObject', id=resource_table_id) - - def delete_tag(self, name): - return self.client.call('SoftLayer_Tag', 'deleteTag', name) - - def set_tags(self, tags, key_name, resource_id): - """Calls SoftLayer_Tag::setTags() - - :param string tags: List of tags. - :param string key_name: Key name of a tag type. - :param int resource_id: ID of the object being tagged. - """ - return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id) From 793e375627c87e54de46fd5125294143bac3c9b7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 9 Jun 2020 15:38:35 -0500 Subject: [PATCH 0135/1385] #1230 added tag cleanup command --- SoftLayer/CLI/core.py | 1 + SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/cleanup.py | 33 +++++++++++++++++++++++++++++++++ SoftLayer/CLI/tags/list.py | 10 ++++------ SoftLayer/CLI/tags/taggable.py | 18 +----------------- SoftLayer/managers/tags.py | 21 ++++++++++++++++++++- 6 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 SoftLayer/CLI/tags/cleanup.py diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 362ff72e7..7257c59d9 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -158,6 +158,7 @@ def cli(env, logger.setLevel(DEBUG_LOGGING_MAP.get(verbose, logging.DEBUG)) env.vars['_timings'] = SoftLayer.DebugTransport(env.client.transport) + env.vars['verbose'] = verbose env.client.transport = env.vars['_timings'] diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 3c15fe772..cc67a7d2f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -291,6 +291,7 @@ ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), ('tags', 'SoftLayer.CLI.tags'), + ('tags:cleanup', 'SoftLayer.CLI.tags.cleanup:cli'), ('tags:list', 'SoftLayer.CLI.tags.list:cli'), ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('tags:details', 'SoftLayer.CLI.tags.details:cli'), diff --git a/SoftLayer/CLI/tags/cleanup.py b/SoftLayer/CLI/tags/cleanup.py new file mode 100644 index 000000000..d7dd0fc80 --- /dev/null +++ b/SoftLayer/CLI/tags/cleanup.py @@ -0,0 +1,33 @@ +"""Removes unused Tags""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers.tags import TagManager +from SoftLayer import utils + +from pprint import pprint as pp +# pylint: disable=unnecessary-lambda + + +@click.command() +@click.option('--dry-run', '-d', is_flag=True, default=False, + help="Don't delete, just show what will be deleted.") +@environment.pass_env +def cli(env, dry_run): + """Removes all empty tags.""" + + tag_manager = TagManager(env.client) + empty_tags = tag_manager.get_unattached_tags() + + for tag in empty_tags: + if dry_run: + click.secho("(Dry Run) Removing {}".format(tag.get('name')), fg='yellow') + else: + result = tag_manager.delete_tag(tag.get('name')) + color = 'green' if result else 'red' + click.secho("Removing {}".format(tag.get('name')), fg=color) + diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index e2f136581..e73ffe718 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -66,13 +66,11 @@ def simple_table(tag_manager): def get_resource_name(tag_manager, resource_id, tag_type): """Returns a string to identify a resource""" + name = None try: resource = tag_manager.reference_lookup(resource_id, tag_type) - if tag_type == 'NETWORK_VLAN_FIREWALL': - resource_row = resource.get('primaryIpAddress') - else: - resource_row = resource.get('fullyQualifiedDomainName') + name = tag_manager.get_resource_name(resource, tag_type) except SoftLayerAPIError as exception: - resource_row = "{}".format(exception.reason) - return resource_row + name = "{}".format(exception.reason) + return name diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py index 2fd9ef2b1..8436cae52 100644 --- a/SoftLayer/CLI/tags/taggable.py +++ b/SoftLayer/CLI/tags/taggable.py @@ -23,22 +23,6 @@ def cli(env): for resource in resources: table.add_row([ resource['resource']['id'], - get_resource_name(resource['resource'], tag_type['keyName']) + tag_manager.get_resource_name(resource['resource'], tag_type['keyName']) ]) env.fout(table) - - -def get_resource_name(resource, tag_type): - """Returns a string that names a resource""" - if tag_type == 'NETWORK_VLAN_FIREWALL': - return resource.get('primaryIpAddress') - elif tag_type == 'NETWORK_VLAN': - return "{} ({})".format(resource.get('vlanNumber'), resource.get('name')) - elif tag_type == 'IMAGE_TEMPLATE' or tag_type == 'APPLICATION_DELIVERY_CONTROLLER': - return resource.get('name') - elif tag_type == 'TICKET': - return resource.get('subjet') - elif tag_type == 'NETWORK_SUBNET': - return resource.get('networkIdentifier') - else: - return resource.get('fullyQualifiedDomainName') \ No newline at end of file diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 19fd077ec..76df6ba43 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -99,7 +99,6 @@ def reference_lookup(self, resource_table_id, tag_type): service = self.type_to_service(tag_type) if service is None: raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) - # return {} return self.client.call(service, 'getObject', id=resource_table_id) def delete_tag(self, name): @@ -182,6 +181,26 @@ def type_to_service(tag_type): service = re.sub(r'(^[a-z]|\_[a-z])', lambda x: x.group().upper(), tag_type) return service + @staticmethod + def get_resource_name(resource, tag_type): + """Returns a string that names a resource + + :param dict resource: A SoftLayer datatype for the given tag_type + :param string tag_type: Key name for the tag_type + """ + if tag_type == 'NETWORK_VLAN_FIREWALL': + return resource.get('primaryIpAddress') + elif tag_type == 'NETWORK_VLAN': + return "{} ({})".format(resource.get('vlanNumber'), resource.get('name')) + elif tag_type == 'IMAGE_TEMPLATE' or tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + return resource.get('name') + elif tag_type == 'TICKET': + return resource.get('subjet') + elif tag_type == 'NETWORK_SUBNET': + return resource.get('networkIdentifier') + else: + return resource.get('fullyQualifiedDomainName') + # @staticmethod # def type_to_datatype(tag_type): # """Returns the SoftLayer datatye for the given tag_type""" From 5a61a95421ac197ec584363986021792b2575a98 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 9 Jun 2020 16:27:19 -0500 Subject: [PATCH 0136/1385] CLI unit tests --- SoftLayer/CLI/tags/delete.py | 2 +- SoftLayer/CLI/tags/taggable.py | 2 -- SoftLayer/fixtures/SoftLayer_Search.py | 23 +++++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Tag.py | 7 +++++++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Search.py diff --git a/SoftLayer/CLI/tags/delete.py b/SoftLayer/CLI/tags/delete.py index f72e4687f..d6957af6b 100644 --- a/SoftLayer/CLI/tags/delete.py +++ b/SoftLayer/CLI/tags/delete.py @@ -20,7 +20,7 @@ def cli(env, identifier, name): tag_name = identifier # If the identifier is a int, and user didn't tell us it was a name. if str.isdigit(identifier) and not name: - tag = tag_manager.get_tag(tag_id) + tag = tag_manager.get_tag(identifier) tag_name = tag.get('name', None) diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py index 8436cae52..304f7007d 100644 --- a/SoftLayer/CLI/tags/taggable.py +++ b/SoftLayer/CLI/tags/taggable.py @@ -3,12 +3,10 @@ import click -from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer.CLI import formatting from SoftLayer.managers.tags import TagManager from SoftLayer.CLI import environment -from pprint import pprint as pp @click.command() @environment.pass_env def cli(env): diff --git a/SoftLayer/fixtures/SoftLayer_Search.py b/SoftLayer/fixtures/SoftLayer_Search.py new file mode 100644 index 000000000..77fe52033 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Search.py @@ -0,0 +1,23 @@ +advancedSearch = [ + { + "relevanceScore": "4", + "resourceType": "SoftLayer_Hardware", + "resource": { + "accountId": 307608, + "domain": "vmware.test.com", + "fullyQualifiedDomainName": "host14.vmware.test.com", + "hardwareStatusId": 5, + "hostname": "host14", + "id": 123456, + "manufacturerSerialNumber": "AAAAAAAAA", + "notes": "A test notes", + "provisionDate": "2018-08-24T12:32:10-06:00", + "serialNumber": "SL12345678", + "serviceProviderId": 1, + "hardwareStatus": { + "id": 5, + "status": "ACTIVE" + } + } + } +] \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index ec4d1163e..5bbccf243 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -22,3 +22,10 @@ getObject = getAttachedTagsForCurrentUser[0] getTagByTagName = getAttachedTagsForCurrentUser + +getAllTagTypes = [ + { + "description": "Hardware", + "keyName": "HARDWARE" + } +] \ No newline at end of file From a13fdd0b5b6945def79ccd2ce840bf1cc59fa714 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 10 Jun 2020 16:36:19 -0500 Subject: [PATCH 0137/1385] #1230 more unit tests, code cleanup --- SoftLayer/CLI/tags/cleanup.py | 9 +--- SoftLayer/CLI/tags/delete.py | 6 +-- SoftLayer/CLI/tags/list.py | 1 - SoftLayer/CLI/tags/set.py | 2 +- SoftLayer/CLI/tags/taggable.py | 3 +- SoftLayer/fixtures/SoftLayer_Search.py | 2 +- SoftLayer/fixtures/SoftLayer_Tag.py | 2 +- SoftLayer/managers/tags.py | 7 ++- docs/cli/tags.rst | 11 +++- tests/CLI/modules/tag_tests.py | 60 +++++++++++++++++++--- tests/managers/tag_tests.py | 69 ++++++++++++++++++++++++++ 11 files changed, 143 insertions(+), 29 deletions(-) diff --git a/SoftLayer/CLI/tags/cleanup.py b/SoftLayer/CLI/tags/cleanup.py index d7dd0fc80..26ddea7ef 100644 --- a/SoftLayer/CLI/tags/cleanup.py +++ b/SoftLayer/CLI/tags/cleanup.py @@ -4,13 +4,7 @@ import click from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer.managers.tags import TagManager -from SoftLayer import utils - -from pprint import pprint as pp -# pylint: disable=unnecessary-lambda @click.command() @@ -22,7 +16,7 @@ def cli(env, dry_run): tag_manager = TagManager(env.client) empty_tags = tag_manager.get_unattached_tags() - + for tag in empty_tags: if dry_run: click.secho("(Dry Run) Removing {}".format(tag.get('name')), fg='yellow') @@ -30,4 +24,3 @@ def cli(env, dry_run): result = tag_manager.delete_tag(tag.get('name')) color = 'green' if result else 'red' click.secho("Removing {}".format(tag.get('name')), fg=color) - diff --git a/SoftLayer/CLI/tags/delete.py b/SoftLayer/CLI/tags/delete.py index d6957af6b..f3bb1e70b 100644 --- a/SoftLayer/CLI/tags/delete.py +++ b/SoftLayer/CLI/tags/delete.py @@ -3,9 +3,8 @@ import click -from SoftLayer.CLI.exceptions import ArgumentError -from SoftLayer.managers.tags import TagManager from SoftLayer.CLI import environment +from SoftLayer.managers.tags import TagManager @click.command() @@ -23,9 +22,8 @@ def cli(env, identifier, name): tag = tag_manager.get_tag(identifier) tag_name = tag.get('name', None) - result = tag_manager.delete_tag(tag_name) if result: click.secho("Tag {} has been removed".format(tag_name), fg='green') else: - click.secho("Failed to remove tag {}".format(tag_name), fg='red') \ No newline at end of file + click.secho("Failed to remove tag {}".format(tag_name), fg='red') diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index e73ffe718..bc8662764 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -73,4 +73,3 @@ def get_resource_name(tag_manager, resource_id, tag_type): except SoftLayerAPIError as exception: name = "{}".format(exception.reason) return name - diff --git a/SoftLayer/CLI/tags/set.py b/SoftLayer/CLI/tags/set.py index e30137ea5..ed409fb99 100644 --- a/SoftLayer/CLI/tags/set.py +++ b/SoftLayer/CLI/tags/set.py @@ -8,7 +8,7 @@ @click.command() -@click.option('--tags', '-t', type=click.STRING, required=True, +@click.option('--tags', '-t', type=click.STRING, required=True, help='Comma seperated list of tags, enclosed in quotes. "tag1, tag2"') @click.option('--key-name', '-k', type=click.STRING, required=True, help="Key name of a tag type e.g. GUEST, HARDWARE. See slcli tags taggable output.") diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py index 304f7007d..0c08acdb0 100644 --- a/SoftLayer/CLI/tags/taggable.py +++ b/SoftLayer/CLI/tags/taggable.py @@ -3,9 +3,10 @@ import click +from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.tags import TagManager -from SoftLayer.CLI import environment + @click.command() @environment.pass_env diff --git a/SoftLayer/fixtures/SoftLayer_Search.py b/SoftLayer/fixtures/SoftLayer_Search.py index 77fe52033..ccb45fe55 100644 --- a/SoftLayer/fixtures/SoftLayer_Search.py +++ b/SoftLayer/fixtures/SoftLayer_Search.py @@ -20,4 +20,4 @@ } } } -] \ No newline at end of file +] diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 5bbccf243..9f6aeaec4 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -28,4 +28,4 @@ "description": "Hardware", "keyName": "HARDWARE" } -] \ No newline at end of file +] diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 76df6ba43..818b0547d 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -120,7 +120,7 @@ def set_tags(self, tags, key_name, resource_id): def get_all_tag_types(self): """Calls SoftLayer_Tag::getAllTagTypes()""" types = self.client.call('SoftLayer_Tag', 'getAllTagTypes') - useable_types = [] + useable_types = [] for tag_type in types: service = self.type_to_service(tag_type['keyName']) # Mostly just to remove the types that are not user taggable. @@ -146,14 +146,14 @@ def taggable_by_type(self, tag_type): to_return = [] # Fake search result output for resource in resources: - to_return.append({'resourceType':service, 'resource':resource}) + to_return.append({'resourceType': service, 'resource': resource}) return to_return elif tag_type == 'NETWORK_SUBNET': resources = self.client.call('SoftLayer_Account', 'getSubnets', iter=True) to_return = [] # Fake search result output for resource in resources: - to_return.append({'resourceType':service, 'resource':resource}) + to_return.append({'resourceType': service, 'resource': resource}) return to_return resources = self.client.call('SoftLayer_Search', 'advancedSearch', search_term, iter=True) return resources @@ -228,4 +228,3 @@ def get_resource_name(resource, tag_type): # datatye = 'blockDeviceTemplateGroups' # return datatye - diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst index 3aa9d75f9..435d4dc59 100644 --- a/docs/cli/tags.rst +++ b/docs/cli/tags.rst @@ -3,6 +3,7 @@ Tag Commands ============ +These commands will allow you to interact with the **IMS** provier tagging service. The `IBM Global Search and Tagging API `_ can be used to interact with both the **GHOST** provider and **IMS** provider. The **GHOST** provider will handle tags for things outside of the Classic Infrastructure (aka SoftLayer) space. .. click:: SoftLayer.CLI.tags.list:cli :prog: tags list @@ -14,4 +15,12 @@ Tag Commands .. click:: SoftLayer.CLI.tags.details:cli :prog: tags details - :show-nested: \ No newline at end of file + :show-nested: + +.. click:: SoftLayer.CLI.tags.delete:cli + :prog: tags delete + :show-nested: + +.. click:: SoftLayer.CLI.tags.taggable:cli + :prog: tags taggable + :show-nested: diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index ac6930d08..b2e29721e 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -6,8 +6,8 @@ """ import mock +from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import testing -from SoftLayer.managers.tags import TagManager class TagCLITests(testing.TestCase): @@ -28,13 +28,23 @@ def test_list_detail(self): self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) + def test_list_detail_ungettable(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') + mock.side_effect = SoftLayerAPIError(404, "TEST ERROR") + result = self.run_command(['tags', 'list', '-d']) + self.assert_no_fail(result) + self.assertIn("TEST ERROR", result.output) # From fixtures/virutal_guest.getObject + # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) + @mock.patch('SoftLayer.CLI.tags.set.click') def test_set_tags(self, click): result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) click.secho.assert_called_with('Set tags successfully', fg='green') self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'setTags', - args=("tag1,tag2", "GUEST", 100), ) + self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) @mock.patch('SoftLayer.CLI.tags.set.click') def test_set_tags_failure(self, click): @@ -43,8 +53,7 @@ def test_set_tags_failure(self, click): result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) click.secho.assert_called_with('Failed to set tags', fg='red') self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'setTags', - args=("tag1,tag2", "GUEST", 100), ) + self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) def test_details_by_name(self): tag_name = 'bs_test_instance' @@ -59,9 +68,46 @@ def test_details_by_id(self): self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) def test_deleteTags_by_name(self): - result = self.run_command(['tags', 'delete', '--name="test"']) + result = self.run_command(['tags', 'delete', 'test']) self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('test',)) def test_deleteTags_by_id(self): - result = self.run_command(['tags', 'delete', '-id=123456']) + result = self.run_command(['tags', 'delete', '123456']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier='123456') + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('bs_test_instance',)) + + def test_deleteTags_by_number_name(self): + result = self.run_command(['tags', 'delete', '123456', '--name']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) + + @mock.patch('SoftLayer.CLI.tags.delete.click') + def test_deleteTags_fail(self, click): + mock = self.set_mock('SoftLayer_Tag', 'deleteTag') + mock.return_value = False + result = self.run_command(['tags', 'delete', '123456', '--name']) + click.secho.assert_called_with('Failed to remove tag 123456', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) + + def test_taggable(self): + result = self.run_command(['tags', 'taggable']) + self.assert_no_fail(result) + self.assertIn('"host14.vmware.test.com', result.output) + self.assert_called_with('SoftLayer_Tag', 'getAllTagTypes') + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) + + def test_cleanup(self): + result = self.run_command(['tags', 'cleanup']) self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('coreos',)) + + def test_cleanup_dry(self): + result = self.run_command(['tags', 'cleanup', '-d']) + self.assert_no_fail(result) + self.assertIn('(Dry Run)', result.output) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assertEqual([], self.calls(service='SoftLayer_Tag', method='deleteTag')) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index e2dba99c9..67c817a6f 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -137,3 +137,72 @@ def test_get_tag_by_name_mask(self): args = (tag_name,) self.assertEqual(tag_name, result[0].get('name')) self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', mask=self.test_mask, args=args) + + def test_taggable_by_type_main(self): + result = self.tag_manager.taggable_by_type("HARDWARE") + self.assertEqual("SoftLayer_Hardware", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) + + def test_taggable_by_type_ticket(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = [ + { + "resourceType": "SoftLayer_Ticket", + "resource": { + "domain": "vmware.test.com", + } + } + ] + + result = self.tag_manager.taggable_by_type("TICKET") + self.assertEqual("SoftLayer_Ticket", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Search', 'advancedSearch', + args=('_objectType:SoftLayer_Ticket status.name: open',)) + + def test_taggable_by_type_image_template(self): + result = self.tag_manager.taggable_by_type("IMAGE_TEMPLATE") + self.assertEqual("Virtual_Guest_Block_Device_Template_Group", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups') + + def test_taggable_by_type_network_subnet(self): + result = self.tag_manager.taggable_by_type("NETWORK_SUBNET") + self.assertEqual("Network_Subnet", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Account', 'getSubnets') + + def test_type_to_service(self): + in_out = [ + {'input': 'ACCOUNT_DOCUMENT', 'output': None}, + {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': 'Network_Application_Delivery_Controller'}, + {'input': 'GUEST', 'output': 'Virtual_Guest'}, + {'input': 'DEDICATED_HOST', 'output': 'Virtual_DedicatedHost'}, + {'input': 'IMAGE_TEMPLATE', 'output': 'Virtual_Guest_Block_Device_Template_Group'}, + {'input': 'HARDWARE', 'output': 'Hardware'}, + {'input': 'NETWORK_VLAN', 'output': 'Network_Vlan'}, + ] + + for test in in_out: + result = self.tag_manager.type_to_service(test.get('input')) + self.assertEqual(test.get('output'), result) + + def test_get_resource_name(self): + resource = { + 'primaryIpAddress': '4.4.4.4', + 'vlanNumber': '12345', + 'name': 'testName', + 'subject': 'TEST SUBJECT', + 'networkIdentifier': '127.0.0.1', + 'fullyQualifiedDomainName': 'test.test.com' + } + in_out = [ + {'input': 'NETWORK_VLAN_FIREWALL', 'output': resource.get('primaryIpAddress')}, + {'input': 'NETWORK_VLAN', 'output': "{} ({})".format(resource.get('vlanNumber'), resource.get('name'))}, + {'input': 'IMAGE_TEMPLATE', 'output': resource.get('name')}, + {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': resource.get('name')}, + {'input': 'TICKET', 'output': resource.get('subjet')}, + {'input': 'NETWORK_SUBNET', 'output': resource.get('networkIdentifier')}, + {'input': 'HARDWARE', 'output': resource.get('fullyQualifiedDomainName')}, + ] + + for test in in_out: + result = self.tag_manager.get_resource_name(resource, test.get('input')) + self.assertEqual(test.get('output'), result) From 39627a9aca5f3fc4425107887456dde9ea7524c6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 10 Jun 2020 16:42:35 -0500 Subject: [PATCH 0138/1385] Added slcli tags clenaup documentation --- docs/cli/tags.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst index 435d4dc59..a5a99b694 100644 --- a/docs/cli/tags.rst +++ b/docs/cli/tags.rst @@ -24,3 +24,7 @@ These commands will allow you to interact with the **IMS** provier tagging servi .. click:: SoftLayer.CLI.tags.taggable:cli :prog: tags taggable :show-nested: + +.. click:: SoftLayer.CLI.tags.cleanup:cli + :prog: tags cleanup + :show-nested: From c293f4ba7467099c1d08c5bff0846a25216c709a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 18 Jun 2020 17:16:48 -0400 Subject: [PATCH 0139/1385] Implement slcli vlan edit functionality. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/edit.py | 41 ++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 3 ++ SoftLayer/managers/network.py | 40 +++++++++++++++++++ tests/CLI/modules/vlan_tests.py | 18 +++++++++ tests/managers/network_tests.py | 9 +++++ 6 files changed, 112 insertions(+) create mode 100644 SoftLayer/CLI/vlan/edit.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index cc67a7d2f..604841144 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -322,6 +322,7 @@ ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), + ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), ('summary', 'SoftLayer.CLI.summary:cli'), diff --git a/SoftLayer/CLI/vlan/edit.py b/SoftLayer/CLI/vlan/edit.py new file mode 100644 index 000000000..20a004921 --- /dev/null +++ b/SoftLayer/CLI/vlan/edit.py @@ -0,0 +1,41 @@ +"""Edit a vlan's details.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment, exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--name', '-n', + help="The optional name for this VLAN") +@click.option('--note', '-e', + help="The note for this vlan.") +@click.option('--tags', '-g', + multiple=True, + help='Tags to set e.g. "tag1,tag2", or empty string to remove all' + ) +@environment.pass_env +def cli(env, identifier, name, note, tags): + """Edit a vlan's details.""" + + data = { + 'name': name, + 'note': note + } + + if tags: + data['tags'] = ','.join(tags) + + mgr = SoftLayer.NetworkManager(env.client) + vlan_id = helpers.resolve_id(mgr.resolve_vlan_ids, identifier, 'VLAN') + vlan = mgr.edit(vlan_id, **data) + + if vlan: + click.secho("Vlan edited successfully", fg='green') + else: + click.secho("Failed to edit the vlan", fg='red') diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 5c7d7232a..b18632534 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -7,3 +7,6 @@ 'vlanNumber': 4444, 'firewallInterfaces': None } + +editObject = True +setTags = True diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index dbfb9c3f6..49dde11d7 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -7,12 +7,17 @@ """ import collections import json +import logging + +from SoftLayer.decoration import retry from SoftLayer import exceptions from SoftLayer import utils from SoftLayer.managers import event_log +LOGGER = logging.getLogger(__name__) + DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', 'ipAddressCount', @@ -685,3 +690,38 @@ def get_nas_credentials(self, identifier, **kwargs): """ result = self.network_storage.getObject(id=identifier, **kwargs) return result + + def edit(self, instance_id, name=None, note=None, tags=None): + """Edit a vlan. + + :param integer instance_id: the instance ID to edit. + :param string name: valid name. + :param string note: note about this particular v;am. + :param string tags: tags to set on the vlan as a comma separated list. + Use the empty string to remove all tags. + :returns: bool -- True or an Exception + """ + + obj = {} + + if tags is not None: + self.set_tags(tags, vlan_id=instance_id) + + if name: + obj['name'] = name + + if note: + obj['note'] = note + + if not obj: + return True + + return self.vlan.editObject(obj, id=instance_id) + + @retry(logger=LOGGER) + def set_tags(self, tags, vlan_id): + """Sets tags on a vlan with a retry decorator + + Just calls vlan.setTags, but if it fails from an APIError will retry. + """ + self.vlan.setTags(tags, id=vlan_id) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 86b15507a..1cdd3f3f5 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -4,6 +4,8 @@ :license: MIT, see LICENSE for more details. """ +import mock + from SoftLayer import testing @@ -76,3 +78,19 @@ def test_detail_hardware_without_hostname(self): vlan_mock.return_value = getObject result = self.run_command(['vlan', 'detail', '1234']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.vlan.edit.click') + def test_vlan_edit(self, click): + result = self.run_command(['vlan', 'edit', '--name=nameTest', '--note=noteTest', '--tags=tag1,tag2', '100']) + click.secho.assert_called_with('Vlan edited successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) + + @mock.patch('SoftLayer.CLI.vlan.edit.click') + def test_vlan_edit_failure(self, click): + mock = self.set_mock('SoftLayer_Network_Vlan', 'editObject') + mock.return_value = False + result = self.run_command(['vlan', 'edit', '--name=nameTest', '--note=noteTest', '--tags=tag1,tag2', '100']) + click.secho.assert_called_with('Failed to edit the vlan', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index fd986e2ea..165d04b71 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -625,3 +625,12 @@ def test_get_cci_event_logs(self): _filter = {'objectName': {'operation': 'CCI'}} self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects', filter=_filter) self.assertEqual(100, log['accountId']) + + def test_vlan_edit(self): + vlan_id = 100 + name = "test" + note = "test note" + tags = "tag1,tag2" + + self.network.edit(vlan_id, name, note, tags) + self.assert_called_with('SoftLayer_Network_Vlan', 'editObject') From 303a63072b4a967203d333105d367e5398f7859c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 18 Jun 2020 17:31:49 -0400 Subject: [PATCH 0140/1385] Add documentation and fix tox analysis. --- SoftLayer/CLI/vlan/edit.py | 3 +-- docs/cli/vlan.rst | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/vlan/edit.py b/SoftLayer/CLI/vlan/edit.py index 20a004921..3d043615d 100644 --- a/SoftLayer/CLI/vlan/edit.py +++ b/SoftLayer/CLI/vlan/edit.py @@ -4,8 +4,7 @@ import click import SoftLayer -from SoftLayer.CLI import environment, exceptions -from SoftLayer.CLI import formatting +from SoftLayer.CLI import environment from SoftLayer.CLI import helpers diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 1733f40e4..6fc084da7 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -7,6 +7,10 @@ VLANs :prog: vlan detail :show-nested: +.. click:: SoftLayer.CLI.vlan.edit:cli + :prog: vlan edit + :show-nested: + .. click:: SoftLayer.CLI.vlan.list:cli :prog: vlan list :show-nested: From 38d4dd49bdcb391854f33bc5251a80e6d2cbc065 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 18 Jun 2020 17:59:43 -0400 Subject: [PATCH 0141/1385] Fix tox analysis. --- SoftLayer/managers/network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 49dde11d7..4c22aa678 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -18,6 +18,8 @@ LOGGER = logging.getLogger(__name__) +# pylint: disable=no-self-use,too-many-lines + DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', 'ipAddressCount', From b5a8dd00acbaf11b43a9595da78430995ef07bd2 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 18 Jun 2020 18:02:51 -0400 Subject: [PATCH 0142/1385] Fix tox analysis. --- SoftLayer/managers/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 4c22aa678..abe625f85 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -18,7 +18,7 @@ LOGGER = logging.getLogger(__name__) -# pylint: disable=no-self-use,too-many-lines +# pylint: disable=too-many-public-methods DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', From 6f821872878cc866cb35df6793a10c145167fb6e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:08:04 -0400 Subject: [PATCH 0143/1385] new feature edit ip note and add ipAddress table in detail --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/subnet/detail.py | 13 ++++++++- SoftLayer/CLI/subnet/edit_ip.py | 27 +++++++++++++++++++ .../fixtures/SoftLayer_Network_Subnet.py | 7 ++++- .../SoftLayer_Network_Subnet_IpAddress.py | 2 ++ SoftLayer/managers/network.py | 5 ++++ tests/CLI/modules/subnet_tests.py | 8 ++++++ 7 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/subnet/edit_ip.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index cc67a7d2f..fe492d46e 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -289,6 +289,7 @@ ('subnet:detail', 'SoftLayer.CLI.subnet.detail:cli'), ('subnet:list', 'SoftLayer.CLI.subnet.list:cli'), ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), + ('subnet:edit-ip', 'SoftLayer.CLI.subnet.edit_ip:cli'), ('tags', 'SoftLayer.CLI.tags'), ('tags:cleanup', 'SoftLayer.CLI.tags.cleanup:cli'), diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 1c8f7e2dc..39dbb700d 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -25,7 +25,10 @@ def cli(env, identifier, no_vs, no_hardware): mgr = SoftLayer.NetworkManager(env.client) subnet_id = helpers.resolve_id(mgr.resolve_subnet_ids, identifier, name='subnet') - subnet = mgr.get_subnet(subnet_id) + + mask = 'mask[ipAddresses[id, ipAddress], datacenter, virtualGuests,hardware]' + + subnet = mgr.get_subnet(subnet_id, mask=mask) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' @@ -45,6 +48,14 @@ def cli(env, identifier, no_vs, no_hardware): table.add_row(['usable ips', subnet.get('usableIpAddressCount', formatting.blank())]) + ip_address = subnet.get('ipAddresses') + + ip_table = formatting.KeyValueTable(['ipAddress','value']) + for address in ip_address: + ip_table.add_row([address.get('id'),address.get('ipAddress')]) + + table.add_row(['ipAddresses', ip_table]) + if not no_vs: if subnet['virtualGuests']: vs_table = formatting.Table(['hostname', 'domain', 'public_ip', 'private_ip']) diff --git a/SoftLayer/CLI/subnet/edit_ip.py b/SoftLayer/CLI/subnet/edit_ip.py new file mode 100644 index 000000000..00077ad2c --- /dev/null +++ b/SoftLayer/CLI/subnet/edit_ip.py @@ -0,0 +1,27 @@ +"""Edit ip note""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('identifier') +@click.option('--ip', required=True, + help='Assume the ipAddress to set the note.') +@click.option('--note', help="set ip address note of subnet") +@environment.pass_env +def cli(env, identifier, ip, note): + """Set the note of the ipAddress subnet""" + + data = { + 'note': note + } + mgr = SoftLayer.NetworkManager(env.client) + ips = mgr.get_subnet(identifier, mask='id,ipAddresses[id,ipAddress]').get('ipAddresses') + + for address in ips: + if ip == address.get('ipAddress'): + mgr.set_subnet_ipddress_note(address.get('id'), data) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index 7fc1e34dd..c6658165a 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -25,5 +25,10 @@ } ], 'hardware': [], - 'usableIpAddressCount': 22 + 'usableIpAddressCount': 22, + 'ipAddresses': [ + {'id': 123456, + 'ipAddress': '16.26.26.25'}, + {'id': 123457, + 'ipAddress': '16.26.26.26'}] } diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py index d7ed9749d..5274ffeda 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py @@ -7,3 +7,5 @@ 'isReserved': False, 'subnetId': 5678, } + +editObject= True diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index dbfb9c3f6..786f45f4c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -685,3 +685,8 @@ def get_nas_credentials(self, identifier, **kwargs): """ result = self.network_storage.getObject(id=identifier, **kwargs) return result + + def set_subnet_ipddress_note(self, identifier, note): + """Set the ip address note of the subnet""" + result = self.client.call('SoftLayer_Network_Subnet_IpAddress','editObject', note, id=identifier) + return result \ No newline at end of file diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 1971aa420..650161dea 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -36,6 +36,9 @@ def test_detail(self): 'private_ip': '10.0.1.2' } ], + 'ipAddresses': { + '123456': '16.26.26.25', + '123457': '16.26.26.26'}, 'hardware': 'none', 'usable ips': 22 }, @@ -134,3 +137,8 @@ def test_create_subnet_static_ipv6(self, confirm_mock): ] self.assertEqual(output, json.loads(result.output)) + + def test_editrou_Ip(self): + result = self.run_command(['subnet', 'edit-ip', '123456', '--ip=16.26.26.26', '--note=test']) + self.assert_no_fail(result) + self.assertTrue(result) From 2d55c6f039a45ff6c75fef65d8994cfa776018ee Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:15:26 -0400 Subject: [PATCH 0144/1385] fix tox tool --- SoftLayer/CLI/subnet/detail.py | 4 ++-- docs/cli/subnet.rst | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 39dbb700d..e965df55d 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -50,9 +50,9 @@ def cli(env, identifier, no_vs, no_hardware): ip_address = subnet.get('ipAddresses') - ip_table = formatting.KeyValueTable(['ipAddress','value']) + ip_table = formatting.KeyValueTable(['ipAddress', 'value']) for address in ip_address: - ip_table.add_row([address.get('id'),address.get('ipAddress')]) + ip_table.add_row([address.get('id'), address.get('ipAddress')]) table.add_row(['ipAddresses', ip_table]) diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 20fce0def..5ba25c1f3 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -22,3 +22,7 @@ Subnets .. click:: SoftLayer.CLI.subnet.lookup:cli :prog: subnet lookup :show-nested: + +.. click:: SoftLayer.CLI.subnet.edit-ip:cli + :prog: subnet edit-ip + :show-nested: From 520d4f68494b531f0f8075427672978f76231d8d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:20:57 -0400 Subject: [PATCH 0145/1385] fix tox tool --- SoftLayer/managers/network.py | 2 +- docs/cli/subnet.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 786f45f4c..fa26ca04b 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -688,5 +688,5 @@ def get_nas_credentials(self, identifier, **kwargs): def set_subnet_ipddress_note(self, identifier, note): """Set the ip address note of the subnet""" - result = self.client.call('SoftLayer_Network_Subnet_IpAddress','editObject', note, id=identifier) + result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) return result \ No newline at end of file diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 5ba25c1f3..124f36cde 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -23,6 +23,6 @@ Subnets :prog: subnet lookup :show-nested: -.. click:: SoftLayer.CLI.subnet.edit-ip:cli +.. click:: SoftLayer.CLI.subnet.edit_ip:cli :prog: subnet edit-ip :show-nested: From a24dcfcba8201353f383f97a2327908c234b6a6d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:24:41 -0400 Subject: [PATCH 0146/1385] fix tox tool --- SoftLayer/managers/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index fa26ca04b..a2fef4e5a 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -689,4 +689,5 @@ def get_nas_credentials(self, identifier, **kwargs): def set_subnet_ipddress_note(self, identifier, note): """Set the ip address note of the subnet""" result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) - return result \ No newline at end of file + return result + \ No newline at end of file From b6cd35aba9459579dad69c385dd029c57f5a6c14 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:28:37 -0400 Subject: [PATCH 0147/1385] fix tox tool --- SoftLayer/managers/network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index a2fef4e5a..cac9b630c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -690,4 +690,3 @@ def set_subnet_ipddress_note(self, identifier, note): """Set the ip address note of the subnet""" result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) return result - \ No newline at end of file From 38e61a2a4245de9bb07f68a9cb05e00e0bfa4b36 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 22 Jun 2020 09:59:19 -0400 Subject: [PATCH 0148/1385] Refactor vlan edit functionality. --- SoftLayer/CLI/vlan/edit.py | 9 +++------ SoftLayer/managers/network.py | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/vlan/edit.py b/SoftLayer/CLI/vlan/edit.py index 3d043615d..c623e5d2d 100644 --- a/SoftLayer/CLI/vlan/edit.py +++ b/SoftLayer/CLI/vlan/edit.py @@ -22,17 +22,14 @@ def cli(env, identifier, name, note, tags): """Edit a vlan's details.""" - data = { - 'name': name, - 'note': note - } + new_tags = None if tags: - data['tags'] = ','.join(tags) + new_tags = ','.join(tags) mgr = SoftLayer.NetworkManager(env.client) vlan_id = helpers.resolve_id(mgr.resolve_vlan_ids, identifier, 'VLAN') - vlan = mgr.edit(vlan_id, **data) + vlan = mgr.edit(vlan_id, name=name, note=note, tags=new_tags) if vlan: click.secho("Vlan edited successfully", fg='green') diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index abe625f85..b3f6f7b6d 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -698,7 +698,7 @@ def edit(self, instance_id, name=None, note=None, tags=None): :param integer instance_id: the instance ID to edit. :param string name: valid name. - :param string note: note about this particular v;am. + :param string note: note about this particular vlan. :param string tags: tags to set on the vlan as a comma separated list. Use the empty string to remove all tags. :returns: bool -- True or an Exception From cc4c6ed2c73e5b4042fcc1ee7ce13cd28b428929 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Jun 2020 14:22:26 -0400 Subject: [PATCH 0149/1385] fix tox tool --- SoftLayer/CLI/subnet/edit_ip.py | 6 +++--- SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py | 2 +- tests/CLI/modules/subnet_tests.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/subnet/edit_ip.py b/SoftLayer/CLI/subnet/edit_ip.py index 00077ad2c..8cf65cb9b 100644 --- a/SoftLayer/CLI/subnet/edit_ip.py +++ b/SoftLayer/CLI/subnet/edit_ip.py @@ -9,11 +9,11 @@ @click.command() @click.argument('identifier') -@click.option('--ip', required=True, +@click.option('--ip-address', required=True, help='Assume the ipAddress to set the note.') @click.option('--note', help="set ip address note of subnet") @environment.pass_env -def cli(env, identifier, ip, note): +def cli(env, identifier, ip_address, note): """Set the note of the ipAddress subnet""" data = { @@ -23,5 +23,5 @@ def cli(env, identifier, ip, note): ips = mgr.get_subnet(identifier, mask='id,ipAddresses[id,ipAddress]').get('ipAddresses') for address in ips: - if ip == address.get('ipAddress'): + if ip_address == address.get('ipAddress'): mgr.set_subnet_ipddress_note(address.get('id'), data) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py index 5274ffeda..15778d238 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py @@ -8,4 +8,4 @@ 'subnetId': 5678, } -editObject= True +editObject = True diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 650161dea..3489ec97e 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -36,9 +36,9 @@ def test_detail(self): 'private_ip': '10.0.1.2' } ], - 'ipAddresses': { - '123456': '16.26.26.25', - '123457': '16.26.26.26'}, + 'ipAddresses': { + '123456': '16.26.26.25', + '123457': '16.26.26.26'}, 'hardware': 'none', 'usable ips': 22 }, @@ -139,6 +139,6 @@ def test_create_subnet_static_ipv6(self, confirm_mock): self.assertEqual(output, json.loads(result.output)) def test_editrou_Ip(self): - result = self.run_command(['subnet', 'edit-ip', '123456', '--ip=16.26.26.26', '--note=test']) + result = self.run_command(['subnet', 'edit-ip', '123456', '--ip-address=16.26.26.26', '--note=test']) self.assert_no_fail(result) self.assertTrue(result) From d2e3958d11b6f0ea41080eb465aab07bde07eb61 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Jun 2020 14:42:06 -0400 Subject: [PATCH 0150/1385] fix tox tool --- SoftLayer/managers/network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index cac9b630c..73ea5fc0f 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -13,6 +13,8 @@ from SoftLayer.managers import event_log +# pylint: disable=too-many-public-methods + DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', 'ipAddressCount', From 4de733f533d900863ef6e66f2d9c861c78acfd74 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 18:39:02 -0400 Subject: [PATCH 0151/1385] add tags and note to subnet detail --- SoftLayer/CLI/subnet/detail.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 1c8f7e2dc..2758838ee 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -44,6 +44,10 @@ def cli(env, identifier, no_vs, no_hardware): table.add_row(['datacenter', subnet['datacenter']['name']]) table.add_row(['usable ips', subnet.get('usableIpAddressCount', formatting.blank())]) + table.add_row(['note', + subnet.get('note', formatting.blank())]) + table.add_row(['tags', + formatting.tags(subnet.get('tagReferences'))]) if not no_vs: if subnet['virtualGuests']: @@ -55,7 +59,7 @@ def cli(env, identifier, no_vs, no_hardware): vsi.get('primaryBackendIpAddress')]) table.add_row(['vs', vs_table]) else: - table.add_row(['vs', 'none']) + table.add_row(['vs', formatting.blank()]) if not no_hardware: if subnet['hardware']: @@ -67,6 +71,6 @@ def cli(env, identifier, no_vs, no_hardware): hardware.get('primaryBackendIpAddress')]) table.add_row(['hardware', hw_table]) else: - table.add_row(['hardware', 'none']) + table.add_row(['hardware', formatting.blank()]) env.fout(table) From a8a50581528c64315e446481c445a647d8c81fa4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 18:41:55 -0400 Subject: [PATCH 0152/1385] add subnet edit --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/subnet/edit.py | 38 +++++++++++++++++++++++++++++++++++ SoftLayer/managers/network.py | 23 +++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 SoftLayer/CLI/subnet/edit.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 604841144..e3aa99b0a 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -286,6 +286,7 @@ ('subnet', 'SoftLayer.CLI.subnet'), ('subnet:cancel', 'SoftLayer.CLI.subnet.cancel:cli'), ('subnet:create', 'SoftLayer.CLI.subnet.create:cli'), + ('subnet:edit', 'SoftLayer.CLI.subnet.edit:cli'), ('subnet:detail', 'SoftLayer.CLI.subnet.detail:cli'), ('subnet:list', 'SoftLayer.CLI.subnet.list:cli'), ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), diff --git a/SoftLayer/CLI/subnet/edit.py b/SoftLayer/CLI/subnet/edit.py new file mode 100644 index 000000000..57856c57b --- /dev/null +++ b/SoftLayer/CLI/subnet/edit.py @@ -0,0 +1,38 @@ +"""Edit a subnet.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command(short_help="Edit note and tags of a subnet") +@click.argument('identifier') +@click.option('--tags', '-t', type=click.STRING, + help='Comma separated list of tags, enclosed in quotes. "tag1, tag2"') +@click.option('--note', '-n', type=click.STRING, + help="The note") +@environment.pass_env +def cli(env, identifier, tags, note): + """Edit note and tags of a subnet.""" + + mgr = SoftLayer.NetworkManager(env.client) + subnet_id = helpers.resolve_id(mgr.resolve_subnet_ids, identifier, + name='subnet') + + if tags: + result = mgr.set_tags_subnet(subnet_id, tags) + print_result(result, "Set tags") + + if note: + result = mgr.edit_note_subnet(subnet_id, note) + print_result(result, "Edit note") + + +def print_result(result, detail): + if result: + click.secho("{} successfully".format(detail), fg='green') + else: + click.secho("Failed to {}".format(detail.lower()), fg='red') diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index b3f6f7b6d..596e4777c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -24,6 +24,15 @@ 'datacenter', 'ipAddressCount', 'virtualGuests', + 'id', + 'networkIdentifier', + 'cidr', + 'subnetType', + 'gateway', + 'broadcastAddress', + 'usableIpAddressCount', + 'note', + 'tagReferences[tag]', 'networkVlan[id,networkSpace]']) DEFAULT_VLAN_MASK = ','.join([ 'firewallInterfaces', @@ -233,6 +242,20 @@ def cancel_subnet(self, subnet_id): billing_id = subnet['billingItem']['id'] return self.client['Billing_Item'].cancelService(id=billing_id) + def set_tags_subnet(self, subnet_id, tags): + """Tag a subnet by passing in one or more tags separated by a comma. + + :param int subnet_id: The ID of the subnet. + """ + return self.subnet.setTags(tags, id=subnet_id) + + def edit_note_subnet(self, subnet_id, note): + """Edit the note for this subnet. + + :param int subnet_id: The ID of the subnet. + """ + return self.subnet.editNote(note, id=subnet_id) + def create_securitygroup(self, name=None, description=None): """Creates a security group. From bc29ec3940511996958c6d9e30f99d8e186b1456 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 18:43:09 -0400 Subject: [PATCH 0153/1385] add subnet edit tests --- .../fixtures/SoftLayer_Network_Subnet.py | 13 +++++- tests/CLI/modules/subnet_tests.py | 40 ++++++++++++++++++- tests/managers/network_tests.py | 20 ++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index 7fc1e34dd..d8c20fbc0 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -25,5 +25,16 @@ } ], 'hardware': [], - 'usableIpAddressCount': 22 + 'usableIpAddressCount': 22, + 'note': 'test note', + 'tagReferences': [ + {'id': 1000123, + 'resourceTableId': 1234, + 'tag': {'id': 100123, + 'name': 'subnet: test tag'}, + } + ] } + +editNote = True +setTags = True diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 1971aa420..493b30c83 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -36,8 +36,12 @@ def test_detail(self): 'private_ip': '10.0.1.2' } ], - 'hardware': 'none', - 'usable ips': 22 + 'hardware': None, + 'usable ips': 22, + 'note': 'test note', + 'tags': [ + 'subnet: test tag' + ], }, json.loads(result.output)) @@ -134,3 +138,35 @@ def test_create_subnet_static_ipv6(self, confirm_mock): ] self.assertEqual(output, json.loads(result.output)) + + @mock.patch('SoftLayer.CLI.subnet.edit.click') + def test_subnet_set_tags(self, click): + result = self.run_command(['subnet', 'edit', '1234', '--tags=tag1,tag2']) + click.secho.assert_called_with('Set tags successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Subnet', 'setTags', identifier=1234, args=("tag1,tag2",)) + + @mock.patch('SoftLayer.CLI.subnet.edit.click') + def test_subnet_edit_note(self, click): + result = self.run_command(['subnet', 'edit', '1234', '--note=test']) + click.secho.assert_called_with('Edit note successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Subnet', 'editNote', identifier=1234, args=("test",)) + + @mock.patch('SoftLayer.CLI.subnet.edit.click') + def test_subnet_set_tags_failure(self, click): + mock = self.set_mock('SoftLayer_Network_Subnet', 'setTags') + mock.return_value = False + result = self.run_command(['subnet', 'edit', '1234', '--tags=tag1,tag2']) + click.secho.assert_called_with('Failed to set tags', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Subnet', 'setTags', identifier=1234, args=("tag1,tag2",)) + + @mock.patch('SoftLayer.CLI.subnet.edit.click') + def test_edit_note_failure(self, click): + mock = self.set_mock('SoftLayer_Network_Subnet', 'editNote') + mock.return_value = False + result = self.run_command(['subnet', 'edit', '1234', '--note=test']) + click.secho.assert_called_with('Failed to edit note', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Subnet', 'editNote', identifier=1234, args=("test",)) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 165d04b71..57106ecee 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -158,6 +158,26 @@ def test_cancel_subnet(self): self.assert_called_with('SoftLayer_Billing_Item', 'cancelService', identifier=1056) + def test_set_tags_subnet(self): + subnet_id = 1234 + tags = 'tags1,tag2' + result = self.network.set_tags_subnet(subnet_id, tags) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Network_Subnet', 'setTags', + identifier=subnet_id, + args=(tags,)) + + def test_edit_note_subnet(self): + subnet_id = 1234 + note = 'test note' + result = self.network.edit_note_subnet(subnet_id, note) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Network_Subnet', 'editNote', + identifier=subnet_id, + args=(note,)) + def test_create_securitygroup(self): result = self.network.create_securitygroup(name='foo', description='bar') From 5e0068429f05e32ab47ce45cf3c8fe032e0c06f9 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 18:44:08 -0400 Subject: [PATCH 0154/1385] add subnet edit docs --- docs/cli/subnet.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 20fce0def..1ad3c379c 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -15,6 +15,10 @@ Subnets :prog: subnet detail :show-nested: +.. click:: SoftLayer.CLI.subnet.edit:cli + :prog: subnet edit + :show-nested: + .. click:: SoftLayer.CLI.subnet.list:cli :prog: subnet list :show-nested: From ca7cc9c76c8873ca491fb28d35406706f03fbf41 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 19:07:07 -0400 Subject: [PATCH 0155/1385] fix tox analysis and conflicts --- SoftLayer/CLI/subnet/edit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/subnet/edit.py b/SoftLayer/CLI/subnet/edit.py index 57856c57b..9a62692b1 100644 --- a/SoftLayer/CLI/subnet/edit.py +++ b/SoftLayer/CLI/subnet/edit.py @@ -32,6 +32,7 @@ def cli(env, identifier, tags, note): def print_result(result, detail): + """Prints a successfully or Failed message.""" if result: click.secho("{} successfully".format(detail), fg='green') else: From ae8bba254781c1b3b5d3f2a766f37b536c766d07 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 22 Jun 2020 20:16:02 -0400 Subject: [PATCH 0156/1385] add docs param for set tag and edit note subnet --- SoftLayer/managers/network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 596e4777c..057977969 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -246,6 +246,7 @@ def set_tags_subnet(self, subnet_id, tags): """Tag a subnet by passing in one or more tags separated by a comma. :param int subnet_id: The ID of the subnet. + :param string tags: Comma separated list of tags. """ return self.subnet.setTags(tags, id=subnet_id) @@ -253,6 +254,7 @@ def edit_note_subnet(self, subnet_id, note): """Edit the note for this subnet. :param int subnet_id: The ID of the subnet. + :param string note: The note. """ return self.subnet.editNote(note, id=subnet_id) From b168484bac6c56288143d6c1cfbd09ec003a0667 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Jun 2020 15:50:50 -0500 Subject: [PATCH 0157/1385] Update SoftLayer_Network_Subnet.py --- SoftLayer/fixtures/SoftLayer_Network_Subnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index fd7973b49..24683da15 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -33,7 +33,7 @@ 'tag': {'id': 100123, 'name': 'subnet: test tag'}, } - ] + ], 'ipAddresses': [ {'id': 123456, 'ipAddress': '16.26.26.25'}, From 0f3803087b424ab4d5af81d9f6cfa89cd08d4151 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Jun 2020 16:14:05 -0500 Subject: [PATCH 0158/1385] Update subnet_tests.py --- tests/CLI/modules/subnet_tests.py | 34 +++---------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 486292abc..d31bede4e 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -17,38 +17,10 @@ class SubnetTests(testing.TestCase): def test_detail(self): result = self.run_command(['subnet', 'detail', '1234']) - + subnet = json.loads(result.output) self.assert_no_fail(result) - self.assertEqual( - { - 'id': 1234, - 'identifier': '1.2.3.4/26', - 'subnet type': 'ADDITIONAL_PRIMARY', - 'network space': 'PUBLIC', - 'gateway': '1.2.3.254', - 'broadcast': '1.2.3.255', - 'datacenter': 'dal10', - 'vs': [ - { - 'hostname': 'hostname0', - 'domain': 'sl.test', - 'public_ip': '1.2.3.10', - 'private_ip': '10.0.1.2' - } - ], - 'hardware': None, - 'usable ips': 22, - 'note': 'test note', - 'tags': [ - 'subnet: test tag' - ], - 'ipAddresses': { - '123456': '16.26.26.25', - '123457': '16.26.26.26'}, - 'hardware': 'none', - 'usable ips': 22 - }, - json.loads(result.output)) + self.assertEqual(subnet.get('id'), 1234) + self.assertEqual(subnet.get('identifier'),'1.2.3.4/26') def test_list(self): result = self.run_command(['subnet', 'list']) From 8ea87f7b53cb1fbeac9404e2159d9537ca7b9a2c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Jun 2020 16:17:16 -0500 Subject: [PATCH 0159/1385] Update subnet_tests.py --- tests/CLI/modules/subnet_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index d31bede4e..52de2cd27 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -20,7 +20,7 @@ def test_detail(self): subnet = json.loads(result.output) self.assert_no_fail(result) self.assertEqual(subnet.get('id'), 1234) - self.assertEqual(subnet.get('identifier'),'1.2.3.4/26') + self.assertEqual(subnet.get('identifier'), '1.2.3.4/26') def test_list(self): result = self.run_command(['subnet', 'list']) From fe4a2f6fb1b5818a10e59103fe3347afc2e9b0a6 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 30 Jun 2020 12:52:29 -0400 Subject: [PATCH 0160/1385] Add functionality to order a hw for capacityRestrictionType PROCCESOR. --- SoftLayer/managers/ordering.py | 2 +- tests/managers/ordering_tests.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index b5e659ee1..3123d1b02 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -396,7 +396,7 @@ def get_item_price_id(core, prices): capacity_min = int(price.get('capacityRestrictionMinimum', -1)) capacity_max = int(price.get('capacityRestrictionMaximum', -1)) # return first match if no restirction, or no core to check - if capacity_min == -1 or core is None: + if capacity_min == -1 or core is None or "PROCESSOR" in price.get("capacityRestrictionType"): price_id = price['id'] # this check is mostly to work nicely with preset configs elif capacity_min <= int(core) <= capacity_max: diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 39caa72f4..0709eea7e 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -666,6 +666,16 @@ def test_get_item_price_id_storage_with_capacity_restriction(self): self.assertEqual(1234, price_id) + def test_get_item_price_id_processor_with_capacity_restriction(self): + category1 = {'categoryCode': 'cat1'} + price1 = [{'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "1", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "PROCESSOR", + 'categories': [category1]}] + + price_id = self.ordering.get_item_price_id("8", price1) + + self.assertEqual(1234, price_id) + def test_issues1067(self): # https://github.com/softlayer/softlayer-python/issues/1067 item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') From aa8fd3d14d824edc562666bac49f124938f864c9 Mon Sep 17 00:00:00 2001 From: Jonathan Woodlief Date: Wed, 1 Jul 2020 16:20:18 -0400 Subject: [PATCH 0161/1385] Removed overly specific reference to Block storage I found a specific reference to block storage in code shared between file and block. reworded it to refer to either block or storage volumes --- SoftLayer/managers/storage.py | 4 ++-- tests/managers/block_tests.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 9a8015d58..fb5190d85 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -396,7 +396,7 @@ def failback_from_replicant(self, volume_id): return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): - """Cancels the given block storage volume. + """Cancels the given storage volume. :param integer volume_id: The volume ID :param string reason: The reason for cancellation @@ -406,7 +406,7 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): volume = self.get_volume_details(volume_id, mask=object_mask) if 'billingItem' not in volume: - raise exceptions.SoftLayerError("Block Storage was already cancelled") + raise exceptions.SoftLayerError("Storage Volume was already cancelled") billing_item_id = volume['billingItem']['id'] diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index c9731a04d..1ba644236 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -61,7 +61,7 @@ def test_cancel_block_volume_exception_billing_item_not_found(self): immediate=True ) self.assertEqual( - 'Block Storage was already cancelled', + 'Storage Volume was already cancelled', str(exception) ) From 88203f6e00bbd165e5f98ca118bebf703169875e Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 2 Jul 2020 18:38:15 -0400 Subject: [PATCH 0162/1385] Refactor functionality to order a hw for capacityRestrictionType PROCCESOR. --- SoftLayer/managers/ordering.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 3123d1b02..bf0d09bbf 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -393,16 +393,23 @@ def get_item_price_id(core, prices): price_id = None for price in prices: if not price['locationGroupId']: - capacity_min = int(price.get('capacityRestrictionMinimum', -1)) - capacity_max = int(price.get('capacityRestrictionMaximum', -1)) - # return first match if no restirction, or no core to check - if capacity_min == -1 or core is None or "PROCESSOR" in price.get("capacityRestrictionType"): - price_id = price['id'] - # this check is mostly to work nicely with preset configs - elif capacity_min <= int(core) <= capacity_max: - if "STORAGE" in price.get("capacityRestrictionType") or "CORE" in price.get( - "capacityRestrictionType"): + restriction = price.get('capacityRestrictionType', False) + # There is a price restriction. Make sure the price is within the restriction + if restriction and core is not None: + capacity_min = int(price.get('capacityRestrictionMinimum', -1)) + capacity_max = int(price.get('capacityRestrictionMaximum', -1)) + if "STORAGE" in restriction: + if capacity_min <= int(core) <= capacity_max: + price_id = price['id'] + if "CORE" in restriction: + if capacity_min <= int(core) <= capacity_max: + price_id = price['id'] + if "PROCESSOR" in restriction: price_id = price['id'] + # No price restrictions + else: + price_id = price['id'] + return price_id def get_item_capacity(self, items, item_keynames): From 02a70614d27de24ca12a31f4d58e5c2d8f4d4142 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 3 Jul 2020 10:24:51 -0400 Subject: [PATCH 0163/1385] add system operation referenceCode in create-option --- SoftLayer/CLI/hardware/create_options.py | 4 +-- SoftLayer/managers/hardware.py | 3 +- tests/CLI/modules/server_tests.py | 39 ++++++++++++------------ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 8c005de54..4eba88c84 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -33,10 +33,10 @@ def cli(env): tables.append(preset_table) # Operating systems - os_table = formatting.Table(['operating_system', 'value']) + os_table = formatting.Table(['operating_system', 'value', 'operatingSystemReferenceCode ']) os_table.sortby = 'value' for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key']]) + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) tables.append(os_table) # Port speed diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ec9981c48..b531910e4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -403,7 +403,8 @@ def get_create_options(self): if item['itemCategory']['categoryCode'] == 'os': operating_systems.append({ 'name': item['softwareDescription']['longDescription'], - 'key': item['keyName'] + 'key': item['keyName'], + 'referenceCode': item['softwareDescription']['referenceCode'] }) # Port speeds diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index a7fd4e908..605bd32bf 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -362,7 +362,8 @@ def test_create_options(self): {'size': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10', 'value': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10'}], [{'operating_system': 'Ubuntu / 14.04-64', - 'value': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT'}], + 'value': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'operatingSystemReferenceCode ': 'UBUNTU_14_64'}], [{'port_speed': '10 Mbps Public & Private Network Uplinks', 'value': '10'}], [{'extras': '1 IPv6 Address', 'value': '1_IPV6_ADDRESS'}]] @@ -694,19 +695,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -749,12 +750,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) From 24456e9280270be0c9f6f540a6382ef91043e2f3 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 3 Jul 2020 11:02:23 -0400 Subject: [PATCH 0164/1385] fix tox tool --- tests/managers/hardware_tests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 69e7ae52a..f6a2445d1 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -121,7 +121,8 @@ def test_get_create_options(self): 'extras': [{'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'}], 'locations': [{'key': 'wdc01', 'name': 'Washington 1'}], 'operating_systems': [{'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'name': 'Ubuntu / 14.04-64'}], + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64'}], 'port_speeds': [{ 'key': '10', 'name': '10 Mbps Public & Private Network Uplinks' @@ -374,10 +375,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): From b5ac831a8edd54a0c7128e47abda21afdd23d622 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 6 Jul 2020 14:49:50 -0500 Subject: [PATCH 0165/1385] v5.8.9 release notes --- CHANGELOG.md | 17 +++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 626c66af6..2b6b28c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Change Log +## [5.8.9] - 2020-07-06 +https://github.com/softlayer/softlayer-python/compare/v5.8.8...v5.8.9 + +- #1252 Automated Snap publisher +- #1230 Tag Management + + slcli tags cleanup + + slcli tags delete + + slcli tags details + + slcli tags list + + slcli tags set + + slcli tags taggable +- #1285 Vlan editing functionality +- #1287 Edit IP note and add ipAddress table in detail view +- #1283 Subnet Tagging +- #1291 Storage documentation updates +- #1293 add system operation referenceCode in create-option + ## [5.8.8] - 2020-05-18 https://github.com/softlayer/softlayer-python/compare/v5.8.7...v5.8.8 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 0ea903bf5..cdcdf07ed 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.8' +VERSION = 'v5.8.9' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 6a6c9a551..8b8308901 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.8', + version='5.8.9', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 83266f6f9645eb2f9f9bf81dfbcbb117526e463d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 8 Jul 2020 17:22:26 -0500 Subject: [PATCH 0166/1385] #828 refactored hardware create to use the ordering manager primarily instead of doing price lookups on its own. Added option to specify a particular networking keyname instead of just a speed --- SoftLayer/CLI/formatting.py | 9 +- SoftLayer/CLI/hardware/create.py | 67 ++---- SoftLayer/CLI/hardware/create_options.py | 28 ++- SoftLayer/managers/hardware.py | 211 +++++++---------- tests/CLI/modules/server_tests.py | 32 +-- tests/managers/hardware_tests.py | 289 +++++------------------ 6 files changed, 193 insertions(+), 443 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b88fe056b..d02ceb1a1 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -301,8 +301,13 @@ def prettytable(self): else: msg = "Column (%s) doesn't exist to sort by" % self.sortby raise exceptions.CLIAbort(msg) - for a_col, alignment in self.align.items(): - table.align[a_col] = alignment + + if isinstance(self.align, str): + table.align = self.align + else: + # Required because PrettyTable has a strict setter function for alignment + for a_col, alignment in self.align.items(): + table.align[a_col] = alignment if self.title: table.title = self.title diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index 472cec2e2..eb83e6664 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -12,56 +12,40 @@ @click.command(epilog="See 'slcli server create-options' for valid options.") -@click.option('--hostname', '-H', - help="Host portion of the FQDN", - required=True, - prompt=True) -@click.option('--domain', '-D', - help="Domain portion of the FQDN", - required=True, - prompt=True) -@click.option('--size', '-s', - help="Hardware size", - required=True, - prompt=True) -@click.option('--os', '-o', help="OS install code", - required=True, - prompt=True) -@click.option('--datacenter', '-d', help="Datacenter shortname", - required=True, - prompt=True) -@click.option('--port-speed', - type=click.INT, - help="Port speeds", - required=True, - prompt=True) -@click.option('--billing', +@click.option('--hostname', '-H', required=True, prompt=True, + help="Host portion of the FQDN") +@click.option('--domain', '-D', required=True, prompt=True, + help="Domain portion of the FQDN") +@click.option('--size', '-s', required=True, prompt=True, + help="Hardware size") +@click.option('--os', '-o', required=True, prompt=True, + help="OS Key value") +@click.option('--datacenter', '-d', required=True, prompt=True, + help="Datacenter shortname") +@click.option('--port-speed', type=click.INT, required=True, prompt=True, + help="Port speeds. DEPRECATED, use --network") +@click.option('--no-public', is_flag=True, + help="Private network only. DEPRECATED, use --network.") +@click.option('--network', + help="Network Option Key.") +@click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), - default='hourly', - show_default=True, help="Billing rate") -@click.option('--postinstall', '-i', help="Post-install script to download") -@helpers.multi_option('--key', '-k', - help="SSH keys to add to the root user") -@click.option('--no-public', - is_flag=True, - help="Private network only") -@helpers.multi_option('--extra', '-e', help="Extra options") -@click.option('--test', - is_flag=True, +@click.option('--postinstall', '-i', + help="Post-install script. Should be a HTTPS URL.") +@click.option('--test', is_flag=True, help="Do not actually create the server") -@click.option('--template', '-t', - is_eager=True, +@click.option('--template', '-t', is_eager=True, callback=template.TemplateCallback(list_args=['key']), help="A template file that defaults the command-line options", type=click.Path(exists=True, readable=True, resolve_path=True)) -@click.option('--export', - type=click.Path(writable=True, resolve_path=True), +@click.option('--export', type=click.Path(writable=True, resolve_path=True), help="Exports options to a template file") -@click.option('--wait', - type=click.INT, +@click.option('--wait', type=click.INT, help="Wait until the server is finished provisioning for up to " "X seconds before returning") +@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") +@helpers.multi_option('--extra', '-e', help="Extra option Key Names") @environment.pass_env def cli(env, **args): """Order/create a dedicated server.""" @@ -86,6 +70,7 @@ def cli(env, **args): 'port_speed': args.get('port_speed'), 'no_public': args.get('no_public') or False, 'extras': args.get('extra'), + 'network': args.get('network') } # Do not create hardware server with --test or --export diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 4eba88c84..2e9949d09 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -19,36 +19,42 @@ def cli(env): tables = [] # Datacenters - dc_table = formatting.Table(['datacenter', 'value']) - dc_table.sortby = 'value' + dc_table = formatting.Table(['Datacenter', 'Value'], title="Datacenters") + dc_table.sortby = 'Value' + dc_table.align = 'l' for location in options['locations']: dc_table.add_row([location['name'], location['key']]) tables.append(dc_table) # Presets - preset_table = formatting.Table(['size', 'value']) - preset_table.sortby = 'value' + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + preset_table.sortby = 'Value' + preset_table.align = 'l' for size in options['sizes']: preset_table.add_row([size['name'], size['key']]) tables.append(preset_table) # Operating systems - os_table = formatting.Table(['operating_system', 'value', 'operatingSystemReferenceCode ']) - os_table.sortby = 'value' + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' + os_table.align = 'l' for operating_system in options['operating_systems']: os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) tables.append(os_table) # Port speed - port_speed_table = formatting.Table(['port_speed', 'value']) - port_speed_table.sortby = 'value' + port_speed_table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") + port_speed_table.sortby = 'Speed' + port_speed_table.align = 'l' + for speed in options['port_speeds']: - port_speed_table.add_row([speed['name'], speed['key']]) + port_speed_table.add_row([speed['name'], speed['speed'], speed['key']]) tables.append(port_speed_table) # Extras - extras_table = formatting.Table(['extras', 'value']) - extras_table.sortby = 'value' + extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") + extras_table.sortby = 'Value' + extras_table.align = 'l' for extra in options['extras']: extras_table.add_row([extra['name'], extra['key']]) tables.append(extras_table) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index b531910e4..cccf6b29f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -22,7 +22,9 @@ EXTRA_CATEGORIES = ['pri_ipv6_addresses', 'static_ipv6_addresses', - 'sec_ip_addresses'] + 'sec_ip_addresses', + 'trusted_platform_module', + 'software_guard_extensions'] class HardwareManager(utils.IdentifierMixin, object): @@ -53,6 +55,7 @@ def __init__(self, client, ordering_manager=None): self.hardware = self.client['Hardware_Server'] self.account = self.client['Account'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] + self.package_keyname = 'BARE_METAL_SERVER' if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) else: @@ -337,16 +340,14 @@ def place_order(self, **kwargs): :param string os: operating system name :param int port_speed: Port speed in Mbps :param list ssh_keys: list of ssh key ids - :param string post_uri: The URI of the post-install script to run - after reload - :param boolean hourly: True if using hourly pricing (default). - False for monthly. - :param boolean no_public: True if this server should only have private - interfaces + :param string post_uri: The URI of the post-install script to run after reload + :param boolean hourly: True if using hourly pricing (default). False for monthly. + :param boolean no_public: True if this server should only have private interfaces :param list extras: List of extra feature names """ create_options = self._generate_create_dict(**kwargs) - return self.client['Product_Order'].placeOrder(create_options) + return self.ordering_manager.place_order(**create_options) + # return self.client['Product_Order'].placeOrder(create_options) def verify_order(self, **kwargs): """Verifies an order for a piece of hardware. @@ -354,7 +355,7 @@ def verify_order(self, **kwargs): See :func:`place_order` for a list of available options. """ create_options = self._generate_create_dict(**kwargs) - return self.client['Product_Order'].verifyOrder(create_options) + return self.ordering_manager.verify_order(**create_options) def get_cancellation_reasons(self): """Returns a dictionary of valid cancellation reasons. @@ -397,33 +398,27 @@ def get_create_options(self): 'key': preset['keyName'] }) - # Operating systems operating_systems = [] + port_speeds = [] + extras = [] for item in package['items']: - if item['itemCategory']['categoryCode'] == 'os': + category = item['itemCategory']['categoryCode'] + # Operating systems + if category == 'os': operating_systems.append({ 'name': item['softwareDescription']['longDescription'], 'key': item['keyName'], 'referenceCode': item['softwareDescription']['referenceCode'] }) - - # Port speeds - port_speeds = [] - for item in package['items']: - if all([item['itemCategory']['categoryCode'] == 'port_speed', - # Hide private options - not _is_private_port_speed_item(item), - # Hide unbonded options - _is_bonded(item)]): + # Port speeds + elif category == 'port_speed': port_speeds.append({ 'name': item['description'], - 'key': item['capacity'], + 'speed': item['capacity'], + 'key': item['keyName'] }) - - # Extras - extras = [] - for item in package['items']: - if item['itemCategory']['categoryCode'] in EXTRA_CATEGORIES: + # Extras + elif category in EXTRA_CATEGORIES: extras.append({ 'name': item['description'], 'key': item['keyName'] @@ -454,9 +449,7 @@ def _get_package(self): accountRestrictedActivePresets, regions[location[location[priceGroups]]] ''' - - package_keyname = 'BARE_METAL_SERVER' - package = self.ordering_manager.get_package_by_key(package_keyname, mask=mask) + package = self.ordering_manager.get_package_by_key(self.package_keyname, mask=mask) return package def _generate_create_dict(self, @@ -470,59 +463,66 @@ def _generate_create_dict(self, post_uri=None, hourly=True, no_public=False, - extras=None): + extras=None, + network=None): """Translates arguments into a dictionary for creating a server.""" extras = extras or [] package = self._get_package() - location = _get_location(package, location) - - prices = [] - for category in ['pri_ip_addresses', - 'vpn_management', - 'remote_management']: - prices.append(_get_default_price_id(package['items'], - option=category, - hourly=hourly, - location=location)) - - prices.append(_get_os_price_id(package['items'], os, - location=location)) - prices.append(_get_bandwidth_price_id(package['items'], - hourly=hourly, - no_public=no_public, - location=location)) - prices.append(_get_port_speed_price_id(package['items'], - port_speed, - no_public, - location=location)) + items = package.get('items', {}) + location_id = _get_location(package, location) + + key_names = [ + '1_IP_ADDRESS', + 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', + 'REBOOT_KVM_OVER_IP' + ] + + # Operating System + key_names.append(os) + + # Bandwidth Options + key_names.append( + _get_bandwidth_key(items, hourly=hourly, no_public=no_public, location=location_id) + ) + + # Port Speed Options + # New option in v5.9.0 + if network: + key_names.append(network) + # Legacy Option, doesn't support bonded/redundant + else: + key_names.append( + _get_port_speed_key(items, port_speed, no_public, location=location_id) + ) + # Extras for extra in extras: - prices.append(_get_extra_price_id(package['items'], - extra, hourly, - location=location)) + key_names.append(extra) - hardware = { - 'hostname': hostname, - 'domain': domain, + extras = { + 'hardware': [{ + 'hostname': hostname, + 'domain': domain, + }] } - - order = { - 'hardware': [hardware], - 'location': location['keyname'], - 'prices': [{'id': price} for price in prices], - 'packageId': package['id'], - 'presetId': _get_preset_id(package, size), - 'useHourlyPricing': hourly, - } - if post_uri: - order['provisionScripts'] = [post_uri] + extras['provisionScripts'] = [post_uri] if ssh_keys: - order['sshKeys'] = [{'sshKeyIds': ssh_keys}] + extras['sshKeys'] = [{'sshKeyIds': ssh_keys}] + order = { + 'package_keyname': self.package_keyname, + 'location': location, + 'item_keynames': key_names, + 'complex_type': 'SoftLayer_Container_Product_Order_Hardware_Server', + 'hourly': hourly, + 'preset_keyname': size, + 'extras': extras, + 'quantity': 1, + } return order def _get_ids_from_hostname(self, hostname): @@ -745,78 +745,38 @@ def _get_extra_price_id(items, key_name, hourly, location): return price['id'] - raise SoftLayerError( - "Could not find valid price for extra option, '%s'" % key_name) - + raise SoftLayerError("Could not find valid price for extra option, '%s'" % key_name) -def _get_default_price_id(items, option, hourly, location): - """Returns a 'free' price id given an option.""" - for item in items: - if utils.lookup(item, 'itemCategory', 'categoryCode') != option: - continue - - for price in item['prices']: - if all([float(price.get('hourlyRecurringFee', 0)) == 0.0, - float(price.get('recurringFee', 0)) == 0.0, - _matches_billing(price, hourly), - _matches_location(price, location)]): - return price['id'] - - raise SoftLayerError( - "Could not find valid price for '%s' option" % option) - - -def _get_bandwidth_price_id(items, - hourly=True, - no_public=False, - location=None): - """Choose a valid price id for bandwidth.""" +def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): + """Picks a valid Bandwidth Item, returns the KeyName""" + keyName = None # Prefer pay-for-use data transfer with hourly for item in items: capacity = float(item.get('capacity', 0)) # Hourly and private only do pay-as-you-go bandwidth - if any([utils.lookup(item, - 'itemCategory', - 'categoryCode') != 'bandwidth', + if any([utils.lookup(item, 'itemCategory', 'categoryCode') != 'bandwidth', (hourly or no_public) and capacity != 0.0, not (hourly or no_public) and capacity == 0.0]): continue + keyName = item['keyName'] for price in item['prices']: if not _matches_billing(price, hourly): continue if not _matches_location(price, location): continue + return keyName - return price['id'] - - raise SoftLayerError( - "Could not find valid price for bandwidth option") - - -def _get_os_price_id(items, os, location): - """Returns the price id matching.""" - - for item in items: - if any([utils.lookup(item, 'itemCategory', 'categoryCode') != 'os', - utils.lookup(item, 'keyName') != os]): - continue - - for price in item['prices']: - if not _matches_location(price, location): - continue - - return price['id'] + raise SoftLayerError("Could not find valid price for bandwidth option") - raise SoftLayerError("Could not find valid price for os: '%s'" % os) - -def _get_port_speed_price_id(items, port_speed, no_public, location): +def _get_port_speed_key(items, port_speed, no_public, location): """Choose a valid price id for port speed.""" + keyName = None for item in items: if utils.lookup(item, 'itemCategory', 'categoryCode') != 'port_speed': continue @@ -826,12 +786,12 @@ def _get_port_speed_price_id(items, port_speed, no_public, location): _is_private_port_speed_item(item) != no_public, not _is_bonded(item)]): continue - + keyName = item['keyName'] for price in item['prices']: if not _matches_location(price, location): continue - return price['id'] + return keyName raise SoftLayerError( "Could not find valid price for port speed: '%s'" % port_speed) @@ -883,12 +843,3 @@ def _get_location(package, location): return region raise SoftLayerError("Could not find valid location for: '%s'" % location) - - -def _get_preset_id(package, size): - """Get the preset id given the keyName of the preset.""" - for preset in package['activePresets'] + package['accountRestrictedActivePresets']: - if preset['keyName'] == size or preset['id'] == size: - return preset['id'] - - raise SoftLayerError("Could not find valid size for: '%s'" % size) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 605bd32bf..d016c7730 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -355,19 +355,10 @@ def test_create_options(self): result = self.run_command(['server', 'create-options']) self.assert_no_fail(result) - expected = [ - [{'datacenter': 'Washington 1', 'value': 'wdc01'}], - [{'size': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', - 'value': 'S1270_8GB_2X1TBSATA_NORAID'}, - {'size': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10', - 'value': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10'}], - [{'operating_system': 'Ubuntu / 14.04-64', - 'value': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'operatingSystemReferenceCode ': 'UBUNTU_14_64'}], - [{'port_speed': '10 Mbps Public & Private Network Uplinks', - 'value': '10'}], - [{'extras': '1 IPv6 Address', 'value': '1_IPV6_ADDRESS'}]] - self.assertEqual(json.loads(result.output), expected) + output = json.loads(result.output) + self.assertEqual(output[0][0]['Value'], 'wdc01') + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): @@ -391,21 +382,6 @@ def test_create_server(self, order_mock): self.assertEqual(json.loads(result.output), {'id': 98765, 'created': '2013-08-02 15:23:47'}) - def test_create_server_missing_required(self): - - # This is missing a required argument - result = self.run_command(['server', 'create', - # Note: no chassis id - '--hostname=test', - '--domain=example.com', - '--datacenter=TEST00', - '--network=100', - '--os=UBUNTU_12_64_MINIMAL', - ]) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, SystemExit) - @mock.patch('SoftLayer.CLI.template.export_to_template') def test_create_server_with_export(self, export_mock): if (sys.platform.startswith("win")): diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index f6a2445d1..2e3d7b3a5 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -7,6 +7,7 @@ import copy import mock +from pprint import pprint as pp import SoftLayer from SoftLayer import fixtures @@ -117,29 +118,29 @@ def test_reload(self): def test_get_create_options(self): options = self.hardware.get_create_options() - expected = { - 'extras': [{'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'}], - 'locations': [{'key': 'wdc01', 'name': 'Washington 1'}], - 'operating_systems': [{'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'name': 'Ubuntu / 14.04-64', - 'referenceCode': 'UBUNTU_14_64'}], - 'port_speeds': [{ - 'key': '10', - 'name': '10 Mbps Public & Private Network Uplinks' - }], - 'sizes': [ - { - 'key': 'S1270_8GB_2X1TBSATA_NORAID', - 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID' - }, - { - 'key': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10', - 'name': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10' - } - ] + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} + locations = {'key': 'wdc01', 'name': 'Washington 1'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64' + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks' + } + sizes = { + 'key': 'S1270_8GB_2X1TBSATA_NORAID', + 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID' } - self.assertEqual(options, expected) + self.assertEqual(options['extras'][0], extras) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0], operating_systems) + self.assertEqual(options['port_speeds'][0]['name'], port_speeds['name']) + self.assertEqual(options['sizes'][0], sizes) + def test_get_create_options_package_missing(self): packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') @@ -148,18 +149,6 @@ def test_get_create_options_package_missing(self): ex = self.assertRaises(SoftLayer.SoftLayerError, self.hardware.get_create_options) self.assertEqual("Package BARE_METAL_SERVER does not exist", str(ex)) - def test_generate_create_dict_no_items(self): - packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - packages_copy = copy.deepcopy( - fixtures.SoftLayer_Product_Package.getAllObjects) - packages_copy[0]['items'] = [] - packages.return_value = packages_copy - - ex = self.assertRaises(SoftLayer.SoftLayerError, - self.hardware._generate_create_dict, - location="wdc01") - self.assertIn("Could not find valid price", str(ex)) - def test_generate_create_dict_no_regions(self): packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') packages_copy = copy.deepcopy( @@ -172,20 +161,6 @@ def test_generate_create_dict_no_regions(self): **MINIMAL_TEST_CREATE_ARGS) self.assertIn("Could not find valid location for: 'wdc01'", str(ex)) - def test_generate_create_dict_invalid_size(self): - args = { - 'size': 'UNKNOWN_SIZE', - 'hostname': 'unicorn', - 'domain': 'giggles.woo', - 'location': 'wdc01', - 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'port_speed': 10, - } - - ex = self.assertRaises(SoftLayer.SoftLayerError, - self.hardware._generate_create_dict, **args) - self.assertIn("Could not find valid size for: 'UNKNOWN_SIZE'", str(ex)) - def test_generate_create_dict(self): args = { 'size': 'S1270_8GB_2X1TBSATA_NORAID', @@ -199,51 +174,59 @@ def test_generate_create_dict(self): 'post_uri': 'http://example.com/script.php', 'ssh_keys': [10], } - - expected = { + + package = 'BARE_METAL_SERVER' + location = 'wdc01' + item_keynames = [ + '1_IP_ADDRESS', + 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', + 'REBOOT_KVM_OVER_IP', + 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'BANDWIDTH_0_GB_2', + '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', + '1_IPV6_ADDRESS' + ] + hourly = True + preset_keyname = 'S1270_8GB_2X1TBSATA_NORAID' + extras = { 'hardware': [{ 'domain': 'giggles.woo', 'hostname': 'unicorn', }], - 'location': 'WASHINGTON_DC', - 'packageId': 200, - 'presetId': 64, - 'prices': [{'id': 21}, - {'id': 420}, - {'id': 906}, - {'id': 37650}, - {'id': 1800}, - {'id': 272}, - {'id': 17129}], - 'useHourlyPricing': True, 'provisionScripts': ['http://example.com/script.php'], - 'sshKeys': [{'sshKeyIds': [10]}], + 'sshKeys' : [{'sshKeyIds': [10]}] } data = self.hardware._generate_create_dict(**args) - self.assertEqual(expected, data) + self.assertEqual(package, data['package_keyname']) + self.assertEqual(location, data['location']) + for keyname in item_keynames: + self.assertIn(keyname, data['item_keynames']) + self.assertEqual(extras, data['extras']) - @mock.patch('SoftLayer.managers.hardware.HardwareManager' - '._generate_create_dict') - def test_verify_order(self, create_dict): + + @mock.patch('SoftLayer.managers.ordering.OrderingManager.verify_order') + @mock.patch('SoftLayer.managers.hardware.HardwareManager._generate_create_dict') + def test_verify_order(self, create_dict, verify_order): create_dict.return_value = {'test': 1, 'verify': 1} + verify_order.return_value = {'test': 2} - self.hardware.verify_order(test=1, verify=1) + result = self.hardware.verify_order(test=1, verify=1) + self.assertEqual(2, result['test']) create_dict.assert_called_once_with(test=1, verify=1) - self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', - args=({'test': 1, 'verify': 1},)) + verify_order.assert_called_once_with(test=1, verify=1) - @mock.patch('SoftLayer.managers.hardware.HardwareManager' - '._generate_create_dict') - def test_place_order(self, create_dict): + @mock.patch('SoftLayer.managers.ordering.OrderingManager.place_order') + @mock.patch('SoftLayer.managers.hardware.HardwareManager._generate_create_dict') + def test_place_order(self, create_dict, place_order): create_dict.return_value = {'test': 1, 'verify': 1} - self.hardware.place_order(test=1, verify=1) - + place_order.return_value = {'test': 1} + result = self.hardware.place_order(test=1, verify=1) + self.assertEqual(1, result['test']) create_dict.assert_called_once_with(test=1, verify=1) - self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', - args=({'test': 1, 'verify': 1},)) + place_order.assert_called_once_with(test=1, verify=1) def test_cancel_hardware_without_reason(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') @@ -629,162 +612,6 @@ def test_get_hard_drive_empty(self): class HardwareHelperTests(testing.TestCase): - def test_get_extra_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_extra_price_id, - [], 'test', True, None) - self.assertEqual("Could not find valid price for extra option, 'test'", str(ex)) - - def test_get_extra_price_mismatched(self): - items = [ - {'keyName': 'TEST', 'prices': [{'id': 1, 'locationGroupId': None, 'recurringFee': 99}]}, - {'keyName': 'TEST', 'prices': [{'id': 2, 'locationGroupId': 55, 'hourlyRecurringFee': 99}]}, - {'keyName': 'TEST', 'prices': [{'id': 3, 'locationGroupId': None, 'hourlyRecurringFee': 99}]}, - ] - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._get_extra_price_id(items, 'TEST', True, location) - self.assertEqual(3, result) - - def test_get_bandwidth_price_mismatched(self): - items = [ - {'itemCategory': {'categoryCode': 'bandwidth'}, - 'capacity': 100, - 'prices': [{'id': 1, 'locationGroupId': None, 'hourlyRecurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'bandwidth'}, - 'capacity': 100, - 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'bandwidth'}, - 'capacity': 100, - 'prices': [{'id': 3, 'locationGroupId': None, 'recurringFee': 99}] - }, - ] - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._get_bandwidth_price_id(items, False, False, location) - self.assertEqual(3, result) - - def test_get_os_price_mismatched(self): - items = [ - {'itemCategory': {'categoryCode': 'os'}, - 'keyName': 'OS_TEST', - 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'os'}, - 'keyName': 'OS_TEST', - 'prices': [{'id': 3, 'locationGroupId': None, 'recurringFee': 99}] - }, - ] - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._get_os_price_id(items, 'OS_TEST', location) - self.assertEqual(3, result) - - def test_get_default_price_id_item_not_first(self): - items = [{ - 'itemCategory': {'categoryCode': 'unknown', 'id': 325}, - 'keyName': 'UNKNOWN', - 'prices': [{'accountRestrictions': [], - 'currentPriceFlag': '', - 'hourlyRecurringFee': '10.0', - 'id': 1245172, - 'recurringFee': '1.0'}], - }] - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_default_price_id, - items, 'unknown', True, None) - self.assertEqual("Could not find valid price for 'unknown' option", str(ex)) - - def test_get_default_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_default_price_id, - [], 'test', True, None) - self.assertEqual("Could not find valid price for 'test' option", str(ex)) - - def test_get_bandwidth_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_bandwidth_price_id, - [], hourly=True, no_public=False) - self.assertEqual("Could not find valid price for bandwidth option", str(ex)) - - def test_get_os_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_os_price_id, - [], 'UBUNTU_14_64', None) - self.assertEqual("Could not find valid price for os: 'UBUNTU_14_64'", str(ex)) - - def test_get_port_speed_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_port_speed_price_id, - [], 10, True, None) - self.assertEqual("Could not find valid price for port speed: '10'", str(ex)) - - def test_get_port_speed_price_id_mismatch(self): - items = [ - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 101, - 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices': [{'id': 1, 'locationGroupId': None, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 100, - 'attributes': [{'attributeTypeKeyName': 'IS_NOT_PRIVATE_NETWORK_ONLY'}], - 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 100, - 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}, {'attributeTypeKeyName': 'NON_LACP'}], - 'prices': [{'id': 3, 'locationGroupId': 55, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 100, - 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices': [{'id': 4, 'locationGroupId': 12, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 100, - 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices': [{'id': 5, 'locationGroupId': None, 'recurringFee': 99}] - }, - ] - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._get_port_speed_price_id(items, 100, True, location) - self.assertEqual(5, result) def test_matches_location(self): price = {'id': 1, 'locationGroupId': 51, 'recurringFee': 99} From b6933ebc0365a7ddb559e6989d916d0978fe5832 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 9 Jul 2020 17:26:12 -0500 Subject: [PATCH 0167/1385] #828 full unit test coverage --- SoftLayer/CLI/hardware/create.py | 47 ++++------- SoftLayer/managers/hardware.py | 27 +++--- tests/CLI/modules/server_tests.py | 25 +++++- tests/managers/hardware_tests.py | 132 ++++++++++++++++++++++++++---- 4 files changed, 168 insertions(+), 63 deletions(-) diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index eb83e6664..40fa871bc 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -12,38 +12,25 @@ @click.command(epilog="See 'slcli server create-options' for valid options.") -@click.option('--hostname', '-H', required=True, prompt=True, - help="Host portion of the FQDN") -@click.option('--domain', '-D', required=True, prompt=True, - help="Domain portion of the FQDN") -@click.option('--size', '-s', required=True, prompt=True, - help="Hardware size") -@click.option('--os', '-o', required=True, prompt=True, - help="OS Key value") -@click.option('--datacenter', '-d', required=True, prompt=True, - help="Datacenter shortname") -@click.option('--port-speed', type=click.INT, required=True, prompt=True, - help="Port speeds. DEPRECATED, use --network") -@click.option('--no-public', is_flag=True, - help="Private network only. DEPRECATED, use --network.") -@click.option('--network', - help="Network Option Key.") -@click.option('--billing', default='hourly', show_default=True, - type=click.Choice(['hourly', 'monthly']), +@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN") +@click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN") +@click.option('--size', '-s', required=True, prompt=True, help="Hardware size") +@click.option('--os', '-o', required=True, prompt=True, help="OS Key value") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--port-speed', type=click.INT, help="Port speeds. DEPRECATED, use --network") +@click.option('--no-public', is_flag=True, help="Private network only. DEPRECATED, use --network.") +@click.option('--network', help="Network Option Key. Use instead of port-speed option") +@click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), help="Billing rate") -@click.option('--postinstall', '-i', - help="Post-install script. Should be a HTTPS URL.") -@click.option('--test', is_flag=True, - help="Do not actually create the server") -@click.option('--template', '-t', is_eager=True, +@click.option('--postinstall', '-i', help="Post-install script. Should be a HTTPS URL.") +@click.option('--test', is_flag=True, help="Do not actually create the server") +@click.option('--template', '-t', is_eager=True, type=click.Path(exists=True, readable=True, resolve_path=True), callback=template.TemplateCallback(list_args=['key']), - help="A template file that defaults the command-line options", - type=click.Path(exists=True, readable=True, resolve_path=True)) + help="A template file that defaults the command-line options") @click.option('--export', type=click.Path(writable=True, resolve_path=True), help="Exports options to a template file") @click.option('--wait', type=click.INT, - help="Wait until the server is finished provisioning for up to " - "X seconds before returning") + help="Wait until the server is finished provisioning for up to X seconds before returning") @helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") @helpers.multi_option('--extra', '-e', help="Extra option Key Names") @environment.pass_env @@ -101,15 +88,13 @@ def cli(env, **args): if args['export']: export_file = args.pop('export') - template.export_to_template(export_file, args, - exclude=['wait', 'test']) + template.export_to_template(export_file, args, exclude=['wait', 'test']) env.fout('Successfully exported options to a template file.') return if do_create: if not (env.skip_confirmations or formatting.confirm( - "This action will incur charges on your account. " - "Continue?")): + "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting dedicated server order.') result = mgr.place_order(**order) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index cccf6b29f..136b22c0a 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -729,23 +729,23 @@ def get_hard_drives(self, instance_id): return self.hardware.getHardDrives(id=instance_id) -def _get_extra_price_id(items, key_name, hourly, location): - """Returns a price id attached to item with the given key_name.""" +# def _get_extra_price_id(items, key_name, hourly, location): +# """Returns a price id attached to item with the given key_name.""" - for item in items: - if utils.lookup(item, 'keyName') != key_name: - continue +# for item in items: +# if utils.lookup(item, 'keyName') != key_name: +# continue - for price in item['prices']: - if not _matches_billing(price, hourly): - continue +# for price in item['prices']: +# if not _matches_billing(price, hourly): +# continue - if not _matches_location(price, location): - continue +# if not _matches_location(price, location): +# continue - return price['id'] +# return price['id'] - raise SoftLayerError("Could not find valid price for extra option, '%s'" % key_name) +# raise SoftLayerError("Could not find valid price for extra option, '%s'" % key_name) def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): @@ -793,8 +793,7 @@ def _get_port_speed_key(items, port_speed, no_public, location): return keyName - raise SoftLayerError( - "Could not find valid price for port speed: '%s'" % port_speed) + raise SoftLayerError("Could not find valid price for port speed: '%s'" % port_speed) def _matches_billing(price, hourly): diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index d016c7730..388fc310d 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -359,7 +359,6 @@ def test_create_options(self): self.assertEqual(output[0][0]['Value'], 'wdc01') self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') - @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): order_mock.return_value = { @@ -860,3 +859,27 @@ def test_billing(self): } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), billing_json) + + def test_create_hw_export(self): + if(sys.platform.startswith("win")): + self.skipTest("Temp files do not work properly in Windows.") + with tempfile.NamedTemporaryFile() as config_file: + result = self.run_command(['hw', 'create', '--hostname=test', '--export', config_file.name, + '--domain=example.com', '--datacenter=TEST00', + '--network=TEST_NETWORK', '--os=UBUNTU_12_64', + '--size=S1270_8GB_2X1TBSATA_NORAID']) + self.assert_no_fail(result) + self.assertTrue('Successfully exported options to a template file.' in result.output) + contents = config_file.read().decode("utf-8") + self.assertIn('hostname=TEST', contents) + self.assertIn('size=S1270_8GB_2X1TBSATA_NORAID', contents) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_hw_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['hw', 'create', '--hostname=test', '--size=S1270_8GB_2X1TBSATA_NORAID', + '--domain=example.com', '--datacenter=TEST00', + '--network=TEST_NETWORK', '--os=UBUNTU_12_64']) + + self.assertEqual(result.exit_code, 2) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 2e3d7b3a5..a61aeece0 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -7,7 +7,6 @@ import copy import mock -from pprint import pprint as pp import SoftLayer from SoftLayer import fixtures @@ -118,7 +117,7 @@ def test_reload(self): def test_get_create_options(self): options = self.hardware.get_create_options() - extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} locations = {'key': 'wdc01', 'name': 'Washington 1'} operating_systems = { 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', @@ -141,7 +140,6 @@ def test_get_create_options(self): self.assertEqual(options['port_speeds'][0]['name'], port_speeds['name']) self.assertEqual(options['sizes'][0], sizes) - def test_get_create_options_package_missing(self): packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') packages.return_value = [] @@ -174,7 +172,7 @@ def test_generate_create_dict(self): 'post_uri': 'http://example.com/script.php', 'ssh_keys': [10], } - + package = 'BARE_METAL_SERVER' location = 'wdc01' item_keynames = [ @@ -194,7 +192,7 @@ def test_generate_create_dict(self): 'hostname': 'unicorn', }], 'provisionScripts': ['http://example.com/script.php'], - 'sshKeys' : [{'sshKeyIds': [10]}] + 'sshKeys': [{'sshKeyIds': [10]}] } data = self.hardware._generate_create_dict(**args) @@ -204,7 +202,25 @@ def test_generate_create_dict(self): for keyname in item_keynames: self.assertIn(keyname, data['item_keynames']) self.assertEqual(extras, data['extras']) + self.assertEqual(preset_keyname, data['preset_keyname']) + self.assertEqual(hourly, data['hourly']) + def test_generate_create_dict_network_key(self): + args = { + 'size': 'S1270_8GB_2X1TBSATA_NORAID', + 'hostname': 'test1', + 'domain': 'test.com', + 'location': 'wdc01', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'network': 'NETWORKING', + 'hourly': True, + 'extras': ['1_IPV6_ADDRESS'], + 'post_uri': 'http://example.com/script.php', + 'ssh_keys': [10], + } + + data = self.hardware._generate_create_dict(**args) + self.assertIn('NETWORKING', data['item_keynames']) @mock.patch('SoftLayer.managers.ordering.OrderingManager.verify_order') @mock.patch('SoftLayer.managers.hardware.HardwareManager._generate_create_dict') @@ -613,17 +629,99 @@ def test_get_hard_drive_empty(self): class HardwareHelperTests(testing.TestCase): + def set_up(self): + self.items = [ + { + "itemCategory": {"categoryCode": "port_speed"}, + "capacity": 100, + "attributes": [ + {'attributeTypeKeyName': 'NON_LACP'}, + {'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'} + ], + "keyName": "ITEM_1", + "prices": [{"id": 1, "locationGroupId": 100}] + }, + { + "itemCategory": {"categoryCode": "port_speed"}, + "capacity": 200, + "attributes": [ + {'attributeTypeKeyName': 'YES_LACP'}, + {'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'} + ], + "keyName": "ITEM_2", + "prices": [{"id": 1, "locationGroupId": 151}] + }, + { + "itemCategory": {"categoryCode": "port_speed"}, + "capacity": 200, + "attributes": [ + {'attributeTypeKeyName': 'YES_LACP'}, + {'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'} + ], + "keyName": "ITEM_3", + "prices": [{"id": 1, "locationGroupId": 51}] + }, + { + "itemCategory": {"categoryCode": "bandwidth"}, + "capacity": 0.0, + "attributes": [], + "keyName": "HOURLY_BANDWIDTH_1", + "prices": [{"id": 1, "locationGroupId": 51, "hourlyRecurringFee": 1.0, "recurringFee": 1.0}] + }, + { + "itemCategory": {"categoryCode": "bandwidth"}, + "capacity": 10.0, + "attributes": [], + "keyName": "MONTHLY_BANDWIDTH_1", + "prices": [{"id": 1, "locationGroupId": 151, "recurringFee": 1.0}] + }, + { + "itemCategory": {"categoryCode": "bandwidth"}, + "capacity": 10.0, + "attributes": [], + "keyName": "MONTHLY_BANDWIDTH_2", + "prices": [{"id": 1, "locationGroupId": 51, "recurringFee": 1.0}] + }, + ] + self.location = {'location': {'location': {'priceGroups': [{'id': 50}, {'id': 51}]}}} + + def test_bandwidth_key(self): + result = managers.hardware._get_bandwidth_key(self.items, True, False, self.location) + self.assertEqual('HOURLY_BANDWIDTH_1', result) + result = managers.hardware._get_bandwidth_key(self.items, False, True, self.location) + self.assertEqual('HOURLY_BANDWIDTH_1', result) + result = managers.hardware._get_bandwidth_key(self.items, False, False, self.location) + self.assertEqual('MONTHLY_BANDWIDTH_2', result) + ex = self.assertRaises(SoftLayer.SoftLayerError, + managers.hardware._get_bandwidth_key, [], True, False, self.location) + self.assertEqual("Could not find valid price for bandwidth option", str(ex)) + + def test_port_speed_key(self): + result = managers.hardware._get_port_speed_key(self.items, 200, True, self.location) + self.assertEqual("ITEM_3", result) + + def test_port_speed_key_exception(self): + items = [] + location = {} + ex = self.assertRaises(SoftLayer.SoftLayerError, + managers.hardware._get_port_speed_key, items, 999, False, location) + self.assertEqual("Could not find valid price for port speed: '999'", str(ex)) + def test_matches_location(self): price = {'id': 1, 'locationGroupId': 51, 'recurringFee': 99} - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._matches_location(price, location) - self.assertTrue(result) + + self.assertTrue(managers.hardware._matches_location(price, self.location)) + price['locationGroupId'] = 99999 + self.assertFalse(managers.hardware._matches_location(price, self.location)) + + def test_is_bonded(self): + item_non_lacp = {'attributes': [{'attributeTypeKeyName': 'NON_LACP'}]} + item_lacp = {'attributes': [{'attributeTypeKeyName': 'YES_LACP'}]} + self.assertFalse(managers.hardware._is_bonded(item_non_lacp)) + self.assertTrue(managers.hardware._is_bonded(item_lacp)) + + def test_is_private(self): + item_private = {'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}]} + item_public = {'attributes': [{'attributeTypeKeyName': 'NOT_PRIVATE_NETWORK_ONLY'}]} + self.assertTrue(managers.hardware._is_private_port_speed_item(item_private)) + self.assertFalse(managers.hardware._is_private_port_speed_item(item_public)) From 6dd7041e24ae4cc15631443ae6dd471680e018c5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 10 Jul 2020 16:26:30 -0500 Subject: [PATCH 0168/1385] #828 code cleanup --- SoftLayer/managers/hardware.py | 19 ------------------- docs/cli/hardware.rst | 2 ++ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 136b22c0a..956d33e3a 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -729,25 +729,6 @@ def get_hard_drives(self, instance_id): return self.hardware.getHardDrives(id=instance_id) -# def _get_extra_price_id(items, key_name, hourly, location): -# """Returns a price id attached to item with the given key_name.""" - -# for item in items: -# if utils.lookup(item, 'keyName') != key_name: -# continue - -# for price in item['prices']: -# if not _matches_billing(price, hourly): -# continue - -# if not _matches_location(price, location): -# continue - -# return price['id'] - -# raise SoftLayerError("Could not find valid price for extra option, '%s'" % key_name) - - def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 0cce23042..5ca325cb7 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -27,6 +27,8 @@ Interacting with Hardware Provides some basic functionality to order a server. `slcli order` has a more full featured method of ordering servers. This command only supports the FAST_PROVISION type. +As of v5.9.0 please use the `--network` option for specifying port speed, as that allows a bit more granularity for choosing your networking type. + .. click:: SoftLayer.CLI.hardware.credentials:cli :prog: hardware credentials :show-nested: From f4c256593ca8066b6f58040c7a542d96751d3ee6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 10 Jul 2020 16:38:11 -0500 Subject: [PATCH 0169/1385] fixed a unit test --- tests/CLI/modules/server_tests.py | 37 +++---------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 388fc310d..f11d9d0a6 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -383,8 +383,7 @@ def test_create_server(self, order_mock): @mock.patch('SoftLayer.CLI.template.export_to_template') def test_create_server_with_export(self, export_mock): - if (sys.platform.startswith("win")): - self.skipTest("Test doesn't work in Windows") + result = self.run_command(['--really', 'server', 'create', '--size=S1270_8GB_2X1TBSATA_NORAID', '--hostname=test', @@ -397,24 +396,8 @@ def test_create_server_with_export(self, export_mock): fmt='raw') self.assert_no_fail(result) - self.assertIn("Successfully exported options to a template file.", - result.output) - export_mock.assert_called_with('/path/to/test_file.txt', - {'billing': 'hourly', - 'datacenter': 'TEST00', - 'domain': 'example.com', - 'extra': (), - 'hostname': 'test', - 'key': (), - 'os': 'UBUNTU_12_64', - 'port_speed': 100, - 'postinstall': None, - 'size': 'S1270_8GB_2X1TBSATA_NORAID', - 'test': False, - 'no_public': True, - 'wait': None, - 'template': None}, - exclude=['wait', 'test']) + self.assertIn("Successfully exported options to a template file.", result.output) + export_mock.assert_called_once() def test_edit_server_userdata_and_file(self): # Test both userdata and userfile at once @@ -860,20 +843,6 @@ def test_billing(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), billing_json) - def test_create_hw_export(self): - if(sys.platform.startswith("win")): - self.skipTest("Temp files do not work properly in Windows.") - with tempfile.NamedTemporaryFile() as config_file: - result = self.run_command(['hw', 'create', '--hostname=test', '--export', config_file.name, - '--domain=example.com', '--datacenter=TEST00', - '--network=TEST_NETWORK', '--os=UBUNTU_12_64', - '--size=S1270_8GB_2X1TBSATA_NORAID']) - self.assert_no_fail(result) - self.assertTrue('Successfully exported options to a template file.' in result.output) - contents = config_file.read().decode("utf-8") - self.assertIn('hostname=TEST', contents) - self.assertIn('size=S1270_8GB_2X1TBSATA_NORAID', contents) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_hw_no_confirm(self, confirm_mock): confirm_mock.return_value = False From d3871cf8aca3f6e92aebed4955344c3948069fdb Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 10 Jul 2020 22:12:50 -0400 Subject: [PATCH 0170/1385] add user notifications --- SoftLayer/CLI/routes.py | 2 + SoftLayer/CLI/user/edit_notifications.py | 34 ++++++++++++ SoftLayer/CLI/user/notifications.py | 34 ++++++++++++ SoftLayer/managers/user.py | 68 ++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 SoftLayer/CLI/user/edit_notifications.py create mode 100644 SoftLayer/CLI/user/notifications.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 48b0af834..049b43c3b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -316,6 +316,8 @@ ('user:detail', 'SoftLayer.CLI.user.detail:cli'), ('user:permissions', 'SoftLayer.CLI.user.permissions:cli'), ('user:edit-permissions', 'SoftLayer.CLI.user.edit_permissions:cli'), + ('user:notifications', 'SoftLayer.CLI.user.notifications:cli'), + ('user:edit-notifications', 'SoftLayer.CLI.user.edit_notifications:cli'), ('user:edit-details', 'SoftLayer.CLI.user.edit_details:cli'), ('user:create', 'SoftLayer.CLI.user.create:cli'), ('user:delete', 'SoftLayer.CLI.user.delete:cli'), diff --git a/SoftLayer/CLI/user/edit_notifications.py b/SoftLayer/CLI/user/edit_notifications.py new file mode 100644 index 000000000..b01272b36 --- /dev/null +++ b/SoftLayer/CLI/user/edit_notifications.py @@ -0,0 +1,34 @@ +"""Enable or Disable specific noticication for the current user""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.option('--enable/--disable', default=True, + help="Enable (DEFAULT) or Disable selected notification") +@click.argument('notification', nargs=-1, required=True) +@environment.pass_env +def cli(env, enable, notification): + """Enable or Disable specific notifications. + + Example:: + + slcli user edit-notifications --enable 'Order Approved' 'Reload Complete' + + """ + + mgr = SoftLayer.UserManager(env.client) + + if enable: + result = mgr.enable_notifications(notification) + else: + result = mgr.disable_notifications(notification) + + if result: + click.secho("Notifications updated successfully: %s" % ", ".join(notification), fg='green') + else: + click.secho("Failed to update notifications: %s" % ", ".join(notification), fg='red') diff --git a/SoftLayer/CLI/user/notifications.py b/SoftLayer/CLI/user/notifications.py new file mode 100644 index 000000000..ddb8019e9 --- /dev/null +++ b/SoftLayer/CLI/user/notifications.py @@ -0,0 +1,34 @@ +"""List user notifications""" +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@environment.pass_env +def cli(env): + """User Notifications.""" + + mgr = SoftLayer.UserManager(env.client) + + all_notifications = mgr.get_all_notifications() + + env.fout(notification_table(all_notifications)) + + +def notification_table(all_notifications): + """Creates a table of available notifications""" + + table = formatting.Table(['Id', 'Name', 'Description', 'Enabled']) + table.align['Id'] = 'l' + table.align['Name'] = 'l' + table.align['Description'] = 'l' + table.align['Enabled'] = 'l' + for notification in all_notifications: + table.add_row([notification['id'], + notification['name'], + notification['description'], + notification['enabled']]) + return table diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 5875d76a8..283208baf 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -36,6 +36,7 @@ def __init__(self, client): self.user_service = self.client['SoftLayer_User_Customer'] self.override_service = self.client['Network_Service_Vpn_Overrides'] self.account_service = self.client['SoftLayer_Account'] + self.subscription_service = self.client['SoftLayer_Email_Subscription'] self.resolvers = [self._get_id_from_username] self.all_permissions = None @@ -85,6 +86,56 @@ def get_all_permissions(self): self.all_permissions = sorted(permissions, key=itemgetter('keyName')) return self.all_permissions + def get_all_notifications(self): + """Calls SoftLayer_Email_Subscription::getAllObjects + + Stores the result in self.all_permissions + :returns: A list of dictionaries that contains all valid permissions + """ + return self.subscription_service.getAllObjects(mask='mask[enabled]') + + def enable_notifications(self, notifications_names): + """Enables a list of notifications for the current a user profile. + + :param list notifications_names: List of notifications names to enable + :returns: True on success + + Example:: + enable_notifications(['Order Approved','Reload Complete']) + """ + + result = False + notifications = self.gather_notifications(notifications_names) + for notification in notifications: + notification_id = notification.get('id') + result = self.subscription_service.enable(id=notification_id) + if result: + continue + else: + return False + return result + + def disable_notifications(self, notifications_names): + """Disable a list of notifications for the current a user profile. + + :param list notifications_names: List of notifications names to disable + :returns: True on success + + Example:: + disable_notifications(['Order Approved','Reload Complete']) + """ + + result = False + notifications = self.gather_notifications(notifications_names) + for notification in notifications: + notification_id = notification.get('id') + result = self.subscription_service.disable(id=notification_id) + if result: + continue + else: + return False + return result + def add_permissions(self, user_id, permissions): """Enables a list of permissions for a user @@ -237,6 +288,23 @@ def format_permission_object(self, permissions): raise exceptions.SoftLayerError("'%s' is not a valid permission" % permission) return pretty_permissions + def gather_notifications(self, notifications_names): + """Gets a list of notifications. + + :param list notifications_names: A list of notifications names. + :returns: list of notifications. + """ + notifications = [] + available_notifications = self.get_all_notifications() + for notification in notifications_names: + result = next((item for item in available_notifications + if item.get('name') == notification), None) + if result: + notifications.append(result) + else: + raise exceptions.SoftLayerError("{} is not a valid notification name".format(notification)) + return notifications + def create_user(self, user_object, password): """Blindly sends user_object to SoftLayer_User_Customer::createObject From 2e4618acc47a33682ee72a430588f771e3357e35 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 10 Jul 2020 22:13:58 -0400 Subject: [PATCH 0171/1385] add user notifications tests --- .../fixtures/SoftLayer_Email_Subscription.py | 22 +++++++++ tests/CLI/modules/user_tests.py | 32 +++++++++++++ tests/managers/user_tests.py | 48 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 SoftLayer/fixtures/SoftLayer_Email_Subscription.py diff --git a/SoftLayer/fixtures/SoftLayer_Email_Subscription.py b/SoftLayer/fixtures/SoftLayer_Email_Subscription.py new file mode 100644 index 000000000..bc3104b16 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Email_Subscription.py @@ -0,0 +1,22 @@ +getAllObjects = [ + {'description': 'Email about your order.', + 'enabled': True, + 'id': 1, + 'name': 'Order Being Reviewed' + }, + {'description': 'Maintenances that will or are likely to cause service ' + 'outages and disruptions', + 'enabled': True, + 'id': 8, + 'name': 'High Impact' + }, + {'description': 'Testing description.', + 'enabled': True, + 'id': 111, + 'name': 'Test notification' + } +] + +enable = True + +disable = True diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 6f58c14a0..f16ef1843 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -305,3 +305,35 @@ def test_vpn_subnet_remove(self, click): result = self.run_command(['user', 'vpn-subnet', '12345', '--remove', '1234']) click.secho.assert_called_with('12345 updated successfully', fg='green') self.assert_no_fail(result) + + """User notification tests""" + + def test_notificacions_list(self): + result = self.run_command(['user', 'notifications']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Email_Subscription', 'getAllObjects', mask='mask[enabled]') + + """User edit-notification tests""" + + def test_edit_notification_on(self): + result = self.run_command(['user', 'edit-notifications', '--enable', 'Test notification']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Email_Subscription', 'enable', identifier=111) + + def test_edit_notification_on_bad(self): + result = self.run_command(['user', 'edit-notifications', '--enable', 'Test not exist']) + self.assertEqual(result.exit_code, 1) + + def test_edit_notifications_off(self): + result = self.run_command(['user', 'edit-notifications', '--disable', 'Test notification']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) + + @mock.patch('SoftLayer.CLI.user.edit_notifications.click') + def test_edit_notification_off_failure(self, click): + notification = self.set_mock('SoftLayer_Email_Subscription', 'disable') + notification.return_value = False + result = self.run_command(['user', 'edit-notifications', '--disable', 'Test notification']) + click.secho.assert_called_with('Failed to update notifications: Test notification', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index b0ab015f9..61f5b4d0a 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -4,7 +4,9 @@ """ import datetime + import mock + import SoftLayer from SoftLayer import exceptions from SoftLayer import testing @@ -246,3 +248,49 @@ def test_vpn_subnet_remove(self): self.manager.vpn_subnet_remove(user_id, [subnet_id]) self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects', args=expected_args) self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) + + def test_get_all_notifications(self): + self.manager.get_all_notifications() + self.assert_called_with('SoftLayer_Email_Subscription', 'getAllObjects') + + def test_enable_notifications(self): + self.manager.enable_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', 'enable', identifier=111) + + def test_disable_notifications(self): + self.manager.disable_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) + + def test_enable_notifications_fail(self): + notification = self.set_mock('SoftLayer_Email_Subscription', 'enable') + notification.return_value = False + result = self.manager.enable_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', 'enable', identifier=111) + self.assertFalse(result) + + def test_disable_notifications_fail(self): + notification = self.set_mock('SoftLayer_Email_Subscription', 'disable') + notification.return_value = False + result = self.manager.disable_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) + self.assertFalse(result) + + def test_gather_notifications(self): + expected_result = [ + {'description': 'Testing description.', + 'enabled': True, + 'id': 111, + 'name': 'Test notification' + } + ] + result = self.manager.gather_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', + 'getAllObjects', + mask='mask[enabled]') + self.assertEqual(result, expected_result) + + def test_gather_notifications_fail(self): + ex = self.assertRaises(SoftLayer.SoftLayerError, + self.manager.gather_notifications, + ['Test not exit']) + self.assertEqual("Test not exit is not a valid notification name", str(ex)) From eb6631697f6de79323c3440eb633169dd542a656 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 10 Jul 2020 22:14:42 -0400 Subject: [PATCH 0172/1385] add user notifications docs --- docs/cli/users.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/cli/users.rst b/docs/cli/users.rst index feb94e352..5195a7788 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -12,10 +12,18 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli :prog: user detail :show-nested: +.. click:: SoftLayer.CLI.user.notifications:cli + :prog: user notifications + :show-nested: + .. click:: SoftLayer.CLI.user.permissions:cli :prog: user permissions :show-nested: +.. click:: SoftLayer.CLI.user.edit_notifications:cli + :prog: user edit-notifications + :show-nested: + .. click:: SoftLayer.CLI.user.edit_permissions:cli :prog: user edit-permissions :show-nested: From e423d6b83f99c0d2bd34b7408211eb72c3485b4d Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 13 Jul 2020 15:24:22 -0400 Subject: [PATCH 0173/1385] fix tox analysis --- SoftLayer/managers/user.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 283208baf..0948df8b3 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -109,9 +109,7 @@ def enable_notifications(self, notifications_names): for notification in notifications: notification_id = notification.get('id') result = self.subscription_service.enable(id=notification_id) - if result: - continue - else: + if not result: return False return result @@ -130,9 +128,7 @@ def disable_notifications(self, notifications_names): for notification in notifications: notification_id = notification.get('id') result = self.subscription_service.disable(id=notification_id) - if result: - continue - else: + if not result: return False return result From 744213a800ca827fbd1a790110ebb6063a914bfa Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 14 Jul 2020 15:55:24 -0400 Subject: [PATCH 0174/1385] improve user notifications docs --- SoftLayer/CLI/user/edit_notifications.py | 3 ++- SoftLayer/CLI/user/notifications.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/user/edit_notifications.py b/SoftLayer/CLI/user/edit_notifications.py index b01272b36..acef3380b 100644 --- a/SoftLayer/CLI/user/edit_notifications.py +++ b/SoftLayer/CLI/user/edit_notifications.py @@ -13,7 +13,8 @@ @click.argument('notification', nargs=-1, required=True) @environment.pass_env def cli(env, enable, notification): - """Enable or Disable specific notifications. + """Enable or Disable specific notifications for the active user. + Notification names should be enclosed in quotation marks. Example:: diff --git a/SoftLayer/CLI/user/notifications.py b/SoftLayer/CLI/user/notifications.py index ddb8019e9..deffd951e 100644 --- a/SoftLayer/CLI/user/notifications.py +++ b/SoftLayer/CLI/user/notifications.py @@ -9,7 +9,7 @@ @click.command() @environment.pass_env def cli(env): - """User Notifications.""" + """My Notifications.""" mgr = SoftLayer.UserManager(env.client) From 9c940744471f4a3bc2482a1c1fdfa086b55ee9c4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 14 Jul 2020 16:06:48 -0400 Subject: [PATCH 0175/1385] clean code --- SoftLayer/CLI/user/edit_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/user/edit_notifications.py b/SoftLayer/CLI/user/edit_notifications.py index acef3380b..1be7527c2 100644 --- a/SoftLayer/CLI/user/edit_notifications.py +++ b/SoftLayer/CLI/user/edit_notifications.py @@ -14,8 +14,8 @@ @environment.pass_env def cli(env, enable, notification): """Enable or Disable specific notifications for the active user. - Notification names should be enclosed in quotation marks. + Notification names should be enclosed in quotation marks. Example:: slcli user edit-notifications --enable 'Order Approved' 'Reload Complete' From 47fb7896c3c004ea1abb7aa669c897e92b5d0163 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 16 Jul 2020 15:10:35 -0500 Subject: [PATCH 0176/1385] #874 added 'vs migrate' command --- SoftLayer/CLI/dedicatedhost/detail.py | 2 +- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/migrate.py | 80 +++++++++++++++++++++++++++ SoftLayer/managers/vs.py | 20 +++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/virt/migrate.py diff --git a/SoftLayer/CLI/dedicatedhost/detail.py b/SoftLayer/CLI/dedicatedhost/detail.py index e1c46b962..ca966d03a 100644 --- a/SoftLayer/CLI/dedicatedhost/detail.py +++ b/SoftLayer/CLI/dedicatedhost/detail.py @@ -19,7 +19,7 @@ @click.option('--guests', is_flag=True, help='Show guests on dedicated host') @environment.pass_env def cli(env, identifier, price=False, guests=False): - """Get details for a virtual server.""" + """Get details for a dedicated host.""" dhost = SoftLayer.DedicatedHostManager(env.client) table = formatting.KeyValueTable(['name', 'value']) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 049b43c3b..424f58957 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -46,6 +46,7 @@ ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), + ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), ('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'), ('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'), diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py new file mode 100644 index 000000000..9e48fb208 --- /dev/null +++ b/SoftLayer/CLI/virt/migrate.py @@ -0,0 +1,80 @@ +"""Manage Migrations of Virtual Guests""" +# :license: MIT, see LICENSE for more details. +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@click.option('--guest', '-g', type=click.INT, help="Guest ID to immediately migrate.") +@click.option('--all', '-a', 'migrate_all', is_flag=True, default=False, + help="Migrate ALL guests that require migration immediately.") +@click.option('--host', '-h', type=click.INT, + help="Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host.") +@environment.pass_env +def cli(env, guest, migrate_all, host): + """Manage VSIs that require migration. Can migrate Dedicated Host VSIs as well.""" + + vsi = SoftLayer.VSManager(env.client) + pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}} + dedicated_filer = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} + mask = """mask[ + id, hostname, domain, datacenter, pendingMigrationFlag, powerState, + primaryIpAddress,primaryBackendIpAddress, dedicatedHost + ]""" + + # No options, just print out a list of guests that can be migrated + if not (guest or migrate_all): + require_migration = vsi.list_instances(filter=pending_filter, mask=mask) + require_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter'], title="Require Migration") + + for vsi_object in require_migration: + require_table.add_row([ + vsi_object.get('id'), + vsi_object.get('hostname'), + vsi_object.get('domain'), + utils.lookup(vsi_object, 'datacenter', 'name') + ]) + + if require_migration: + env.fout(require_table) + else: + click.secho("No guests require migration at this time", fg='green') + + migrateable = vsi.list_instances(filter=dedicated_filer, mask=mask) + migrateable_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter', 'Host Name', 'Host Id'], + title="Dedicated Guests") + for vsi_object in migrateable: + migrateable_table.add_row([ + vsi_object.get('id'), + vsi_object.get('hostname'), + vsi_object.get('domain'), + utils.lookup(vsi_object, 'datacenter', 'name'), + utils.lookup(vsi_object, 'dedicatedHost', 'name'), + utils.lookup(vsi_object, 'dedicatedHost', 'id') + ]) + env.fout(migrateable_table) + # Migrate all guests with pendingMigrationFlag=True + elif migrate_all: + require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]") + for vsi_object in require_migration: + migrate(vsi, guest) + # Just migrate based on the options + else: + migrate(vsi, guest, host) + + +def migrate(vsi_manager, vsi_id, host_id=None): + """Handles actually migrating virtual guests and handling the exception""" + + try: + if host_id: + vsi_manager.migrate_dedicated(vsi_id, host_id) + else: + vsi_manager.migrate(vsi_id) + click.secho("Started a migration on {}".format(vsi_id), fg='green') + except SoftLayer.exceptions.SoftLayerAPIError as ex: + click.secho("Failed to migrate {}. {}".format(vsi_id, str(ex)), fg='red') diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index e63d7a80f..d9df28704 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1158,3 +1158,23 @@ def get_local_disks(self, instance_id): """ mask = 'mask[diskImage]' return self.guest.getBlockDevices(mask=mask, id=instance_id) + + def migrate(self, instance_id): + """Calls SoftLayer_Virtual_Guest::migrate + + Only actually does anything if the virtual server requires a migration. + Will return an exception otherwise. + + :param int instance_id: Id of the virtual server + """ + return self.guest.migrate(id=instance_id) + + def migrate_dedicated(self, instance_id, host_id): + """Calls SoftLayer_Virtual_Guest::migrate + + Only actually does anything if the virtual server requires a migration. + Will return an exception otherwise. + + :param int instance_id: Id of the virtual server + """ + return self.guest.migrateDedicatedHost(host_id, id=instance_id) From 8a22d39708cde41729bb84a2c8d4d3555b195e61 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 16 Jul 2020 17:26:24 -0500 Subject: [PATCH 0177/1385] adding unit tests --- SoftLayer/CLI/virt/migrate.py | 2 +- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 3 ++ SoftLayer/testing/__init__.py | 10 +++++ tests/CLI/modules/vs/vs_tests.py | 43 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py index 9e48fb208..5b97ea46b 100644 --- a/SoftLayer/CLI/virt/migrate.py +++ b/SoftLayer/CLI/virt/migrate.py @@ -61,7 +61,7 @@ def cli(env, guest, migrate_all, host): elif migrate_all: require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]") for vsi_object in require_migration: - migrate(vsi, guest) + migrate(vsi, vsi_object['id']) # Just migrate based on the options else: migrate(vsi, guest, host) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index c42963c8e..08742a784 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -767,3 +767,6 @@ } } ] + +migrate = True +migrateDedicatedHost = True \ No newline at end of file diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index 9c8b81c47..40a224aac 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -142,6 +142,16 @@ def assert_called_with(self, service, method, **props): raise AssertionError('%s::%s was not called with given properties: %s' % (service, method, props)) + def assert_not_called_with(self, service, method, **props): + """Used to assert that API calls were NOT called with given properties. + + Props are properties of the given transport.Request object. + """ + + if self.calls(service, method, **props): + raise AssertionError('%s::%s was called with given properties: %s' % (service, method, props)) + + def assert_no_fail(self, result): """Fail when a failing click result has an error""" if result.exception: diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a4bf28509..ee743a137 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -815,3 +815,46 @@ def test_billing(self): } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), vir_billing) + + def test_vs_migrate_list(self): + result = self.run_command(['vs', 'migrate']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + + def test_vs_migrate_guest(self): + result = self.run_command(['vs', 'migrate', '-g', '100']) + + self.assert_no_fail(result) + self.assertIn('Started a migration on', result.output) + self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + + def test_vs_migrate_all(self): + result = self.run_command(['vs', 'migrate', '-a']) + self.assert_no_fail(result) + self.assertIn('Started a migration on', result.output) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + + def test_vs_migrate_dedicated(self): + result = self.run_command(['vs', 'migrate', '-g', '100', '-h', '999']) + self.assert_no_fail(result) + self.assertIn('Started a migration on', result.output) + self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100) + + def test_vs_migrate_exception(self): + ex = SoftLayerAPIError('SoftLayer_Exception', 'PROBLEM') + mock = self.set_mock('SoftLayer_Virtual_Guest', 'migrate') + mock.side_effect = ex + result = self.run_command(['vs', 'migrate', '-g', '100']) + self.assert_no_fail(result) + self.assertIn('Failed to migrate', result.output) + self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100) From cda861a2fc1ef5420136c9d1606953c6b462bf49 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 16 Jul 2020 18:31:54 -0400 Subject: [PATCH 0178/1385] #1298 refactor get local type disks --- SoftLayer/CLI/virt/detail.py | 13 +------------ SoftLayer/CLI/virt/storage.py | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index d17e489f2..c07ef657c 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -9,6 +9,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer.CLI.virt.storage import get_local_type from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -200,15 +201,3 @@ def _get_security_table(result): return secgroup_table else: return None - - -def get_local_type(disks): - """Returns the virtual server local disk type. - - :param disks: virtual serve local disks. - """ - disk_type = 'System' - if 'SWAP' in disks['diskImage']['description']: - disk_type = 'Swap' - - return disk_type diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py index 8d1b65854..802ae32d9 100644 --- a/SoftLayer/CLI/virt/storage.py +++ b/SoftLayer/CLI/virt/storage.py @@ -67,7 +67,7 @@ def get_local_type(disks): :param disks: virtual serve local disks. """ disk_type = 'System' - if 'SWAP' in disks['diskImage']['description']: + if 'SWAP' in disks.get('diskImage', {}).get('description', []): disk_type = 'Swap' return disk_type From 3f8361834f46f17e56d6662922d3e5d466a0d175 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 17 Jul 2020 16:42:01 -0500 Subject: [PATCH 0179/1385] finishing up tests --- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 2 +- SoftLayer/testing/__init__.py | 1 - tests/CLI/modules/vs/vs_tests.py | 11 ++++++++++- tests/managers/vs/vs_tests.py | 10 ++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 08742a784..755c284ff 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -769,4 +769,4 @@ ] migrate = True -migrateDedicatedHost = True \ No newline at end of file +migrateDedicatedHost = True diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index 40a224aac..f1404b423 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -151,7 +151,6 @@ def assert_not_called_with(self, service, method, **props): if self.calls(service, method, **props): raise AssertionError('%s::%s was called with given properties: %s' % (service, method, props)) - def assert_no_fail(self, result): """Fail when a failing click result has an error""" if result.exception: diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index ee743a137..cb451fa60 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -823,9 +823,18 @@ def test_vs_migrate_list(self): self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate') self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + def test_vs_migrate_list_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getVirtualGuests') + mock.return_value = [] + result = self.run_command(['vs', 'migrate']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + self.assertIn("No guests require migration at this time", result.output) + def test_vs_migrate_guest(self): result = self.run_command(['vs', 'migrate', '-g', '100']) - self.assert_no_fail(result) self.assertIn('Started a migration on', result.output) self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 40fb3063f..1128e3b0e 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1132,3 +1132,13 @@ def test_get_local_disks_swap(self): } } ], result) + + def test_migrate(self): + result = self.vs.migrate(1234) + self.assertTrue(result) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=1234) + + def test_migrate_dedicated(self): + result = self.vs.migrate_dedicated(1234, 5555) + self.assertTrue(result) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(5555,), identifier=1234) From e331f57803d2bde0fe15d3181d5ec9bad141afeb Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 17 Jul 2020 17:09:23 -0500 Subject: [PATCH 0180/1385] documentation --- docs/cli/vs.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 49b99e09c..09539a72b 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -264,6 +264,11 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual credentials :show-nested: +.. click:: SoftLayer.CLI.virt.migrate:cli + :prog: virtual migrate + :show-nested: + +Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity ----------------- From beace276a551a79cdf3ea1b9130b0dfbd40b116f Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 22 Jul 2020 12:42:14 -0400 Subject: [PATCH 0181/1385] List hardware vs associated. Add vs list hw vs associated. --- SoftLayer/CLI/hardware/guests.py | 38 ++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/list.py | 22 ++++++- .../fixtures/SoftLayer_Hardware_Server.py | 20 +++++++ SoftLayer/fixtures/SoftLayer_Virtual_Host.py | 40 +++++++++++++ SoftLayer/managers/hardware.py | 14 ++++- SoftLayer/managers/vs.py | 7 +++ tests/CLI/modules/server_tests.py | 12 ++++ tests/managers/hardware_tests.py | 48 +++++++++++++++ tests/managers/vs/vs_tests.py | 58 +++++++++++++++++++ 10 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/hardware/guests.py create mode 100644 SoftLayer/fixtures/SoftLayer_Virtual_Host.py diff --git a/SoftLayer/CLI/hardware/guests.py b/SoftLayer/CLI/hardware/guests.py new file mode 100644 index 000000000..0bb6a0501 --- /dev/null +++ b/SoftLayer/CLI/hardware/guests.py @@ -0,0 +1,38 @@ +"""List the Hardware server associated virtual guests.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer import utils +from SoftLayer.CLI import environment, formatting, exceptions +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """List the Hardware server associated virtual guests.""" + + mgr = SoftLayer.HardwareManager(env.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'hardware') + hw_guests = mgr.get_hardware_guests(hw_id) + + if not hw_guests: + raise exceptions.CLIAbort("The hardware server does not has associated virtual guests.") + + table = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', 'powerState']) + table.sortby = 'hostname' + for guest in hw_guests: + table.add_row([ + guest['id'], + guest['hostname'], + '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), + guest['maxMemory'], + utils.clean_time(guest['createDate']), + guest['status']['keyName'], + guest['powerState']['keyName'] + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 049b43c3b..a84de0157 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -236,6 +236,7 @@ ('hardware:detail', 'SoftLayer.CLI.hardware.detail:cli'), ('hardware:billing', 'SoftLayer.CLI.hardware.billing:cli'), ('hardware:edit', 'SoftLayer.CLI.hardware.edit:cli'), + ('hardware:guests', 'SoftLayer.CLI.hardware.guests:cli'), ('hardware:list', 'SoftLayer.CLI.hardware.list:cli'), ('hardware:power-cycle', 'SoftLayer.CLI.hardware.power:power_cycle'), ('hardware:power-off', 'SoftLayer.CLI.hardware.power:power_off'), diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 6bf9e6bb6..3a24ffe9d 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -4,12 +4,12 @@ import click import SoftLayer +from SoftLayer import utils from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers - # pylint: disable=unnecessary-lambda COLUMNS = [ @@ -93,3 +93,23 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, for value in columns.row(guest)]) env.fout(table) + + hardware_guests = vsi.get_hardware_guests() + for hardware in hardware_guests: + if 'virtualHost' in hardware and hardware['virtualHost']['guests']: + table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', + 'powerState'], title="Hardware(id = {hardwareId}) guests " + "associated".format(hardwareId=hardware['id']) + ) + table_hardware_guest.sortby = 'hostname' + for guest in hardware['virtualHost']['guests']: + table_hardware_guest.add_row([ + guest['id'], + guest['hostname'], + '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), + guest['maxMemory'], + utils.clean_time(guest['createDate']), + guest['status']['keyName'], + guest['powerState']['keyName'] + ]) + env.fout(table_hardware_guest) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index e90288753..5c26d20da 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -242,3 +242,23 @@ } } ] + +getVirtualHost = { + "accountId": 11111, + "createDate": "2018-10-08T10:54:48-06:00", + "description": "host16.vmware.chechu.com", + "hardwareId": 22222, + "id": 33333, + "name": "host16.vmware.chechu.com", + "uuid": "00000000-0000-0000-0000-0cc11111", + "hardware": { + "accountId": 11111, + "domain": "chechu.com", + "hostname": "host16.vmware", + "id": 22222, + "hardwareStatus": { + "id": 5, + "status": "ACTIVE" + } + } +} diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Host.py b/SoftLayer/fixtures/SoftLayer_Virtual_Host.py new file mode 100644 index 000000000..c5b20a34b --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Host.py @@ -0,0 +1,40 @@ +getGuests = [ + { + "accountId": 11111, + "createDate": "2019-09-05T17:03:42-06:00", + "fullyQualifiedDomainName": "NSX-T Manager", + "hostname": "NSX-T Manager", + "id": 22222, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "startCpus": 16, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }, + { + "accountId": 11111, + "createDate": "2019-09-23T06:00:53-06:00", + "hostname": "NSX-T Manager2", + "id": 33333, + "maxCpu": 12, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "startCpus": 12, + "statusId": 1001, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 956d33e3a..2214ee8c2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,11 +9,11 @@ import socket import time +from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager -from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -728,6 +728,18 @@ def get_hard_drives(self, instance_id): """ return self.hardware.getHardDrives(id=instance_id) + def get_hardware_guests(self, instance_id): + """Returns the hardware server guests. + + :param int instance_id: Id of the hardware server. + """ + mask = "mask[id]" + virtual_host = self.hardware.getVirtualHost(mask=mask, id=instance_id) + if virtual_host: + return self.client.call('SoftLayer_Virtual_Host', 'getGuests', mask='mask[powerState]', + id=virtual_host['id']) + return virtual_host + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index e63d7a80f..9426fb53f 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1158,3 +1158,10 @@ def get_local_disks(self, instance_id): """ mask = 'mask[diskImage]' return self.guest.getBlockDevices(mask=mask, id=instance_id) + + def get_hardware_guests(self): + """Returns the hardware virtual server associated. + """ + object_filter = {"hardware": {"networkGatewayMemberFlag": {"operation": 0}}} + mask = "mask[networkGatewayMemberFlag,virtualHost[guests[powerState]]]" + return self.client.call('SoftLayer_Account', 'getHardware', mask=mask, filter=object_filter) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f11d9d0a6..4ef1a7354 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -852,3 +852,15 @@ def test_create_hw_no_confirm(self, confirm_mock): '--network=TEST_NETWORK', '--os=UBUNTU_12_64']) self.assertEqual(result.exit_code, 2) + + def test_get_hardware_guests(self): + result = self.run_command(['hw', 'guests', '123456']) + self.assert_no_fail(result) + + def test_hardware_guests_empty(self): + mock = self.set_mock('SoftLayer_Virtual_Host', 'getGuests') + mock.return_value = None + + result = self.run_command(['hw', 'guests', '123456']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index a61aeece0..a7824dd61 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -626,6 +626,54 @@ def test_get_hard_drive_empty(self): self.assertEqual([], result) + def test_get_hardware_guests_empty_virtualHost(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getVirtualHost') + mock.return_value = None + + result = self.hardware.get_hardware_guests(1234) + + self.assertEqual(None, result) + + def test_get_hardware_guests(self): + mock = self.set_mock('SoftLayer_Virtual_Host', 'getGuests') + mock.return_value = [ + { + "accountId": 11111, + "hostname": "NSX-T Manager", + "id": 22222, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }] + + result = self.hardware.get_hardware_guests(1234) + + self.assertEqual([ + { + "accountId": 11111, + "hostname": "NSX-T Manager", + "id": 22222, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }], result) + class HardwareHelperTests(testing.TestCase): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 40fb3063f..fcee57be5 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1132,3 +1132,61 @@ def test_get_local_disks_swap(self): } } ], result) + + def test_get_hardware_guests(self): + mock = self.set_mock('SoftLayer_Account', 'getHardware') + mock.return_value = [{ + "accountId": 11111, + "domain": "vmware.chechu.com", + "hostname": "host14", + "id": 22222, + "virtualHost": { + "accountId": 11111, + "id": 33333, + "name": "host14.vmware.chechu.com", + "guests": [ + { + "accountId": 11111, + "hostname": "NSX-T Manager", + "id": 44444, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }]}}] + + result = self.vs.get_hardware_guests() + + self.assertEqual([{ + "accountId": 11111, + "domain": "vmware.chechu.com", + "hostname": "host14", + "id": 22222, + "virtualHost": { + "accountId": 11111, + "id": 33333, + "name": "host14.vmware.chechu.com", + "guests": [ + { + "accountId": 11111, + "hostname": "NSX-T Manager", + "id": 44444, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }]}}], result) From a3d6ef5a29c33ba3e2d537898d7b29363266201a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 22 Jul 2020 12:50:26 -0400 Subject: [PATCH 0182/1385] Add hardware guests documentation. --- docs/cli/hardware.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 5ca325cb7..f7691e700 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -115,3 +115,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.storage:cli :prog: hardware storage :show-nested: + +.. click:: SoftLayer.CLI.hardware.guests:cli + :prog: hardware guests + :show-nested: From 69c90adbf8db457a3888769662f14af741659748 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 22 Jul 2020 13:12:36 -0400 Subject: [PATCH 0183/1385] Fi tox analysis. --- SoftLayer/CLI/hardware/guests.py | 6 ++++-- SoftLayer/CLI/virt/list.py | 2 +- SoftLayer/managers/hardware.py | 2 +- SoftLayer/managers/vs.py | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/guests.py b/SoftLayer/CLI/hardware/guests.py index 0bb6a0501..418cf9d6e 100644 --- a/SoftLayer/CLI/hardware/guests.py +++ b/SoftLayer/CLI/hardware/guests.py @@ -4,9 +4,11 @@ import click import SoftLayer -from SoftLayer import utils -from SoftLayer.CLI import environment, formatting, exceptions +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer import utils @click.command() diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 3a24ffe9d..925f33d2a 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -4,11 +4,11 @@ import click import SoftLayer -from SoftLayer import utils from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer import utils # pylint: disable=unnecessary-lambda diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 2214ee8c2..1c2a097f1 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,11 +9,11 @@ import socket import time -from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager +from SoftLayer import utils LOGGER = logging.getLogger(__name__) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 9426fb53f..6c4d67786 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1160,7 +1160,9 @@ def get_local_disks(self, instance_id): return self.guest.getBlockDevices(mask=mask, id=instance_id) def get_hardware_guests(self): - """Returns the hardware virtual server associated. + """Returns the hardware server vs associated. + + :return SoftLayer_Hardware[]. """ object_filter = {"hardware": {"networkGatewayMemberFlag": {"operation": 0}}} mask = "mask[networkGatewayMemberFlag,virtualHost[guests[powerState]]]" From fb59874ea7eee7fb30411d56d80066abdbb44f31 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 22 Jul 2020 15:30:07 -0500 Subject: [PATCH 0184/1385] #875 added option to reload bare metal servers with LVM enabled --- SoftLayer/CLI/hardware/reload.py | 13 ++++++------- SoftLayer/managers/hardware.py | 12 +++++++----- tests/CLI/modules/server_tests.py | 10 +++++++--- tests/managers/hardware_tests.py | 12 +++++++----- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/hardware/reload.py b/SoftLayer/CLI/hardware/reload.py index 11286d0d9..798e01968 100644 --- a/SoftLayer/CLI/hardware/reload.py +++ b/SoftLayer/CLI/hardware/reload.py @@ -13,17 +13,16 @@ @click.command() @click.argument('identifier') @click.option('--postinstall', '-i', - help=("Post-install script to download " - "(Only HTTPS executes, HTTP leaves file in /root")) + help=("Post-install script to download (Only HTTPS executes, HTTP leaves file in /root")) @helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") +@click.option('--lvm', '-l', is_flag=True, default=False, show_default=True, + help="A flag indicating that the provision should use LVM for all logical drives.") @environment.pass_env -def cli(env, identifier, postinstall, key): +def cli(env, identifier, postinstall, key, lvm): """Reload operating system on a server.""" hardware = SoftLayer.HardwareManager(env.client) - hardware_id = helpers.resolve_id(hardware.resolve_ids, - identifier, - 'hardware') + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') key_list = [] if key: for single_key in key: @@ -33,4 +32,4 @@ def cli(env, identifier, postinstall, key): if not (env.skip_confirmations or formatting.no_going_back(hardware_id)): raise exceptions.CLIAbort('Aborted') - hardware.reload(hardware_id, postinstall, key_list) + hardware.reload(hardware_id, postinstall, key_list, lvm) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 956d33e3a..2db54ed25 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -269,13 +269,14 @@ def get_hardware(self, hardware_id, **kwargs): return self.hardware.getObject(id=hardware_id, **kwargs) - def reload(self, hardware_id, post_uri=None, ssh_keys=None): + def reload(self, hardware_id, post_uri=None, ssh_keys=None, lvm=False): """Perform an OS reload of a server with its current configuration. + https://sldn.softlayer.com/reference/datatypes/SoftLayer_Container_Hardware_Server_Configuration/ :param integer hardware_id: the instance ID to reload - :param string post_uri: The URI of the post-install script to run - after reload + :param string post_uri: The URI of the post-install script to run after reload :param list ssh_keys: The SSH keys to add to the root user + :param bool lvm: A flag indicating that the provision should use LVM for all logical drives. """ config = {} @@ -285,9 +286,10 @@ def reload(self, hardware_id, post_uri=None, ssh_keys=None): if ssh_keys: config['sshKeyIds'] = list(ssh_keys) + if lvm: + config['lvmFlag'] = lvm - return self.hardware.reloadOperatingSystem('FORCE', config, - id=hardware_id) + return self.hardware.reloadOperatingSystem('FORCE', config, id=hardware_id) def rescue(self, hardware_id): """Reboot a server into the a recsue kernel. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f11d9d0a6..1f539c84e 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -219,11 +219,15 @@ def test_server_reload(self, reload_mock, ngb_mock): ngb_mock.return_value = False # Check the positive case - result = self.run_command(['--really', 'server', 'reload', '12345', - '--key=4567']) + result = self.run_command(['--really', 'server', 'reload', '12345', '--key=4567']) self.assert_no_fail(result) - reload_mock.assert_called_with(12345, None, [4567]) + reload_mock.assert_called_with(12345, None, [4567], False) + + # LVM switch + result = self.run_command(['--really', 'server', 'reload', '12345', '--lvm']) + self.assert_no_fail(result) + reload_mock.assert_called_with(12345, None, [], True) # Now check to make sure we properly call CLIAbort in the negative case result = self.run_command(['server', 'reload', '12345']) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index a61aeece0..d7516aec0 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -107,13 +107,15 @@ def test_reload(self): result = self.hardware.reload(1, post_uri=post_uri, ssh_keys=[1701]) self.assertEqual(result, 'OK') - self.assert_called_with('SoftLayer_Hardware_Server', - 'reloadOperatingSystem', - args=('FORCE', - {'customProvisionScriptUri': post_uri, - 'sshKeyIds': [1701]}), + self.assert_called_with('SoftLayer_Hardware_Server', 'reloadOperatingSystem', + args=('FORCE', {'customProvisionScriptUri': post_uri, 'sshKeyIds': [1701]}), identifier=1) + result = self.hardware.reload(100, lvm=True) + self.assertEqual(result, 'OK') + self.assert_called_with('SoftLayer_Hardware_Server', 'reloadOperatingSystem', + args=('FORCE', {'lvmFlag': True}), identifier=100) + def test_get_create_options(self): options = self.hardware.get_create_options() From 3863aaf5270ff8d7848371c7a00a3ddd9a620b8d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 23 Jul 2020 10:51:07 -0400 Subject: [PATCH 0185/1385] Refactored Code. --- SoftLayer/CLI/hardware/guests.py | 6 +++--- SoftLayer/CLI/virt/list.py | 7 +++---- SoftLayer/managers/vs.py | 6 +++--- tests/managers/hardware_tests.py | 18 +----------------- tests/managers/vs/vs_tests.py | 27 +-------------------------- 5 files changed, 11 insertions(+), 53 deletions(-) diff --git a/SoftLayer/CLI/hardware/guests.py b/SoftLayer/CLI/hardware/guests.py index 418cf9d6e..238897e84 100644 --- a/SoftLayer/CLI/hardware/guests.py +++ b/SoftLayer/CLI/hardware/guests.py @@ -1,4 +1,4 @@ -"""List the Hardware server associated virtual guests.""" +"""Lists the Virtual Guests running on this server.""" # :license: MIT, see LICENSE for more details. import click @@ -15,14 +15,14 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """List the Hardware server associated virtual guests.""" + """Lists the Virtual Guests running on this server.""" mgr = SoftLayer.HardwareManager(env.client) hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'hardware') hw_guests = mgr.get_hardware_guests(hw_id) if not hw_guests: - raise exceptions.CLIAbort("The hardware server does not has associated virtual guests.") + raise exceptions.CLIAbort("No Virtual Guests found.") table = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', 'powerState']) table.sortby = 'hostname' diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 925f33d2a..1a3c4d545 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -96,11 +96,10 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, hardware_guests = vsi.get_hardware_guests() for hardware in hardware_guests: - if 'virtualHost' in hardware and hardware['virtualHost']['guests']: + if hardware['virtualHost']['guests']: + title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hardware['id']) table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', - 'powerState'], title="Hardware(id = {hardwareId}) guests " - "associated".format(hardwareId=hardware['id']) - ) + 'powerState'], title=title) table_hardware_guest.sortby = 'hostname' for guest in hardware['virtualHost']['guests']: table_hardware_guest.add_row([ diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6c4d67786..a322c78a1 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1160,10 +1160,10 @@ def get_local_disks(self, instance_id): return self.guest.getBlockDevices(mask=mask, id=instance_id) def get_hardware_guests(self): - """Returns the hardware server vs associated. + """Returns all virtualHost capable hardware objects and their guests. :return SoftLayer_Hardware[]. """ - object_filter = {"hardware": {"networkGatewayMemberFlag": {"operation": 0}}} - mask = "mask[networkGatewayMemberFlag,virtualHost[guests[powerState]]]" + object_filter = {"hardware": {"virtualHost": {"id": {"operation": "not null"}}}} + mask = "mask[virtualHost[guests[powerState]]]" return self.client.call('SoftLayer_Account', 'getHardware', mask=mask, filter=object_filter) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index a7824dd61..e98c57492 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -656,23 +656,7 @@ def test_get_hardware_guests(self): result = self.hardware.get_hardware_guests(1234) - self.assertEqual([ - { - "accountId": 11111, - "hostname": "NSX-T Manager", - "id": 22222, - "maxCpu": 16, - "maxCpuUnits": "CORE", - "maxMemory": 49152, - "powerState": { - "keyName": "RUNNING", - "name": "Running" - }, - "status": { - "keyName": "ACTIVE", - "name": "Active" - } - }], result) + self.assertEqual("NSX-T Manager", result[0]['hostname']) class HardwareHelperTests(testing.TestCase): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index fcee57be5..34e07dd06 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1164,29 +1164,4 @@ def test_get_hardware_guests(self): result = self.vs.get_hardware_guests() - self.assertEqual([{ - "accountId": 11111, - "domain": "vmware.chechu.com", - "hostname": "host14", - "id": 22222, - "virtualHost": { - "accountId": 11111, - "id": 33333, - "name": "host14.vmware.chechu.com", - "guests": [ - { - "accountId": 11111, - "hostname": "NSX-T Manager", - "id": 44444, - "maxCpu": 16, - "maxCpuUnits": "CORE", - "maxMemory": 49152, - "powerState": { - "keyName": "RUNNING", - "name": "Running" - }, - "status": { - "keyName": "ACTIVE", - "name": "Active" - } - }]}}], result) + self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname']) From d1c59f925179f8b3109802eda16103ee41eead7e Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 23 Jul 2020 12:11:16 -0400 Subject: [PATCH 0186/1385] Fixed unit test issues. --- SoftLayer/fixtures/SoftLayer_Account.py | 44 +++++++++++++++++++++++-- tests/CLI/modules/vs/vs_tests.py | 13 -------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 95692ef00..dcb6d32f0 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -146,6 +146,28 @@ 'id': 6660 } }, + "virtualHost": { + "accountId": 11111, + "id": 22222, + "name": "vmware.chechu.com", + "guests": [ + { + "accountId": 11111, + "createDate": "2019-09-05T17:03:42-06:00", + "hostname": "NSX-T Manager", + "id": 33333, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }]} }, { 'id': 1001, 'metricTrackingObject': {'id': 4}, @@ -190,7 +212,13 @@ 'vlanNumber': 3672, 'id': 19082 }, - ] + ], + "virtualHost": { + "accountId": 11111, + "id": 22222, + "name": "host14.vmware.chechu.com", + "guests": [] + } }, { 'id': 1002, 'metricTrackingObject': {'id': 5}, @@ -234,9 +262,21 @@ 'vlanNumber': 3672, 'id': 19082 }, - ] + ], + "virtualHost": { + "accountId": 11111, + "id": 22222, + "name": "host14.vmware.chechu.com", + "guests": [] + } }, { 'id': 1003, + "virtualHost": { + "accountId": 11111, + "id": 22222, + "name": "host14.vmware.chechu.com", + "guests": [] + } }] getDomains = [{'name': 'example.com', 'id': 12345, diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a4bf28509..783b52743 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -143,19 +143,6 @@ def test_list_vs(self): result = self.run_command(['vs', 'list', '--tag=tag']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - [{'datacenter': 'TEST00', - 'primary_ip': '172.16.240.2', - 'hostname': 'vs-test1', - 'action': None, - 'id': 100, - 'backend_ip': '10.45.19.37'}, - {'datacenter': 'TEST00', - 'primary_ip': '172.16.240.7', - 'hostname': 'vs-test2', - 'action': None, - 'id': 104, - 'backend_ip': '10.45.19.35'}]) @mock.patch('SoftLayer.utils.lookup') def test_detail_vs_empty_billing(self, mock_lookup): From d0ebf61672f3f32230535a599b407a021e958c99 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 24 Jul 2020 09:20:04 -0400 Subject: [PATCH 0187/1385] vs upgrade disk and add new disk --- SoftLayer/CLI/virt/upgrade.py | 12 +++-- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 46 ++++++++++++++++--- SoftLayer/managers/vs.py | 43 ++++++++++++++--- tests/CLI/modules/vs/vs_tests.py | 24 ++++++++++ 4 files changed, 108 insertions(+), 17 deletions(-) diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index 463fc077e..23e521863 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -2,6 +2,7 @@ # :license: MIT, see LICENSE for more details. import click +import json import SoftLayer from SoftLayer.CLI import environment @@ -20,15 +21,17 @@ help="CPU core will be on a dedicated host server.") @click.option('--memory', type=virt.MEM_TYPE, help="Memory in megabytes") @click.option('--network', type=click.INT, help="Network port speed in Mbps") +@click.option('--add', type=click.INT, required=False, help="add Hard disk in GB") +@click.option('--disk', nargs=1, help="update the number and capacity in GB Hard disk, E.G {'number':2,'capacity':100}") @click.option('--flavor', type=click.STRING, help="Flavor keyName\nDo not use --memory, --cpu or --private, if you are using flavors") @environment.pass_env -def cli(env, identifier, cpu, private, memory, network, flavor): +def cli(env, identifier, cpu, private, memory, network, flavor, disk, add): """Upgrade a virtual server.""" vsi = SoftLayer.VSManager(env.client) - if not any([cpu, memory, network, flavor]): + if not any([cpu, memory, network, flavor, disk, add]): raise exceptions.ArgumentError("Must provide [--cpu], [--memory], [--network], or [--flavor] to upgrade") if private and not cpu: @@ -40,6 +43,9 @@ def cli(env, identifier, cpu, private, memory, network, flavor): if memory: memory = int(memory / 1024) + if disk is not None: + disk = json.loads(disk) - if not vsi.upgrade(vs_id, cpus=cpu, memory=memory, nic_speed=network, public=not private, preset=flavor): + if not vsi.upgrade(vs_id, cpus=cpu, memory=memory, nic_speed=network, public=not private, preset=flavor, + disk=disk, add=add): raise exceptions.CLIAbort('VS Upgrade Failed') diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index c42963c8e..47eebe424 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -8,11 +8,16 @@ 'id': 6327, 'nextInvoiceTotalRecurringAmount': 1.54, 'children': [ - {'nextInvoiceTotalRecurringAmount': 1}, - {'nextInvoiceTotalRecurringAmount': 1}, - {'nextInvoiceTotalRecurringAmount': 1}, - {'nextInvoiceTotalRecurringAmount': 1}, - {'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'port_speed', + 'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'guest_core', + 'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'ram', + 'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'guest_core', + 'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'guest_disk1', + 'nextInvoiceTotalRecurringAmount': 1}, ], 'package': { "id": 835, @@ -622,7 +627,35 @@ 'capacity': '2', 'description': 'RAM', } - }, + }, { + "id": 2255, + "categories": [ + { + "categoryCode": "guest_disk1", + "id": 82, + "name": "Second Disk" + }, + { + "categoryCode": "guest_disk2", + "id": 92, + "name": "Third Disk" + }, + { + "categoryCode": "guest_disk3", + "id": 93, + "name": "Fourth Disk" + }, + { + "categoryCode": "guest_disk4", + "id": 116, + "name": "Fifth Disk" + } + ], + "item": { + "capacity": "10", + "description": "10 GB (SAN)" + } + } ] DEDICATED_GET_UPGRADE_ITEM_PRICES = [ @@ -641,7 +674,6 @@ getMetricTrackingObjectId = 1000 - getBandwidthAllotmentDetail = { 'allocationId': 25465663, 'bandwidthAllotmentId': 138442, diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index e63d7a80f..23f51b138 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -18,6 +18,7 @@ LOGGER = logging.getLogger(__name__) + # pylint: disable=no-self-use,too-many-lines @@ -818,7 +819,8 @@ def capture(self, instance_id, name, additional_disks=False, notes=None): return self.guest.createArchiveTransaction( name, disks_to_capture, notes, id=instance_id) - def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True, preset=None): + def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True, preset=None, + disk=None, add=None): """Upgrades a VS instance. Example:: @@ -851,7 +853,10 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr if memory is not None and preset is not None: raise ValueError("Do not use memory, private or cpu if you are using flavors") data['memory'] = memory - + if disk is not None: + data['disk'] = disk.get('capacity') + elif add is not None: + data['disk'] = add maintenance_window = datetime.datetime.now(utils.UTC()) order = { 'complexType': 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade', @@ -874,9 +879,30 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr raise exceptions.SoftLayerError( "Unable to find %s option with value %s" % (option, value)) - prices.append({'id': price_id}) - order['prices'] = prices - + if disk is not None: + category = {'categories': [{ + 'categoryCode': 'guest_disk' + str(disk.get('number')), + 'complexType': "SoftLayer_Product_Item_Category" + }], 'complexType': 'SoftLayer_Product_Item_Price'} + prices.append(category) + prices[0]['id'] = price_id + elif add: + vsi_disk = self.get_instance(instance_id) + disk_number = 0 + for item in vsi_disk.get('billingItem').get('children'): + if item.get('categoryCode').__contains__('guest_disk'): + if disk_number < int("".join(filter(str.isdigit, item.get('categoryCode')))): + disk_number = int("".join(filter(str.isdigit, item.get('categoryCode')))) + category = {'categories': [{ + 'categoryCode': 'guest_disk' + str(disk_number + 1), + 'complexType': "SoftLayer_Product_Item_Category" + }], 'complexType': 'SoftLayer_Product_Item_Price'} + prices.append(category) + prices[0]['id'] = price_id + else: + prices.append({'id': price_id}) + + order['prices'] = prices if preset is not None: vs_object = self.get_instance(instance_id)['billingItem']['package'] order['presetId'] = self.ordering_manager.get_preset_by_key(vs_object['keyName'], preset)['id'] @@ -994,7 +1020,8 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public option_category = { 'memory': 'ram', 'cpus': 'guest_core', - 'nic_speed': 'port_speed' + 'nic_speed': 'port_speed', + 'disk': 'guest_disk' } category_code = option_category.get(option) for price in upgrade_prices: @@ -1006,7 +1033,7 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public or product.get('units') == 'DEDICATED_CORE') for category in price.get('categories'): - if not (category.get('categoryCode') == category_code + if not (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) and str(product.get('capacity')) == str(value)): continue @@ -1020,6 +1047,8 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public elif option == 'nic_speed': if 'Public' in product.get('description'): return price.get('id') + elif option == 'disk': + return price.get('id') else: return price.get('id') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a4bf28509..a90811fe8 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -570,6 +570,19 @@ def test_upgrade(self, confirm_mock): self.assertIn({'id': 1122}, order_container['prices']) self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vs', 'upgrade', '100', '--flavor=M1_64X512X100', + '--disk={"number":1,"capacity":10}']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(799, order_container['presetId']) + self.assertIn({'id': 100}, order_container['virtualGuests']) + self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_with_flavor(self, confirm_mock): confirm_mock.return_value = True @@ -582,6 +595,17 @@ def test_upgrade_with_flavor(self, confirm_mock): self.assertIn({'id': 100}, order_container['virtualGuests']) self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_with_add_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vs', 'upgrade', '100', '--add=10']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertIn({'id': 100}, order_container['virtualGuests']) + self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_with_cpu_memory_and_flavor(self, confirm_mock): confirm_mock.return_value = True From bb7b220b6f3fcc6c97f53e3c761166960a43ae51 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 24 Jul 2020 11:14:16 -0400 Subject: [PATCH 0188/1385] fix tox tool --- SoftLayer/managers/vs.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 23f51b138..ffc67db7d 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1033,7 +1033,12 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public or product.get('units') == 'DEDICATED_CORE') for category in price.get('categories'): - if not (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) + if option == 'disk': + if not (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) + and str(product.get('capacity')) == str(value)): + return price.get('id') + + if not (category.get('categoryCode') == category_code and str(product.get('capacity')) == str(value)): continue From f1abcfc226095be4c8949c09b2c9c8897626ed37 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 24 Jul 2020 11:57:41 -0400 Subject: [PATCH 0189/1385] fix tox tool --- SoftLayer/CLI/virt/upgrade.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index 23e521863..ef4477d86 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -1,9 +1,10 @@ """Upgrade a virtual server.""" # :license: MIT, see LICENSE for more details. -import click import json +import click + import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions From 81bd07df42060b1d2db3848e091878b87a441305 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 24 Jul 2020 16:57:24 -0400 Subject: [PATCH 0190/1385] fix the empty lines --- SoftLayer/CLI/order/preset_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/order/preset_list.py b/SoftLayer/CLI/order/preset_list.py index 7397f9428..412d95ee7 100644 --- a/SoftLayer/CLI/order/preset_list.py +++ b/SoftLayer/CLI/order/preset_list.py @@ -43,8 +43,8 @@ def cli(env, package_keyname, keyword): for preset in presets: table.add_row([ - preset['name'], - preset['keyName'], - preset['description'] + str(preset['name']).strip(), + str(preset['keyName']).strip(), + str(preset['description']).strip() ]) env.fout(table) From 730ed0fc48b8c0f2257f3aab0bfc0efe1c58e27b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 24 Jul 2020 16:59:16 -0500 Subject: [PATCH 0191/1385] fixed a typo --- SoftLayer/CLI/virt/migrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py index 5b97ea46b..55ba251ec 100644 --- a/SoftLayer/CLI/virt/migrate.py +++ b/SoftLayer/CLI/virt/migrate.py @@ -20,7 +20,7 @@ def cli(env, guest, migrate_all, host): vsi = SoftLayer.VSManager(env.client) pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}} - dedicated_filer = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} + dedicated_filter = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} mask = """mask[ id, hostname, domain, datacenter, pendingMigrationFlag, powerState, primaryIpAddress,primaryBackendIpAddress, dedicatedHost @@ -44,7 +44,7 @@ def cli(env, guest, migrate_all, host): else: click.secho("No guests require migration at this time", fg='green') - migrateable = vsi.list_instances(filter=dedicated_filer, mask=mask) + migrateable = vsi.list_instances(filter=dedicated_filter, mask=mask) migrateable_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter', 'Host Name', 'Host Id'], title="Dedicated Guests") for vsi_object in migrateable: From 2e15494c935346bd85258b8b4a36f9ba340d0e8e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 24 Jul 2020 17:58:16 -0500 Subject: [PATCH 0192/1385] tox fix --- tests/managers/vs/vs_tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index fa2e7464e..a9f256c80 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1133,7 +1133,6 @@ def test_get_local_disks_swap(self): } ], result) - def test_migrate(self): result = self.vs.migrate(1234) self.assertTrue(result) @@ -1175,4 +1174,3 @@ def test_get_hardware_guests(self): result = self.vs.get_hardware_guests() self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname']) - From 47f236c61000ce43b1581ad8d133136cb9e77e02 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 24 Jul 2020 18:18:34 -0500 Subject: [PATCH 0193/1385] added message for empty migrations --- SoftLayer/CLI/virt/migrate.py | 2 ++ tests/CLI/modules/vs/vs_tests.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py index 55ba251ec..d06673365 100644 --- a/SoftLayer/CLI/virt/migrate.py +++ b/SoftLayer/CLI/virt/migrate.py @@ -60,6 +60,8 @@ def cli(env, guest, migrate_all, host): # Migrate all guests with pendingMigrationFlag=True elif migrate_all: require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]") + if not require_migration: + click.secho("No guests require migration at this time", fg='green') for vsi_object in require_migration: migrate(vsi, vsi_object['id']) # Just migrate based on the options diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 60bc8183c..8278ab23f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -836,6 +836,16 @@ def test_vs_migrate_all(self): self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104) self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + def test_vs_migrate_all_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getVirtualGuests') + mock.return_value = [] + result = self.run_command(['vs', 'migrate', '-a']) + self.assert_no_fail(result) + self.assertIn('No guests require migration at this time', result.output) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + def test_vs_migrate_dedicated(self): result = self.run_command(['vs', 'migrate', '-g', '100', '-h', '999']) self.assert_no_fail(result) From 60fbbbad396f33dad52fd48d72b9ac98d09179b7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Sun, 26 Jul 2020 12:30:57 -0500 Subject: [PATCH 0194/1385] fixed vs_tests --- tests/CLI/modules/vs/vs_tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 8278ab23f..94b913923 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -842,9 +842,6 @@ def test_vs_migrate_all_empty(self): result = self.run_command(['vs', 'migrate', '-a']) self.assert_no_fail(result) self.assertIn('No guests require migration at this time', result.output) - self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) - self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104) - self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') def test_vs_migrate_dedicated(self): result = self.run_command(['vs', 'migrate', '-g', '100', '-h', '999']) From ded403848217ff778006c15be6f92fdc5033a62c Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 16:42:22 -0400 Subject: [PATCH 0195/1385] #1302 fix lots of whitespace slcli vs create-options --- SoftLayer/CLI/virt/create_options.py | 189 ++++++++++++++++----------- tests/CLI/modules/vs/vs_tests.py | 93 +++++++------ 2 files changed, 170 insertions(+), 112 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 601c0f3ac..7e671d028 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -18,56 +18,121 @@ def cli(env): """Virtual server order options.""" vsi = SoftLayer.VSManager(env.client) - result = vsi.get_create_options() + options = vsi.get_create_options() - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' + tables = [ + _get_datacenter_table(options), + _get_flavors_table(options), + _get_cpu_table(options), + _get_memory_table(options), + _get_os_table(options), + _get_disk_table(options), + _get_network_table(options), + ] - # Datacenters + env.fout(formatting.listing(tables, separator='\n')) + + +def _get_datacenter_table(create_options): datacenters = [dc['template']['datacenter']['name'] - for dc in result['datacenters']] + for dc in create_options['datacenters']] + datacenters = sorted(datacenters) - table.add_row(['datacenter', - formatting.listing(datacenters, separator='\n')]) + dc_table = formatting.Table(['datacenter'], title='Datacenters') + dc_table.sortby = 'datacenter' + dc_table.align = 'l' + for datacenter in datacenters: + dc_table.add_row([datacenter]) + return dc_table - _add_flavors_to_table(result, table) - # CPUs - standard_cpus = [int(x['template']['startCpus']) for x in result['processors'] +def _get_flavors_table(create_options): + flavor_table = formatting.Table(['flavor', 'value'], title='Flavors') + flavor_table.sortby = 'flavor' + flavor_table.align = 'l' + grouping = { + 'balanced': {'key_starts_with': 'B1', 'flavors': []}, + 'balanced local - hdd': {'key_starts_with': 'BL1', 'flavors': []}, + 'balanced local - ssd': {'key_starts_with': 'BL2', 'flavors': []}, + 'compute': {'key_starts_with': 'C1', 'flavors': []}, + 'memory': {'key_starts_with': 'M1', 'flavors': []}, + 'GPU': {'key_starts_with': 'AC', 'flavors': []}, + 'transient': {'transient': True, 'flavors': []}, + } + + if create_options.get('flavors', None) is None: + return + + for flavor_option in create_options['flavors']: + flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') + + for name, group in grouping.items(): + if utils.lookup(flavor_option, 'template', 'transientGuestFlag') is True: + if utils.lookup(group, 'transient') is True: + group['flavors'].append(flavor_key_name) + break + + elif utils.lookup(group, 'key_starts_with') is not None \ + and flavor_key_name.startswith(group['key_starts_with']): + group['flavors'].append(flavor_key_name) + break + + for name, group in grouping.items(): + if len(group['flavors']) > 0: + flavor_table.add_row(['{}'.format(name), + formatting.listing(group['flavors'], + separator='\n')]) + return flavor_table + + +def _get_cpu_table(create_options): + cpu_table = formatting.Table(['cpu', 'value'], title='CPUs') + cpu_table.sortby = 'cpu' + cpu_table.align = 'l' + standard_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] if not x['template'].get('dedicatedAccountHostOnlyFlag', False) and not x['template'].get('dedicatedHost', None)] - ded_cpus = [int(x['template']['startCpus']) for x in result['processors'] + ded_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] if x['template'].get('dedicatedAccountHostOnlyFlag', False)] - ded_host_cpus = [int(x['template']['startCpus']) for x in result['processors'] + ded_host_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] if x['template'].get('dedicatedHost', None)] standard_cpus = sorted(standard_cpus) - table.add_row(['cpus (standard)', formatting.listing(standard_cpus, separator=',')]) + cpu_table.add_row(['standard', formatting.listing(standard_cpus, separator=',')]) ded_cpus = sorted(ded_cpus) - table.add_row(['cpus (dedicated)', formatting.listing(ded_cpus, separator=',')]) + cpu_table.add_row(['dedicated', formatting.listing(ded_cpus, separator=',')]) ded_host_cpus = sorted(ded_host_cpus) - table.add_row(['cpus (dedicated host)', formatting.listing(ded_host_cpus, separator=',')]) + cpu_table.add_row(['dedicated host', formatting.listing(ded_host_cpus, separator=',')]) + return cpu_table + - # Memory - memory = [int(m['template']['maxMemory']) for m in result['memory'] +def _get_memory_table(create_options): + memory_table = formatting.Table(['memory', 'value'], title='Memories') + memory_table.sortby = 'memory' + memory_table.align = 'l' + memory = [int(m['template']['maxMemory']) for m in create_options['memory'] if not m['itemPrice'].get('dedicatedHostInstanceFlag', False)] - ded_host_memory = [int(m['template']['maxMemory']) for m in result['memory'] + ded_host_memory = [int(m['template']['maxMemory']) for m in create_options['memory'] if m['itemPrice'].get('dedicatedHostInstanceFlag', False)] memory = sorted(memory) - table.add_row(['memory', - formatting.listing(memory, separator=',')]) + memory_table.add_row(['standard', + formatting.listing(memory, separator=',')]) ded_host_memory = sorted(ded_host_memory) - table.add_row(['memory (dedicated host)', - formatting.listing(ded_host_memory, separator=',')]) + memory_table.add_row(['dedicated host', + formatting.listing(ded_host_memory, separator=',')]) + return memory_table - # Operating Systems + +def _get_os_table(create_options): + os_table = formatting.Table(['os', 'value'], title='Operating Systems') + os_table.sortby = 'os' + os_table.align = 'l' op_sys = [o['template']['operatingSystemReferenceCode'] for o in - result['operatingSystems']] + create_options['operatingSystems']] op_sys = sorted(op_sys) os_summary = set() @@ -76,24 +141,29 @@ def cli(env): os_summary.add(operating_system[0:operating_system.find('_')]) for summary in sorted(os_summary): - table.add_row([ - 'os (%s)' % summary, + os_table.add_row([ + summary, os.linesep.join(sorted([x for x in op_sys if x[0:len(summary)] == summary])) ]) + return os_table + - # Disk - local_disks = [x for x in result['blockDevices'] +def _get_disk_table(create_options): + disk_table = formatting.Table(['disk', 'value'], title='Disks') + disk_table.sortby = 'disk' + disk_table.align = 'l' + local_disks = [x for x in create_options['blockDevices'] if x['template'].get('localDiskFlag', False) and not x['itemPrice'].get('dedicatedHostInstanceFlag', False)] - ded_host_local_disks = [x for x in result['blockDevices'] + ded_host_local_disks = [x for x in create_options['blockDevices'] if x['template'].get('localDiskFlag', False) and x['itemPrice'].get('dedicatedHostInstanceFlag', False)] - san_disks = [x for x in result['blockDevices'] + san_disks = [x for x in create_options['blockDevices'] if not x['template'].get('localDiskFlag', False)] def add_block_rows(disks, name): @@ -109,18 +179,23 @@ def add_block_rows(disks, name): simple[bid].append(str(block['diskImage']['capacity'])) for label in sorted(simple): - table.add_row(['%s disk(%s)' % (name, label), - formatting.listing(simple[label], - separator=',')]) + disk_table.add_row(['%s disk(%s)' % (name, label), + formatting.listing(simple[label], + separator=',')]) add_block_rows(san_disks, 'san') add_block_rows(local_disks, 'local') add_block_rows(ded_host_local_disks, 'local (dedicated host)') + return disk_table + - # Network +def _get_network_table(create_options): + network_table = formatting.Table(['network', 'value'], title='Network') + network_table.sortby = 'network' + network_table.align = 'l' speeds = [] ded_host_speeds = [] - for option in result['networkComponents']: + for option in create_options['networkComponents']: template = option.get('template', None) price = option.get('itemPrice', None) @@ -140,43 +215,9 @@ def add_block_rows(disks, name): speeds.append(max_speed) speeds = sorted(speeds) - table.add_row(['nic', formatting.listing(speeds, separator=',')]) + network_table.add_row(['nic', formatting.listing(speeds, separator=',')]) ded_host_speeds = sorted(ded_host_speeds) - table.add_row(['nic (dedicated host)', - formatting.listing(ded_host_speeds, separator=',')]) - - env.fout(table) - - -def _add_flavors_to_table(result, table): - grouping = { - 'balanced': {'key_starts_with': 'B1', 'flavors': []}, - 'balanced local - hdd': {'key_starts_with': 'BL1', 'flavors': []}, - 'balanced local - ssd': {'key_starts_with': 'BL2', 'flavors': []}, - 'compute': {'key_starts_with': 'C1', 'flavors': []}, - 'memory': {'key_starts_with': 'M1', 'flavors': []}, - 'GPU': {'key_starts_with': 'AC', 'flavors': []}, - 'transient': {'transient': True, 'flavors': []}, - } - - if result.get('flavors', None) is None: - return - - for flavor_option in result['flavors']: - flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') - - for name, group in grouping.items(): - if utils.lookup(flavor_option, 'template', 'transientGuestFlag') is True: - if utils.lookup(group, 'transient') is True: - group['flavors'].append(flavor_key_name) - break - - elif utils.lookup(group, 'key_starts_with') is not None \ - and flavor_key_name.startswith(group['key_starts_with']): - group['flavors'].append(flavor_key_name) - break - - for name, group in grouping.items(): - if len(group['flavors']) > 0: - table.add_row(['flavors (%s)' % name, formatting.listing(group['flavors'], separator='\n')]) + network_table.add_row(['nic (dedicated host)', + formatting.listing(ded_host_speeds, separator=',')]) + return network_table diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 94b913923..6494f0c40 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -315,28 +315,45 @@ def test_detail_vs_ptr_error(self): def test_create_options(self): result = self.run_command(['vs', 'create-options']) + expected_json_result = [ + [ + {"Datacenter": "ams01"}, + {"Datacenter": "dal05"} + ], + [ + {"flavor": "balanced", "value": ["B1_1X2X25", "B1_1X2X100"]}, + {"flavor": "balanced local - hdd", "value": ["BL1_1X2X100"]}, + {"flavor": "balanced local - ssd", "value": ["BL2_1X2X100"]}, + {"flavor": "compute", "value": ["C1_1X2X25"]}, + {"flavor": "memory", "value": ["M1_1X2X100"]}, + {"flavor": "GPU", "value": ["AC1_1X2X100", "ACL1_1X2X100"]}, + {"flavor": "transient", "value": ["B1_1X2X25_TRANSIENT"]} + ], + [ + {"cpu": "standard", "value": [1, 2, 3, 4]}, + {"cpu": "dedicated", "value": [1]}, + {"cpu": "dedicated host", "value": [4, 56]} + ], + [ + {"memory": "standard", "value": [1024, 2048, 3072, 4096]}, + {"memory": "dedicated host", "value": [8192, 65536]} + ], + [ + {"os": "CENTOS", "value": "CENTOS_6_64"}, + {"os": "DEBIAN", "value": "DEBIAN_7_64"}, + {"os": "UBUNTU", "value": "UBUNTU_12_64"} + ], + [ + {"disk": "local disk(0)", "value": ["25", "100"]} + ], + [ + {"network": "nic", "value": ["10", "100", "1000"]}, + {"network": "nic (dedicated host)", "value": ["1000"]} + ] + ] self.assert_no_fail(result) - self.assertEqual({'cpus (dedicated host)': [4, 56], - 'cpus (dedicated)': [1], - 'cpus (standard)': [1, 2, 3, 4], - 'datacenter': ['ams01', 'dal05'], - 'flavors (balanced)': ['B1_1X2X25', 'B1_1X2X100'], - 'flavors (balanced local - hdd)': ['BL1_1X2X100'], - 'flavors (balanced local - ssd)': ['BL2_1X2X100'], - 'flavors (compute)': ['C1_1X2X25'], - 'flavors (memory)': ['M1_1X2X100'], - 'flavors (GPU)': ['AC1_1X2X100', 'ACL1_1X2X100'], - 'flavors (transient)': ['B1_1X2X25_TRANSIENT'], - 'local disk(0)': ['25', '100'], - 'memory': [1024, 2048, 3072, 4096], - 'memory (dedicated host)': [8192, 65536], - 'nic': ['10', '100', '1000'], - 'nic (dedicated host)': ['1000'], - 'os (CENTOS)': 'CENTOS_6_64', - 'os (DEBIAN)': 'DEBIAN_7_64', - 'os (UBUNTU)': 'UBUNTU_12_64'}, - json.loads(result.output)) + self.assertEqual(expected_json_result, json.loads(result.output)) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): @@ -357,19 +374,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -412,12 +429,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) From 6f873b04971268effc5c365badac3d098504f868 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 16:56:57 -0400 Subject: [PATCH 0196/1385] fix vs tests create options --- tests/CLI/modules/vs/vs_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 6494f0c40..bf0221170 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -317,8 +317,8 @@ def test_create_options(self): result = self.run_command(['vs', 'create-options']) expected_json_result = [ [ - {"Datacenter": "ams01"}, - {"Datacenter": "dal05"} + {"datacenter": "ams01"}, + {"datacenter": "dal05"} ], [ {"flavor": "balanced", "value": ["B1_1X2X25", "B1_1X2X100"]}, From b8d56f38161b356c2f2c8b8920f7c42c1708b282 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 19:04:32 -0400 Subject: [PATCH 0197/1385] fix tox issues --- SoftLayer/CLI/virt/create_options.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 7e671d028..85139b8ba 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -62,7 +62,7 @@ def _get_flavors_table(create_options): } if create_options.get('flavors', None) is None: - return + return flavor_table for flavor_option in create_options['flavors']: flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index bf0221170..629b6e14f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -351,7 +351,7 @@ def test_create_options(self): {"network": "nic (dedicated host)", "value": ["1000"]} ] ] - + self.maxDiff = None self.assert_no_fail(result) self.assertEqual(expected_json_result, json.loads(result.output)) From 452922eb17c906ba0a644d25ed5cd2feada46f1b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 19:56:29 -0400 Subject: [PATCH 0198/1385] fix the VirtTests.test_create_options test --- tests/CLI/modules/vs/vs_tests.py | 44 +++++--------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 629b6e14f..06d3147ac 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -315,45 +315,13 @@ def test_detail_vs_ptr_error(self): def test_create_options(self): result = self.run_command(['vs', 'create-options']) - expected_json_result = [ - [ - {"datacenter": "ams01"}, - {"datacenter": "dal05"} - ], - [ - {"flavor": "balanced", "value": ["B1_1X2X25", "B1_1X2X100"]}, - {"flavor": "balanced local - hdd", "value": ["BL1_1X2X100"]}, - {"flavor": "balanced local - ssd", "value": ["BL2_1X2X100"]}, - {"flavor": "compute", "value": ["C1_1X2X25"]}, - {"flavor": "memory", "value": ["M1_1X2X100"]}, - {"flavor": "GPU", "value": ["AC1_1X2X100", "ACL1_1X2X100"]}, - {"flavor": "transient", "value": ["B1_1X2X25_TRANSIENT"]} - ], - [ - {"cpu": "standard", "value": [1, 2, 3, 4]}, - {"cpu": "dedicated", "value": [1]}, - {"cpu": "dedicated host", "value": [4, 56]} - ], - [ - {"memory": "standard", "value": [1024, 2048, 3072, 4096]}, - {"memory": "dedicated host", "value": [8192, 65536]} - ], - [ - {"os": "CENTOS", "value": "CENTOS_6_64"}, - {"os": "DEBIAN", "value": "DEBIAN_7_64"}, - {"os": "UBUNTU", "value": "UBUNTU_12_64"} - ], - [ - {"disk": "local disk(0)", "value": ["25", "100"]} - ], - [ - {"network": "nic", "value": ["10", "100", "1000"]}, - {"network": "nic (dedicated host)", "value": ["1000"]} - ] - ] - self.maxDiff = None self.assert_no_fail(result) - self.assertEqual(expected_json_result, json.loads(result.output)) + self.assertIn('datacenter', result.output) + self.assertIn('flavor', result.output) + self.assertIn('memory', result.output) + self.assertIn('cpu', result.output) + self.assertIn('OS', result.output) + self.assertIn('network', result.output) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): From 9ff03c0f3f2d8b8a4b1c7bd6a7236c732a97d3a8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 19:57:24 -0400 Subject: [PATCH 0199/1385] fix tox analysis issue --- SoftLayer/CLI/virt/create_options.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 85139b8ba..693de74a2 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -1,9 +1,6 @@ """Virtual server order options.""" # :license: MIT, see LICENSE for more details. # pylint: disable=too-many-statements -import os -import os.path - import click import SoftLayer @@ -128,23 +125,21 @@ def _get_memory_table(create_options): def _get_os_table(create_options): - os_table = formatting.Table(['os', 'value'], title='Operating Systems') - os_table.sortby = 'os' + os_table = formatting.Table(['KeyName', 'Description'], title='Operating Systems') + os_table.sortby = 'KeyName' os_table.align = 'l' - op_sys = [o['template']['operatingSystemReferenceCode'] for o in - create_options['operatingSystems']] - - op_sys = sorted(op_sys) - os_summary = set() + op_sys = [] + for operating_system in create_options['operatingSystems']: + os_option = { + 'referenceCode': operating_system['template']['operatingSystemReferenceCode'], + 'description': operating_system['itemPrice']['item']['description'] + } + op_sys.append(os_option) for operating_system in op_sys: - os_summary.add(operating_system[0:operating_system.find('_')]) - - for summary in sorted(os_summary): os_table.add_row([ - summary, - os.linesep.join(sorted([x for x in op_sys - if x[0:len(summary)] == summary])) + operating_system['referenceCode'], + operating_system['description'] ]) return os_table From 1d25ad81141989c871c70170e5ed2841a31fdb79 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 27 Jul 2020 16:56:44 -0500 Subject: [PATCH 0200/1385] added support for filteredMask --- SoftLayer/transports.py | 3 ++- tests/transport_tests.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 02cd7a214..5722e1867 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -564,7 +564,8 @@ def _format_object_mask(objectmask): objectmask = objectmask.strip() if (not objectmask.startswith('mask') and - not objectmask.startswith('[')): + not objectmask.startswith('[') and + not objectmask.startswith('filteredMask')): objectmask = "mask[%s]" % objectmask return objectmask diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 6e6c793fd..a2e500bd8 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -228,6 +228,22 @@ def test_mask_call_v2(self, request): "mask[something[nested]]", kwargs['data']) + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_filteredMask(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "filteredMask[something[nested]]" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + "filteredMask[something[nested]]", + kwargs['data']) + @mock.patch('SoftLayer.transports.requests.Session.request') def test_mask_call_v2_dot(self, request): request.return_value = self.response From 73ea275c20d196c3f9a31d4af3287d1818b692f2 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 28 Jul 2020 18:37:06 -0400 Subject: [PATCH 0201/1385] #1305 update the old Bluemix URLs to the IBM Cloud Docs URL --- CHANGELOG.md | 2 +- SoftLayer/CLI/block/order.py | 2 +- SoftLayer/CLI/file/order.py | 2 +- SoftLayer/CLI/image/export.py | 6 ++++-- SoftLayer/CLI/image/import.py | 10 +++++----- SoftLayer/managers/image.py | 2 +- SoftLayer/managers/vs_capacity.py | 2 +- docs/cli/vs/reserved_capacity.rst | 4 ++-- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b6b28c4e..de7ae2c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -228,7 +228,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.2...v5.8.3 ## [5.6.0] - 2018-10-16 - Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.3...v5.6.0 -+ #1026 Support for [Reserved Capacity](https://console.bluemix.net/docs/vsi/vsi_about_reserved.html#about-reserved-virtual-servers) ++ #1026 Support for [Reserved Capacity](https://cloud.ibm.com/docs/virtual-servers?topic=virtual-servers-about-reserved-virtual-servers) * `slcli vs capacity create` * `slcli vs capacity create-guest` * `slcli vs capacity create-options` diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index 302b45cc8..738080fd0 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -63,7 +63,7 @@ def cli(env, storage_type, size, iops, tier, os_type, """Order a block storage volume. Valid size and iops options can be found here: - https://console.bluemix.net/docs/infrastructure/BlockStorage/index.html#provisioning + https://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations """ block_manager = SoftLayer.BlockStorageManager(env.client) storage_type = storage_type.lower() diff --git a/SoftLayer/CLI/file/order.py b/SoftLayer/CLI/file/order.py index 9e1c9cd29..e665a088b 100644 --- a/SoftLayer/CLI/file/order.py +++ b/SoftLayer/CLI/file/order.py @@ -52,7 +52,7 @@ def cli(env, storage_type, size, iops, tier, """Order a file storage volume. Valid size and iops options can be found here: - https://console.bluemix.net/docs/infrastructure/FileStorage/index.html#provisioning + https://cloud.ibm.com/docs/FileStorage/index.html#provisioning-considerations """ file_manager = SoftLayer.FileStorageManager(env.client) storage_type = storage_type.lower() diff --git a/SoftLayer/CLI/image/export.py b/SoftLayer/CLI/image/export.py index eb9081ac7..c8215c28c 100644 --- a/SoftLayer/CLI/image/export.py +++ b/SoftLayer/CLI/image/export.py @@ -16,8 +16,10 @@ default=None, help="The IBM Cloud API Key with access to IBM Cloud Object " "Storage instance. For help creating this key see " - "https://console.bluemix.net/docs/services/cloud-object-" - "storage/iam/users-serviceids.html#serviceidapikeys") + "https://cloud.ibm.com/docs/cloud-object-storage?" + "topic=cloud-object-storage-iam-overview#iam-overview" + "-service-id-api-key " + ) @environment.pass_env def cli(env, identifier, uri, ibm_api_key): """Export an image to object storage. diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index 53082c9ac..83d2abcf2 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -22,17 +22,17 @@ default=None, help="The IBM Cloud API Key with access to IBM Cloud Object " "Storage instance and IBM KeyProtect instance. For help " - "creating this key see https://console.bluemix.net/docs/" - "services/cloud-object-storage/iam/users-serviceids.html" - "#serviceidapikeys") + "creating this key see https://cloud.ibm.com/docs/" + "cloud-object-storage?topic=cloud-object-storage" + "-iam-overview#iam-overview-service-id-api-key") @click.option('--root-key-crn', default=None, help="CRN of the root key in your KMS instance") @click.option('--wrapped-dek', default=None, help="Wrapped Data Encryption Key provided by IBM KeyProtect. " - "For more info see https://console.bluemix.net/docs/" - "services/key-protect/wrap-keys.html#wrap-keys") + "For more info see " + "https://cloud.ibm.com/docs/key-protect?topic=key-protect-wrap-keys") @click.option('--cloud-init', is_flag=True, help="Specifies if image is cloud-init") diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index d30b05305..b76a959a6 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -17,7 +17,7 @@ class ImageManager(utils.IdentifierMixin, object): """Manages SoftLayer server images. See product information here: - https://console.bluemix.net/docs/infrastructure/image-templates/image_index.html + https://cloud.ibm.com/docs/image-templates :param SoftLayer.API.BaseClient client: the client instance """ diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 3f6574f12..813e2d565 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -24,7 +24,7 @@ class CapacityManager(utils.IdentifierMixin, object): Product Information - - https://console.bluemix.net/docs/vsi/vsi_about_reserved.html + - https://cloud.ibm.com/docs/virtual-servers?topic=virtual-servers-about-reserved-virtual-servers - https://softlayer.github.io/reference/services/SoftLayer_Virtual_ReservedCapacityGroup/ - https://softlayer.github.io/reference/services/SoftLayer_Virtual_ReservedCapacityGroup_Instance/ diff --git a/docs/cli/vs/reserved_capacity.rst b/docs/cli/vs/reserved_capacity.rst index 3193febff..37e74000c 100644 --- a/docs/cli/vs/reserved_capacity.rst +++ b/docs/cli/vs/reserved_capacity.rst @@ -5,8 +5,8 @@ Working with Reserved Capacity There are two main concepts for Reserved Capacity. The `Reserved Capacity Group `_ and the `Reserved Capacity Instance `_ The Reserved Capacity Group, is a set block of capacity set aside for you at the time of the order. It will contain a set number of Instances which are all the same size. Instances can be ordered like normal VSIs, with the exception that you need to include the reservedCapacityGroupId, and it must be the same size as the group you are ordering the instance in. -- `About Reserved Capacity `_ -- `Reserved Capacity FAQ `_ +- `About Reserved Capacity `_ +- `Reserved Capacity FAQ `_ The SLCLI supports some basic Reserved Capacity Features. From 95ad3be5be757c53ecf1b0732237eda4acaf54f8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 29 Jul 2020 19:13:11 -0400 Subject: [PATCH 0202/1385] 1305 update softlayer.com urls to ibm.com/cloud urls --- SoftLayer/managers/block.py | 2 +- SoftLayer/managers/dns.py | 2 +- SoftLayer/managers/firewall.py | 2 +- SoftLayer/managers/hardware.py | 2 +- SoftLayer/managers/network.py | 2 +- SoftLayer/managers/object_storage.py | 2 +- SoftLayer/managers/ssl.py | 2 +- SoftLayer/managers/ticket.py | 2 +- SoftLayer/managers/vs.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 4d129d07c..a16500919 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -15,7 +15,7 @@ class BlockStorageManager(StorageManager): """Manages SoftLayer Block Storage volumes. - See product information here: http://www.softlayer.com/block-storage + See product information here: https://www.ibm.com/cloud/block-storage """ def list_block_volume_limit(self): diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 3a2ea9147..1e89ec9cf 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -14,7 +14,7 @@ class DNSManager(utils.IdentifierMixin, object): """Manage SoftLayer DNS. - See product information here: http://www.softlayer.com/DOMAIN-SERVICES + See product information here: https://www.ibm.com/cloud/dns :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 2b1d8e452..34b197521 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -32,7 +32,7 @@ def has_firewall(vlan): class FirewallManager(utils.IdentifierMixin, object): """Manages SoftLayer firewalls - See product information here: http://www.softlayer.com/firewalls + See product information here: https://www.ibm.com/cloud/network-security :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8ac8d7edd..3a7acbfbd 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -41,7 +41,7 @@ class HardwareManager(utils.IdentifierMixin, object): client = SoftLayer.Client() mgr = SoftLayer.HardwareManager(client) - See product information here: http://www.softlayer.com/bare-metal-servers + See product information here: https://www.ibm.com/cloud/bare-metal-servers :param SoftLayer.API.BaseClient client: the client instance :param SoftLayer.managers.OrderingManager ordering_manager: an optional diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index f9c5f4af0..1e9296cac 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -57,7 +57,7 @@ class NetworkManager(object): """Manage SoftLayer network objects: VLANs, subnets, IPs and rwhois - See product information here: http://www.softlayer.com/networking + See product information here: https://www.ibm.com/cloud/network :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index 2560d26c8..a3a84414b 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -18,7 +18,7 @@ class ObjectStorageManager(object): """Manager for SoftLayer Object Storage accounts. - See product information here: http://www.softlayer.com/object-storage + See product information here: https://www.ibm.com/cloud/object-storage :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/ssl.py b/SoftLayer/managers/ssl.py index 3fa2ac1dd..5474d9cb3 100644 --- a/SoftLayer/managers/ssl.py +++ b/SoftLayer/managers/ssl.py @@ -10,7 +10,7 @@ class SSLManager(object): """Manages SSL certificates in SoftLayer. - See product information here: http://www.softlayer.com/ssl-certificates + See product information here: https://www.ibm.com/cloud/ssl-certificates Example:: diff --git a/SoftLayer/managers/ticket.py b/SoftLayer/managers/ticket.py index 9ff361d4b..bbd2eddd2 100644 --- a/SoftLayer/managers/ticket.py +++ b/SoftLayer/managers/ticket.py @@ -11,7 +11,7 @@ class TicketManager(utils.IdentifierMixin, object): """Manages SoftLayer support tickets. - See product information here: http://www.softlayer.com/support + See product information here: https://www.ibm.com/cloud/support :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 0e27c4cd3..9995e7ec2 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -24,7 +24,7 @@ class VSManager(utils.IdentifierMixin, object): """Manages SoftLayer Virtual Servers. - See product information here: http://www.softlayer.com/virtual-servers + See product information here: https://www.ibm.com/cloud/virtual-servers Example:: From 194723993b0db8dc7346f9f32b5ed143fad9a8e6 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 3 Aug 2020 19:11:38 -0400 Subject: [PATCH 0203/1385] fix and improve the new code --- SoftLayer/CLI/virt/upgrade.py | 29 +++++++++----- SoftLayer/managers/vs.py | 65 +++++++++++++++++--------------- tests/CLI/modules/vs/vs_tests.py | 40 ++++++++++---------- 3 files changed, 73 insertions(+), 61 deletions(-) diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index ef4477d86..4afeb8be6 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -1,8 +1,6 @@ """Upgrade a virtual server.""" # :license: MIT, see LICENSE for more details. -import json - import click import SoftLayer @@ -22,18 +20,20 @@ help="CPU core will be on a dedicated host server.") @click.option('--memory', type=virt.MEM_TYPE, help="Memory in megabytes") @click.option('--network', type=click.INT, help="Network port speed in Mbps") -@click.option('--add', type=click.INT, required=False, help="add Hard disk in GB") -@click.option('--disk', nargs=1, help="update the number and capacity in GB Hard disk, E.G {'number':2,'capacity':100}") +@click.option('--add-disk', type=click.INT, multiple=True, required=False, help="add Hard disk in GB") +@click.option('--resize-disk', nargs=2, multiple=True, type=(int, int), + help="Update disk number to size in GB. --resize-disk 250 2 ") @click.option('--flavor', type=click.STRING, help="Flavor keyName\nDo not use --memory, --cpu or --private, if you are using flavors") @environment.pass_env -def cli(env, identifier, cpu, private, memory, network, flavor, disk, add): +def cli(env, identifier, cpu, private, memory, network, flavor, add_disk, resize_disk): """Upgrade a virtual server.""" vsi = SoftLayer.VSManager(env.client) - if not any([cpu, memory, network, flavor, disk, add]): - raise exceptions.ArgumentError("Must provide [--cpu], [--memory], [--network], or [--flavor] to upgrade") + if not any([cpu, memory, network, flavor, resize_disk, add_disk]): + raise exceptions.ArgumentError("Must provide [--cpu]," + " [--memory], [--network], [--flavor], [--resize-disk], or [--add] to upgrade") if private and not cpu: raise exceptions.ArgumentError("Must specify [--cpu] when using [--private]") @@ -44,9 +44,18 @@ def cli(env, identifier, cpu, private, memory, network, flavor, disk, add): if memory: memory = int(memory / 1024) - if disk is not None: - disk = json.loads(disk) + if resize_disk: + disk_json = list() + for guest_disk in resize_disk: + disks = {'capacity': guest_disk[0], 'number': guest_disk[1]} + disk_json.append(disks) + + elif add_disk: + disk_json = list() + for guest_disk in add_disk: + disks = {'capacity': guest_disk, 'number': -1} + disk_json.append(disks) if not vsi.upgrade(vs_id, cpus=cpu, memory=memory, nic_speed=network, public=not private, preset=flavor, - disk=disk, add=add): + disk=disk_json): raise exceptions.CLIAbort('VS Upgrade Failed') diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index ffc67db7d..672a74701 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -819,8 +819,7 @@ def capture(self, instance_id, name, additional_disks=False, notes=None): return self.guest.createArchiveTransaction( name, disks_to_capture, notes, id=instance_id) - def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True, preset=None, - disk=None, add=None): + def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True, preset=None, disk=None): """Upgrades a VS instance. Example:: @@ -853,10 +852,7 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr if memory is not None and preset is not None: raise ValueError("Do not use memory, private or cpu if you are using flavors") data['memory'] = memory - if disk is not None: - data['disk'] = disk.get('capacity') - elif add is not None: - data['disk'] = add + maintenance_window = datetime.datetime.now(utils.UTC()) order = { 'complexType': 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade', @@ -867,6 +863,35 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr 'virtualGuests': [{'id': int(instance_id)}], } + if disk: + disk_number = 0 + vsi_disk = self.get_instance(instance_id) + for item in vsi_disk.get('billingItem').get('children'): + if item.get('categoryCode').__contains__('guest_disk'): + if disk_number < int("".join(filter(str.isdigit, item.get('categoryCode')))): + disk_number = int("".join(filter(str.isdigit, item.get('categoryCode')))) + for disk_guest in disk: + if disk_guest.get('number') > 0: + price_id = self._get_price_id_for_upgrade_option(upgrade_prices, 'disk', + disk_guest.get('capacity'), + public) + disk_number = disk_guest.get('number') + + else: + price_id = self._get_price_id_for_upgrade_option(upgrade_prices, 'disk', + disk_guest.get('capacity'), + public) + disk_number = disk_number + 1 + + category = {'categories': [{ + 'categoryCode': 'guest_disk' + str(disk_number), + 'complexType': "SoftLayer_Product_Item_Category"}], + 'complexType': 'SoftLayer_Product_Item_Price', + 'id': price_id} + + prices.append(category) + order['prices'] = prices + for option, value in data.items(): if not value: continue @@ -879,28 +904,7 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr raise exceptions.SoftLayerError( "Unable to find %s option with value %s" % (option, value)) - if disk is not None: - category = {'categories': [{ - 'categoryCode': 'guest_disk' + str(disk.get('number')), - 'complexType': "SoftLayer_Product_Item_Category" - }], 'complexType': 'SoftLayer_Product_Item_Price'} - prices.append(category) - prices[0]['id'] = price_id - elif add: - vsi_disk = self.get_instance(instance_id) - disk_number = 0 - for item in vsi_disk.get('billingItem').get('children'): - if item.get('categoryCode').__contains__('guest_disk'): - if disk_number < int("".join(filter(str.isdigit, item.get('categoryCode')))): - disk_number = int("".join(filter(str.isdigit, item.get('categoryCode')))) - category = {'categories': [{ - 'categoryCode': 'guest_disk' + str(disk_number + 1), - 'complexType': "SoftLayer_Product_Item_Category" - }], 'complexType': 'SoftLayer_Product_Item_Price'} - prices.append(category) - prices[0]['id'] = price_id - else: - prices.append({'id': price_id}) + prices.append({'id': price_id}) order['prices'] = prices if preset is not None: @@ -1024,6 +1028,7 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public 'disk': 'guest_disk' } category_code = option_category.get(option) + for price in upgrade_prices: if price.get('categories') is None or price.get('item') is None: continue @@ -1034,7 +1039,7 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public for category in price.get('categories'): if option == 'disk': - if not (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) + if (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) and str(product.get('capacity')) == str(value)): return price.get('id') @@ -1052,8 +1057,6 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public elif option == 'nic_speed': if 'Public' in product.get('description'): return price.get('id') - elif option == 'disk': - return price.get('id') else: return price.get('id') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a90811fe8..138a9f832 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -370,19 +370,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -425,12 +425,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) @@ -574,7 +574,7 @@ def test_upgrade(self, confirm_mock): def test_upgrade_disk(self, confirm_mock): confirm_mock.return_value = True result = self.run_command(['vs', 'upgrade', '100', '--flavor=M1_64X512X100', - '--disk={"number":1,"capacity":10}']) + '--resize-disk=10', '1', '--resize-disk=10', '2']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] @@ -598,7 +598,7 @@ def test_upgrade_with_flavor(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_with_add_disk(self, confirm_mock): confirm_mock.return_value = True - result = self.run_command(['vs', 'upgrade', '100', '--add=10']) + result = self.run_command(['vs', 'upgrade', '100', '--add-disk=10', '--add-disk=10']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] From d149472298a5f0c8195ffd598ecb6e25ef3684c8 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 4 Aug 2020 09:29:58 -0400 Subject: [PATCH 0204/1385] fix tox tool --- SoftLayer/CLI/virt/upgrade.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index 4afeb8be6..fdfa37822 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -42,16 +42,15 @@ def cli(env, identifier, cpu, private, memory, network, flavor, add_disk, resize if not (env.skip_confirmations or formatting.confirm("This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') + disk_json = list() if memory: memory = int(memory / 1024) if resize_disk: - disk_json = list() for guest_disk in resize_disk: disks = {'capacity': guest_disk[0], 'number': guest_disk[1]} disk_json.append(disks) elif add_disk: - disk_json = list() for guest_disk in add_disk: disks = {'capacity': guest_disk, 'number': -1} disk_json.append(disks) From d6583f3c5f346a527a087c4e2c9fdb38ae6cc747 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 4 Aug 2020 15:04:50 -0500 Subject: [PATCH 0205/1385] 5.9.0 changelog entry --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de7ae2c03..92939ef75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## [5.9.0] - 2020-08-03 +https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 + +- #1280 Notification Management + + slcli user notifications + + slcli user edit-notifications +- #828 Added networking options to slcli hw create-options + + Refactored slcli hw create to use the ordering manager + + Added --network option to slcli hw create for more granular network choices. + + Deprecated --port-speed and --no-public . They still work for now, but will be removed in a future release. +- #1298 Fix Unhandled exception in CLI - vs detail +- #1309 Fix the empty lines in slcli vs create-options +- #1301 Ability to list VirtualHost capable guests + + slcli hardware guests + + slcli vs list will show guests on VirtualHost servers +- #875 added option to reload bare metal servers with LVM enabled +- #874 Added Migrate command +- #1313 Added support for filteredMask +- #1305 Update docs links +- #1302 Fix lots of whitespace slcli vs create-options ## [5.8.9] - 2020-07-06 https://github.com/softlayer/softlayer-python/compare/v5.8.8...v5.8.9 From 2193048843cac26950bb20efb7286b33b7cbda18 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 4 Aug 2020 17:36:16 -0500 Subject: [PATCH 0206/1385] Support for STDIN on creating and updating tickets. #900 --- SoftLayer/CLI/ticket/create.py | 29 +++++++++++++--- SoftLayer/CLI/ticket/update.py | 29 +++++++++++++--- docs/cli/tickets.rst | 6 ++++ tests/CLI/modules/ticket_tests.py | 55 +++++++++++++++++++++++++++---- 4 files changed, 103 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/ticket/create.py b/SoftLayer/CLI/ticket/create.py index 5ebad8cf4..17667d74f 100644 --- a/SoftLayer/CLI/ticket/create.py +++ b/SoftLayer/CLI/ticket/create.py @@ -12,8 +12,7 @@ @click.command() @click.option('--title', required=True, help="The title of the ticket") @click.option('--subject-id', type=int, required=True, - help="""The subject id to use for the ticket, - issue 'slcli ticket subjects' to get the list""") + help="""The subject id to use for the ticket, run 'slcli ticket subjects' to get the list""") @click.option('--body', help="The ticket body") @click.option('--hardware', 'hardware_identifier', help="The identifier for hardware to attach") @@ -24,11 +23,31 @@ Only settable with Advanced and Premium support. See https://www.ibm.com/cloud/support""") @environment.pass_env def cli(env, title, subject_id, body, hardware_identifier, virtual_identifier, priority): - """Create a support ticket.""" - ticket_mgr = SoftLayer.TicketManager(env.client) + """Create a Infrastructure support ticket. + + Example:: + + Will create the ticket with `Some text`. + + slcli ticket create --body="Some text" --subject-id 1522 --hardware 12345 --title "My New Ticket" + Will create the ticket with text from STDIN + + cat sometfile.txt | slcli ticket create --subject-id 1003 --virtual 111111 --title "Reboot Me" + + Will open the default text editor, and once closed, use that text to create the ticket + + slcli ticket create --subject-id 1482 --title "Vyatta Questions..." + """ + ticket_mgr = SoftLayer.TicketManager(env.client) if body is None: - body = click.edit('\n\n' + ticket.TEMPLATE_MSG) + stdin = click.get_text_stream('stdin') + # Means there is text on the STDIN buffer, read it and add to the ticket + if not stdin.isatty(): + body = stdin.read() + # This is an interactive terminal, open a text editor + else: + body = click.edit('\n\n' + ticket.TEMPLATE_MSG) created_ticket = ticket_mgr.create_ticket( title=title, body=body, diff --git a/SoftLayer/CLI/ticket/update.py b/SoftLayer/CLI/ticket/update.py index 9c10971ad..f04d36f94 100644 --- a/SoftLayer/CLI/ticket/update.py +++ b/SoftLayer/CLI/ticket/update.py @@ -11,16 +11,35 @@ @click.command() @click.argument('identifier') -@click.option('--body', help="The entry that will be appended to the ticket") +@click.option('--body', help="Text to add to the ticket. STDIN or the default text editor will be used otherwise.") @environment.pass_env def cli(env, identifier, body): - """Adds an update to an existing ticket.""" + """Adds an update to an existing ticket. + + Example:: + + Will update the ticket with `Some text`. + + slcli ticket update 123456 --body="Some text" + + Will update the ticket with text from STDIN + + cat sometfile.txt | slcli ticket update 123456 + + Will open the default text editor, and once closed, use that text to update the ticket + + slcli ticket update 123456 + """ mgr = SoftLayer.TicketManager(env.client) ticket_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'ticket') - if body is None: - body = click.edit('\n\n' + ticket.TEMPLATE_MSG) - + stdin = click.get_text_stream('stdin') + # Means there is text on the STDIN buffer, read it and add to the ticket + if not stdin.isatty(): + body = stdin.read() + # This is an interactive terminal, open a text editor + else: + body = click.edit('\n\n' + ticket.TEMPLATE_MSG) mgr.update_ticket(ticket_id=ticket_id, body=body) env.fout("Ticket Updated!") diff --git a/docs/cli/tickets.rst b/docs/cli/tickets.rst index ad5f23428..71ef3192c 100644 --- a/docs/cli/tickets.rst +++ b/docs/cli/tickets.rst @@ -3,6 +3,12 @@ Support Tickets =============== +The SoftLayer ticket API is used to create "classic" or Infrastructure Support cases. +These tickets will still show up in your web portal, but for the more unified case management API, +see the `Case Management API `_ + +.. note:: Windows Git-Bash users might run into issues with `ticket create` and `ticket update` if --body isn't used, as it doesn't report that it is a real TTY to python, so the default editor can not be launched. + .. click:: SoftLayer.CLI.ticket.create:cli :prog: ticket create :show-nested: diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 4f8db62a8..7b2363eab 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -13,6 +13,22 @@ from SoftLayer import testing +class FakeTTY(): + """A fake object to fake STD input""" + def __init__(self, isatty=False, read="Default Output"): + """Sets isatty and read""" + self._isatty = isatty + self._read = read + + def isatty(self): + """returns self.isatty""" + return self._isatty + + def read(self): + """returns self.read""" + return self._read + + class TicketTests(testing.TestCase): def test_list(self): @@ -99,18 +115,33 @@ def test_create_and_attach(self): identifier=100) @mock.patch('click.edit') - def test_create_no_body(self, edit_mock): + @mock.patch('click.get_text_stream') + def test_create_no_body(self, isatty_mock, edit_mock): + fake_tty = FakeTTY(True, "TEST") + isatty_mock.return_value = fake_tty edit_mock.return_value = 'ticket body' - result = self.run_command(['ticket', 'create', '--title=Test', - '--subject-id=1000']) + result = self.run_command(['ticket', 'create', '--title=Test', '--subject-id=1000']) self.assert_no_fail(result) args = ({'subjectId': 1000, 'assignedUserId': 12345, 'title': 'Test'}, 'ticket body') - self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket', - args=args) + self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket', args=args) + + @mock.patch('click.get_text_stream') + def test_create_no_body_stdin(self, isatty_mock): + fake_tty = FakeTTY(False, "TEST TICKET BODY") + isatty_mock.return_value = fake_tty + result = self.run_command(['ticket', 'create', '--title=Test', '--subject-id=1000']) + print(result.output) + self.assert_no_fail(result) + + args = ({'subjectId': 1000, + 'assignedUserId': 12345, + 'title': 'Test'}, 'TEST TICKET BODY') + + self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket', args=args) def test_subjects(self): list_expected_ids = [1001, 1002, 1003, 1004, 1005] @@ -294,12 +325,24 @@ def test_ticket_update(self): self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing'},), identifier=100) @mock.patch('click.edit') - def test_ticket_update_no_body(self, edit_mock): + @mock.patch('click.get_text_stream') + def test_ticket_update_no_body(self, isatty_mock, edit_mock): + fake_tty = FakeTTY(True, "TEST TICKET BODY") + isatty_mock.return_value = fake_tty edit_mock.return_value = 'Testing1' result = self.run_command(['ticket', 'update', '100']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) + @mock.patch('click.get_text_stream') + def test_ticket_update_no_body_stdin(self, isatty_mock): + fake_tty = FakeTTY(False, "TEST TICKET BODY") + isatty_mock.return_value = fake_tty + result = self.run_command(['ticket', 'update', '100']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Ticket', 'addUpdate', + args=({'entry': 'TEST TICKET BODY'},), identifier=100) + def test_ticket_json(self): result = self.run_command(['--format=json', 'ticket', 'detail', '1']) expected = {'Case_Number': 'CS123456', From 68f46e294edbaf093bdfa0ed003f8c57ee9bd63a Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 11 Aug 2020 09:39:30 -0400 Subject: [PATCH 0207/1385] fix tox Christopher commet code review --- SoftLayer/managers/vs.py | 5 +++++ tests/CLI/modules/vs/vs_tests.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 672a74701..8f799b1ac 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -883,6 +883,11 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr public) disk_number = disk_number + 1 + if price_id is None: + raise exceptions.SoftLayerAPIError(500, + 'Unable to find %s option with value %s' % ( + ('disk', disk_guest.get('capacity')))) + category = {'categories': [{ 'categoryCode': 'guest_disk' + str(disk_number), 'complexType': "SoftLayer_Product_Item_Category"}], diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 138a9f832..0c8092e7f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -583,6 +583,14 @@ def test_upgrade_disk(self, confirm_mock): self.assertIn({'id': 100}, order_container['virtualGuests']) self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_error(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vs', 'upgrade', '100', '--flavor=M1_64X512X100', + '--resize-disk=1000', '1', '--resize-disk=10', '2']) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerAPIError) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_with_flavor(self, confirm_mock): confirm_mock.return_value = True From db56ca4f5d4cf3412fb9c7af4e3b104c651770e4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 11 Aug 2020 14:49:41 -0400 Subject: [PATCH 0208/1385] Ordering price information improvements. --- SoftLayer/CLI/hardware/create_options.py | 204 +++++++++++++++--- SoftLayer/CLI/order/item_list.py | 86 +++++++- .../fixtures/SoftLayer_Product_Package.py | 50 ++++- SoftLayer/managers/hardware.py | 51 ++++- SoftLayer/managers/ordering.py | 13 ++ tests/CLI/modules/order_tests.py | 26 +++ tests/CLI/modules/server_tests.py | 26 +++ tests/managers/hardware_tests.py | 98 ++++++++- tests/managers/ordering_tests.py | 34 +++ 9 files changed, 534 insertions(+), 54 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 2e9949d09..f3a3dec14 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -4,13 +4,17 @@ import click from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import hardware @click.command() +@click.option('--prices', '-p', default=False, help='Filter Item Prices, prices(DEFAULT False)') +@click.option('--location', '-l', type=click.STRING, help='To filter the item prices by location, enter the Location ' + 'keyName e.g. AMSTERDAM02') @environment.pass_env -def cli(env): +def cli(env, prices, location): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) @@ -22,41 +26,171 @@ def cli(env): dc_table = formatting.Table(['Datacenter', 'Value'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' - for location in options['locations']: - dc_table.add_row([location['name'], location['key']]) + for location_info in options['locations']: + dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - # Presets - preset_table = formatting.Table(['Size', 'Value'], title="Sizes") - preset_table.sortby = 'Value' - preset_table.align = 'l' - for size in options['sizes']: - preset_table.add_row([size['name'], size['key']]) - tables.append(preset_table) - - # Operating systems - os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") - os_table.sortby = 'Key' - os_table.align = 'l' - for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) - tables.append(os_table) - - # Port speed - port_speed_table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") - port_speed_table.sortby = 'Speed' - port_speed_table.align = 'l' - - for speed in options['port_speeds']: - port_speed_table.add_row([speed['name'], speed['speed'], speed['key']]) - tables.append(port_speed_table) - - # Extras - extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") - extras_table.sortby = 'Value' - extras_table.align = 'l' - for extra in options['extras']: - extras_table.add_row([extra['name'], extra['key']]) - tables.append(extras_table) + if location and prices: + raise exceptions.CLIAbort("Please select --prices or --location to get the prices, not both") + + if prices: + _preset_prices_table(options['sizes'], tables) + _os_prices_table(options['operating_systems'], tables) + _port_speed_prices_table(options['port_speeds'], tables) + _extras_prices_table(options['extras'], tables) + elif location: + _preset_prices_table(options['sizes'], tables) + location_prices = hardware_manager.get_hardware_item_prices(location) + _location_item_prices(location_prices, tables) + else: + # Presets + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + preset_table.sortby = 'Value' + preset_table.align = 'l' + for size in options['sizes']: + preset_table.add_row([size['name'], size['key']]) + tables.append(preset_table) + + # Operating systems + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' + os_table.align = 'l' + for operating_system in options['operating_systems']: + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + tables.append(os_table) + + # Port speed + port_speed_table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") + port_speed_table.sortby = 'Speed' + port_speed_table.align = 'l' + for speed in options['port_speeds']: + port_speed_table.add_row([speed['name'], speed['speed'], speed['key']]) + tables.append(port_speed_table) + + # Extras + extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") + extras_table.sortby = 'Value' + extras_table.align = 'l' + for extra in options['extras']: + extras_table.add_row([extra['name'], extra['key']]) + tables.append(extras_table) env.fout(formatting.listing(tables, separator='\n')) + + +def _preset_prices_table(sizes, tables): + """Shows Server Preset options prices. + + :param [] sizes: List of Hardware Server sizes. + :param tables: Table formatting. + """ + preset_prices_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") + preset_prices_table.sortby = 'Value' + preset_prices_table.align = 'l' + for size in sizes: + preset_prices_table.add_row([size['name'], size['key'], size['hourlyRecurringFee'], size['recurringFee']]) + tables.append(preset_prices_table) + + +def _os_prices_table(operating_systems, tables): + """Shows Server Operating Systems prices cost and capacity restriction. + + :param [] operating_systems: List of Hardware Server operating systems. + :param tables: Table formatting. + """ + os_prices_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'capacityRestrictionMaximum', + 'capacityRestrictionMinimum', 'capacityRestrictionType'], + title="Operating Systems Prices") + os_prices_table.sortby = 'OS Key' + os_prices_table.align = 'l' + for operating_system in operating_systems: + for price in operating_system['prices']: + os_prices_table.add_row( + [operating_system['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType')]) + tables.append(os_prices_table) + + +def _port_speed_prices_table(port_speeds, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] port_speeds: List of Hardware Server Port Speeds. + :param tables: Table formatting. + """ + port_speed_prices_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', + 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', + 'capacityRestrictionType'], title="Network Options Prices") + port_speed_prices_table.sortby = 'Speed' + port_speed_prices_table.align = 'l' + for speed in port_speeds: + for price in speed['prices']: + port_speed_prices_table.add_row( + [speed['key'], speed['speed'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType')]) + tables.append(port_speed_prices_table) + + +def _extras_prices_table(extras, tables): + """Shows Server extras prices cost and capacity restriction. + + :param [] extras: List of Hardware Server Extras. + :param tables: Table formatting. + """ + extras_prices_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', + 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', + 'capacityRestrictionType'], title="Extras Prices") + extras_prices_table.align = 'l' + for extra in extras: + for price in extra['prices']: + extras_prices_table.add_row( + [extra['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType') + ]) + tables.append(extras_prices_table) + + +def _get_price_data(price, item): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + result = '-' + if item in price: + result = price[item] + return result + + +def _location_item_prices(location_prices, tables): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', + 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', + 'capacityRestrictionType']) + location_prices_table.sortby = 'keyName' + location_prices_table.align = 'l' + for price in location_prices: + location_prices_table.add_row( + [price['item']['keyName'], price['id'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType') + ]) + tables.append(location_prices_table) diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index ad9ae537f..97e9c985e 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -3,19 +3,25 @@ import click from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import ordering from SoftLayer.utils import lookup COLUMNS = ['category', 'keyName', 'description', 'priceId'] +COLUMNS_ITEM_PRICES = ['keyName', 'priceId', 'Hourly', 'Monthly', 'CRMax', 'CRMim', 'CRType'] +COLUMNS_ITEM_PRICES_LOCATION = ['keyName', 'priceId', 'Hourly', 'Monthly', 'CRMax', 'CRMim', 'CRType'] @click.command() @click.argument('package_keyname') @click.option('--keyword', help="A word (or string) used to filter item names.") @click.option('--category', help="Category code to filter items by") +@click.option('--prices', '-p', default=False, help='Filter Item Prices, prices(DEFAULT False)') +@click.option('--location', '-l', type=click.STRING, help='To filter the item prices by location, enter the Location ' + 'keyName e.g. AMSTERDAM02') @environment.pass_env -def cli(env, package_keyname, keyword, category): +def cli(env, package_keyname, keyword, category, prices, location): """List package items used for ordering. The item keyNames listed can be used with `slcli order place` to specify @@ -34,9 +40,11 @@ def cli(env, package_keyname, keyword, category): slcli order item-list BARE_METAL_SERVER --category os --keyword ubuntu """ - table = formatting.Table(COLUMNS) manager = ordering.OrderingManager(env.client) + if location and prices: + raise exceptions.CLIAbort("Please select --prices or --location to get the prices, not both") + _filter = {'items': {}} if keyword: _filter['items']['description'] = {'operation': '*= %s' % keyword} @@ -47,9 +55,22 @@ def cli(env, package_keyname, keyword, category): sorted_items = sort_items(items) categories = sorted_items.keys() - for catname in sorted(categories): - for item in sorted_items[catname]: - table.add_row([catname, item['keyName'], item['description'], get_price(item)]) + if prices: + table = formatting.Table(COLUMNS_ITEM_PRICES, title="CRMax = CapacityRestrictionMaximum, " + "CRMin = CapacityRestrictionMinimum, " + "CRType = CapacityRestrictionType") + table = _item_list_prices(categories, sorted_items, table) + elif location: + table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="CRMax = CapacityRestrictionMaximum, " + "CRMin = CapacityRestrictionMinimum, " + "CRType = CapacityRestrictionType") + location_prices = manager.get_item_prices_by_location(location, package_keyname) + table = _location_item_prices(location_prices, table) + else: + table = formatting.Table(COLUMNS) + for catname in sorted(categories): + for item in sorted_items[catname]: + table.add_row([catname, item['keyName'], item['description'], get_price(item)]) env.fout(table) @@ -73,3 +94,58 @@ def get_price(item): if not price.get('locationGroupId'): return price.get('id') return 0 + + +def _item_list_prices(categories, sorted_items, table): + """Add the item prices cost and capacity restriction to the table""" + for catname in sorted(categories): + for item in sorted_items[catname]: + for price in item['prices']: + if not price.get('locationGroupId'): + table.add_row([item['keyName'], price['id'], + get_item_price_data(price, 'hourlyRecurringFee'), + get_item_price_data(price, 'recurringFee'), + get_item_price_data(price, 'capacityRestrictionMaximum'), + get_item_price_data(price, 'capacityRestrictionMinimum'), + get_item_price_data(price, 'capacityRestrictionType')]) + return table + + +def get_item_price_data(price, item_attribute): + """Given an SoftLayer_Product_Item_Price, returns its default price data""" + result = '-' + if item_attribute in price: + result = price[item_attribute] + return result + + +def _location_item_prices(location_prices, table): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + table.sortby = 'keyName' + table.align = 'l' + for price in location_prices: + table.add_row( + [price['item']['keyName'], price['id'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType') + ]) + return table + + +def _get_price_data(price, item): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + result = '-' + if item in price: + result = price[item] + return result diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index b131f8916..f38f828e5 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -8,6 +8,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 1245172, + "locationGroupId": '', 'itemId': 935954, 'laborFee': '0', 'onSaleFlag': '', @@ -26,6 +27,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 17129, + "locationGroupId": '', 'itemId': 4097, 'laborFee': '0', 'onSaleFlag': '', @@ -43,6 +45,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 272, + "locationGroupId": '', 'itemId': 186, 'laborFee': '0', 'onSaleFlag': '', @@ -60,6 +63,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 37650, + "locationGroupId": '', 'itemId': 4702, 'laborFee': '0', 'onSaleFlag': '', @@ -80,6 +84,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 21, + "locationGroupId": '', 'itemId': 15, 'laborFee': '0', 'onSaleFlag': '', @@ -97,6 +102,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 420, + "locationGroupId": '', 'itemId': 309, 'laborFee': '0', 'onSaleFlag': '', @@ -114,6 +120,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 906, + "locationGroupId": '', 'itemId': 504, 'laborFee': '0', 'onSaleFlag': '', @@ -130,6 +137,7 @@ 'prices': [{'accountRestrictions': [], 'currentPriceFlag': '', 'id': 22505, + "locationGroupId": '', 'itemId': 4481, 'laborFee': '0', 'onSaleFlag': '', @@ -147,6 +155,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 1800, + "locationGroupId": '', 'itemId': 439, 'laborFee': '0', 'onSaleFlag': '', @@ -755,7 +764,15 @@ 'isActive': '1', 'keyName': 'S1270_8GB_2X1TBSATA_NORAID', 'name': 'S1270 8GB 2X1TBSATA NORAID', - 'packageId': 200 + 'packageId': 200, + 'prices': [ + { + "hourlyRecurringFee": "1.18", + "id": 165711, + "locationGroupId": '', + "recurringFee": "780", + } + ] } activePreset2 = { @@ -764,7 +781,15 @@ 'isActive': '1', 'keyName': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10', 'name': 'DGOLD 6140 384GB 4X960GB SSD SED RAID 10', - 'packageId': 200 + 'packageId': 200, + 'prices': [ + { + "hourlyRecurringFee": "1.18", + "id": 165711, + "locationGroupId": '', + "recurringFee": "780", + } + ] } getAllObjects = [{ @@ -790,6 +815,9 @@ "id": 205911, "laborFee": "0", "locationGroupId": 505, + "capacityRestrictionMaximum": "40", + "capacityRestrictionMinimum": "40", + "capacityRestrictionType": "CORE", "item": { "capacity": "0", "description": "Load Balancer Uptime", @@ -1507,7 +1535,14 @@ "id": 449610, "longName": "Montreal 1", "name": "mon01", - "statusId": 2 + "statusId": 2, + "regions": [ + { + "description": "MON01 - Montreal", + "keyname": "MONTREAL", + "sortOrder": 94 + } + ] }, { "id": 449618, @@ -1531,7 +1566,14 @@ "id": 221894, "longName": "Amsterdam 2", "name": "ams02", - "statusId": 2 + "statusId": 2, + "regions": [ + { + "description": "AMS02 POP - Amsterdam", + "keyname": "AMSTERDAM02", + "sortOrder": 12 + } + ] }, { "id": 265592, diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8ac8d7edd..d658d6272 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,11 +9,11 @@ import socket import time +from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager -from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -397,7 +397,9 @@ def get_create_options(self): for preset in package['activePresets'] + package['accountRestrictedActivePresets']: sizes.append({ 'name': preset['description'], - 'key': preset['keyName'] + 'key': preset['keyName'], + 'hourlyRecurringFee': _get_preset_cost(preset['prices'], 'hourly'), + 'recurringFee': _get_preset_cost(preset['prices'], 'monthly') }) operating_systems = [] @@ -410,20 +412,23 @@ def get_create_options(self): operating_systems.append({ 'name': item['softwareDescription']['longDescription'], 'key': item['keyName'], - 'referenceCode': item['softwareDescription']['referenceCode'] + 'referenceCode': item['softwareDescription']['referenceCode'], + 'prices': get_item_price(item['prices']) }) # Port speeds elif category == 'port_speed': port_speeds.append({ 'name': item['description'], 'speed': item['capacity'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices']) }) # Extras elif category in EXTRA_CATEGORIES: extras.append({ 'name': item['description'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices']) }) return { @@ -447,8 +452,8 @@ def _get_package(self): softwareDescription[id,referenceCode,longDescription], prices ], - activePresets, - accountRestrictedActivePresets, + activePresets[prices], + accountRestrictedActivePresets[prices], regions[location[location[priceGroups]]] ''' package = self.ordering_manager.get_package_by_key(self.package_keyname, mask=mask) @@ -742,6 +747,18 @@ def get_hardware_guests(self, instance_id): id=virtual_host['id']) return virtual_host + def get_hardware_item_prices(self, location): + """Returns the hardware server item prices by location. + + :param string location: location to get the item prices. + """ + object_mask = "filteredMask[pricingLocationGroup[locations[regions]]]" + object_filter = { + "itemPrices": {"pricingLocationGroup": {"locations": {"regions": {"keyname": {"operation": location}}}}}} + package = self.ordering_manager.get_package_by_key(self.package_keyname) + return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, + id=package['id']) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" @@ -837,3 +854,23 @@ def _get_location(package, location): return region raise SoftLayerError("Could not find valid location for: '%s'" % location) + + +def _get_preset_cost(prices, type_cost): + """Get the preset cost.""" + item_cost = 0.00 + for price in prices: + if type_cost is 'hourly': + item_cost += float(price['hourlyRecurringFee']) + else: + item_cost += float(price['recurringFee']) + return item_cost + + +def get_item_price(prices): + """Get item prices""" + prices_list = [] + for price in prices: + if not price['locationGroupId']: + prices_list.append(price) + return prices_list diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index bf0d09bbf..ed72ee1b6 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -646,3 +646,16 @@ def get_location_id(self, location): if len(datacenter) != 1: raise exceptions.SoftLayerError("Unable to find location: %s" % location) return datacenter[0]['id'] + + def get_item_prices_by_location(self, location, package_keyname): + """Returns the hardware server item prices by location. + + :param string package_keyname: The package for which to get the items. + :param string location: location to get the item prices. + """ + object_mask = "filteredMask[pricingLocationGroup[locations[regions]]]" + object_filter = { + "itemPrices": {"pricingLocationGroup": {"locations": {"regions": {"keyname": {"operation": location}}}}}} + package = self.get_package_by_key(package_keyname) + return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, + id=package['id']) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index a82b731fc..7b5a1dbec 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -45,6 +45,32 @@ def test_item_list(self): self.assertIn('testing', result.output) self.assertIn('item2', result.output) + def test_item_list_prices(self): + result = self.run_command(['order', 'item-list', '--prices=true', 'package']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[0]['Hourly'], 0.0) + self.assertEqual(output[1]['CRMim'], '-') + self.assertEqual(output[1]['keyName'], 'KeyName015') + self.assert_called_with('SoftLayer_Product_Package', 'getItems') + + def test_item_list_location(self): + result = self.run_command(['order', 'item-list', '--location=AMSTERDAM02', 'package']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[0]['Hourly'], '.093') + self.assertEqual(output[1]['keyName'], 'GUEST_DISK_100_GB_LOCAL_3') + self.assertEqual(output[1]['CRMax'], '-') + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + + def test_item_list_prices_location(self): + result = self.run_command(['order', 'item-list', '--prices=true', '--location=AMSTERDAM02', 'package']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + def test_package_list(self): p_mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') p_mock.return_value = _get_all_packages() diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 3e7bc0ff5..587124a96 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -363,6 +363,32 @@ def test_create_options(self): self.assertEqual(output[0][0]['Value'], 'wdc01') self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + def test_create_options_prices(self): + result = self.run_command(['server', 'create-options', '--prices=true']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[1][0]['Hourly'], 1.18) + self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') + self.assertEqual(output[3][0]['capacityRestrictionMaximum'], '-') + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + + def test_create_options_location(self): + result = self.run_command(['server', 'create-options', '--location=AMSTERDAM02']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[1][0]['Monthly'], 780.0) + self.assertEqual(output[1][0]['Hourly'], 1.18) + self.assertEqual(output[1][0]['Value'], 'S1270_8GB_2X1TBSATA_NORAID') + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + + def test_create_options_prices_location(self): + result = self.run_command(['server', 'create-options', '--prices=true', '--location=AMSTERDAM02']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): order_mock.return_value = { diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e68989df0..b6cfc7724 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -133,15 +133,107 @@ def test_get_create_options(self): } sizes = { 'key': 'S1270_8GB_2X1TBSATA_NORAID', - 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID' + 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', + 'hourlyRecurringFee': 1.18, + 'recurringFee': 780.0 } - self.assertEqual(options['extras'][0], extras) + self.assertEqual(options['extras'][0]['key'], extras['key']) self.assertEqual(options['locations'][0], locations) - self.assertEqual(options['operating_systems'][0], operating_systems) + self.assertEqual(options['operating_systems'][0]['referenceCode'], + operating_systems['referenceCode']) self.assertEqual(options['port_speeds'][0]['name'], port_speeds['name']) self.assertEqual(options['sizes'][0], sizes) + def test_get_create_options_prices(self): + options = self.hardware.get_create_options() + + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + locations = {'key': 'wdc01', 'name': 'Washington 1'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + sizes = { + 'key': 'S1270_8GB_2X1TBSATA_NORAID', + 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', + 'hourlyRecurringFee': 1.18, + 'recurringFee': 780.0 + } + + self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], + extras['prices'][0]['hourlyRecurringFee']) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0]['prices'][0]['locationGroupId'], + operating_systems['prices'][0]['locationGroupId']) + self.assertEqual(options['port_speeds'][0]['prices'][0]['id'], port_speeds['prices'][0]['id']) + self.assertEqual(options['sizes'][0], sizes) + + def test_get_hardware_item_prices(self): + options = self.hardware.get_hardware_item_prices("MONTREAL") + item_prices = [ + { + "hourlyRecurringFee": ".093", + "id": 204015, + "recurringFee": "62", + "item": { + "description": "4 x 2.0 GHz or higher Cores", + "id": 859, + "keyName": "GUEST_CORES_4", + }, + "pricingLocationGroup": { + "id": 503, + "locations": [ + { + "id": 449610, + "longName": "Montreal 1", + "name": "mon01", + "regions": [ + { + "description": "MON01 - Montreal", + "keyname": "MONTREAL", + } + ] + } + ] + } + } + ] + + self.assertEqual(options[0]['item']['keyName'], item_prices[0]['item']['keyName']) + self.assertEqual(options[0]['hourlyRecurringFee'], item_prices[0]['hourlyRecurringFee']) + def test_get_create_options_package_missing(self): packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') packages.return_value = [] diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 0709eea7e..675ac290d 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -771,3 +771,37 @@ def test_get_item_capacity_intel(self): item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) self.assertEqual(24, int(item_capacity)) + + def test_get_item_prices_by_location(self): + options = self.ordering.get_item_prices_by_location("MONTREAL", "MONTREAL") + item_prices = [ + { + "hourlyRecurringFee": ".093", + "id": 204015, + "recurringFee": "62", + "item": { + "description": "4 x 2.0 GHz or higher Cores", + "id": 859, + "keyName": "GUEST_CORES_4", + }, + "pricingLocationGroup": { + "id": 503, + "locations": [ + { + "id": 449610, + "longName": "Montreal 1", + "name": "mon01", + "regions": [ + { + "description": "MON01 - Montreal", + "keyname": "MONTREAL", + } + ] + } + ] + } + } + ] + + self.assertEqual(options[0]['item']['keyName'], item_prices[0]['item']['keyName']) + self.assertEqual(options[0]['hourlyRecurringFee'], item_prices[0]['hourlyRecurringFee']) From 33b4f726a009c60ff06dfeec7281e24e2d3008b6 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 11 Aug 2020 15:10:56 -0400 Subject: [PATCH 0209/1385] Fix the tox analysis. --- SoftLayer/managers/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d658d6272..5caaa5005 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,11 +9,11 @@ import socket import time -from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager +from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -860,7 +860,7 @@ def _get_preset_cost(prices, type_cost): """Get the preset cost.""" item_cost = 0.00 for price in prices: - if type_cost is 'hourly': + if type_cost == 'hourly': item_cost += float(price['hourlyRecurringFee']) else: item_cost += float(price['recurringFee']) From f0a64542367d0e689cfc19ccf19c72954334a415 Mon Sep 17 00:00:00 2001 From: try Date: Thu, 13 Aug 2020 16:12:45 +0530 Subject: [PATCH 0210/1385] Review the change --- SoftLayer/CLI/block/refresh.py | 7 ++++--- SoftLayer/fixtures/SoftLayer_Network_Storage.py | 4 ++-- SoftLayer/managers/storage.py | 8 +++++--- tests/CLI/modules/block_tests.py | 2 +- tests/managers/block_tests.py | 8 ++++---- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 66a2f3c22..aebc5e668 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -1,4 +1,4 @@ -"""Refresh a dependent duplicate volume with a snapshot from its parent.""" +"""Refresh a duplicate volume with a snapshot from its parent.""" # :license: MIT, see LICENSE for more details. import click @@ -11,8 +11,9 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """"Refresh a dependent duplicate volume with a snapshot from its parent.""" + """"Refresh a duplicate volume with a snapshot from its parent.""" block_manager = SoftLayer.BlockStorageManager(env.client) - resp = block_manager.refresh_dep_dupe(volume_id, snapshot_id) + resp = block_manager.refresh_dupe(volume_id, snapshot_id) #remove dep_ in refresh_dep_dupe click.echo(resp) + diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 611e88005..6fc9ed3e3 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -234,8 +234,8 @@ 'provisionedCount': 100 } -refreshDependentDuplicate = { - 'dependentDuplicate': 1 +refreshDuplicate = { #remove Dependent from refreshDependentDuplicate + 'DependentDuplicate': 1 } convertCloneDependentToIndependent = { diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index fb5190d85..9f750272c 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -415,13 +415,15 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) - def refresh_dep_dupe(self, volume_id, snapshot_id): - """"Refresh a dependent duplicate volume with a snapshot from its parent. + def refresh_dupe(self, volume_id, snapshot_id): #remove dep + """"Refresh a duplicate volume with a snapshot from its parent. :param integer volume_id: The id of the volume :param integer snapshot_id: The id of the snapshot """ - return self.client.call('Network_Storage', 'refreshDependentDuplicate', snapshot_id, id=volume_id) + return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id) #remove Dependent + + def convert_dep_dupe(self, volume_id): """Convert a dependent duplicate volume to an independent volume. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index e5f6ba8c5..3aeaa0dc6 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -717,7 +717,7 @@ def test_volume_limit(self, list_mock): result = self.run_command(['block', 'volume-limits']) self.assert_no_fail(result) - def test_dep_dupe_refresh(self): + def test_dupe_refresh(self): #remove _dep in test_dep_dupe_refresh result = self.run_command(['block', 'volume-refresh', '102', '103']) self.assert_no_fail(result) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 1ba644236..cd99f6221 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1043,13 +1043,13 @@ def test_get_ids_from_username_empty(self): self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') self.assertEqual([], result) - def test_refresh_block_depdupe(self): - result = self.block.refresh_dep_dupe(123, snapshot_id=321) - self.assertEqual(SoftLayer_Network_Storage.refreshDependentDuplicate, result) + def test_refresh_block_dupe(self): #remove dep in block_depdupe + result = self.block.refresh_dupe(123, snapshot_id=321) #remove dep in refresh_dep_dupe + self.assertEqual(SoftLayer_Network_Storage.refreshDuplicate, result) #remove Dependent in refreshDependentDuplicate self.assert_called_with( 'SoftLayer_Network_Storage', - 'refreshDependentDuplicate', + 'refreshDuplicate', #remove Dependent in refreshDependentDuplicate identifier=123 ) From 2c66f4c368aed25e1bc12e04c9d79abd02da8d5b Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 13 Aug 2020 12:45:01 -0400 Subject: [PATCH 0211/1385] #1318 add Drive number in guest drives details using the device number --- SoftLayer/CLI/virt/detail.py | 10 +++------- SoftLayer/CLI/virt/storage.py | 27 +++++++++++++++++++++------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index c07ef657c..94c0c7994 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -9,7 +9,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers -from SoftLayer.CLI.virt.storage import get_local_type +from SoftLayer.CLI.virt.storage import get_local_storage_table from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -35,11 +35,7 @@ def cli(env, identifier, passwords=False, price=False): result = utils.NestedDict(result) local_disks = vsi.get_local_disks(vs_id) - table_local_disks = formatting.Table(['Type', 'Name', 'Capacity']) - for disks in local_disks: - if 'diskImage' in disks: - table_local_disks.add_row([get_local_type(disks), disks['mountType'], - str(disks['diskImage']['capacity']) + " " + str(disks['diskImage']['units'])]) + table_local_disks = get_local_storage_table(local_disks) table.add_row(['id', result['id']]) table.add_row(['guid', result['globalIdentifier']]) @@ -173,7 +169,7 @@ def _get_owner_row(result): owner = utils.lookup(result, 'billingItem', 'orderItem', 'order', 'userRecord', 'username') else: owner = formatting.blank() - return(['owner', owner]) + return (['owner', owner]) def _get_vlan_table(result): diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py index 802ae32d9..90252207f 100644 --- a/SoftLayer/CLI/virt/storage.py +++ b/SoftLayer/CLI/virt/storage.py @@ -48,11 +48,8 @@ def cli(env, identifier): nas['allowedVirtualGuests'][0]['datacenter']['longName'], nas.get('notes', None)]) - table_local_disks = formatting.Table(['Type', 'Name', 'Capacity'], title="Other storage details") - for disks in local_disks: - if 'diskImage' in disks: - table_local_disks.add_row([get_local_type(disks), disks['mountType'], - str(disks['diskImage']['capacity']) + " " + str(disks['diskImage']['units'])]) + table_local_disks = get_local_storage_table(local_disks) + table_local_disks.title = "Other storage details" env.fout(table_credentials) env.fout(table_iscsi) @@ -64,10 +61,28 @@ def cli(env, identifier): def get_local_type(disks): """Returns the virtual server local disk type. - :param disks: virtual serve local disks. + :param disks: virtual server local disks. """ disk_type = 'System' if 'SWAP' in disks.get('diskImage', {}).get('description', []): disk_type = 'Swap' return disk_type + + +def get_local_storage_table(local_disks): + """Returns a formatting local disk table + + :param local_disks: virtual server local disks. + """ + table_local_disks = formatting.Table(['Type', 'Name', 'Drive', 'Capacity']) + for disk in local_disks: + if 'diskImage' in disk: + table_local_disks.add_row([ + get_local_type(disk), + disk['mountType'], + disk['device'], + "{capacity} {unit}".format(capacity=disk['diskImage']['capacity'], + unit=disk['diskImage']['units']) + ]) + return table_local_disks From abb1da7e5a4840a822b6b1fb1de649d2522fa9ad Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 13 Aug 2020 14:49:33 -0400 Subject: [PATCH 0212/1385] add vs list hardware and all option --- SoftLayer/CLI/virt/list.py | 50 +++++++++++++++++--------------- tests/CLI/modules/vs/vs_tests.py | 4 +++ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 1a3c4d545..e80417657 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -52,6 +52,8 @@ @click.option('--hourly', is_flag=True, help='Show only hourly instances') @click.option('--monthly', is_flag=True, help='Show only monthly instances') @click.option('--transient', help='Filter by transient instances', type=click.BOOL) +@click.option('--hardware', is_flag=True, default=False, help='Show the all VSI related to hardware') +@click.option('--all', is_flag=True, default=False, help='Show the all VSI and hardware VSIs') @helpers.multi_option('--tag', help='Filter by tags') @click.option('--sortby', help='Column to sort by', @@ -69,7 +71,7 @@ show_default=True) @environment.pass_env def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, - hourly, monthly, tag, columns, limit, transient): + hourly, monthly, tag, columns, limit, transient, all, hardware): """List virtual servers.""" vsi = SoftLayer.VSManager(env.client) @@ -88,27 +90,29 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, table = formatting.Table(columns.columns) table.sortby = sortby - for guest in guests: - table.add_row([value or formatting.blank() - for value in columns.row(guest)]) + if not hardware or all: + for guest in guests: + table.add_row([value or formatting.blank() + for value in columns.row(guest)]) - env.fout(table) + env.fout(table) - hardware_guests = vsi.get_hardware_guests() - for hardware in hardware_guests: - if hardware['virtualHost']['guests']: - title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hardware['id']) - table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', - 'powerState'], title=title) - table_hardware_guest.sortby = 'hostname' - for guest in hardware['virtualHost']['guests']: - table_hardware_guest.add_row([ - guest['id'], - guest['hostname'], - '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), - guest['maxMemory'], - utils.clean_time(guest['createDate']), - guest['status']['keyName'], - guest['powerState']['keyName'] - ]) - env.fout(table_hardware_guest) + if hardware or all: + hardware_guests = vsi.get_hardware_guests() + for hardware in hardware_guests: + if hardware['virtualHost']['guests']: + title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hardware['id']) + table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', + 'powerState'], title=title) + table_hardware_guest.sortby = 'hostname' + for guest in hardware['virtualHost']['guests']: + table_hardware_guest.add_row([ + guest['id'], + guest['hostname'], + '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), + guest['maxMemory'], + utils.clean_time(guest['createDate']), + guest['status']['keyName'], + guest['powerState']['keyName'] + ]) + env.fout(table_hardware_guest) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 06d3147ac..bce887178 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -846,3 +846,7 @@ def test_vs_migrate_exception(self): self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100) + + def test_list_vsi(self): + result = self.run_command(['vs', 'list', '--hardware']) + self.assert_no_fail(result) From 487ae59238faf4addb1c031fd28af1ba890af355 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 13 Aug 2020 17:22:53 -0400 Subject: [PATCH 0213/1385] fix tox tool --- SoftLayer/CLI/virt/list.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index e80417657..ffb9fd4a0 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -53,7 +53,7 @@ @click.option('--monthly', is_flag=True, help='Show only monthly instances') @click.option('--transient', help='Filter by transient instances', type=click.BOOL) @click.option('--hardware', is_flag=True, default=False, help='Show the all VSI related to hardware') -@click.option('--all', is_flag=True, default=False, help='Show the all VSI and hardware VSIs') +@click.option('--all-guests', is_flag=True, default=False, help='Show the all VSI and hardware VSIs') @helpers.multi_option('--tag', help='Filter by tags') @click.option('--sortby', help='Column to sort by', @@ -71,7 +71,7 @@ show_default=True) @environment.pass_env def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, - hourly, monthly, tag, columns, limit, transient, all, hardware): + hourly, monthly, tag, columns, limit, transient, hardware, all_guests): """List virtual servers.""" vsi = SoftLayer.VSManager(env.client) @@ -90,22 +90,22 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, table = formatting.Table(columns.columns) table.sortby = sortby - if not hardware or all: + if not hardware or all_guests: for guest in guests: table.add_row([value or formatting.blank() for value in columns.row(guest)]) env.fout(table) - if hardware or all: + if hardware or all_guests: hardware_guests = vsi.get_hardware_guests() - for hardware in hardware_guests: - if hardware['virtualHost']['guests']: - title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hardware['id']) + for hd_guest in hardware_guests: + if hd_guest['virtualHost']['guests']: + title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hd_guest['id']) table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', 'powerState'], title=title) table_hardware_guest.sortby = 'hostname' - for guest in hardware['virtualHost']['guests']: + for guest in hd_guest['virtualHost']['guests']: table_hardware_guest.add_row([ guest['id'], guest['hostname'], From 00cc11f4dc2baedd632f039288b501071628e97a Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 Aug 2020 09:56:57 -0400 Subject: [PATCH 0214/1385] fix the ha option firewall add and implement unit test --- SoftLayer/CLI/firewall/add.py | 2 +- .../fixtures/SoftLayer_Product_Package.py | 22 +++++++++----- tests/CLI/modules/firewall_tests.py | 29 +++++++++++++++++++ 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/firewall/add.py b/SoftLayer/CLI/firewall/add.py index 5eba9b778..12c7abef3 100644 --- a/SoftLayer/CLI/firewall/add.py +++ b/SoftLayer/CLI/firewall/add.py @@ -15,7 +15,7 @@ type=click.Choice(['vs', 'vlan', 'server']), help='Firewall type', required=True) -@click.option('--ha', '--high-availability', +@click.option('-ha', '--high-availability', is_flag=True, help='High available firewall option') @environment.pass_env diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index b131f8916..07aefab69 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -833,6 +833,7 @@ 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, 'prices': [{'id': 1122, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], @@ -845,6 +846,7 @@ 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, 'prices': [{'id': 4477, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], @@ -857,6 +859,7 @@ 'itemCategory': {'categoryCode': 'RAM'}, 'prices': [{'id': 1133, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 3, 'name': 'RAM', 'categoryCode': 'ram'}]}], @@ -870,6 +873,7 @@ 'itemCategory': {'categoryCode': 'Computing Instance'}, 'prices': [{'id': 1007, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], @@ -884,6 +888,7 @@ 'prices': [{'id': 1144, 'locationGroupId': None, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], @@ -898,6 +903,7 @@ 'prices': [{'id': 332211, 'locationGroupId': 1, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], @@ -908,7 +914,7 @@ 'capacity': '1', 'description': '1 GB iSCSI Storage', 'itemCategory': {'categoryCode': 'iscsi'}, - 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 1121, @@ -924,7 +930,7 @@ 'capacity': '4', 'description': '4 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 8880, @@ -932,7 +938,7 @@ 'capacity': '8', 'description': '8 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 44400, @@ -940,7 +946,7 @@ 'capacity': '4', 'description': '4 Portable Private IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_priv'}, - 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 88800, @@ -948,7 +954,7 @@ 'capacity': '8', 'description': '8 Portable Private IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_priv'}, - 'prices': [{'id': 88881, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 88881, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 10, @@ -956,7 +962,7 @@ 'capacity': '0', 'description': 'Global IPv4', 'itemCategory': {'categoryCode': 'global_ipv4'}, - 'prices': [{'id': 11, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 11, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 66464, @@ -964,7 +970,7 @@ 'capacity': '64', 'description': '/64 Block Portable Public IPv6 Addresses', 'itemCategory': {'categoryCode': 'static_ipv6_addresses'}, - 'prices': [{'id': 664641, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 664641, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 610, @@ -972,7 +978,7 @@ 'capacity': '0', 'description': 'Global IPv6', 'itemCategory': {'categoryCode': 'global_ipv6'}, - 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }] getItemPricesISCSI = [ diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index f83022d7e..270619b2f 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -5,6 +5,9 @@ :license: MIT, see LICENSE for more details. """ import json +from unittest import mock + +from SoftLayer.CLI import exceptions from SoftLayer import testing @@ -28,3 +31,29 @@ def test_list_firewalls(self): 'firewall id': 'server:1234', 'server/vlan id': 1, 'type': 'Server - standard'}]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_add_vs(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'add', '1000', '--firewall-type=vlan', '-ha']) + self.assert_no_fail(result) + self.assertIn("Firewall is being created!", result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_add_vlan(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'add', '1000', '--firewall-type=vs']) + self.assert_no_fail(result) + self.assertIn("Firewall is being created!", result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_add_server(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) + self.assert_no_fail(result) + self.assertIn("Firewall is being created!", result.output) + + def test_add_server_fail(self): + result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) From 8a6cf5e8166e08c769adf962c39a55ac0436c3a4 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 Aug 2020 10:19:05 -0400 Subject: [PATCH 0215/1385] fix tox tool --- tests/CLI/modules/firewall_tests.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 270619b2f..a90038065 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -52,8 +52,3 @@ def test_add_server(self, confirm_mock): result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) self.assert_no_fail(result) self.assertIn("Firewall is being created!", result.output) - - def test_add_server_fail(self): - result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) From fa8c48357225778e4f93a7c1363bb074b03b0d9f Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 Aug 2020 10:26:12 -0400 Subject: [PATCH 0216/1385] fix tox tool --- tests/CLI/modules/firewall_tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index a90038065..b3ac8c0b2 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -7,8 +7,6 @@ import json from unittest import mock -from SoftLayer.CLI import exceptions - from SoftLayer import testing From 7a3d4d175e68e30e57be68b6df4046a26fea9565 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 18 Aug 2020 14:44:54 -0500 Subject: [PATCH 0217/1385] v5.9.0 changelog --- CHANGELOG.md | 3 +++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92939ef75..6cb588bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 - #1313 Added support for filteredMask - #1305 Update docs links - #1302 Fix lots of whitespace slcli vs create-options +- #900 Support for STDIN on creating and updating tickets. +- #1318 add Drive number in guest drives details using the device number +- #1323 add vs list hardware and all option ## [5.8.9] - 2020-07-06 https://github.com/softlayer/softlayer-python/compare/v5.8.8...v5.8.9 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index cdcdf07ed..8dd619aa4 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.9' +VERSION = 'v5.9.0' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 8b8308901..33df75d76 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.9', + version='5.9.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 169837d621c2e5e2b82e6a787c5eb74135065a67 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 21 Aug 2020 15:35:18 -0500 Subject: [PATCH 0218/1385] #972 added BluePages and IntegratedOfferingTeam_Region to list of prefixes to not add SoftLayer_ to --- SoftLayer/API.py | 9 ++++----- SoftLayer/fixtures/BluePages_Search.py | 1 + tests/api_tests.py | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 SoftLayer/fixtures/BluePages_Search.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index b20b13aaa..e353ae1af 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -162,8 +162,7 @@ def authenticate_with_password(self, username, password, :param string username: your SoftLayer username :param string password: your SoftLayer password :param int security_question_id: The security question id to answer - :param string security_question_answer: The answer to the security - question + :param string security_question_answer: The answer to the security question """ self.auth = None @@ -202,8 +201,7 @@ def call(self, service, method, *args, **kwargs): :param dict raw_headers: (optional) HTTP transport headers :param int limit: (optional) return at most this many results :param int offset: (optional) offset results by this many - :param boolean iter: (optional) if True, returns a generator with the - results + :param boolean iter: (optional) if True, returns a generator with the results :param bool verify: verify SSL cert :param cert: client certificate path @@ -224,7 +222,8 @@ def call(self, service, method, *args, **kwargs): raise TypeError( 'Invalid keyword arguments: %s' % ','.join(invalid_kwargs)) - if self._prefix and not service.startswith(self._prefix): + prefixes = (self._prefix, 'BluePages_Search', 'IntegratedOfferingTeam_Region') + if self._prefix and not service.startswith(prefixes): service = self._prefix + service http_headers = {'Accept': '*/*'} diff --git a/SoftLayer/fixtures/BluePages_Search.py b/SoftLayer/fixtures/BluePages_Search.py new file mode 100644 index 000000000..24830c54f --- /dev/null +++ b/SoftLayer/fixtures/BluePages_Search.py @@ -0,0 +1 @@ +findBluePagesProfile = True \ No newline at end of file diff --git a/tests/api_tests.py b/tests/api_tests.py index 4f1a31e66..39f596b3c 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -259,6 +259,11 @@ def test_call_compression_override(self): headers = calls[0].transport_headers self.assertEqual(headers.get('accept-encoding'), 'gzip') + def test_special_services(self): + # Tests for the special classes that don't need to start with SoftLayer_ + self.client.call('BluePages_Search', 'findBluePagesProfile') + self.assert_called_with('BluePages_Search', 'findBluePagesProfile') + class UnauthenticatedAPIClient(testing.TestCase): def set_up(self): From 1f1e00326e559397d630a045a98f3ed6df6b86fa Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 21 Aug 2020 15:47:38 -0500 Subject: [PATCH 0219/1385] fixed whitespace issue --- SoftLayer/API.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index e353ae1af..430ed2d14 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -222,7 +222,7 @@ def call(self, service, method, *args, **kwargs): raise TypeError( 'Invalid keyword arguments: %s' % ','.join(invalid_kwargs)) - prefixes = (self._prefix, 'BluePages_Search', 'IntegratedOfferingTeam_Region') + prefixes = (self._prefix, 'BluePages_Search', 'IntegratedOfferingTeam_Region') if self._prefix and not service.startswith(prefixes): service = self._prefix + service From d1358b1e66a49d4c13cbfbb74f652bb025792622 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 21 Aug 2020 15:57:24 -0500 Subject: [PATCH 0220/1385] tox issue --- SoftLayer/fixtures/BluePages_Search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/BluePages_Search.py b/SoftLayer/fixtures/BluePages_Search.py index 24830c54f..9682f63dc 100644 --- a/SoftLayer/fixtures/BluePages_Search.py +++ b/SoftLayer/fixtures/BluePages_Search.py @@ -1 +1 @@ -findBluePagesProfile = True \ No newline at end of file +findBluePagesProfile = True From f0f5a21afbd41a8731b285b4288c6a62561d1223 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 25 Aug 2020 11:45:13 -0400 Subject: [PATCH 0221/1385] implement unit test to classes not pass the coverage --- SoftLayer/fixtures/SoftLayer_Account.py | 31 ++++++++++-- .../fixtures/SoftLayer_Network_Subnet.py | 1 + .../SoftLayer_Network_Subnet_IpAddress.py | 21 ++++++++ SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 1 + .../SoftLayer_Network_Vlan_Firewall.py | 43 +++++++++++++++- .../SoftLayer_Security_Certificate.py | 20 +++++++- tests/CLI/modules/firewall_tests.py | 49 +++++++++++++++++-- tests/CLI/modules/globalip_tests.py | 19 +++++++ tests/CLI/modules/ssl_tests.py | 36 ++++++++++++++ tests/CLI/modules/subnet_tests.py | 27 ++++++++++ tests/CLI/modules/vlan_tests.py | 5 ++ tests/CLI/modules/vs/vs_tests.py | 8 +++ 12 files changed, 251 insertions(+), 10 deletions(-) create mode 100644 tests/CLI/modules/ssl_tests.py diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index dcb6d32f0..04a29de96 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -397,7 +397,8 @@ getSecurityCertificates = [{'certificate': '1234', 'commonName': 'cert', - 'id': 1234}] + 'id': 1234, + 'validityDays': 0, }] getExpiredSecurityCertificates = getSecurityCertificates getValidSecurityCertificates = getSecurityCertificates @@ -490,6 +491,7 @@ 'networkSpace': 'PRIVATE', 'hardwareCount': 0, 'hardware': [], + 'vlanNumber': 12, 'networkComponents': [], 'primaryRouter': { 'datacenter': {'name': 'dal00'} @@ -502,13 +504,26 @@ 'totalPrimaryIpAddressCount': 1, 'subnetCount': 0, 'subnets': [], + 'firewallInterfaces': [ + { + 'id': 1, + 'name': 'outside' + }, + { + 'id': 12, + 'name': 'inside' + } + ] }, { 'id': 2, 'networkSpace': 'PRIVATE', 'totalPrimaryIpAddressCount': 2, 'dedicatedFirewallFlag': False, + 'highAvailabilityFirewallFlag': True, + 'networkVlanFirewall': {'id': 7896}, 'hardwareCount': 0, 'hardware': [], + 'vlanNumber': 13, 'networkComponents': [], 'primaryRouter': { 'datacenter': {'name': 'dal00'} @@ -523,6 +538,8 @@ 'id': 1234, 'networkComponent': {'downlinkComponent': {'hardwareId': 1}}, 'status': 'ok'}], + 'firewallInterfaces': [], + 'subnetCount': 0, 'subnets': [], }, { @@ -530,11 +547,20 @@ 'networkSpace': 'PRIVATE', 'name': 'dal00', 'hardwareCount': 1, + 'dedicatedFirewallFlag': True, + 'highAvailabilityFirewallFlag': True, + 'networkVlanFirewall': {'id': 23456}, + 'vlanNumber': 14, 'hardware': [{'id': 1}], 'networkComponents': [{'id': 2}], 'primaryRouter': { 'datacenter': {'name': 'dal00'} }, + 'firewallInterfaces': [ + { + 'id': 31, + 'name': 'outside' + }], 'totalPrimaryIpAddressCount': 3, 'subnetCount': 0, 'subnets': [], @@ -662,7 +688,6 @@ 'id': 12345 }] - getUsers = [ {'displayName': 'ChristopherG', 'hardwareCount': 138, @@ -742,7 +767,6 @@ } ] - getPlacementGroups = [{ "createDate": "2019-01-18T16:08:44-06:00", "id": 12345, @@ -893,7 +917,6 @@ } ] - getPortableStorageVolumes = [ { "capacity": 200, diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index 24683da15..ac3b9d74a 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -43,3 +43,4 @@ editNote = True setTags = True +cancel = True diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py index 15778d238..46ae38cf0 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py @@ -6,6 +6,27 @@ 'isNetwork': False, 'isReserved': False, 'subnetId': 5678, + "hardware": { + "id": 12856, + "fullyQualifiedDomainName": "unit.test.com" + }, + "subnet": { + "broadcastAddress": "10.0.1.91", + "cidr": 26, + "gateway": "10.47.16.129", + "id": 258369, + "isCustomerOwned": False, + "isCustomerRoutable": False, + "modifyDate": "2019-04-02T13:45:52-06:00", + "netmask": "255.255.255.192", + "networkIdentifier": "10.0.1.38", + "networkVlanId": 1236987, + "sortOrder": "0", + "subnetType": "PRIMARY", + "totalIpAddresses": "64", + "usableIpAddressCount": "61", + "version": 4 + } } editObject = True diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index b18632534..758fe3b39 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -10,3 +10,4 @@ editObject = True setTags = True +getList = [getObject] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py index c956bf5c0..5d78cf53b 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py @@ -41,7 +41,45 @@ ] } ] - } + }, + "rules": [ + {'destinationIpAddress': 'any on server', + 'protocol': 'tcp', + 'orderValue': 1, + 'destinationIpSubnetMask': '255.255.255.255', + 'destinationPortRangeStart': 80, + 'sourceIpSubnetMask': '0.0.0.0', + 'destinationPortRangeEnd': 80, + 'version': 4, + 'action': 'permit', + 'sourceIpAddress': '0.0.0.0' + }, + { + 'destinationIpAddress': 'any on server', + 'protocol': 'tcp', + 'orderValue': 2, + 'destinationIpSubnetMask': '255.255.255.255', + 'destinationPortRangeStart': 1, + 'sourceIpSubnetMask': '255.255.255.255', + 'destinationPortRangeEnd': 65535, + 'version': 4, + 'action': 'permit', + 'sourceIpAddress': '193.212.1.10' + }, + { + 'destinationIpAddress': 'any on server', + 'protocol': 'tcp', + 'orderValue': 3, + 'destinationIpSubnetMask': '255.255.255.255', + 'destinationPortRangeStart': 80, + 'sourceIpSubnetMask': '0.0.0.0', + 'destinationPortRangeEnd': 800, + 'version': 4, + 'action': 'permit', + 'sourceIpAddress': '0.0.0.0' + } + ] + } getRules = [ @@ -59,7 +97,7 @@ }, { 'destinationIpAddress': 'any on server', - 'protocol': 'tcp', + 'protocol': 'tmp', 'orderValue': 2, 'destinationIpSubnetMask': '255.255.255.255', 'destinationPortRangeStart': 1, @@ -82,3 +120,4 @@ 'sourceIpAddress': '0.0.0.0' } ] +edit = True diff --git a/SoftLayer/fixtures/SoftLayer_Security_Certificate.py b/SoftLayer/fixtures/SoftLayer_Security_Certificate.py index fba859384..9f79ca2cc 100644 --- a/SoftLayer/fixtures/SoftLayer_Security_Certificate.py +++ b/SoftLayer/fixtures/SoftLayer_Security_Certificate.py @@ -1,4 +1,22 @@ -getObject = {} +getObject = { + "certificate": "-----BEGIN CERTIFICATE----- \nMIIEJTCCAw2gAwIBAgIDCbQ0MA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVT" + " -----END CERTIFICATE-----", + "certificateSigningRequest": "-----BEGIN CERTIFICATE REQUEST-----\n" + "MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G\n" + "-----END CERTIFICATE REQUEST-----", + "commonName": "techbabble.xyz", + "createDate": "2016-01-20T10:56:44-06:00", + "id": 123456, + "intermediateCertificate": "", + "keySize": 258369, + "modifyDate": "2019-06-05T14:10:40-06:00", + "organizationName": "Unspecified", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxf67Ayf07eutrK8OAjWXtwQSdE\n" + "-----END RSA PRIVATE KEY-----", + "validityBegin": "2016-01-19T17:59:37-06:00", + "validityDays": 0, + "validityEnd": "2017-01-20T13:48:41-06:00" +} createObject = {} editObject = True deleteObject = True diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index b3ac8c0b2..7362f1557 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -17,10 +17,14 @@ def test_list_firewalls(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - [{'type': 'VLAN - dedicated', + [{'features': ['HA'], + 'firewall id': 'vlan:1234', 'server/vlan id': 1, - 'features': ['HA'], - 'firewall id': 'vlan:1234'}, + 'type': 'VLAN - dedicated'}, + {'features': ['HA'], + 'firewall id': 'vlan:23456', + 'server/vlan id': 3, + 'type': 'VLAN - dedicated'}, {'features': '-', 'firewall id': 'vs:1234', 'server/vlan id': 1, @@ -50,3 +54,42 @@ def test_add_server(self, confirm_mock): result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) self.assert_no_fail(result) self.assertIn("Firewall is being created!", result.output) + + def test_detail(self): + result = self.run_command(['firewall', 'detail', 'vlan:1234']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + [{'#': 1, + 'action': 'permit', + 'dest': 'any on server:80-80', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}, + {'#': 2, + 'action': 'permit', + 'dest': 'any on server:1-65535', + 'dest_mask': '255.255.255.255', + 'protocol': 'tmp', + 'src_ip': '193.212.1.10', + 'src_mask': '255.255.255.255'}, + {'#': 3, + 'action': 'permit', + 'dest': 'any on server:80-800', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_cancel_firewall(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'cancel', 'vlan:1234']) + self.assert_no_fail(result) + self.assertIn("Firewall with id vlan:1234 is being cancelled!", result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_edit(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'edit', 'vlan:1234']) + print(result.output) diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 00716c563..6f2ee40d5 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -66,3 +66,22 @@ def test_ip_list(self): 'id': '201', 'ip': '127.0.0.1', 'target': '127.0.0.1 (example.com)'}]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['globalip', 'create', '-v6']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), [{ + "item": "this is a thing", + "cost": "2.00" + }, + { + "item": "Total monthly cost", + "cost": "2.00" + }]) + + def test_ip_unassign(self): + result = self.run_command(['globalip', 'unassign', '1']) + self.assert_no_fail(result) + self.assertEqual(result.output, "") diff --git a/tests/CLI/modules/ssl_tests.py b/tests/CLI/modules/ssl_tests.py new file mode 100644 index 000000000..79b04df41 --- /dev/null +++ b/tests/CLI/modules/ssl_tests.py @@ -0,0 +1,36 @@ +""" + SoftLayer.tests.CLI.modules.ssl_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer import testing + +import json +import mock + + +class SslTests(testing.TestCase): + def test_list(self): + result = self.run_command(['ssl', 'list', '--status', 'all']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), [ + { + "id": 1234, + "common_name": "cert", + "days_until_expire": 0, + "notes": None + } + ]) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_remove(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['ssl', 'remove', '123456']) + self.assert_no_fail(result) + self.assertEqual(result.exit_code, 0) + + def test_download(self): + result = self.run_command(['ssl', 'download', '123456']) + self.assert_no_fail(result) + self.assertEqual(result.exit_code, 0) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 52de2cd27..57e7dbbb4 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -157,3 +157,30 @@ def test_editrou_Id(self): result = self.run_command(['subnet', 'edit-ip', '123456', '--note=test']) self.assert_no_fail(result) self.assertTrue(result) + + def test_lookup(self): + result = self.run_command(['subnet', 'lookup', '1.2.3.10']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), {'device': { + 'id': 12856, + 'name': 'unit.test.com', + 'type': 'server'}, + "id": 12345, + "ip": "10.0.1.37", + "subnet": { + "id": 258369, + "identifier": "10.0.1.38/26", + "netmask": "255.255.255.192", + "gateway": "10.47.16.129", + "type": "PRIMARY" + }}) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_cancel(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['subnet', 'cancel', '1234']) + self.assert_no_fail(result) + + def test_cancel_fail(self): + result = self.run_command(['subnet', 'cancel', '1234']) + self.assertEqual(result.exit_code, 2) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 1cdd3f3f5..ee606f513 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -94,3 +94,8 @@ def test_vlan_edit_failure(self, click): click.secho.assert_called_with('Failed to edit the vlan', fg='red') self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) + + def test_vlan_list(self): + result = self.run_command(['vlan', 'list']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getNetworkVlans') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 0a5520121..c9b6c9c0b 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -882,3 +882,11 @@ def test_vs_migrate_exception(self): def test_list_vsi(self): result = self.run_command(['vs', 'list', '--hardware']) self.assert_no_fail(result) + + def test_credentail(self): + result = self.run_command(['vs', 'credentials', '100']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), [{ + "username": "user", + "password": "pass" + }]) From 689c6ff10954b7c68a30ca038e5a293665b71c99 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 Aug 2020 13:45:54 -0500 Subject: [PATCH 0222/1385] #1330 TOX fixes for the latest version --- SoftLayer/CLI/exceptions.py | 6 +++--- SoftLayer/CLI/formatting.py | 4 ++-- SoftLayer/CLI/hardware/dns.py | 5 +++-- SoftLayer/CLI/metadata.py | 9 ++++----- SoftLayer/CLI/report/bandwidth.py | 6 +++--- SoftLayer/CLI/virt/dns.py | 5 +++-- SoftLayer/fixtures/SoftLayer_Account.py | 3 --- SoftLayer/managers/ordering.py | 13 +++++-------- SoftLayer/managers/storage_utils.py | 14 ++++++-------- SoftLayer/managers/vs_capacity.py | 4 ++-- SoftLayer/testing/__init__.py | 4 ++-- SoftLayer/transports.py | 14 ++++++++------ 12 files changed, 41 insertions(+), 46 deletions(-) diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index e3ae7ef45..611d854ea 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -12,7 +12,7 @@ class CLIHalt(SystemExit): """Smoothly halt the execution of the command. No error.""" def __init__(self, code=0, *args): - super(CLIHalt, self).__init__(*args) + super().__init__(*args) self.code = code def __str__(self): @@ -26,7 +26,7 @@ class CLIAbort(CLIHalt): """Halt the execution of the command. Gives an exit code of 2.""" def __init__(self, msg, *args): - super(CLIAbort, self).__init__(code=2, *args) + super().__init__(code=2, *args) self.message = msg @@ -34,5 +34,5 @@ class ArgumentError(CLIAbort): """Halt the execution of the command because of invalid arguments.""" def __init__(self, msg, *args): - super(ArgumentError, self).__init__(msg, *args) + super().__init__(msg, *args) self.message = "Argument Error: %s" % msg diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index d02ceb1a1..0462197c0 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -235,7 +235,7 @@ class SequentialOutput(list): def __init__(self, separator=os.linesep, *args, **kwargs): self.separator = separator - super(SequentialOutput, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def to_python(self): """returns itself, since it itself is a list.""" @@ -252,7 +252,7 @@ def default(self, obj): """Encode object if it implements to_python().""" if hasattr(obj, 'to_python'): return obj.to_python() - return super(CLIJSONEncoder, self).default(obj) + return super().default(obj) class Table(object): diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py index 3b7458003..9d8810920 100644 --- a/SoftLayer/CLI/hardware/dns.py +++ b/SoftLayer/CLI/hardware/dns.py @@ -60,5 +60,6 @@ def cli(env, identifier, a_record, aaaa_record, ptr, ttl): # done this way to stay within 80 character lines ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress'] dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl) - except KeyError: - raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName']) + except KeyError as ex: + message = "{} does not have an ipv6 address".format(instance['fullyQualifiedDomainName']) + raise exceptions.CLIAbort(message) from ex diff --git a/SoftLayer/CLI/metadata.py b/SoftLayer/CLI/metadata.py index 26d6f2d48..754974885 100644 --- a/SoftLayer/CLI/metadata.py +++ b/SoftLayer/CLI/metadata.py @@ -54,11 +54,10 @@ def cli(env, prop): meta_prop = META_MAPPING.get(prop) or prop env.fout(SoftLayer.MetadataManager().get(meta_prop)) - except SoftLayer.TransportError: - raise exceptions.CLIAbort( - 'Cannot connect to the backend service address. Make sure ' - 'this command is being ran from a device on the backend ' - 'network.') + except SoftLayer.TransportError as ex: + message = 'Cannot connect to the backend service address. Make sure '\ + 'this command is being ran from a device on the backend network.' + raise exceptions.CLIAbort(message) from ex def get_network(): diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index e2b15d981..23d1a157c 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -19,9 +19,9 @@ def _validate_datetime(ctx, param, value): try: return datetime.datetime.strptime(value, "%Y-%m-%d %H:%M:%S") - except (ValueError, TypeError): - raise click.BadParameter( - "not in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") + except (ValueError, TypeError) as ex: + message = "not in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'" + raise click.BadParameter(message) from ex def _get_pooled_bandwidth(env, start, end): diff --git a/SoftLayer/CLI/virt/dns.py b/SoftLayer/CLI/virt/dns.py index 26b4904cd..dfcc3003e 100644 --- a/SoftLayer/CLI/virt/dns.py +++ b/SoftLayer/CLI/virt/dns.py @@ -60,5 +60,6 @@ def cli(env, identifier, a_record, aaaa_record, ptr, ttl): # done this way to stay within 80 character lines ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress'] dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl) - except KeyError: - raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName']) + except KeyError as ex: + message = "{} does not have an ipv6 address".format(instance['fullyQualifiedDomainName']) + raise exceptions.CLIAbort(message) from ex diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index dcb6d32f0..fa0678cc5 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1,6 +1,3 @@ -# -*- coding: UTF-8 -*- - -# # pylint: disable=bad-continuation getPrivateBlockDeviceTemplateGroups = [{ 'accountId': 1234, 'blockDevices': [], diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index bf0d09bbf..9bedd4005 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -12,10 +12,7 @@ from SoftLayer import exceptions -CATEGORY_MASK = '''id, - isRequired, - itemCategory[id, name, categoryCode] - ''' +CATEGORY_MASK = '''id, isRequired, itemCategory[id, name, categoryCode]''' ITEM_MASK = '''id, keyName, description, itemCategory, categories, prices''' @@ -359,10 +356,10 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): # keyName with the current item we are searching for matching_item = [i for i in items if i['keyName'] == item_keyname][0] - except IndexError: - raise exceptions.SoftLayerError( - "Item {} does not exist for package {}".format(item_keyname, - package_keyname)) + except IndexError as ex: + message = "Item {} does not exist for package {}".format(item_keyname, + package_keyname) + raise exceptions.SoftLayerError(message) from ex # we want to get the price ID that has no location attached to it, # because that is the most generic price. verifyOrder/placeOrder diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 0d82d5e25..4419d4d62 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -533,10 +533,9 @@ def prepare_volume_order_object(manager, storage_type, location, size, # Find the ID for the requested location try: location_id = get_location_id(manager, location) - except ValueError: - raise exceptions.SoftLayerError( - "Invalid datacenter name specified. " - "Please provide the lower case short name (e.g.: dal09)") + except ValueError as ex: + message = "Invalid datacenter name specified. Please provide the lower case short name (e.g.: dal09)" + raise exceptions.SoftLayerError(message) from ex # Determine the category code to use for the order (and product package) order_type_is_saas, order_category_code = _get_order_type_and_category( @@ -676,10 +675,9 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, # Find the ID for the requested location try: location_id = get_location_id(manager, location) - except ValueError: - raise exceptions.SoftLayerError( - "Invalid datacenter name specified. " - "Please provide the lower case short name (e.g.: dal09)") + except ValueError as ex: + message = "Invalid datacenter name specified. Please provide the lower case short name (e.g.: dal09)" + raise exceptions.SoftLayerError(message) from ex # Get sizes and properties needed for the order volume_size = int(volume['capacityGb']) diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 813e2d565..8ce5cf250 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -143,8 +143,8 @@ def create_guest(self, capacity_id, test, guest_object): try: capacity_flavor = capacity['instances'][0]['billingItem']['item']['keyName'] flavor = _flavor_string(capacity_flavor, guest_object['primary_disk']) - except KeyError: - raise SoftLayerError("Unable to find capacity Flavor.") + except KeyError as ex: + raise SoftLayerError("Unable to find capacity Flavor.") from ex guest_object['flavor'] = flavor guest_object['datacenter'] = capacity['backendRouter']['datacenter']['name'] diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index f1404b423..563b02494 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -113,7 +113,7 @@ def setUp(self): # NOQA self.set_up() def tearDown(self): # NOQA - super(TestCase, self).tearDown() + super().tearDown() self.tear_down() self.mocks.clear() @@ -184,7 +184,7 @@ def assertRaises(self, exception, function_callable, *args, **kwds): # pylint: But switching to just using unittest breaks assertRaises because the format is slightly different. This basically just reformats the call so I don't have to re-write a bunch of tests. """ - with super(TestCase, self).assertRaises(exception) as cm: + with super().assertRaises(exception) as cm: function_callable(*args, **kwds) return cm.exception diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 5722e1867..d0afe3d3b 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -142,7 +142,7 @@ def __init__(self, items=None, total_count=0): #: total count of items that exist on the server. This is useful when #: paginating through a large list of objects. self.total_count = total_count - super(SoftLayerListResult, self).__init__(items) + super().__init__(items) class XmlRpcTransport(object): @@ -245,7 +245,7 @@ def __call__(self, request): '-32300': exceptions.TransportError, } _ex = error_mapping.get(ex.faultCode, exceptions.SoftLayerAPIError) - raise _ex(ex.faultCode, ex.faultString) + raise _ex(ex.faultCode, ex.faultString) from ex except requests.HTTPError as ex: raise exceptions.TransportError(ex.response.status_code, str(ex)) except requests.RequestException as ex: @@ -533,12 +533,14 @@ def __call__(self, call): try: module_path = 'SoftLayer.fixtures.%s' % call.service module = importlib.import_module(module_path) - except ImportError: - raise NotImplementedError('%s fixture is not implemented' % call.service) + except ImportError as ex: + message = '{} fixture is not implemented'.format(call.service) + raise NotImplementedError(message) from ex try: return getattr(module, call.method) - except AttributeError: - raise NotImplementedError('%s::%s fixture is not implemented' % (call.service, call.method)) + except AttributeError as ex: + message = '{}::{} fixture is not implemented'.format(call.service, call.method) + raise NotImplementedError(message) from ex def print_reproduceable(self, call): """Not Implemented""" From 4450dbcc77b8930fa40c8691bff6eaf9aba463a0 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 25 Aug 2020 15:19:00 -0400 Subject: [PATCH 0223/1385] fix and update the tox tool --- SoftLayer/fixtures/SoftLayer_Security_Certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Security_Certificate.py b/SoftLayer/fixtures/SoftLayer_Security_Certificate.py index 9f79ca2cc..466cfef14 100644 --- a/SoftLayer/fixtures/SoftLayer_Security_Certificate.py +++ b/SoftLayer/fixtures/SoftLayer_Security_Certificate.py @@ -2,7 +2,7 @@ "certificate": "-----BEGIN CERTIFICATE----- \nMIIEJTCCAw2gAwIBAgIDCbQ0MA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVT" " -----END CERTIFICATE-----", "certificateSigningRequest": "-----BEGIN CERTIFICATE REQUEST-----\n" - "MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G\n" + "MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh123456QMA4G\n" "-----END CERTIFICATE REQUEST-----", "commonName": "techbabble.xyz", "createDate": "2016-01-20T10:56:44-06:00", @@ -11,7 +11,7 @@ "keySize": 258369, "modifyDate": "2019-06-05T14:10:40-06:00", "organizationName": "Unspecified", - "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxf67Ayf07eutrK8OAjWXtwQSdE\n" + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxff07eutrK12345678WXtwQSdE\n" "-----END RSA PRIVATE KEY-----", "validityBegin": "2016-01-19T17:59:37-06:00", "validityDays": 0, From 0608306ed24b15b988278e4d6b6f4c87d2f715b4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 25 Aug 2020 16:42:44 -0400 Subject: [PATCH 0224/1385] #1322 add set notes for storage --- SoftLayer/CLI/block/detail.py | 3 ++ SoftLayer/CLI/block/list.py | 15 ++++++++- SoftLayer/CLI/block/set_note.py | 28 ++++++++++++++++ SoftLayer/CLI/file/detail.py | 5 ++- SoftLayer/CLI/file/list.py | 20 +++++++++--- SoftLayer/CLI/file/set_note.py | 28 ++++++++++++++++ SoftLayer/CLI/routes.py | 2 ++ .../fixtures/SoftLayer_Network_Storage.py | 1 + SoftLayer/managers/file.py | 1 + SoftLayer/managers/storage.py | 32 +++++++++++++------ 10 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 SoftLayer/CLI/block/set_note.py create mode 100644 SoftLayer/CLI/file/set_note.py diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 2e7b115e7..e0cdc8ed1 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -110,4 +110,7 @@ def cli(env, volume_id): original_volume_info.add_row(['Original Snapshot Name', block_volume['originalSnapshotName']]) table.add_row(['Original Volume Properties', original_volume_info]) + notes = '{}'.format(block_volume.get('notes', '')) + table.add_row(['Notes', notes]) + env.fout(table) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 948e6c127..7019166b8 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -34,6 +34,7 @@ column_helper.Column( 'created_by', ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), + column_helper.Column('notes', ('notes',), mask="notes"), ] DEFAULT_COLUMNS = [ @@ -47,9 +48,12 @@ 'ip_addr', 'lunId', 'active_transactions', - 'rep_partner_count' + 'rep_partner_count', + 'notes' ] +DEFAULT_NOTES_SIZE = 20 + @click.command() @click.option('--username', '-u', help='Volume username') @@ -75,8 +79,17 @@ def cli(env, sortby, columns, datacenter, username, storage_type): table = formatting.Table(columns.columns) table.sortby = sortby + reduce_notes(block_volumes) + for block_volume in block_volumes: table.add_row([value or formatting.blank() for value in columns.row(block_volume)]) env.fout(table) + + +def reduce_notes(block_volumes): + for block_volume in block_volumes: + if len(block_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: + shortened_notes = block_volume['notes'][:DEFAULT_NOTES_SIZE] + block_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/block/set_note.py b/SoftLayer/CLI/block/set_note.py new file mode 100644 index 000000000..eeef42e8e --- /dev/null +++ b/SoftLayer/CLI/block/set_note.py @@ -0,0 +1,28 @@ +"""Set note for an existing block storage volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('volume-id') +@click.option('--note', '-n', + type=str, + required=True, + help='Public notes related to a Storage volume') +@environment.pass_env +def cli(env, volume_id, note): + """Set note for an existing block storage volume.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') + + result = block_manager.volume_set_note(block_volume_id, note) + + if result: + click.echo("Set note successfully!") + + else: + click.echo("Note could not be set! Please verify your options and try again.") diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index cea86e351..8ab7b726d 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -29,7 +29,7 @@ def cli(env, volume_id): table.add_row(['Type', storage_type]) table.add_row(['Capacity (GB)', "%iGB" % file_volume['capacityGb']]) - used_space = int(file_volume['bytesUsed'])\ + used_space = int(file_volume['bytesUsed']) \ if file_volume['bytesUsed'] else 0 if used_space < (1 << 10): table.add_row(['Used Space', "%dB" % used_space]) @@ -126,4 +126,7 @@ def cli(env, volume_id): original_volume_info.add_row(['Original Snapshot Name', file_volume['originalSnapshotName']]) table.add_row(['Original Volume Properties', original_volume_info]) + notes = '{}'.format(file_volume.get('notes', '')) + table.add_row(['Notes', notes]) + env.fout(table) diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 86028f4ee..731d0ee1c 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -7,7 +7,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting - COLUMNS = [ column_helper.Column('id', ('id',), mask="id"), column_helper.Column('username', ('username',), mask="username"), @@ -18,7 +17,7 @@ 'storage_type', lambda b: b['storageType']['keyName'].split('_').pop(0) if 'storageType' in b and 'keyName' in b['storageType'] - and isinstance(b['storageType']['keyName'], str) + and isinstance(b['storageType']['keyName'], str) else '-', mask="storageType.keyName"), column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"), @@ -28,12 +27,13 @@ column_helper.Column('active_transactions', ('activeTransactionCount',), mask="activeTransactionCount"), column_helper.Column('mount_addr', ('fileNetworkMountAddress',), - mask="fileNetworkMountAddress",), + mask="fileNetworkMountAddress", ), column_helper.Column('rep_partner_count', ('replicationPartnerCount',), mask="replicationPartnerCount"), column_helper.Column( 'created_by', ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), + column_helper.Column('notes', ('notes',), mask="notes"), ] DEFAULT_COLUMNS = [ @@ -46,9 +46,12 @@ 'ip_addr', 'active_transactions', 'mount_addr', - 'rep_partner_count' + 'rep_partner_count', + 'notes', ] +DEFAULT_NOTES_SIZE = 20 + @click.command() @click.option('--username', '-u', help='Volume username') @@ -74,8 +77,17 @@ def cli(env, sortby, columns, datacenter, username, storage_type): table = formatting.Table(columns.columns) table.sortby = sortby + reduce_notes(file_volumes) + for file_volume in file_volumes: table.add_row([value or formatting.blank() for value in columns.row(file_volume)]) env.fout(table) + + +def reduce_notes(file_volumes): + for file_volume in file_volumes: + if len(file_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: + shortened_notes = file_volume['notes'][:DEFAULT_NOTES_SIZE] + file_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/file/set_note.py b/SoftLayer/CLI/file/set_note.py new file mode 100644 index 000000000..7f5162f0a --- /dev/null +++ b/SoftLayer/CLI/file/set_note.py @@ -0,0 +1,28 @@ +"""Set note for an existing File storage volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('volume-id') +@click.option('--note', '-n', + type=str, + required=True, + help='Public notes related to a Storage volume') +@environment.pass_env +def cli(env, volume_id, note): + """Set note for an existing file storage volume.""" + file_manager = SoftLayer.FileStorageManager(env.client) + file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') + + result = file_manager.volume_set_note(file_volume_id, note) + + if result: + click.echo("Set note successfully!") + + else: + click.echo("Note could not be set! Please verify your options and try again.") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 8d9c8b1e8..ff3e1c180 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -114,6 +114,7 @@ ('block:volume-limits', 'SoftLayer.CLI.block.limit:cli'), ('block:volume-refresh', 'SoftLayer.CLI.block.refresh:cli'), ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), + ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), @@ -147,6 +148,7 @@ ('file:volume-limits', 'SoftLayer.CLI.file.limit:cli'), ('file:volume-refresh', 'SoftLayer.CLI.file.refresh:cli'), ('file:volume-convert', 'SoftLayer.CLI.file.convert:cli'), + ('file:volume-set-note', 'SoftLayer.CLI.file.set_note:cli'), ('firewall', 'SoftLayer.CLI.firewall'), ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 611e88005..fff20fd6a 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -215,6 +215,7 @@ ] deleteObject = True +editObject = True allowAccessFromHostList = True removeAccessFromHostList = True failoverToReplicant = True diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index ce1b951c8..1810a90dc 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -102,6 +102,7 @@ def get_file_volume_details(self, volume_id, **kwargs): 'serviceResourceBackendIpAddress,' 'serviceResource[datacenter[name]],' 'replicationSchedule[type[keyname]]]', + 'notes', ] kwargs['mask'] = ','.join(items) return self.get_volume_details(volume_id, **kwargs) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index fb5190d85..a54da9d70 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -9,6 +9,7 @@ from SoftLayer.managers import storage_utils from SoftLayer import utils + # pylint: disable=too-many-public-methods @@ -65,6 +66,7 @@ def get_volume_details(self, volume_id, **kwargs): 'serviceResourceBackendIpAddress,' 'serviceResource[datacenter[name]],' 'replicationSchedule[type[keyname]]]', + 'notes', ] kwargs['mask'] = ','.join(items) return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) @@ -173,10 +175,10 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - block_mask = 'billingItem[activeChildren,hourlyFlag],'\ - 'storageTierLevel,osType,staasVersion,'\ - 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ - 'intervalSchedule,hourlySchedule,dailySchedule,'\ + block_mask = 'billingItem[activeChildren,hourlyFlag],' \ + 'storageTierLevel,osType,staasVersion,' \ + 'hasEncryptionAtRest,snapshotCapacityGb,schedules,' \ + 'intervalSchedule,hourlySchedule,dailySchedule,' \ 'weeklySchedule,storageType[keyName],provisionedIops' block_volume = self.get_volume_details(volume_id, mask=block_mask) @@ -213,9 +215,9 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, dupl :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ - 'storageType[keyName],capacityGb,originalVolumeSize,'\ - 'provisionedIops,storageTierLevel,osType[keyName],'\ + block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,' \ + 'storageType[keyName],capacityGb,originalVolumeSize,' \ + 'provisionedIops,storageTierLevel,osType[keyName],' \ 'staasVersion,hasEncryptionAtRest' origin_volume = self.get_volume_details(origin_volume_id, mask=block_mask) storage_class = storage_utils.block_or_file(origin_volume['storageType']['keyName']) @@ -270,6 +272,16 @@ def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tie return self.client.call('Product_Order', 'placeOrder', order) + def volume_set_note(self, volume_id, note): + """Set the notes for an existing block volume. + + :param volume_id: The ID of the volume to be modified + :param note: the note + :return: Returns true if success + """ + template = {'notes': note} + return self.client.call('SoftLayer_Network_Storage', 'editObject', template, id=volume_id) + def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. @@ -295,9 +307,9 @@ def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): :param boolean upgrade: Flag to indicate if this order is an upgrade :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - object_mask = 'id,billingItem[location,hourlyFlag],'\ - 'storageType[keyName],storageTierLevel,provisionedIops,'\ - 'staasVersion,hasEncryptionAtRest' + object_mask = 'id,billingItem[location,hourlyFlag],' \ + 'storageType[keyName],storageTierLevel,provisionedIops,' \ + 'staasVersion,hasEncryptionAtRest' volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs) order = storage_utils.prepare_snapshot_order_object(self, volume, capacity, tier, upgrade) From c75e0a9e452173d0dca945aea31d493b2f93ff27 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 25 Aug 2020 16:44:10 -0400 Subject: [PATCH 0225/1385] #1322 add tests for volumen set notes --- tests/CLI/modules/block_tests.py | 20 ++++++++++++++++++++ tests/CLI/modules/file_tests.py | 20 ++++++++++++++++++++ tests/managers/block_tests.py | 3 ++- tests/managers/file_tests.py | 3 ++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index e5f6ba8c5..08e7f0d74 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -64,6 +64,7 @@ def test_volume_detail(self): self.assertEqual({ 'Username': 'username', 'LUN Id': '2', + 'Notes': "{'status': 'available'}", 'Endurance Tier': 'READHEAVY_TIER', 'IOPs': 1000, 'Snapshot Capacity (GB)': '10', @@ -132,6 +133,7 @@ def test_volume_list(self): 'iops': None, 'ip_addr': '10.1.2.3', 'lunId': None, + 'notes': "{'status': 'availabl", 'rep_partner_count': None, 'storage_type': 'ENDURANCE', 'username': 'username', @@ -726,3 +728,21 @@ def test_dep_dupe_convert(self): result = self.run_command(['block', 'volume-convert', '102']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.BlockStorageManager.volume_set_note') + def test_volume_set_note(self, set_note): + set_note.return_value = True + + result = self.run_command(['block', 'volume-set-note', '102', '--note=testing']) + + self.assert_no_fail(result) + self.assertIn("successfully!", result.output) + + @mock.patch('SoftLayer.BlockStorageManager.volume_set_note') + def test_volume_not_set_note(self, set_note): + set_note.return_value = False + + result = self.run_command(['block', 'volume-set-note', '102', '--note=testing']) + + self.assert_no_fail(result) + self.assertIn("Note could not be set!", result.output) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 1d64f54ae..f895b87db 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -50,6 +50,7 @@ def test_volume_list(self): 'username': 'user', 'active_transactions': None, 'mount_addr': '127.0.0.1:/TEST', + 'notes': None, 'rep_partner_count': None }], json.loads(result.output)) @@ -137,6 +138,7 @@ def test_volume_detail(self): 'Data Center': 'dal05', 'Type': 'ENDURANCE', 'ID': 100, + 'Notes': "{'status': 'available'}", '# of Active Transactions': '1', 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', 'Replicant Count': '1', @@ -705,3 +707,21 @@ def test_dep_dupe_convert(self): result = self.run_command(['file', 'volume-convert', '102']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.FileStorageManager.volume_set_note') + def test_volume_set_note(self, set_note): + set_note.return_value = True + + result = self.run_command(['file', 'volume-set-note', '102', '--note=testing']) + + self.assert_no_fail(result) + self.assertIn("successfully!", result.output) + + @mock.patch('SoftLayer.FileStorageManager.volume_set_note') + def test_volume_not_set_note(self, set_note): + set_note.return_value = False + + result = self.run_command(['file', 'volume-set-note', '102', '--note=testing']) + + self.assert_no_fail(result) + self.assertIn("Note could not be set!", result.output) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 1ba644236..19d2d914c 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -111,7 +111,8 @@ def test_get_block_volume_details(self): 'replicationPartners[id,username,' \ 'serviceResourceBackendIpAddress,' \ 'serviceResource[datacenter[name]],' \ - 'replicationSchedule[type[keyname]]]' + 'replicationSchedule[type[keyname]]],' \ + 'notes' self.assert_called_with( 'SoftLayer_Network_Storage', diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 6df2b8721..3a8b4b9c8 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -145,7 +145,8 @@ def test_get_file_volume_details(self): 'replicationPartners[id,username,'\ 'serviceResourceBackendIpAddress,'\ 'serviceResource[datacenter[name]],'\ - 'replicationSchedule[type[keyname]]]' + 'replicationSchedule[type[keyname]]],' \ + 'notes' self.assert_called_with( 'SoftLayer_Network_Storage', From 16e17e14711706f9dba7fda21f553c5de920c681 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 25 Aug 2020 19:31:49 -0400 Subject: [PATCH 0226/1385] #1322 add volume-set-note docs --- SoftLayer/CLI/block/list.py | 8 ++++++-- SoftLayer/CLI/file/list.py | 8 ++++++-- docs/cli/block.rst | 4 ++++ docs/cli/file.rst | 4 ++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 7019166b8..6df25e13e 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -79,7 +79,7 @@ def cli(env, sortby, columns, datacenter, username, storage_type): table = formatting.Table(columns.columns) table.sortby = sortby - reduce_notes(block_volumes) + _reduce_notes(block_volumes) for block_volume in block_volumes: table.add_row([value or formatting.blank() @@ -88,7 +88,11 @@ def cli(env, sortby, columns, datacenter, username, storage_type): env.fout(table) -def reduce_notes(block_volumes): +def _reduce_notes(block_volumes): + """Reduces the size of the notes in a volume list. + + :param block_volumes: An list of block volumes + """ for block_volume in block_volumes: if len(block_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: shortened_notes = block_volume['notes'][:DEFAULT_NOTES_SIZE] diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 731d0ee1c..8bb782884 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -77,7 +77,7 @@ def cli(env, sortby, columns, datacenter, username, storage_type): table = formatting.Table(columns.columns) table.sortby = sortby - reduce_notes(file_volumes) + _reduce_notes(file_volumes) for file_volume in file_volumes: table.add_row([value or formatting.blank() @@ -86,7 +86,11 @@ def cli(env, sortby, columns, datacenter, username, storage_type): env.fout(table) -def reduce_notes(file_volumes): +def _reduce_notes(file_volumes): + """Reduces the size of the notes in a volume list. + + :param file_volumes: An list of file volumes + """ for file_volume in file_volumes: if len(file_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: shortened_notes = file_volume['notes'][:DEFAULT_NOTES_SIZE] diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 8b31d5a99..10ec8e6a6 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -138,3 +138,7 @@ Block Commands .. click:: SoftLayer.CLI.block.convert:cli :prog: block volume-convert :show-nested: + +.. click:: SoftLayer.CLI.block.set_note:cli + :prog: block volume-set-note + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 52dad83a4..5af9b65cc 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -117,4 +117,8 @@ File Commands .. click:: SoftLayer.CLI.file.convert:cli :prog: file volume-convert + :show-nested: + +.. click:: SoftLayer.CLI.file.set_note:cli + :prog: file volume-set-note :show-nested: \ No newline at end of file From 13cca31f5edad81ad258c91c04fdd1580a563072 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 25 Aug 2020 19:58:12 -0400 Subject: [PATCH 0227/1385] Refactor hardware create-options. --- SoftLayer/CLI/hardware/create_options.py | 67 +++++++++++------------- tests/CLI/modules/server_tests.py | 13 ++--- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index f3a3dec14..7bd5ebcaf 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -4,17 +4,17 @@ import click from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import hardware @click.command() -@click.option('--prices', '-p', default=False, help='Filter Item Prices, prices(DEFAULT False)') -@click.option('--location', '-l', type=click.STRING, help='To filter the item prices by location, enter the Location ' - 'keyName e.g. AMSTERDAM02') +@click.argument('location', required=False) +@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and ' + 'to list the Item Prices by location, add it to the ' + '--prices option, e.g. --prices AMSTERDAM02') @environment.pass_env -def cli(env, prices, location): +def cli(env, prices, location=None): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) @@ -30,18 +30,14 @@ def cli(env, prices, location): dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - if location and prices: - raise exceptions.CLIAbort("Please select --prices or --location to get the prices, not both") - if prices: _preset_prices_table(options['sizes'], tables) _os_prices_table(options['operating_systems'], tables) _port_speed_prices_table(options['port_speeds'], tables) _extras_prices_table(options['extras'], tables) - elif location: - _preset_prices_table(options['sizes'], tables) - location_prices = hardware_manager.get_hardware_item_prices(location) - _location_item_prices(location_prices, tables) + if location: + location_prices = hardware_manager.get_hardware_item_prices(location) + _location_item_prices(location_prices, tables) else: # Presets preset_table = formatting.Table(['Size', 'Value'], title="Sizes") @@ -98,20 +94,20 @@ def _os_prices_table(operating_systems, tables): :param [] operating_systems: List of Hardware Server operating systems. :param tables: Table formatting. """ - os_prices_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'capacityRestrictionMaximum', - 'capacityRestrictionMinimum', 'capacityRestrictionType'], + os_prices_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], title="Operating Systems Prices") os_prices_table.sortby = 'OS Key' os_prices_table.align = 'l' for operating_system in operating_systems: for price in operating_system['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') os_prices_table.add_row( [operating_system['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType')]) + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(os_prices_table) @@ -121,20 +117,20 @@ def _port_speed_prices_table(port_speeds, tables): :param [] port_speeds: List of Hardware Server Port Speeds. :param tables: Table formatting. """ - port_speed_prices_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', - 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', - 'capacityRestrictionType'], title="Network Options Prices") + port_speed_prices_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', 'Restriction'], + title="Network Options Prices") port_speed_prices_table.sortby = 'Speed' port_speed_prices_table.align = 'l' for speed in port_speeds: for price in speed['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') port_speed_prices_table.add_row( [speed['key'], speed['speed'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType')]) + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(port_speed_prices_table) @@ -144,20 +140,19 @@ def _extras_prices_table(extras, tables): :param [] extras: List of Hardware Server Extras. :param tables: Table formatting. """ - extras_prices_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', - 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', - 'capacityRestrictionType'], title="Extras Prices") + extras_prices_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', 'Restriction'], + title="Extras Prices") extras_prices_table.align = 'l' for extra in extras: for price in extra['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') extras_prices_table.add_row( [extra['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType') - ]) + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(extras_prices_table) @@ -179,18 +174,16 @@ def _location_item_prices(location_prices, tables): :param price: Hardware Server price. :param string item: Hardware Server price data. """ - location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', - 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', - 'capacityRestrictionType']) + location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction']) location_prices_table.sortby = 'keyName' location_prices_table.align = 'l' for price in location_prices: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') location_prices_table.add_row( [price['item']['keyName'], price['id'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType') - ]) + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(location_prices_table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 587124a96..9b235af12 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -364,17 +364,18 @@ def test_create_options(self): self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') def test_create_options_prices(self): - result = self.run_command(['server', 'create-options', '--prices=true']) + result = self.run_command(['server', 'create-options', '--prices']) self.assert_no_fail(result) output = json.loads(result.output) + print(output[3][0]) self.assertEqual(output[1][0]['Hourly'], 1.18) self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') - self.assertEqual(output[3][0]['capacityRestrictionMaximum'], '-') + self.assertEqual(output[3][0]['Monthly'], '0') self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') def test_create_options_location(self): - result = self.run_command(['server', 'create-options', '--location=AMSTERDAM02']) + result = self.run_command(['server', 'create-options', '--prices', 'AMSTERDAM02']) self.assert_no_fail(result) output = json.loads(result.output) @@ -383,12 +384,6 @@ def test_create_options_location(self): self.assertEqual(output[1][0]['Value'], 'S1270_8GB_2X1TBSATA_NORAID') self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') - def test_create_options_prices_location(self): - result = self.run_command(['server', 'create-options', '--prices=true', '--location=AMSTERDAM02']) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): order_mock.return_value = { From 41d4ef5262bdf11e95f91a6e2d612c66bdb2b39f Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 26 Aug 2020 16:22:57 -0500 Subject: [PATCH 0228/1385] #999 dns record-list can handle zones with a missing Host record (like SRV records). Improved the zone-list command a bit by adding the records count column --- SoftLayer/CLI/dns/record_list.py | 28 ++++++++++------------------ SoftLayer/CLI/dns/zone_list.py | 20 +++++++++++++------- SoftLayer/managers/dns.py | 20 +++++++++----------- tests/CLI/modules/dns_tests.py | 28 +++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/SoftLayer/CLI/dns/record_list.py b/SoftLayer/CLI/dns/record_list.py index 4e46cb80c..4861582c9 100644 --- a/SoftLayer/CLI/dns/record_list.py +++ b/SoftLayer/CLI/dns/record_list.py @@ -14,36 +14,28 @@ @click.argument('zone') @click.option('--data', help='Record data, such as an IP address') @click.option('--record', help='Host record, such as www') -@click.option('--ttl', - type=click.INT, +@click.option('--ttl', type=click.INT, help='TTL value in seconds, such as 86400') -@click.option('--type', help='Record type, such as A or CNAME') +@click.option('--type', 'record_type', help='Record type, such as A or CNAME') @environment.pass_env -def cli(env, zone, data, record, ttl, type): +def cli(env, zone, data, record, ttl, record_type): """List all records in a zone.""" manager = SoftLayer.DNSManager(env.client) table = formatting.Table(['id', 'record', 'type', 'ttl', 'data']) - - table.align['ttl'] = 'l' - table.align['record'] = 'r' - table.align['data'] = 'l' + table.align = 'l' zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') - records = manager.get_records(zone_id, - record_type=type, - host=record, - ttl=ttl, - data=data) + records = manager.get_records(zone_id, record_type=record_type, host=record, ttl=ttl, data=data) for the_record in records: table.add_row([ - the_record['id'], - the_record['host'], - the_record['type'].upper(), - the_record['ttl'], - the_record['data'] + the_record.get('id'), + the_record.get('host', ''), + the_record.get('type').upper(), + the_record.get('ttl'), + the_record.get('data') ]) env.fout(table) diff --git a/SoftLayer/CLI/dns/zone_list.py b/SoftLayer/CLI/dns/zone_list.py index f555677c7..a57d19c09 100644 --- a/SoftLayer/CLI/dns/zone_list.py +++ b/SoftLayer/CLI/dns/zone_list.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.utils import clean_time @click.command() @@ -14,17 +15,22 @@ def cli(env): """List all zones.""" manager = SoftLayer.DNSManager(env.client) - zones = manager.list_zones() - table = formatting.Table(['id', 'zone', 'serial', 'updated']) - table.align['serial'] = 'c' - table.align['updated'] = 'c' - + objectMask = "mask[id,name,serial,updateDate,resourceRecordCount]" + zones = manager.list_zones(mask=objectMask) + table = formatting.Table(['id', 'zone', 'serial', 'updated', 'records']) + table.align = 'l' for zone in zones: + zone_serial = str(zone.get('serial')) + zone_date = zone.get('updateDate', None) + if zone_date is None: + # The serial is just YYYYMMDD##, and since there is no createDate, just format it like it was one. + zone_date = "{}-{}-{}T00:00:00-06:00".format(zone_serial[0:4], zone_serial[4:6], zone_serial[6:8]) table.add_row([ zone['id'], zone['name'], - zone['serial'], - zone['updateDate'], + zone_serial, + clean_time(zone_date), + zone.get('resourceRecordCount', 0) ]) env.fout(table) diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 1e89ec9cf..332e96518 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -39,7 +39,10 @@ def list_zones(self, **kwargs): :returns: A list of dictionaries representing the matching zones. """ - return self.client['Account'].getDomains(**kwargs) + if kwargs.get('iter') is None: + kwargs['iter'] = True + return self.client.call('SoftLayer_Account', 'getDomains', **kwargs) + # return self.client['Account'].getDomains(**kwargs) def get_zone(self, zone_id, records=True): """Get a zone and its records. @@ -181,8 +184,7 @@ def get_record(self, record_id): """ return self.record.getObject(id=record_id) - def get_records(self, zone_id, ttl=None, data=None, host=None, - record_type=None): + def get_records(self, zone_id, ttl=None, data=None, host=None, record_type=None): """List, and optionally filter, records within a zone. :param zone: the zone name in which to search. @@ -191,8 +193,7 @@ def get_records(self, zone_id, ttl=None, data=None, host=None, :param str host: record's host :param str record_type: the type of record - :returns: A list of dictionaries representing the matching records - within the specified zone. + :returns: A list of dictionaries representing the matching records within the specified zone. """ _filter = utils.NestedDict() @@ -208,12 +209,9 @@ def get_records(self, zone_id, ttl=None, data=None, host=None, if record_type: _filter['resourceRecords']['type'] = utils.query_filter(record_type.lower()) - results = self.service.getResourceRecords( - id=zone_id, - mask='id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson', - filter=_filter.to_dict(), - ) - + objectMask = 'id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson' + results = self.client.call('SoftLayer_Dns_Domain', 'getResourceRecords', id=zone_id, + mask=objectMask, filter=_filter.to_dict(), iter=True) return results def edit_record(self, record): diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 3c1329b39..525e6bc7f 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -54,11 +54,8 @@ def test_list_zones(self): result = self.run_command(['dns', 'zone-list']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - [{'serial': 2014030728, - 'updated': '2014-03-07T13:52:31-06:00', - 'id': 12345, - 'zone': 'example.com'}]) + actual_output = json.loads(result.output) + self.assertEqual(actual_output[0]['zone'], 'example.com') def test_list_records(self): result = self.run_command(['dns', 'record-list', '1234']) @@ -261,3 +258,24 @@ def test_import_zone(self): self.assertEqual(call.args[0], expected_call) self.assertIn("Finished", result.output) + + def test_issues_999(self): + """Makes sure certain zones with a None host record are pritable""" + + # SRV records can have a None `host` record, or just a plain missing one. + fake_records = [ + { + 'data': '1.2.3.4', + 'id': 137416416, + 'ttl': 900, + 'type': 'srv' + } + ] + record_mock = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') + record_mock.return_value = fake_records + result = self.run_command(['dns', 'record-list', '1234']) + + self.assert_no_fail(result) + actual_output = json.loads(result.output)[0] + self.assertEqual(actual_output['id'], 137416416) + self.assertEqual(actual_output['record'], '') \ No newline at end of file From dc6abfcaf69eabee9051e01e6f58e0d13050b5b6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 27 Aug 2020 13:08:14 -0500 Subject: [PATCH 0229/1385] tox and style fixes --- SoftLayer/CLI/dns/zone_list.py | 4 +- .../SoftLayer_Dns_Domain_ResourceRecord.py | 1 + SoftLayer/managers/dns.py | 4 +- tests/CLI/modules/dns_tests.py | 18 ++++- tests/managers/dns_tests.py | 73 +++++++------------ 5 files changed, 47 insertions(+), 53 deletions(-) diff --git a/SoftLayer/CLI/dns/zone_list.py b/SoftLayer/CLI/dns/zone_list.py index a57d19c09..7e0b5fe9e 100644 --- a/SoftLayer/CLI/dns/zone_list.py +++ b/SoftLayer/CLI/dns/zone_list.py @@ -15,8 +15,8 @@ def cli(env): """List all zones.""" manager = SoftLayer.DNSManager(env.client) - objectMask = "mask[id,name,serial,updateDate,resourceRecordCount]" - zones = manager.list_zones(mask=objectMask) + object_mask = "mask[id,name,serial,updateDate,resourceRecordCount]" + zones = manager.list_zones(mask=object_mask) table = formatting.Table(['id', 'zone', 'serial', 'updated', 'records']) table.align = 'l' for zone in zones: diff --git a/SoftLayer/fixtures/SoftLayer_Dns_Domain_ResourceRecord.py b/SoftLayer/fixtures/SoftLayer_Dns_Domain_ResourceRecord.py index ce85e95d8..e098e159f 100644 --- a/SoftLayer/fixtures/SoftLayer_Dns_Domain_ResourceRecord.py +++ b/SoftLayer/fixtures/SoftLayer_Dns_Domain_ResourceRecord.py @@ -1,3 +1,4 @@ createObject = {'name': 'example.com'} deleteObject = True editObject = True +getObject = {'id': 12345, 'ttl': 7200, 'data': 'd', 'host': 'a', 'type': 'cname'} diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 332e96518..6484fd7d9 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -209,9 +209,9 @@ def get_records(self, zone_id, ttl=None, data=None, host=None, record_type=None) if record_type: _filter['resourceRecords']['type'] = utils.query_filter(record_type.lower()) - objectMask = 'id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson' + object_mask = 'id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson' results = self.client.call('SoftLayer_Dns_Domain', 'getResourceRecords', id=zone_id, - mask=objectMask, filter=_filter.to_dict(), iter=True) + mask=object_mask, filter=_filter.to_dict(), iter=True) return results def edit_record(self, record): diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 525e6bc7f..90012de3f 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -278,4 +278,20 @@ def test_issues_999(self): self.assert_no_fail(result) actual_output = json.loads(result.output)[0] self.assertEqual(actual_output['id'], 137416416) - self.assertEqual(actual_output['record'], '') \ No newline at end of file + self.assertEqual(actual_output['record'], '') + + def test_list_zones_no_update(self): + fake_zones = [ + { + 'name': 'example.com', + 'id': 12345, + 'serial': 2014030728, + 'updateDate': None} + ] + domains_mock = self.set_mock('SoftLayer_Account', 'getDomains') + domains_mock.return_value = fake_zones + result = self.run_command(['dns', 'zone-list']) + + self.assert_no_fail(result) + actual_output = json.loads(result.output) + self.assertEqual(actual_output[0]['updated'], '2014-03-07 00:00') diff --git a/tests/managers/dns_tests.py b/tests/managers/dns_tests.py index 6b32af918..0c7c1cade 100644 --- a/tests/managers/dns_tests.py +++ b/tests/managers/dns_tests.py @@ -25,24 +25,19 @@ def test_get_zone(self): res = self.dns_client.get_zone(12345) self.assertEqual(res, fixtures.SoftLayer_Dns_Domain.getObject) - self.assert_called_with('SoftLayer_Dns_Domain', 'getObject', - identifier=12345, - mask='mask[resourceRecords]') + self.assert_called_with('SoftLayer_Dns_Domain', 'getObject', identifier=12345, mask='mask[resourceRecords]') def test_get_zone_without_records(self): self.dns_client.get_zone(12345, records=False) - self.assert_called_with('SoftLayer_Dns_Domain', 'getObject', - identifier=12345, - mask=None) + self.assert_called_with('SoftLayer_Dns_Domain', 'getObject', identifier=12345, mask=None) def test_resolve_zone_name(self): res = self.dns_client._get_zone_id_from_name('example.com') self.assertEqual([12345], res) _filter = {"domains": {"name": {"operation": "_= example.com"}}} - self.assert_called_with('SoftLayer_Account', 'getDomains', - filter=_filter) + self.assert_called_with('SoftLayer_Account', 'getDomains', filter=_filter) def test_resolve_zone_name_no_matches(self): mock = self.set_mock('SoftLayer_Account', 'getDomains') @@ -52,8 +47,7 @@ def test_resolve_zone_name_no_matches(self): self.assertEqual([], res) _filter = {"domains": {"name": {"operation": "_= example.com"}}} - self.assert_called_with('SoftLayer_Account', 'getDomains', - filter=_filter) + self.assert_called_with('SoftLayer_Account', 'getDomains', filter=_filter) def test_create_zone(self): res = self.dns_client.create_zone('example.com', serial='2014110201') @@ -61,27 +55,22 @@ def test_create_zone(self): args = ({'serial': '2014110201', 'name': 'example.com', 'resourceRecords': {}},) - self.assert_called_with('SoftLayer_Dns_Domain', 'createObject', - args=args) + self.assert_called_with('SoftLayer_Dns_Domain', 'createObject', args=args) self.assertEqual(res, {'name': 'example.com'}) def test_delete_zone(self): self.dns_client.delete_zone(1) - self.assert_called_with('SoftLayer_Dns_Domain', 'deleteObject', - identifier=1) + self.assert_called_with('SoftLayer_Dns_Domain', 'deleteObject', identifier=1) def test_edit_zone(self): self.dns_client.edit_zone('example.com') - self.assert_called_with('SoftLayer_Dns_Domain', 'editObject', - args=('example.com',)) + self.assert_called_with('SoftLayer_Dns_Domain', 'editObject', args=('example.com',)) def test_create_record(self): - res = self.dns_client.create_record(1, 'test', 'TXT', 'testing', - ttl=1200) + res = self.dns_client.create_record(1, 'test', 'TXT', 'testing', ttl=1200) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ 'domainId': 1, 'ttl': 1200, @@ -94,8 +83,7 @@ def test_create_record(self): def test_create_record_mx(self): res = self.dns_client.create_record_mx(1, 'test', 'testing', ttl=1200, priority=21) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ 'domainId': 1, 'ttl': 1200, @@ -110,8 +98,7 @@ def test_create_record_srv(self): res = self.dns_client.create_record_srv(1, 'record', 'test_data', 'SLS', 8080, 'foobar', ttl=1200, priority=21, weight=15) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ 'complexType': 'SoftLayer_Dns_Domain_ResourceRecord_SrvType', 'domainId': 1, @@ -130,14 +117,8 @@ def test_create_record_srv(self): def test_create_record_ptr(self): res = self.dns_client.create_record_ptr('test', 'testing', ttl=1200) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', - args=({ - 'ttl': 1200, - 'host': 'test', - 'type': 'PTR', - 'data': 'testing' - },)) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', + args=({'ttl': 1200, 'host': 'test', 'type': 'PTR', 'data': 'testing'},)) self.assertEqual(res, {'name': 'example.com'}) def test_generate_create_dict(self): @@ -161,27 +142,21 @@ def test_generate_create_dict(self): def test_delete_record(self): self.dns_client.delete_record(1) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'deleteObject', - identifier=1) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'deleteObject', identifier=1) def test_edit_record(self): self.dns_client.edit_record({'id': 1, 'name': 'test', 'ttl': '1800'}) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'editObject', - args=({'id': 1, - 'name': 'test', - 'ttl': '1800'},), + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'editObject', + args=({'id': 1, 'name': 'test', 'ttl': '1800'},), identifier=1) def test_dump_zone(self): self.dns_client.dump_zone(1) - self.assert_called_with('SoftLayer_Dns_Domain', 'getZoneFileContents', - identifier=1) + self.assert_called_with('SoftLayer_Dns_Domain', 'getZoneFileContents', identifier=1) - def test_get_record(self): + def test_get_records(self): # maybe valid domain, but no records matching mock = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') mock.return_value = [] @@ -190,11 +165,7 @@ def test_get_record(self): mock.reset_mock() records = fixtures.SoftLayer_Dns_Domain.getResourceRecords mock.return_value = [records[0]] - self.dns_client.get_records(12345, - record_type='a', - host='hostname', - data='a', - ttl='86400') + self.dns_client.get_records(12345, record_type='a', host='hostname', data='a', ttl='86400') _filter = {'resourceRecords': {'type': {'operation': '_= a'}, 'host': {'operation': '_= hostname'}, @@ -203,3 +174,9 @@ def test_get_record(self): self.assert_called_with('SoftLayer_Dns_Domain', 'getResourceRecords', identifier=12345, filter=_filter) + + def test_get_record(self): + record_id = 1234 + record = self.dns_client.get_record(record_id) + self.assertEqual(record['id'], 12345) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'getObject', identifier=record_id) From 78ce548873e227d4fe8be801165369917dc274bd Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 27 Aug 2020 14:49:59 -0500 Subject: [PATCH 0230/1385] fixed a unit test for older versions of python --- tests/CLI/modules/dns_tests.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 90012de3f..82403d1a9 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -6,6 +6,7 @@ """ import json import os.path +import sys import mock @@ -281,6 +282,7 @@ def test_issues_999(self): self.assertEqual(actual_output['record'], '') def test_list_zones_no_update(self): + pyversion = sys.version_info fake_zones = [ { 'name': 'example.com', @@ -294,4 +296,7 @@ def test_list_zones_no_update(self): self.assert_no_fail(result) actual_output = json.loads(result.output) - self.assertEqual(actual_output[0]['updated'], '2014-03-07 00:00') + if pyversion.major >= 3 and pyversion.minor >= 7: + self.assertEqual(actual_output[0]['updated'], '2014-03-07 00:00') + else: + self.assertEqual(actual_output[0]['updated'], '2014-03-07T00:00:00-06:00') From 7c8669208d56bb3f32640bdeb006848a2ed6312d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 27 Aug 2020 16:34:54 -0400 Subject: [PATCH 0231/1385] Refactor order item-list. --- SoftLayer/CLI/hardware/create_options.py | 2 +- SoftLayer/CLI/order/item_list.py | 76 ++++++++++++------------ tests/CLI/modules/order_tests.py | 22 +++---- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 7bd5ebcaf..71a988451 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -12,7 +12,7 @@ @click.argument('location', required=False) @click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and ' 'to list the Item Prices by location, add it to the ' - '--prices option, e.g. --prices AMSTERDAM02') + '--prices option using location KeyName, e.g. --prices AMSTERDAM02') @environment.pass_env def cli(env, prices, location=None): """Server order options for a given chassis.""" diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index 97e9c985e..d22be8b56 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -3,25 +3,25 @@ import click from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import ordering from SoftLayer.utils import lookup COLUMNS = ['category', 'keyName', 'description', 'priceId'] -COLUMNS_ITEM_PRICES = ['keyName', 'priceId', 'Hourly', 'Monthly', 'CRMax', 'CRMim', 'CRType'] -COLUMNS_ITEM_PRICES_LOCATION = ['keyName', 'priceId', 'Hourly', 'Monthly', 'CRMax', 'CRMim', 'CRType'] +COLUMNS_ITEM_PRICES = ['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction'] +COLUMNS_ITEM_PRICES_LOCATION = ['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction'] @click.command() +@click.argument('location', required=False, nargs=-1, type=click.UNPROCESSED) @click.argument('package_keyname') @click.option('--keyword', help="A word (or string) used to filter item names.") @click.option('--category', help="Category code to filter items by") -@click.option('--prices', '-p', default=False, help='Filter Item Prices, prices(DEFAULT False)') -@click.option('--location', '-l', type=click.STRING, help='To filter the item prices by location, enter the Location ' - 'keyName e.g. AMSTERDAM02') +@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and to list the ' + 'Item Prices by location, add it to the --prices option using ' + 'location KeyName, e.g. --prices AMSTERDAM02') @environment.pass_env -def cli(env, package_keyname, keyword, category, prices, location): +def cli(env, location, package_keyname, keyword, category, prices): """List package items used for ordering. The item keyNames listed can be used with `slcli order place` to specify @@ -42,8 +42,7 @@ def cli(env, package_keyname, keyword, category, prices, location): """ manager = ordering.OrderingManager(env.client) - if location and prices: - raise exceptions.CLIAbort("Please select --prices or --location to get the prices, not both") + tables = [] _filter = {'items': {}} if keyword: @@ -56,22 +55,18 @@ def cli(env, package_keyname, keyword, category, prices, location): categories = sorted_items.keys() if prices: - table = formatting.Table(COLUMNS_ITEM_PRICES, title="CRMax = CapacityRestrictionMaximum, " - "CRMin = CapacityRestrictionMinimum, " - "CRType = CapacityRestrictionType") - table = _item_list_prices(categories, sorted_items, table) - elif location: - table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="CRMax = CapacityRestrictionMaximum, " - "CRMin = CapacityRestrictionMinimum, " - "CRType = CapacityRestrictionType") - location_prices = manager.get_item_prices_by_location(location, package_keyname) - table = _location_item_prices(location_prices, table) + _item_list_prices(categories, sorted_items, tables) + if location: + location = location[0] + location_prices = manager.get_item_prices_by_location(location, package_keyname) + _location_item_prices(location_prices, location, tables) else: - table = formatting.Table(COLUMNS) + table_items_detail = formatting.Table(COLUMNS) for catname in sorted(categories): for item in sorted_items[catname]: - table.add_row([catname, item['keyName'], item['description'], get_price(item)]) - env.fout(table) + table_items_detail.add_row([catname, item['keyName'], item['description'], get_price(item)]) + tables.append(table_items_detail) + env.fout(formatting.listing(tables, separator='\n')) def sort_items(items): @@ -96,19 +91,21 @@ def get_price(item): return 0 -def _item_list_prices(categories, sorted_items, table): +def _item_list_prices(categories, sorted_items, tables): """Add the item prices cost and capacity restriction to the table""" + table_prices = formatting.Table(COLUMNS_ITEM_PRICES) for catname in sorted(categories): for item in sorted_items[catname]: for price in item['prices']: if not price.get('locationGroupId'): - table.add_row([item['keyName'], price['id'], - get_item_price_data(price, 'hourlyRecurringFee'), - get_item_price_data(price, 'recurringFee'), - get_item_price_data(price, 'capacityRestrictionMaximum'), - get_item_price_data(price, 'capacityRestrictionMinimum'), - get_item_price_data(price, 'capacityRestrictionType')]) - return table + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + table_prices.add_row([item['keyName'], price['id'], + get_item_price_data(price, 'hourlyRecurringFee'), + get_item_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(table_prices) def get_item_price_data(price, item_attribute): @@ -119,24 +116,25 @@ def get_item_price_data(price, item_attribute): return result -def _location_item_prices(location_prices, table): +def _location_item_prices(location_prices, location, tables): """Get a specific data from HS price. :param price: Hardware Server price. :param string item: Hardware Server price data. """ - table.sortby = 'keyName' - table.align = 'l' + location_prices_table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="Item Prices for %s" % location) + location_prices_table.sortby = 'keyName' + location_prices_table.align = 'l' for price in location_prices: - table.add_row( + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + location_prices_table.add_row( [price['item']['keyName'], price['id'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType') - ]) - return table + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(location_prices_table) def _get_price_data(price, item): diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 7b5a1dbec..7cc0ed611 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -46,31 +46,25 @@ def test_item_list(self): self.assertIn('item2', result.output) def test_item_list_prices(self): - result = self.run_command(['order', 'item-list', '--prices=true', 'package']) + result = self.run_command(['order', 'item-list', '--prices', 'package']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[0]['Hourly'], 0.0) - self.assertEqual(output[1]['CRMim'], '-') - self.assertEqual(output[1]['keyName'], 'KeyName015') + self.assertEqual(output[0][0]['priceId'], 1007) + self.assertEqual(output[0][1]['Restriction'], '- - - -') + self.assertEqual(output[0][1]['keyName'], 'KeyName015') self.assert_called_with('SoftLayer_Product_Package', 'getItems') def test_item_list_location(self): - result = self.run_command(['order', 'item-list', '--location=AMSTERDAM02', 'package']) + result = self.run_command(['order', 'item-list', '--prices', 'AMSTERDAM02', 'package']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[0]['Hourly'], '.093') - self.assertEqual(output[1]['keyName'], 'GUEST_DISK_100_GB_LOCAL_3') - self.assertEqual(output[1]['CRMax'], '-') + self.assertEqual(output[0][0]['Hourly'], 0.0) + self.assertEqual(output[0][1]['keyName'], 'KeyName015') + self.assertEqual(output[0][1]['Monthly'], '-') self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') - def test_item_list_prices_location(self): - result = self.run_command(['order', 'item-list', '--prices=true', '--location=AMSTERDAM02', 'package']) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - def test_package_list(self): p_mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') p_mock.return_value = _get_all_packages() From cd8dde130bcc0d123e7e3cb10d13e8ad0feca479 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 27 Aug 2020 17:25:14 -0400 Subject: [PATCH 0232/1385] Fix tox test. --- tests/CLI/modules/order_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 7cc0ed611..13ccb0f80 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -62,7 +62,7 @@ def test_item_list_location(self): output = json.loads(result.output) self.assertEqual(output[0][0]['Hourly'], 0.0) self.assertEqual(output[0][1]['keyName'], 'KeyName015') - self.assertEqual(output[0][1]['Monthly'], '-') + self.assertEqual(output[0][1]['priceId'], 1144) self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') def test_package_list(self): From 02a2488bfe47b88554781a96637bc483c4a4f545 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 28 Aug 2020 12:54:06 -0400 Subject: [PATCH 0233/1385] Refactor hw create-options size decimals. --- SoftLayer/CLI/hardware/create_options.py | 3 ++- tests/CLI/modules/server_tests.py | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 71a988451..1ae06a9be 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -84,7 +84,8 @@ def _preset_prices_table(sizes, tables): preset_prices_table.sortby = 'Value' preset_prices_table.align = 'l' for size in sizes: - preset_prices_table.add_row([size['name'], size['key'], size['hourlyRecurringFee'], size['recurringFee']]) + preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) tables.append(preset_prices_table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9b235af12..ebf3dc1c0 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -368,8 +368,7 @@ def test_create_options_prices(self): self.assert_no_fail(result) output = json.loads(result.output) - print(output[3][0]) - self.assertEqual(output[1][0]['Hourly'], 1.18) + self.assertEqual(output[1][0]['Hourly'], "%.4f" % 1.18) self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') self.assertEqual(output[3][0]['Monthly'], '0') self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') @@ -379,8 +378,8 @@ def test_create_options_location(self): self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Monthly'], 780.0) - self.assertEqual(output[1][0]['Hourly'], 1.18) + self.assertEqual(output[1][0]['Monthly'], "%.4f" % 780.0) + self.assertEqual(output[1][0]['Hourly'], "%.4f" % 1.18) self.assertEqual(output[1][0]['Value'], 'S1270_8GB_2X1TBSATA_NORAID') self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') From f84b4b66e993d24d66510a94667650d92e141c32 Mon Sep 17 00:00:00 2001 From: try Date: Tue, 1 Sep 2020 22:00:23 +0530 Subject: [PATCH 0234/1385] Please review slcli --- SoftLayer/CLI/block/refresh.py | 2 +- SoftLayer/CLI/file/refresh.py | 4 ++-- SoftLayer/fixtures/SoftLayer_Network_Storage.py | 4 ++-- SoftLayer/managers/storage.py | 4 ++-- tests/CLI/modules/block_tests.py | 2 +- tests/CLI/modules/file_tests.py | 2 +- tests/managers/block_tests.py | 8 ++++---- tests/managers/file_tests.py | 8 ++++---- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index aebc5e668..0c94ae67f 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -13,7 +13,7 @@ def cli(env, volume_id, snapshot_id): """"Refresh a duplicate volume with a snapshot from its parent.""" block_manager = SoftLayer.BlockStorageManager(env.client) - resp = block_manager.refresh_dupe(volume_id, snapshot_id) #remove dep_ in refresh_dep_dupe + resp = block_manager.refresh_dupe(volume_id, snapshot_id) click.echo(resp) diff --git a/SoftLayer/CLI/file/refresh.py b/SoftLayer/CLI/file/refresh.py index c566dac91..765e82730 100644 --- a/SoftLayer/CLI/file/refresh.py +++ b/SoftLayer/CLI/file/refresh.py @@ -11,8 +11,8 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """"Refresh a dependent duplicate volume with a snapshot from its parent.""" + """"Refresh a duplicate volume with a snapshot from its parent.""" file_manager = SoftLayer.FileStorageManager(env.client) - resp = file_manager.refresh_dep_dupe(volume_id, snapshot_id) + resp = file_manager.refresh_dupe(volume_id, snapshot_id) click.echo(resp) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 6fc9ed3e3..8f9de6dc2 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -234,8 +234,8 @@ 'provisionedCount': 100 } -refreshDuplicate = { #remove Dependent from refreshDependentDuplicate - 'DependentDuplicate': 1 +refreshDuplicate = { + 'dependentDuplicate': 1 } convertCloneDependentToIndependent = { diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 9f750272c..cd582701d 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -415,13 +415,13 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) - def refresh_dupe(self, volume_id, snapshot_id): #remove dep + def refresh_dupe(self, volume_id, snapshot_id): """"Refresh a duplicate volume with a snapshot from its parent. :param integer volume_id: The id of the volume :param integer snapshot_id: The id of the snapshot """ - return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id) #remove Dependent + return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 3aeaa0dc6..672bd3922 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -717,7 +717,7 @@ def test_volume_limit(self, list_mock): result = self.run_command(['block', 'volume-limits']) self.assert_no_fail(result) - def test_dupe_refresh(self): #remove _dep in test_dep_dupe_refresh + def test_dupe_refresh(self): result = self.run_command(['block', 'volume-refresh', '102', '103']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 1d64f54ae..f29809646 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -696,7 +696,7 @@ def test_volume_limit(self, list_mock): result = self.run_command(['file', 'volume-limits']) self.assert_no_fail(result) - def test_dep_dupe_refresh(self): + def test_dupe_refresh(self): result = self.run_command(['file', 'volume-refresh', '102', '103']) self.assert_no_fail(result) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index cd99f6221..6d155345e 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1043,13 +1043,13 @@ def test_get_ids_from_username_empty(self): self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') self.assertEqual([], result) - def test_refresh_block_dupe(self): #remove dep in block_depdupe - result = self.block.refresh_dupe(123, snapshot_id=321) #remove dep in refresh_dep_dupe - self.assertEqual(SoftLayer_Network_Storage.refreshDuplicate, result) #remove Dependent in refreshDependentDuplicate + def test_refresh_block_dupe(self): + result = self.block.refresh_dupe(123, snapshot_id=321) + self.assertEqual(SoftLayer_Network_Storage.refreshDuplicate, result) self.assert_called_with( 'SoftLayer_Network_Storage', - 'refreshDuplicate', #remove Dependent in refreshDependentDuplicate + 'refreshDuplicate', identifier=123 ) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 6df2b8721..141ab9602 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -892,13 +892,13 @@ def test_get_ids_from_username_empty(self): self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage') self.assertEqual([], result) - def test_refresh_file_depdupe(self): - result = self.file.refresh_dep_dupe(123, snapshot_id=321) - self.assertEqual(SoftLayer_Network_Storage.refreshDependentDuplicate, result) + def test_refresh_file_dupe(self): + result = self.file.refresh_dupe(123, snapshot_id=321) + self.assertEqual(SoftLayer_Network_Storage.refreshDuplicate, result) self.assert_called_with( 'SoftLayer_Network_Storage', - 'refreshDependentDuplicate', + 'refreshDuplicate', identifier=123 ) From 93356bb8d9ed18c1195116fe243364bc75f68b4f Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 1 Sep 2020 15:58:36 -0400 Subject: [PATCH 0235/1385] refactor vsi create-option --- SoftLayer/CLI/virt/create_options.py | 275 +++++------------- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 109 ++++++- SoftLayer/managers/vs.py | 91 +++++- tests/CLI/modules/vs/vs_tests.py | 14 +- tests/managers/vs/vs_tests.py | 7 +- 5 files changed, 269 insertions(+), 227 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 693de74a2..64a2c71a3 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -6,213 +6,92 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer import utils @click.command(short_help="Get options to use for creating virtual servers.") +@click.option('--vsi-type', required=False, type=click.Choice(['TRANSIENT', 'SUSPEND']), + help="Billing rate") @environment.pass_env -def cli(env): +def cli(env, vsi_type): """Virtual server order options.""" + if vsi_type is None: + vsi_type = 'PUBLIC' + vsi_type = vsi_type + '_CLOUD_SERVER' vsi = SoftLayer.VSManager(env.client) - options = vsi.get_create_options() - - tables = [ - _get_datacenter_table(options), - _get_flavors_table(options), - _get_cpu_table(options), - _get_memory_table(options), - _get_os_table(options), - _get_disk_table(options), - _get_network_table(options), - ] - - env.fout(formatting.listing(tables, separator='\n')) + options = vsi.get_create_options(vsi_type) + tables = [] -def _get_datacenter_table(create_options): - datacenters = [dc['template']['datacenter']['name'] - for dc in create_options['datacenters']] + # Datacenters + dc_table = formatting.Table(['datacenter', 'Value'], title="Datacenters") + dc_table.sortby = 'Value' + dc_table.align = 'l' - datacenters = sorted(datacenters) + for location in options['locations']: + dc_table.add_row([location['name'], location['key']]) + tables.append(dc_table) - dc_table = formatting.Table(['datacenter'], title='Datacenters') - dc_table.sortby = 'datacenter' - dc_table.align = 'l' - for datacenter in datacenters: - dc_table.add_row([datacenter]) - return dc_table - - -def _get_flavors_table(create_options): - flavor_table = formatting.Table(['flavor', 'value'], title='Flavors') - flavor_table.sortby = 'flavor' - flavor_table.align = 'l' - grouping = { - 'balanced': {'key_starts_with': 'B1', 'flavors': []}, - 'balanced local - hdd': {'key_starts_with': 'BL1', 'flavors': []}, - 'balanced local - ssd': {'key_starts_with': 'BL2', 'flavors': []}, - 'compute': {'key_starts_with': 'C1', 'flavors': []}, - 'memory': {'key_starts_with': 'M1', 'flavors': []}, - 'GPU': {'key_starts_with': 'AC', 'flavors': []}, - 'transient': {'transient': True, 'flavors': []}, - } - - if create_options.get('flavors', None) is None: - return flavor_table - - for flavor_option in create_options['flavors']: - flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') - - for name, group in grouping.items(): - if utils.lookup(flavor_option, 'template', 'transientGuestFlag') is True: - if utils.lookup(group, 'transient') is True: - group['flavors'].append(flavor_key_name) - break - - elif utils.lookup(group, 'key_starts_with') is not None \ - and flavor_key_name.startswith(group['key_starts_with']): - group['flavors'].append(flavor_key_name) - break - - for name, group in grouping.items(): - if len(group['flavors']) > 0: - flavor_table.add_row(['{}'.format(name), - formatting.listing(group['flavors'], - separator='\n')]) - return flavor_table - - -def _get_cpu_table(create_options): - cpu_table = formatting.Table(['cpu', 'value'], title='CPUs') - cpu_table.sortby = 'cpu' - cpu_table.align = 'l' - standard_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] - if not x['template'].get('dedicatedAccountHostOnlyFlag', - False) - and not x['template'].get('dedicatedHost', None)] - ded_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] - if x['template'].get('dedicatedAccountHostOnlyFlag', False)] - ded_host_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] - if x['template'].get('dedicatedHost', None)] - - standard_cpus = sorted(standard_cpus) - cpu_table.add_row(['standard', formatting.listing(standard_cpus, separator=',')]) - ded_cpus = sorted(ded_cpus) - cpu_table.add_row(['dedicated', formatting.listing(ded_cpus, separator=',')]) - ded_host_cpus = sorted(ded_host_cpus) - cpu_table.add_row(['dedicated host', formatting.listing(ded_host_cpus, separator=',')]) - return cpu_table - - -def _get_memory_table(create_options): - memory_table = formatting.Table(['memory', 'value'], title='Memories') - memory_table.sortby = 'memory' - memory_table.align = 'l' - memory = [int(m['template']['maxMemory']) for m in create_options['memory'] - if not m['itemPrice'].get('dedicatedHostInstanceFlag', False)] - ded_host_memory = [int(m['template']['maxMemory']) for m in create_options['memory'] - if m['itemPrice'].get('dedicatedHostInstanceFlag', False)] - - memory = sorted(memory) - memory_table.add_row(['standard', - formatting.listing(memory, separator=',')]) - - ded_host_memory = sorted(ded_host_memory) - memory_table.add_row(['dedicated host', - formatting.listing(ded_host_memory, separator=',')]) - return memory_table - - -def _get_os_table(create_options): - os_table = formatting.Table(['KeyName', 'Description'], title='Operating Systems') - os_table.sortby = 'KeyName' + # Operation system + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' os_table.align = 'l' - op_sys = [] - for operating_system in create_options['operatingSystems']: - os_option = { - 'referenceCode': operating_system['template']['operatingSystemReferenceCode'], - 'description': operating_system['itemPrice']['item']['description'] - } - op_sys.append(os_option) - - for operating_system in op_sys: - os_table.add_row([ - operating_system['referenceCode'], - operating_system['description'] - ]) - return os_table - - -def _get_disk_table(create_options): - disk_table = formatting.Table(['disk', 'value'], title='Disks') - disk_table.sortby = 'disk' - disk_table.align = 'l' - local_disks = [x for x in create_options['blockDevices'] - if x['template'].get('localDiskFlag', False) - and not x['itemPrice'].get('dedicatedHostInstanceFlag', - False)] - - ded_host_local_disks = [x for x in create_options['blockDevices'] - if x['template'].get('localDiskFlag', False) - and x['itemPrice'].get('dedicatedHostInstanceFlag', - False)] - - san_disks = [x for x in create_options['blockDevices'] - if not x['template'].get('localDiskFlag', False)] - - def add_block_rows(disks, name): - """Add block rows to the table.""" - simple = {} - for disk in disks: - block = disk['template']['blockDevices'][0] - bid = block['device'] - - if bid not in simple: - simple[bid] = [] - - simple[bid].append(str(block['diskImage']['capacity'])) - - for label in sorted(simple): - disk_table.add_row(['%s disk(%s)' % (name, label), - formatting.listing(simple[label], - separator=',')]) - - add_block_rows(san_disks, 'san') - add_block_rows(local_disks, 'local') - add_block_rows(ded_host_local_disks, 'local (dedicated host)') - return disk_table - - -def _get_network_table(create_options): - network_table = formatting.Table(['network', 'value'], title='Network') - network_table.sortby = 'network' - network_table.align = 'l' - speeds = [] - ded_host_speeds = [] - for option in create_options['networkComponents']: - template = option.get('template', None) - price = option.get('itemPrice', None) - - if not template or not price \ - or not template.get('networkComponents', None): - continue - - if not template['networkComponents'][0] \ - or not template['networkComponents'][0].get('maxSpeed', None): - continue - - max_speed = str(template['networkComponents'][0]['maxSpeed']) - if price.get('dedicatedHostInstanceFlag', False) \ - and max_speed not in ded_host_speeds: - ded_host_speeds.append(max_speed) - elif max_speed not in speeds: - speeds.append(max_speed) - - speeds = sorted(speeds) - network_table.add_row(['nic', formatting.listing(speeds, separator=',')]) - - ded_host_speeds = sorted(ded_host_speeds) - network_table.add_row(['nic (dedicated host)', - formatting.listing(ded_host_speeds, separator=',')]) - return network_table + + for operating_system in options['operating_systems']: + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + tables.append(os_table) + + flavors_table = formatting.Table(['flavor', 'Name'], title="Flavors") + flavors_table.sortby = 'Name' + flavors_table.align = 'l' + + for flavor in options['flavors']: + flavors_table.add_row([flavor['flavor']['keyName'], flavor['flavor']['name']]) + tables.append(flavors_table) + + # RAM + ram_table = formatting.Table(['memory', 'Value'], title="RAM") + ram_table.sortby = 'Value' + ram_table.align = 'l' + + for ram in options['ram']: + ram_table.add_row([ram['name'], ram['key']]) + tables.append(ram_table) + + # Data base + database_table = formatting.Table(['database', 'Value'], title="Databases") + database_table.sortby = 'Value' + database_table.align = 'l' + + for database in options['database']: + database_table.add_row([database['name'], database['key']]) + tables.append(database_table) + + # Guest_core + guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") + guest_core_table.sortby = 'Value' + guest_core_table.align = 'l' + + for guest_core in options['guest_core']: + guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) + tables.append(guest_core_table) + + # Guest_core + guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") + guest_disk_table.sortby = 'Value' + guest_disk_table.align = 'l' + + for guest_disk in options['guest_disk']: + guest_disk_table.add_row([guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) + tables.append(guest_disk_table) + + # Port speed + port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") + port_speed_table.sortby = 'Key' + port_speed_table.align = 'l' + + for speed in options['port_speed']: + port_speed_table.add_row([speed['name'], speed['key']]) + tables.append(port_speed_table) + + env.fout(formatting.listing(tables, separator='\n')) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 3b9a24302..987fd12d7 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -68,12 +68,12 @@ }], 'tagReferences': [{'tag': {'name': 'production'}}], } - getCreateObjectOptions = { 'flavors': [ { 'flavor': { - 'keyName': 'B1_1X2X25' + 'keyName': 'B1_1X2X25', + 'name': 'B1-1X2X25' }, 'template': { 'supplementalCreateObjectOptions': { @@ -83,7 +83,8 @@ }, { 'flavor': { - 'keyName': 'B1_1X2X25_TRANSIENT' + 'keyName': 'B1_1X2X25_TRANSIENT', + 'name': 'B1-1X2X25_TRANSIENT' }, 'template': { 'supplementalCreateObjectOptions': { @@ -94,7 +95,8 @@ }, { 'flavor': { - 'keyName': 'B1_1X2X100' + 'keyName': 'B1_1X2X100', + 'name': 'B1-1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -104,7 +106,8 @@ }, { 'flavor': { - 'keyName': 'BL1_1X2X100' + 'keyName': 'BL1_1X2X100', + 'name': 'BL1_1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -114,7 +117,8 @@ }, { 'flavor': { - 'keyName': 'BL2_1X2X100' + 'keyName': 'BL2_1X2X100', + 'name': 'BL2-1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -124,7 +128,8 @@ }, { 'flavor': { - 'keyName': 'C1_1X2X25' + 'keyName': 'C1_1X2X25', + 'name': 'C1-1X2X25' }, 'template': { 'supplementalCreateObjectOptions': { @@ -134,7 +139,8 @@ }, { 'flavor': { - 'keyName': 'M1_1X2X100' + 'keyName': 'M1_1X2X100', + 'name': 'M1_1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -144,7 +150,8 @@ }, { 'flavor': { - 'keyName': 'AC1_1X2X100' + 'keyName': 'AC1_1X2X100', + 'name': 'AC1_1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -154,7 +161,8 @@ }, { 'flavor': { - 'keyName': 'ACL1_1X2X100' + 'keyName': 'ACL1_1X2X100', + 'name': 'ACL1_1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -245,6 +253,12 @@ ], 'memory': [ { + "description": "1 GB ", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 4 + }, 'itemPrice': { 'item': {'description': '1 GB'}, 'hourlyRecurringFee': '.03', @@ -253,14 +267,27 @@ 'template': {'maxMemory': 1024} }, { - 'itemPrice': { - 'item': {'description': '2 GB'}, - 'hourlyRecurringFee': '.06', - 'recurringFee': '42' + "description": "2 GB ", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 5 }, + 'itemPrice': + { + 'item': {'description': '2 GB'}, + 'hourlyRecurringFee': '.06', + 'recurringFee': '42' + }, 'template': {'maxMemory': 2048} }, { + "description": "3 GB", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 65 + }, 'itemPrice': { 'item': {'description': '3 GB'}, 'hourlyRecurringFee': '.085', @@ -268,6 +295,12 @@ 'template': {'maxMemory': 3072} }, { + "description": "4 GB", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 35 + }, 'itemPrice': { 'item': {'description': '4 GB'}, 'hourlyRecurringFee': '.11', @@ -276,6 +309,12 @@ 'template': {'maxMemory': 4096} }, { + "description": "64 GB (Dedicated Host)", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 3 + }, 'itemPrice': { 'hourlyRecurringFee': '0', 'recurringFee': '0', @@ -289,6 +328,12 @@ } }, { + "description": "8 GB (Dedicated Host)", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 36 + }, 'itemPrice': { 'hourlyRecurringFee': '0', 'recurringFee': '0', @@ -423,6 +468,42 @@ {'template': {'datacenter': {'name': 'ams01'}}}, {'template': {'datacenter': {'name': 'dal05'}}}, ], + 'guest_disk': [{ + "description": "25 GB (SAN)", + "attributes": [ + { + "id": 197, + "attributeTypeKeyName": "SAN_DISK" + } + ], + "itemCategory": { + "categoryCode": "guest_disk0", + "id": 81 + }}, { + "description": "250 GB (SAN)", + "attributes": [ + { + "id": 198, + "attributeTypeKeyName": "SAN_DISK" + }], + "itemCategory": { + "categoryCode": "guest_disk0", + "id": 89 + }}], + 'guest_core': [{ + "description": "4 x 2.0 GHz or higher Cores (Dedicated)", + "attributes": [], + "itemCategory": { + "categoryCode": "guest_core", + "id": 80 + }}, + { + "description": "8 x 2.0 GHz or higher Cores", + "attributes": [], + "itemCategory": { + "categoryCode": "guest_core", + "id": 90 + }}] } getReverseDomainRecords = [{ diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 2a58d9b62..6c6fec747 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -249,7 +249,7 @@ def get_instance(self, instance_id, **kwargs): return self.guest.getObject(id=instance_id, **kwargs) @retry(logger=LOGGER) - def get_create_options(self): + def get_create_options(self, vsi_type): """Retrieves the available options for creating a VS. :returns: A dictionary of creation options. @@ -260,7 +260,94 @@ def get_create_options(self): options = mgr.get_create_options() print(options) """ - return self.guest.getCreateObjectOptions() + + # TRANSIENT_CLOUD_SERVER + # SUSPEND_CLOUD_SERVER + package = self._get_package(vsi_type) + + # Locations + locations = [] + for region in package['regions']: + locations.append({ + 'name': region['location']['location']['longName'], + 'key': region['location']['location']['name'], + }) + + operating_systems = [] + database = [] + port_speeds = [] + guest_core = [] + local_disk = [] + ram = [] + for item in package['items']: + category = item['itemCategory']['categoryCode'] + # Operating systems + if category == 'os': + operating_systems.append({ + 'name': item['softwareDescription']['longDescription'], + 'key': item['keyName'], + 'referenceCode': item['softwareDescription']['referenceCode'] + }) + # database + elif category == 'database': + database.append({ + 'name': item['description'], + 'key': item['keyName'] + }) + + elif category == 'port_speed': + port_speeds.append({ + 'name': item['description'], + 'key': item['keyName'] + }) + + elif category == 'guest_core': + guest_core.append({ + 'name': item['description'], + 'capacity': item['capacity'], + 'key': item['keyName'] + }) + + elif category == 'ram': + ram.append({ + 'name': item['description'], + 'capacity': item['capacity'], + 'key': item['keyName'] + }) + + elif category.__contains__('guest_disk'): + local_disk.append({ + 'name': item['description'], + 'capacity': item['capacity'], + 'key': item['keyName'], + 'disk': category + }) + # Extras + + return { + 'locations': locations, + 'ram': ram, + 'database': database, + 'operating_systems': operating_systems, + 'guest_core': guest_core, + 'port_speed': port_speeds, + 'guest_disk': local_disk, + 'flavors': self.guest.getCreateObjectOptions()['flavors'] + } + + @retry(logger=LOGGER) + def _get_package(self, package_keyname): + """Get the package related to simple hardware ordering.""" + mask = ''' + items[ + description, keyName, capacity, + attributes[id,attributeTypeKeyName], + itemCategory[ id, categoryCode], + softwareDescription[id,referenceCode,longDescription],prices], + regions[location[location[priceGroups]]] + ''' + package = self.ordering_manager.get_package_by_key(package_keyname, mask=mask) + return package def cancel_instance(self, instance_id): """Cancel an instance immediately, deleting all its data. diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index c9b6c9c0b..fd562f69d 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -314,14 +314,10 @@ def test_detail_vs_ptr_error(self): self.assertEqual(output.get('ptr', None), None) def test_create_options(self): - result = self.run_command(['vs', 'create-options']) + result = self.run_command(['vs', 'create-options', '--vsi-type', 'TRANSIENT']) self.assert_no_fail(result) - self.assertIn('datacenter', result.output) - self.assertIn('flavor', result.output) - self.assertIn('memory', result.output) - self.assertIn('cpu', result.output) - self.assertIn('OS', result.output) - self.assertIn('network', result.output) + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assert_called_with('SoftLayer_Virtual_Guest', 'getCreateObjectOptions') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): @@ -887,6 +883,6 @@ def test_credentail(self): result = self.run_command(['vs', 'credentials', '100']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), [{ - "username": "user", - "password": "pass" + "username": "user", + "password": "pass" }]) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index a9f256c80..c00941433 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -116,10 +116,9 @@ def test_get_instance(self): identifier=100) def test_get_create_options(self): - results = self.vs.get_create_options() - - self.assertEqual( - fixtures.SoftLayer_Virtual_Guest.getCreateObjectOptions, results) + self.vs.get_create_options('PUBLIC_CLOUD_SERVER') + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assert_called_with('SoftLayer_Virtual_Guest', 'getCreateObjectOptions') def test_cancel_instance(self): result = self.vs.cancel_instance(1) From be524bb9d29cd9670d3492baed83ced94895982b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 1 Sep 2020 16:44:37 -0500 Subject: [PATCH 0236/1385] #1299 did a lot of work getting location specific pricing working. Still needs testing --- SoftLayer/CLI/hardware/create_options.py | 12 +- SoftLayer/managers/hardware.py | 136 +++++++++++++++++------ 2 files changed, 106 insertions(+), 42 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 1ae06a9be..abc36d845 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -7,18 +7,17 @@ from SoftLayer.CLI import formatting from SoftLayer.managers import hardware - @click.command() @click.argument('location', required=False) -@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and ' - 'to list the Item Prices by location, add it to the ' - '--prices option using location KeyName, e.g. --prices AMSTERDAM02') +@click.option('--prices', '-p', is_flag=True, + help='Use --prices to list the server item prices, and to list the Item Prices by location,' + 'add it to the --prices option using location short name, e.g. --prices dal13') @environment.pass_env def cli(env, prices, location=None): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) - options = hardware_manager.get_create_options() + options = hardware_manager.get_create_options(location) tables = [] @@ -35,9 +34,6 @@ def cli(env, prices, location=None): _os_prices_table(options['operating_systems'], tables) _port_speed_prices_table(options['port_speeds'], tables) _extras_prices_table(options['extras'], tables) - if location: - location_prices = hardware_manager.get_hardware_item_prices(location) - _location_item_prices(location_prices, tables) else: # Presets preset_table = formatting.Table(['Size', 'Value'], title="Sizes") diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 660838870..a10586b65 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -379,18 +379,38 @@ def get_cancellation_reasons(self): } @retry(logger=LOGGER) - def get_create_options(self): - """Returns valid options for ordering hardware.""" + def get_create_options(self, datacenter=None): + """Returns valid options for ordering hardware. + :param string datacenter: short name, like dal09 + """ + package = self._get_package() + location_group_id = None + if datacenter: + _filter = {"name":{"operation":datacenter}} + _mask = "mask[priceGroups]" + dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', + mask=_mask, filter=_filter, limit=1) + if not dc_details: + raise SoftLayerError("Unable to find a datacenter named {}".format(datacenter)) + # A DC will have several price groups, no good way to deal with this other than checking each. + # An item should only belong to one type of price group. + for group in dc_details[0].get('priceGroups', []): + # We only care about SOME of the priceGroups, which all SHOULD start with `Location Group X` + # Hopefully this never changes.... + if group.get('description').startswith('Location'): + location_group_id = group.get('id') + # Locations locations = [] for region in package['regions']: - locations.append({ - 'name': region['location']['location']['longName'], - 'key': region['location']['location']['name'], - }) + if datacenter is None or datacenter == region['location']['location']['name']: + locations.append({ + 'name': region['location']['location']['longName'], + 'key': region['location']['location']['name'], + }) # Sizes sizes = [] @@ -398,8 +418,8 @@ def get_create_options(self): sizes.append({ 'name': preset['description'], 'key': preset['keyName'], - 'hourlyRecurringFee': _get_preset_cost(preset['prices'], 'hourly'), - 'recurringFee': _get_preset_cost(preset['prices'], 'monthly') + 'hourlyRecurringFee': _get_preset_cost(preset, package['items'], 'hourly', location_group_id), + 'recurringFee': _get_preset_cost(preset, package['items'], 'monthly', location_group_id) }) operating_systems = [] @@ -413,7 +433,7 @@ def get_create_options(self): 'name': item['softwareDescription']['longDescription'], 'key': item['keyName'], 'referenceCode': item['softwareDescription']['referenceCode'], - 'prices': get_item_price(item['prices']) + 'prices': get_item_price(item['prices'], location_group_id) }) # Port speeds elif category == 'port_speed': @@ -421,14 +441,14 @@ def get_create_options(self): 'name': item['description'], 'speed': item['capacity'], 'key': item['keyName'], - 'prices': get_item_price(item['prices']) + 'prices': get_item_price(item['prices'], location_group_id) }) # Extras elif category in EXTRA_CATEGORIES: extras.append({ 'name': item['description'], 'key': item['keyName'], - 'prices': get_item_price(item['prices']) + 'prices': get_item_price(item['prices'], location_group_id) }) return { @@ -442,21 +462,25 @@ def get_create_options(self): @retry(logger=LOGGER) def _get_package(self): """Get the package related to simple hardware ordering.""" - mask = ''' - items[ - keyName, - capacity, - description, - attributes[id,attributeTypeKeyName], - itemCategory[id,categoryCode], - softwareDescription[id,referenceCode,longDescription], - prices - ], - activePresets[prices], - accountRestrictedActivePresets[prices], - regions[location[location[priceGroups]]] - ''' - package = self.ordering_manager.get_package_by_key(self.package_keyname, mask=mask) + from pprint import pprint as pp + items_mask = 'mask[id,keyName,capacity,description,attributes[id,attributeTypeKeyName],' \ + 'itemCategory[id,categoryCode],softwareDescription[id,referenceCode,longDescription],' \ + 'prices]' + # The preset prices list will only have default prices. The prices->item->prices will have location specific + presets_mask = 'mask[prices]' + region_mask = 'location[location[priceGroups]]' + package = {'items': None,'activePresets': None, 'accountRestrictedActivePresets': None, 'regions': None} + package_info = self.ordering_manager.get_package_by_key(self.package_keyname, mask="mask[id]") + + package['items'] = self.client.call('SoftLayer_Product_Package', 'getItems', + id=package_info.get('id'), mask=items_mask) + package['activePresets'] = self.client.call('SoftLayer_Product_Package', 'getActivePresets', + id=package_info.get('id'), mask=presets_mask) + package['accountRestrictedActivePresets'] = self.client.call('SoftLayer_Product_Package', + 'getAccountRestrictedActivePresets', + id=package_info.get('id'), mask=presets_mask) + package['regions'] = self.client.call('SoftLayer_Product_Package', 'getRegions', + id=package_info.get('id'), mask=region_mask) return package def _generate_create_dict(self, @@ -856,21 +880,65 @@ def _get_location(package, location): raise SoftLayerError("Could not find valid location for: '%s'" % location) -def _get_preset_cost(prices, type_cost): - """Get the preset cost.""" +def _get_preset_cost(preset, items, type_cost, location_group_id=None): + """Get the preset cost. + + :param preset list: SoftLayer_Product_Package_Preset[] + :param items list: SoftLayer_Product_Item[] + :param type_cost string: 'hourly' or 'monthly' + :param location_group_id int: locationGroupId's to get price for. + """ + + # Location based pricing on presets is a huge pain. Requires a few steps + # 1. Get the presets prices, which are only ever the default prices + # 2. Get that prices item ID, and use that to match the packages item + # 3. find the package item, THEN find that items prices + # 4. from those item prices, find the one that matches your locationGroupId + item_cost = 0.00 - for price in prices: - if type_cost == 'hourly': - item_cost += float(price['hourlyRecurringFee']) + if type_cost == 'hourly': + cost_key = 'hourlyRecurringFee' + else: + cost_key = 'recurringFee' + for price in preset.get('prices', []): + # Need to find the location specific price + if location_group_id: + # Find the item in the packages item list + for item in items: + # Same item as the price's item + if item.get('id') == price.get('itemId'): + # Find the items location specific price. + for location_price in item.get('prices', []): + if location_price.get('locationGroupId', 0) == location_group_id: + item_cost += float(location_price.get(cost_key)) else: - item_cost += float(price['recurringFee']) + item_cost += float(price.get(cost_key)) return item_cost -def get_item_price(prices): - """Get item prices""" +def get_item_price(prices, location_group_id=None): + """Get item prices, optionally for a specific location. + + Will return the default pricing information if there isn't any location specific pricing. + + :param prices list: SoftLayer_Product_Item_Price[] + :param location_group_id int: locationGroupId's to get price for. + """ prices_list = [] + location_price = [] for price in prices: + # Only look up location prices if we need to + if location_group_id: + if price['locationGroupId'] == location_group_id: + location_price.append(price) + # Always keep track of default prices if not price['locationGroupId']: prices_list.append(price) + + # If this item has location specific pricing, return that + if location_price: + return location_price + + # Otherwise reutrn the default price list. return prices_list + \ No newline at end of file From 3a513c0db9f69e04d8577c0244493d486542faca Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Sep 2020 16:00:42 -0400 Subject: [PATCH 0237/1385] Refactor hw create-options. --- .../fixtures/SoftLayer_Product_Package.py | 87 ++++++++++++++++++- SoftLayer/managers/hardware.py | 8 +- tests/CLI/modules/server_tests.py | 21 +++-- tests/managers/hardware_tests.py | 26 +++--- 4 files changed, 110 insertions(+), 32 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 9a2019909..91839447e 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -859,6 +859,11 @@ 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, + 'softwareDescription': { + 'id': 1228, + 'longDescription': 'Redhat EL 5.10-64', + 'referenceCode': 'REDHAT_5_64' + }, 'prices': [{'id': 1122, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0, @@ -994,11 +999,11 @@ }, { 'id': 66464, - 'keyName': 'KeyName0211', + 'keyName': '1_IPV6_ADDRESS', 'capacity': '64', 'description': '/64 Block Portable Public IPv6 Addresses', 'itemCategory': {'categoryCode': 'static_ipv6_addresses'}, - 'prices': [{'id': 664641, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 664641, 'hourlyRecurringFee': '0', 'locationGroupId': '', 'recurringFee': '0'}], }, { 'id': 610, @@ -1007,7 +1012,64 @@ 'description': 'Global IPv6', 'itemCategory': {'categoryCode': 'global_ipv6'}, 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], - }] + }, + {'attributes': [], + 'capacity': '0', + 'description': '0 GB Bandwidth', + 'itemCategory': {'categoryCode': 'bandwidth', 'id': 10}, + 'keyName': 'BANDWIDTH_0_GB_2', + 'prices': [{'accountRestrictions': [], + 'currentPriceFlag': '', + 'hourlyRecurringFee': '0', + 'id': 1800, + "locationGroupId": '', + 'itemId': 439, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'quantity': '', + 'setupFee': '0', + 'sort': 99}]}, + {'attributes': [], + 'capacity': '10', + 'description': '10 Mbps Public & Private Network Uplinks', + 'itemCategory': {'categoryCode': 'port_speed', 'id': 26}, + 'keyName': '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', + 'prices': [{'accountRestrictions': [], + 'currentPriceFlag': '', + 'hourlyRecurringFee': '0', + 'id': 272, + "locationGroupId": '', + 'itemId': 186, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'quantity': '', + 'recurringFee': '0', + 'setupFee': '0', + 'sort': 5}]}, + {'attributes': [], + 'capacity': '0', + 'description': 'Ubuntu Linux 14.04 LTS Trusty Tahr (64 bit)', + 'itemCategory': {'categoryCode': 'os', 'id': 12}, + 'keyName': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'prices': [{'accountRestrictions': [], + 'currentPriceFlag': '', + 'hourlyRecurringFee': '0', + 'id': 37650, + "locationGroupId": '', + 'itemId': 4702, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'quantity': '', + 'recurringFee': '0', + 'setupFee': '0', + 'sort': 9}], + 'softwareDescription': {'id': 1362, + 'longDescription': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64'}} +] getItemPricesISCSI = [ { @@ -1504,6 +1566,25 @@ getRegions = [{ "description": "WDC07 - Washington, DC", "keyname": "WASHINGTON07", + "location": { + "locationId": 2017603, + "location": { + "id": 2017603, + "longName": "Washington 7", + "name": "wdc07", + "priceGroups": [ + { + "description": "COS Regional - US East", + "id": 1305, + "locationGroupTypeId": 82, + "name": "us-east", + "locationGroupType": { + "name": "PRICING" + } + } + ] + } + }, "locations": [{ "location": { "euCompliantFlag": False, diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index a10586b65..54efef5d2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -389,7 +389,7 @@ def get_create_options(self, datacenter=None): location_group_id = None if datacenter: - _filter = {"name":{"operation":datacenter}} + _filter = {"name": {"operation": datacenter}} _mask = "mask[priceGroups]" dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', mask=_mask, filter=_filter, limit=1) @@ -462,14 +462,13 @@ def get_create_options(self, datacenter=None): @retry(logger=LOGGER) def _get_package(self): """Get the package related to simple hardware ordering.""" - from pprint import pprint as pp items_mask = 'mask[id,keyName,capacity,description,attributes[id,attributeTypeKeyName],' \ 'itemCategory[id,categoryCode],softwareDescription[id,referenceCode,longDescription],' \ - 'prices]' + 'prices[categories]]' # The preset prices list will only have default prices. The prices->item->prices will have location specific presets_mask = 'mask[prices]' region_mask = 'location[location[priceGroups]]' - package = {'items': None,'activePresets': None, 'accountRestrictedActivePresets': None, 'regions': None} + package = {'items': None, 'activePresets': None, 'accountRestrictedActivePresets': None, 'regions': None} package_info = self.ordering_manager.get_package_by_key(self.package_keyname, mask="mask[id]") package['items'] = self.client.call('SoftLayer_Product_Package', 'getItems', @@ -941,4 +940,3 @@ def get_item_price(prices, location_group_id=None): # Otherwise reutrn the default price list. return prices_list - \ No newline at end of file diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index ebf3dc1c0..48a0fc100 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -360,28 +360,27 @@ def test_create_options(self): self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[0][0]['Value'], 'wdc01') - self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assertEqual(output[0][0]['Value'], 'wdc07') def test_create_options_prices(self): result = self.run_command(['server', 'create-options', '--prices']) + print("-----------------") + print(result.output) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Hourly'], "%.4f" % 1.18) - self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') - self.assertEqual(output[3][0]['Monthly'], '0') - self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) + self.assertEqual(output[1][0]['Value'], 'M1_64X512X25') + self.assertEqual(output[1][0]['Size'], 'M1.64x512x25') def test_create_options_location(self): - result = self.run_command(['server', 'create-options', '--prices', 'AMSTERDAM02']) + result = self.run_command(['server', 'create-options', '--prices', 'dal13']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Monthly'], "%.4f" % 780.0) - self.assertEqual(output[1][0]['Hourly'], "%.4f" % 1.18) - self.assertEqual(output[1][0]['Value'], 'S1270_8GB_2X1TBSATA_NORAID') - self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + self.assertEqual(output[1][0]['Monthly'], "%.4f" % 0.0) + self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) + self.assertEqual(output[1][0]['Value'], 'M1_64X512X25') @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index b6cfc7724..8b361b935 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -120,7 +120,7 @@ def test_get_create_options(self): options = self.hardware.get_create_options() extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} - locations = {'key': 'wdc01', 'name': 'Washington 1'} + locations = {'key': 'wdc07', 'name': 'Washington 7'} operating_systems = { 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'name': 'Ubuntu / 14.04-64', @@ -132,10 +132,10 @@ def test_get_create_options(self): 'name': '10 Mbps Public & Private Network Uplinks' } sizes = { - 'key': 'S1270_8GB_2X1TBSATA_NORAID', - 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', - 'hourlyRecurringFee': 1.18, - 'recurringFee': 780.0 + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 } self.assertEqual(options['extras'][0]['key'], extras['key']) @@ -158,7 +158,7 @@ def test_get_create_options_prices(self): } ] } - locations = {'key': 'wdc01', 'name': 'Washington 1'} + locations = {'key': 'wdc07', 'name': 'Washington 7'} operating_systems = { 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'name': 'Ubuntu / 14.04-64', @@ -186,10 +186,10 @@ def test_get_create_options_prices(self): ] } sizes = { - 'key': 'S1270_8GB_2X1TBSATA_NORAID', - 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', - 'hourlyRecurringFee': 1.18, - 'recurringFee': 780.0 + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 } self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], @@ -258,7 +258,7 @@ def test_generate_create_dict(self): 'size': 'S1270_8GB_2X1TBSATA_NORAID', 'hostname': 'unicorn', 'domain': 'giggles.woo', - 'location': 'wdc01', + 'location': 'wdc07', 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'port_speed': 10, 'hourly': True, @@ -268,7 +268,7 @@ def test_generate_create_dict(self): } package = 'BARE_METAL_SERVER' - location = 'wdc01' + location = 'wdc07' item_keynames = [ '1_IP_ADDRESS', 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', @@ -304,7 +304,7 @@ def test_generate_create_dict_network_key(self): 'size': 'S1270_8GB_2X1TBSATA_NORAID', 'hostname': 'test1', 'domain': 'test.com', - 'location': 'wdc01', + 'location': 'wdc07', 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'network': 'NETWORKING', 'hourly': True, From faa829bfcfaae440498acb85eafb96853a52675a Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 3 Sep 2020 16:27:33 -0400 Subject: [PATCH 0238/1385] #1336 add Invoice Item id as param valid in account item-detail command --- SoftLayer/CLI/account/item_detail.py | 104 +++++++++--------- .../SoftLayer_Billing_Invoice_Item.py | 3 + SoftLayer/managers/account.py | 25 ++++- tests/managers/account_tests.py | 16 +++ 4 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Billing_Invoice_Item.py diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py index 7a2c53df3..9af5b4bf2 100644 --- a/SoftLayer/CLI/account/item_detail.py +++ b/SoftLayer/CLI/account/item_detail.py @@ -1,52 +1,52 @@ -"""Gets some details about a specific billing item.""" -# :license: MIT, see LICENSE for more details. -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.managers.account import AccountManager as AccountManager -from SoftLayer import utils - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Gets detailed information about a billing item.""" - manager = AccountManager(env.client) - item = manager.get_billing_item(identifier) - env.fout(item_table(item)) - - -def item_table(item): - """Formats a table for billing items""" - - date_format = '%Y-%m-%d' - table = formatting.KeyValueTable(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) - table.add_row(['createDate', utils.clean_time(item.get('createDate'), date_format, date_format)]) - table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) - table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) - table.add_row(['description', item.get('description')]) - fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) - if fqdn != ".": - table.add_row(['FQDN', fqdn]) - - if item.get('hourlyFlag', False): - table.add_row(['hourlyRecurringFee', item.get('hourlyRecurringFee')]) - table.add_row(['hoursUsed', item.get('hoursUsed')]) - table.add_row(['currentHourlyCharge', item.get('currentHourlyCharge')]) - else: - table.add_row(['recurringFee', item.get('recurringFee')]) - - ordered_by = "IBM" - user = utils.lookup(item, 'orderItem', 'order', 'userRecord') - if user: - ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) - table.add_row(['Ordered By', ordered_by]) - table.add_row(['Notes', item.get('notes')]) - table.add_row(['Location', utils.lookup(item, 'location', 'name')]) - if item.get('children'): - for child in item.get('children'): - table.add_row([child.get('categoryCode'), child.get('description')]) - - return table +"""Gets some details about a specific billing item.""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Gets detailed information about a billing item.""" + manager = AccountManager(env.client) + item = manager.get_item_detail(identifier) + env.fout(item_table(item)) + + +def item_table(item): + """Formats a table for billing items""" + + date_format = '%Y-%m-%d' + table = formatting.KeyValueTable(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) + table.add_row(['createDate', utils.clean_time(item.get('createDate'), date_format, date_format)]) + table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) + table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) + table.add_row(['description', item.get('description')]) + fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) + if fqdn != ".": + table.add_row(['FQDN', fqdn]) + + if item.get('hourlyFlag', False): + table.add_row(['hourlyRecurringFee', item.get('hourlyRecurringFee')]) + table.add_row(['hoursUsed', item.get('hoursUsed')]) + table.add_row(['currentHourlyCharge', item.get('currentHourlyCharge')]) + else: + table.add_row(['recurringFee', item.get('recurringFee')]) + + ordered_by = "IBM" + user = utils.lookup(item, 'orderItem', 'order', 'userRecord') + if user: + ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + table.add_row(['Ordered By', ordered_by]) + table.add_row(['Notes', item.get('notes')]) + table.add_row(['Location', utils.lookup(item, 'location', 'name')]) + if item.get('children'): + for child in item.get('children'): + table.add_row([child.get('categoryCode'), child.get('description')]) + + return table diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Invoice_Item.py b/SoftLayer/fixtures/SoftLayer_Billing_Invoice_Item.py new file mode 100644 index 000000000..4be040cbe --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Billing_Invoice_Item.py @@ -0,0 +1,3 @@ +from SoftLayer.fixtures.SoftLayer_Billing_Item import getObject as billingItem + +getBillingItem = billingItem diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 56f951c28..ff595f8aa 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -8,6 +8,7 @@ import logging +from SoftLayer import SoftLayerAPIError from SoftLayer import utils # Invalid names are ignored due to long method names and short argument names @@ -205,7 +206,7 @@ def get_account_billing_items(self, mask=None): def get_billing_item(self, identifier, mask=None): """Gets details about a billing item - :param int identifier Billing_Item id + :param int identifier: Billing_Item id :param string mask: Object mask to use. :return: Billing_Item """ @@ -219,6 +220,28 @@ def get_billing_item(self, identifier, mask=None): return self.client.call('Billing_Item', 'getObject', id=identifier, mask=mask) + def get_billing_item_from_invoice(self, identifier): + """Gets details about a billing item of a billing invoice item + + :param int identifier: Billing_Invoice_Item id + :return: Billing_Item + """ + return self.client.call('Billing_Invoice_Item', 'getBillingItem', id=identifier) + + def get_item_detail(self, identifier): + """Gets details about a billing item + + :param int identifier: Billing_Item id or Billing_Invoice_Item + :return: Billing_Item + """ + + try: + return self.get_billing_item(identifier) + except SoftLayerAPIError as exception: + if exception.faultCode == 404: + return self.get_billing_item_from_invoice(identifier) + raise + def cancel_item(self, identifier, reason="No longer needed", note=None): """Cancels a specific billing item with a reason diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index b47ec6abb..b051e5ee7 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -5,6 +5,7 @@ """ from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import SoftLayerAPIError from SoftLayer import testing @@ -133,3 +134,18 @@ def test_cancel_item(self): self.manager.cancel_item(12345, reason, note) self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', args=(False, True, reason, note), identifier=12345) + + def test_get_billing_item_from_invoice(self): + self.manager.get_billing_item_from_invoice(12345) + self.assert_called_with('SoftLayer_Billing_Invoice_Item', 'getBillingItem', identifier=12345) + + def test_get_item_details_with_billing_item_id(self): + self.manager.get_item_detail(12345) + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=12345) + + def test_get_item_details_with_invoice_item_id(self): + mock = self.set_mock('SoftLayer_Billing_Item', 'getObject') + mock.side_effect = SoftLayerAPIError(404, "Unable to find object with id of '123456'.") + self.manager.get_item_detail(123456) + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=123456) + self.assert_called_with('SoftLayer_Billing_Invoice_Item', 'getBillingItem', identifier=123456) From 6b8bbbb4e5a40076aecd6a2ddad3807514db4e5b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Sep 2020 16:39:24 -0400 Subject: [PATCH 0239/1385] Fix tox test. --- .../fixtures/SoftLayer_Product_Package.py | 47 +++++++++++++++++++ tests/CLI/modules/vs/vs_create_tests.py | 3 ++ 2 files changed, 50 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 91839447e..f47bca334 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1138,6 +1138,53 @@ 'sort': 0 }] +getItemsVS = [ + { + 'id': 1234, + 'keyName': 'KeyName01', + 'capacity': '1000', + 'description': 'Public & Private Networks', + 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, + 'softwareDescription': { + 'id': 1228, + 'longDescription': 'Redhat EL 5.10-64', + 'referenceCode': 'REDHAT_5_64' + }, + 'prices': [{'id': 1122, + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, + 'categories': [{'id': 26, + 'name': 'Uplink Port Speeds', + 'categoryCode': 'port_speed'}]}], + }, + { + 'id': 2233, + 'keyName': 'KeyName02', + 'capacity': '1000', + 'description': 'Public & Private Networks', + 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, + 'prices': [{'id': 4477, + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, + 'categories': [{'id': 26, + 'name': 'Uplink Port Speeds', + 'categoryCode': 'port_speed'}]}], + }, + { + 'id': 1239, + 'keyName': 'KeyName03', + 'capacity': '2', + 'description': 'RAM', + 'itemCategory': {'categoryCode': 'RAM'}, + 'prices': [{'id': 1133, + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, + 'categories': [{'id': 3, + 'name': 'RAM', + 'categoryCode': 'ram'}]}], + } +] + verifyOrderDH = { 'preTaxSetup': '0', 'storageGroups': [], diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 761778db6..413bb6c18 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -639,6 +639,9 @@ def test_create_with_ipv6_no_prices(self, confirm_mock): Since its hard to test if the price ids gets added to placeOrder call, this test juse makes sure that code block isn't being skipped """ + confirm_mock.return_value = True + amock = self.set_mock('SoftLayer_Product_Package', 'getItems') + amock.return_value = SoftLayer_Product_Package.getItemsVS result = self.run_command(['vs', 'create', '--test', '--hostname', 'TEST', '--domain', 'TESTING', '--flavor', 'B1_2X8X25', '--datacenter', 'TEST00', '--os', 'UBUNTU_LATEST', From b7b3dd184b0656a7432ca482bce5fd0e08cfaf19 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Sep 2020 16:49:00 -0400 Subject: [PATCH 0240/1385] Fix tox analysis. --- SoftLayer/CLI/hardware/create_options.py | 1 + SoftLayer/managers/hardware.py | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index abc36d845..d61695be3 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -7,6 +7,7 @@ from SoftLayer.CLI import formatting from SoftLayer.managers import hardware + @click.command() @click.argument('location', required=False) @click.option('--prices', '-p', is_flag=True, diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 54efef5d2..849026ed4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -384,15 +384,14 @@ def get_create_options(self, datacenter=None): :param string datacenter: short name, like dal09 """ - + package = self._get_package() location_group_id = None if datacenter: _filter = {"name": {"operation": datacenter}} _mask = "mask[priceGroups]" - dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', - mask=_mask, filter=_filter, limit=1) + dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', mask=_mask, filter=_filter, limit=1) if not dc_details: raise SoftLayerError("Unable to find a datacenter named {}".format(datacenter)) # A DC will have several price groups, no good way to deal with this other than checking each. @@ -881,7 +880,7 @@ def _get_location(package, location): def _get_preset_cost(preset, items, type_cost, location_group_id=None): """Get the preset cost. - + :param preset list: SoftLayer_Product_Package_Preset[] :param items list: SoftLayer_Product_Item[] :param type_cost string: 'hourly' or 'monthly' From d1ff3fb9e657329898af71c3be5c6a7599a71bf4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 7 Sep 2020 14:16:53 -0400 Subject: [PATCH 0241/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 32 ++++++++++++----- tests/CLI/modules/server_tests.py | 2 -- tests/managers/hardware_tests.py | 58 +++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 849026ed4..53939f2e1 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -410,7 +410,6 @@ def get_create_options(self, datacenter=None): 'name': region['location']['location']['longName'], 'key': region['location']['location']['name'], }) - # Sizes sizes = [] for preset in package['activePresets'] + package['accountRestrictedActivePresets']: @@ -467,7 +466,7 @@ def _get_package(self): # The preset prices list will only have default prices. The prices->item->prices will have location specific presets_mask = 'mask[prices]' region_mask = 'location[location[priceGroups]]' - package = {'items': None, 'activePresets': None, 'accountRestrictedActivePresets': None, 'regions': None} + package = {'items': [], 'activePresets': [], 'accountRestrictedActivePresets': [], 'regions': []} package_info = self.ordering_manager.get_package_by_key(self.package_keyname, mask="mask[id]") package['items'] = self.client.call('SoftLayer_Product_Package', 'getItems', @@ -902,18 +901,33 @@ def _get_preset_cost(preset, items, type_cost, location_group_id=None): # Need to find the location specific price if location_group_id: # Find the item in the packages item list - for item in items: - # Same item as the price's item - if item.get('id') == price.get('itemId'): - # Find the items location specific price. - for location_price in item.get('prices', []): - if location_price.get('locationGroupId', 0) == location_group_id: - item_cost += float(location_price.get(cost_key)) + item_cost = find_item_in_package(cost_key, items, location_group_id, price) else: item_cost += float(price.get(cost_key)) return item_cost +def find_item_in_package(cost_key, items, location_group_id, price): + """Find the item in the packages item list. + + Will return the item cost. + + :param string cost_key: item cost key hourlyRecurringFee or recurringFee. + :param list items: items list. + :param int location_group_id: locationGroupId's to get price for. + :param price: price data. + """ + item_cost = 0.00 + for item in items: + # Same item as the price's item + if item.get('id') == price.get('itemId'): + # Find the items location specific price. + for location_price in item.get('prices', []): + if location_price.get('locationGroupId', 0) == location_group_id: + item_cost += float(location_price.get(cost_key)) + return item_cost + + def get_item_price(prices, location_group_id=None): """Get item prices, optionally for a specific location. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 48a0fc100..dc9d70726 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -365,8 +365,6 @@ def test_create_options(self): def test_create_options_prices(self): result = self.run_command(['server', 'create-options', '--prices']) - print("-----------------") - print(result.output) self.assert_no_fail(result) output = json.loads(result.output) self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 8b361b935..e5b532647 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -200,6 +200,64 @@ def test_get_create_options_prices(self): self.assertEqual(options['port_speeds'][0]['prices'][0]['id'], port_speeds['prices'][0]['id']) self.assertEqual(options['sizes'][0], sizes) + def test_get_create_options_prices_by_location(self): + options = self.hardware.get_create_options('wdc07') + + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + locations = {'key': 'wdc07', 'name': 'Washington 7'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + sizes = { + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 + } + + print("---------") + print(options) + + self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], + extras['prices'][0]['hourlyRecurringFee']) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0]['prices'][0]['locationGroupId'], + operating_systems['prices'][0]['locationGroupId']) + self.assertEqual(options['port_speeds'][0]['prices'][0]['id'], port_speeds['prices'][0]['id']) + self.assertEqual(options['sizes'][0], sizes) + def test_get_hardware_item_prices(self): options = self.hardware.get_hardware_item_prices("MONTREAL") item_prices = [ From a96c034f1609d2d4612c64b0c27d1ec62fd809b3 Mon Sep 17 00:00:00 2001 From: try Date: Thu, 10 Sep 2020 22:12:09 +0530 Subject: [PATCH 0242/1385] final repo-modified the help console comments --- SoftLayer/CLI/block/refresh.py | 2 +- SoftLayer/CLI/file/refresh.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 0c94ae67f..9e610f04a 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -11,7 +11,7 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """"Refresh a duplicate volume with a snapshot from its parent.""" + """Refresh a duplicate volume with a snapshot from its parent.""" block_manager = SoftLayer.BlockStorageManager(env.client) resp = block_manager.refresh_dupe(volume_id, snapshot_id) diff --git a/SoftLayer/CLI/file/refresh.py b/SoftLayer/CLI/file/refresh.py index 765e82730..8e2b1c543 100644 --- a/SoftLayer/CLI/file/refresh.py +++ b/SoftLayer/CLI/file/refresh.py @@ -11,7 +11,7 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """"Refresh a duplicate volume with a snapshot from its parent.""" + """Refresh a duplicate volume with a snapshot from its parent.""" file_manager = SoftLayer.FileStorageManager(env.client) resp = file_manager.refresh_dupe(volume_id, snapshot_id) From 7669134285cee0c55cb3809cd8bb3f2e6f1aaea5 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 10 Sep 2020 19:16:32 -0400 Subject: [PATCH 0243/1385] fix the Christopher code review --- SoftLayer/CLI/virt/create_options.py | 22 ++++++++++----------- SoftLayer/managers/vs.py | 29 ++++++++++++++++++---------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 64a2c71a3..4e73d55be 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -9,15 +9,14 @@ @click.command(short_help="Get options to use for creating virtual servers.") -@click.option('--vsi-type', required=False, type=click.Choice(['TRANSIENT', 'SUSPEND']), - help="Billing rate") +@click.option('--vsi-type', required=False, show_default=True, default='PUBLIC_CLOUD_SERVER', + type=click.Choice(['TRANSIENT_CLOUD_SERVER', 'SUSPEND_CLOUD_SERVER', 'PUBLIC_CLOUD_SERVER']), + help="Display options for a specific virtual server packages, for default is PUBLIC_CLOUD_SERVER, " + "choose between TRANSIENT_CLOUD_SERVER, SUSPEND_CLOUD_SERVER, PUBLIC_CLOUD_SERVER") @environment.pass_env def cli(env, vsi_type): """Virtual server order options.""" - if vsi_type is None: - vsi_type = 'PUBLIC' - vsi_type = vsi_type + '_CLOUD_SERVER' vsi = SoftLayer.VSManager(env.client) options = vsi.get_create_options(vsi_type) @@ -41,13 +40,14 @@ def cli(env, vsi_type): os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) tables.append(os_table) - flavors_table = formatting.Table(['flavor', 'Name'], title="Flavors") - flavors_table.sortby = 'Name' - flavors_table.align = 'l' + # Sizes + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + preset_table.sortby = 'Value' + preset_table.align = 'l' - for flavor in options['flavors']: - flavors_table.add_row([flavor['flavor']['keyName'], flavor['flavor']['name']]) - tables.append(flavors_table) + for size in options['sizes']: + preset_table.add_row([size['name'], size['key']]) + tables.append(preset_table) # RAM ram_table = formatting.Table(['memory', 'Value'], title="RAM") diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6c6fec747..1ddbdc3e1 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -249,7 +249,7 @@ def get_instance(self, instance_id, **kwargs): return self.guest.getObject(id=instance_id, **kwargs) @retry(logger=LOGGER) - def get_create_options(self, vsi_type): + def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): """Retrieves the available options for creating a VS. :returns: A dictionary of creation options. @@ -279,6 +279,14 @@ def get_create_options(self, vsi_type): guest_core = [] local_disk = [] ram = [] + + sizes = [] + for preset in package['activePresets'] + package['accountRestrictedActivePresets']: + sizes.append({ + 'name': preset['description'], + 'key': preset['keyName'] + }) + for item in package['items']: category = item['itemCategory']['categoryCode'] # Operating systems @@ -332,21 +340,22 @@ def get_create_options(self, vsi_type): 'guest_core': guest_core, 'port_speed': port_speeds, 'guest_disk': local_disk, - 'flavors': self.guest.getCreateObjectOptions()['flavors'] + 'sizes': sizes } @retry(logger=LOGGER) def _get_package(self, package_keyname): """Get the package related to simple hardware ordering.""" mask = ''' - items[ - description, keyName, capacity, - attributes[id,attributeTypeKeyName], - itemCategory[ id, categoryCode], - softwareDescription[id,referenceCode,longDescription],prices], - regions[location[location[priceGroups]]] - ''' - package = self.ordering_manager.get_package_by_key(package_keyname, mask=mask) + activePresets, accountRestrictedActivePresets, + items[description, keyName, capacity, + attributes[id, attributeTypeKeyName], + itemCategory[id, categoryCode], + softwareDescription[id, referenceCode, longDescription], prices], + regions[location[location[priceGroups]]]''' + + package_id = self.ordering_manager.get_package_by_key(package_keyname, mask="mask[id]")['id'] + package = self.client.call('Product_Package', 'getObject', id=package_id, mask=mask) return package def cancel_instance(self, instance_id): From 77248dc2569ea3c890a27d605bef24df9ef89e7c Mon Sep 17 00:00:00 2001 From: try Date: Fri, 11 Sep 2020 17:57:28 +0530 Subject: [PATCH 0244/1385] removed extra line --- SoftLayer/CLI/block/refresh.py | 1 - SoftLayer/managers/storage.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 9e610f04a..369a6c815 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -16,4 +16,3 @@ def cli(env, volume_id, snapshot_id): resp = block_manager.refresh_dupe(volume_id, snapshot_id) click.echo(resp) - diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index cd582701d..8a3c816d2 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -423,8 +423,6 @@ def refresh_dupe(self, volume_id, snapshot_id): """ return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id) - - def convert_dep_dupe(self, volume_id): """Convert a dependent duplicate volume to an independent volume. From d9185a721dd0926f2ba37fcc0b5404403dd825b8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 11 Sep 2020 21:02:46 -0400 Subject: [PATCH 0245/1385] add to get_billing_item_from_invoice order user information --- SoftLayer/managers/account.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index ff595f8aa..884e4335b 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -22,6 +22,11 @@ class AccountManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ + _DEFAULT_BILLING_ITEM_MASK = """mask[ + orderItem[id,order[id,userRecord[id,email,displayName,userStatus]]], + nextInvoiceTotalRecurringAmount, + location, hourlyFlag, children + ]""" def __init__(self, client): self.client = client @@ -212,21 +217,20 @@ def get_billing_item(self, identifier, mask=None): """ if mask is None: - mask = """mask[ - orderItem[id,order[id,userRecord[id,email,displayName,userStatus]]], - nextInvoiceTotalRecurringAmount, - location, hourlyFlag, children - ]""" + mask = self._DEFAULT_BILLING_ITEM_MASK return self.client.call('Billing_Item', 'getObject', id=identifier, mask=mask) - def get_billing_item_from_invoice(self, identifier): + def get_billing_item_from_invoice(self, identifier, mask=None): """Gets details about a billing item of a billing invoice item :param int identifier: Billing_Invoice_Item id + :param mask: Object mask to use. :return: Billing_Item """ - return self.client.call('Billing_Invoice_Item', 'getBillingItem', id=identifier) + if mask is None: + mask = self._DEFAULT_BILLING_ITEM_MASK + return self.client.call('Billing_Invoice_Item', 'getBillingItem', id=identifier, mask=mask) def get_item_detail(self, identifier): """Gets details about a billing item From f4af694d7a784e68e6bdd0acc6654d77202f4c8b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 15 Sep 2020 15:11:07 -0400 Subject: [PATCH 0246/1385] fix tox tool and fix unit test --- .../fixtures/SoftLayer_Product_Package.py | 105 ++++++++++++++++++ techbabble.xyz.crt | 2 + techbabble.xyz.csr | 3 + techbabble.xyz.icc | 0 techbabble.xyz.key | 3 + tests/CLI/modules/vs/vs_tests.py | 4 +- tests/managers/vs/vs_tests.py | 6 +- 7 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 techbabble.xyz.crt create mode 100644 techbabble.xyz.csr create mode 100644 techbabble.xyz.icc create mode 100644 techbabble.xyz.key diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 07aefab69..2549847d4 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1779,3 +1779,108 @@ ] } ] + +getObject = { + 'id': 200, + 'regions': [{'description': 'WDC01 - Washington, DC - East Coast U.S.', + 'keyname': 'WASHINGTON_DC', + 'location': {'location': {'id': 37473, + 'longName': 'Washington 1', + 'name': 'wdc01'}}, + 'sortOrder': 10}], + 'accountRestrictedActivePresets': [], + 'activePresets': [ + { + 'description': 'AC2.8x60x25', + 'id': 861, + 'isActive': '1', + 'keyName': 'AC2_8X60X25', + 'name': 'AC2.8x60x25', + 'packageId': 835 + }, + { + 'description': 'AC2.8x60x100', + 'id': 863, + 'isActive': '1', + 'keyName': 'AC2_8X60X100', + 'name': 'AC2.8x60x100', + 'packageId': 835 + }], + "items": [{ + "capacity": "56", + "description": "56 Cores x 360 RAM x 1.2 TB x 2 GPU P100 [encryption enabled]", + "bundleItems": [ + { + "capacity": "1200", + "keyName": "1.2 TB Local Storage (Dedicated Host Capacity)", + "categories": [{ + "categoryCode": "dedicated_host_disk" + }] + }, + { + "capacity": "242", + "keyName": "2_GPU_P100_DEDICATED", + "hardwareGenericComponentModel": { + "capacity": "16", + "id": 849, + "hardwareComponentType": { + "id": 20, + "keyName": "GPU" + } + }, + "categories": [{ + "categoryCode": "dedicated_host_ram" + }, { + "capacity": "2", + "description": "2 x 2.0 GHz or higher Cores", + "keyName": "GUEST_CORES_2", + "attributes": [ + { + "id": 8261, + "attributeTypeKeyName": "ORDER_SAVES_USAGE_FEES" + } + ], + "itemCategory": { + "categoryCode": "guest_core", + "id": 80 + }}] + } + ], + "prices": [ + { + "itemId": 10195, + "setupFee": "0", + "recurringFee": "2099", + "tierMinimumThreshold": "", + "hourlyRecurringFee": "3.164", + "oneTimeFee": "0", + "currentPriceFlag": "", + "id": 200269, + "sort": 0, + "onSaleFlag": "", + "laborFee": "0", + "locationGroupId": "", + "quantity": "" + }, + { + "itemId": 10195, + "setupFee": "0", + "recurringFee": "2161.97", + "tierMinimumThreshold": "", + "hourlyRecurringFee": "3.258", + "oneTimeFee": "0", + "currentPriceFlag": "", + "id": 200271, + "sort": 0, + "onSaleFlag": "", + "laborFee": "0", + "locationGroupId": 503, + "quantity": "" + } + ], + "keyName": "56_CORES_X_484_RAM_X_1_5_TB_X_2_GPU_P100", + "id": 10195, + "itemCategory": { + "categoryCode": "dedicated_virtual_hosts" + } + }]} diff --git a/techbabble.xyz.crt b/techbabble.xyz.crt new file mode 100644 index 000000000..01d72d87f --- /dev/null +++ b/techbabble.xyz.crt @@ -0,0 +1,2 @@ +-----BEGIN CERTIFICATE----- +MIIEJTCCAw2gAwIBAgIDCbQ0MA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVT -----END CERTIFICATE----- \ No newline at end of file diff --git a/techbabble.xyz.csr b/techbabble.xyz.csr new file mode 100644 index 000000000..78d978dca --- /dev/null +++ b/techbabble.xyz.csr @@ -0,0 +1,3 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh123456QMA4G +-----END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/techbabble.xyz.icc b/techbabble.xyz.icc new file mode 100644 index 000000000..e69de29bb diff --git a/techbabble.xyz.key b/techbabble.xyz.key new file mode 100644 index 000000000..1d906abc7 --- /dev/null +++ b/techbabble.xyz.key @@ -0,0 +1,3 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxff07eutrK12345678WXtwQSdE +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index fd562f69d..a680e3c72 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -314,10 +314,8 @@ def test_detail_vs_ptr_error(self): self.assertEqual(output.get('ptr', None), None) def test_create_options(self): - result = self.run_command(['vs', 'create-options', '--vsi-type', 'TRANSIENT']) + result = self.run_command(['vs', 'create-options', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') - self.assert_called_with('SoftLayer_Virtual_Guest', 'getCreateObjectOptions') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index c00941433..cf701d21f 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -116,9 +116,9 @@ def test_get_instance(self): identifier=100) def test_get_create_options(self): - self.vs.get_create_options('PUBLIC_CLOUD_SERVER') - self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') - self.assert_called_with('SoftLayer_Virtual_Guest', 'getCreateObjectOptions') + self.vs.get_create_options() + self.assert_called_with('SoftLayer_Product_Package', 'getObject', + identifier=200) def test_cancel_instance(self): result = self.vs.cancel_instance(1) From 9599d22bbd8041f3004cc9b0a1625b715efb5742 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 15 Sep 2020 15:12:08 -0400 Subject: [PATCH 0247/1385] fix tox tool and fix unit test --- techbabble.xyz.crt | 2 -- techbabble.xyz.csr | 3 --- techbabble.xyz.icc | 0 techbabble.xyz.key | 3 --- 4 files changed, 8 deletions(-) delete mode 100644 techbabble.xyz.crt delete mode 100644 techbabble.xyz.csr delete mode 100644 techbabble.xyz.icc delete mode 100644 techbabble.xyz.key diff --git a/techbabble.xyz.crt b/techbabble.xyz.crt deleted file mode 100644 index 01d72d87f..000000000 --- a/techbabble.xyz.crt +++ /dev/null @@ -1,2 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEJTCCAw2gAwIBAgIDCbQ0MA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVT -----END CERTIFICATE----- \ No newline at end of file diff --git a/techbabble.xyz.csr b/techbabble.xyz.csr deleted file mode 100644 index 78d978dca..000000000 --- a/techbabble.xyz.csr +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh123456QMA4G ------END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/techbabble.xyz.icc b/techbabble.xyz.icc deleted file mode 100644 index e69de29bb..000000000 diff --git a/techbabble.xyz.key b/techbabble.xyz.key deleted file mode 100644 index 1d906abc7..000000000 --- a/techbabble.xyz.key +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxff07eutrK12345678WXtwQSdE ------END RSA PRIVATE KEY----- \ No newline at end of file From b69d6c7ba229bf1421de8ce26caac7fd8d952eec Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 15 Sep 2020 18:37:58 -0500 Subject: [PATCH 0248/1385] v5.9.1 changelog --- CHANGELOG.md | 13 +++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb588bfd..299f1b8ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log +## [5.9.1] - 2020-09-15 +https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 + +- Fix the ha option for firewalls, add and implement unit test #1327 +- BluePages_Search and IntegratedOfferingTeam_Region don't need SoftLayer_ prefix #972 +- Fix new TOX issues #1330 +- Add more unit test coverage #1331 +- Set notes for network storage #1322 +- Some improvements to the dns commands #999 + + dns zone-list: added resourceRecordCount, added automatic pagination for large zones + + dns record-list: fixed an issue where a record (like SRV types) that don't have a host would cause the command to fail +- Renamed managers.storage.refresh_dep_dupe to SoftLayer.managers.storage.refresh_dupe #1342 to support the new API method. CLI commands now use this method. + ## [5.9.0] - 2020-08-03 https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 8dd619aa4..23ee96975 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.0' +VERSION = 'v5.9.1' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 33df75d76..60147ff00 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.9.0', + version='5.9.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 3d2e2e40668fb3ce64f40d7a5a8b80f13e99840c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 16 Sep 2020 16:14:36 -0500 Subject: [PATCH 0249/1385] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 23 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..5d10555b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,23 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: Bug +assignees: '' + +--- + +> Reminder: No username or APIkeys should be added to these issues, as they are public. + + +**Describe the bug** +A clear and concise description of what the bug is. Include the command you used, make sure to include the `-v` flag, as that information is very helpful. Ex: `slcli -v vs list` + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Version** +Include the output of `slcli --version` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..b8a79ec6f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: New Feature +assignees: '' + +--- + +> REMINDER: Never add usernames or apikeys in these issues, as they are public. + +**What are you trying to do?** +A brief explanation of what you are trying to do. Could be something simple like `slcli vs list` doesn't support a filter you need. Or more complex like recreating some functionality that exists in the cloud.ibm.com portal + +**Screen shots** +If the functionality you want exists in the portal, please add a screenshot so we have a better idea of what you need. + +**Additional context** +Add any other context or screenshots about the feature request here. From 4b4857a07defc26219af6c7d8dd62530c29a1115 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Sep 2020 15:25:47 -0500 Subject: [PATCH 0250/1385] Update item_detail.py updated item-detail table alignment --- SoftLayer/CLI/account/item_detail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py index 9af5b4bf2..54175ffe1 100644 --- a/SoftLayer/CLI/account/item_detail.py +++ b/SoftLayer/CLI/account/item_detail.py @@ -27,6 +27,7 @@ def item_table(item): table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) table.add_row(['description', item.get('description')]) + table.align = 'l' fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) if fqdn != ".": table.add_row(['FQDN', fqdn]) From 2f2af4686e0b4050babb79ac114c70512c658849 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Sep 2020 16:56:09 -0500 Subject: [PATCH 0251/1385] added pylint igmore to a fixture --- SoftLayer/fixtures/SoftLayer_Product_Package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 15a434c25..79a36e325 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1,3 +1,4 @@ +# pylint: skip-file HARDWARE_ITEMS = [ {'attributes': [], 'capacity': '999', From 9c05c457325b46598ffa17eeabcdebc2ff303ae8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Sep 2020 17:04:30 -0500 Subject: [PATCH 0252/1385] Update item_detail.py not sure why, but using `table.align` with a KeyValueTable causes a whole bunch of pylint errors.... not really sure why KeyValueTable is a thing anymore. --- SoftLayer/CLI/account/item_detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py index 54175ffe1..ddc2d31ed 100644 --- a/SoftLayer/CLI/account/item_detail.py +++ b/SoftLayer/CLI/account/item_detail.py @@ -22,7 +22,7 @@ def item_table(item): """Formats a table for billing items""" date_format = '%Y-%m-%d' - table = formatting.KeyValueTable(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) + table = formatting.Table(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) table.add_row(['createDate', utils.clean_time(item.get('createDate'), date_format, date_format)]) table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) From 8a873b002f1e3fb6d4afffbae6c51612f769eddf Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Sep 2020 17:19:51 -0500 Subject: [PATCH 0253/1385] Update README.rst updated snapcraft badge --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index b4a321866..4f741a5d0 100644 --- a/README.rst +++ b/README.rst @@ -15,8 +15,8 @@ SoftLayer API Python Client .. image:: https://coveralls.io/repos/github/softlayer/softlayer-python/badge.svg?branch=master :target: https://coveralls.io/github/softlayer/softlayer-python?branch=master -.. image:: https://build.snapcraft.io/badge/softlayer/softlayer-python.svg - :target: https://build.snapcraft.io/user/softlayer/softlayer-python +.. image:: https://snapcraft.io//slcli/badge.svg + :target: https://snapcraft.io/slcli This library provides a simple Python client to interact with `SoftLayer's From 5122f954517f1e3c00019e0ca590f8fe6d6e97ad Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Sep 2020 09:21:28 -0400 Subject: [PATCH 0254/1385] slcli account orders --- SoftLayer/CLI/account/orders.py | 38 +++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Billing_Order.py | 47 +++++++++++++++++++ SoftLayer/managers/account.py | 25 ++++++++++ tests/CLI/modules/account_tests.py | 7 ++- 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/account/orders.py create mode 100644 SoftLayer/fixtures/SoftLayer_Billing_Order.py diff --git a/SoftLayer/CLI/account/orders.py b/SoftLayer/CLI/account/orders.py new file mode 100644 index 000000000..7129fddb9 --- /dev/null +++ b/SoftLayer/CLI/account/orders.py @@ -0,0 +1,38 @@ +"""Order list account""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@click.option('--limit', '-l', + help='How many results to get in one api call, default is 100', + default=100, + show_default=True) +@environment.pass_env +def cli(env, limit): + """Order list account.""" + manager = AccountManager(env.client) + orders = manager.get_account_all_billing_orders(limit) + + table = [] + order_table = formatting.Table(['id', 'State', 'user', 'PurchaseDate', 'orderTotalAmount', 'Items'], + title="orders") + order_table.sortby = 'orderTotalAmount' + order_table.align = 'l' + + for order in orders: + items = [] + for item in order['items']: + items.append(item['description']) + create_date = utils.clean_time(order['createDate'], in_format='%Y-%m-%d', out_format='%Y-%m-%d') + + order_table.add_row([order['id'], order['status'], order['userRecord']['username'], create_date, + order['orderTotalAmount'], utils.trim_to(' '.join(map(str, items)), 50)]) + table.append(order_table) + env.fout(formatting.listing(table, separator='\n')) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index ff3e1c180..2633dc2cd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -20,6 +20,7 @@ ('account:billing-items', 'SoftLayer.CLI.account.billing_items:cli'), ('account:item-detail', 'SoftLayer.CLI.account.item_detail:cli'), ('account:cancel-item', 'SoftLayer.CLI.account.cancel_item:cli'), + ('account:orders', 'SoftLayer.CLI.account.orders:cli'), ('virtual', 'SoftLayer.CLI.virt'), ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order.py b/SoftLayer/fixtures/SoftLayer_Billing_Order.py new file mode 100644 index 000000000..f8c5505bc --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order.py @@ -0,0 +1,47 @@ +getAllObjects = [{ + 'accountId': 123456, + 'createDate': '2020-09-15T13:12:08-06:00', + 'id': 112356450, + 'modifyDate': '2020-09-15T13:13:13-06:00', + 'status': 'COMPLETED', + 'userRecordId': 987456321, + 'userRecord': { + 'username': 'test@test.com' + }, + 'items': [ + { + 'categoryCode': 'port_speed', + 'description': '100 Mbps Private Network Uplink' + }, + { + 'categoryCode': 'service_port', + 'description': '100 Mbps Private Uplink' + }, + { + 'categoryCode': 'public_port', + 'description': '0 Mbps Public Uplink' + } + ], + 'orderApprovalDate': '2020-09-15T13:13:13-06:00', + 'orderTotalAmount': '0' +}, + { + 'accountId': 123456, + 'createDate': '2019-09-15T13:12:08-06:00', + 'id': 645698550, + 'modifyDate': '2019-09-15T13:13:13-06:00', + 'status': 'COMPLETED', + 'userRecordId': 987456321, + 'userRecord': { + 'username': 'test@test.com' + }, + 'items': [ + { + 'categoryCode': 'port_speed', + 'description': '100 Mbps Private Network Uplink' + }, + + ], + 'orderApprovalDate': '2019-09-15T13:13:13-06:00', + 'orderTotalAmount': '0' + }] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 56f951c28..ad89f3558 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -233,3 +233,28 @@ def cancel_item(self, identifier, reason="No longer needed", note=None): note = "Cancelled by {} with the SLCLI".format(user.get('username')) return self.client.call('Billing_Item', 'cancelItem', False, True, reason, note, id=identifier) + + def get_account_all_billing_orders(self, limit, mask=None): + """Gets all the topLevelBillingItems currently active on the account + + :param string mask: Object Mask + :return: Billing_Item + """ + + if mask is None: + mask = """ + orderTotalAmount, userRecord, + initialInvoice[id,amount,invoiceTotalAmount], + items[description] + """ + object_filter = { + 'createDate': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC'] + }]} + } + + return self.client.call('Billing_Order', 'getAllObjects', + limit=limit, mask=mask, filter=object_filter) diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index e231bb2be..9b88e3fae 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -110,4 +110,9 @@ def test_account_get_billing_item_detail(self): def test_account_cancel_item(self): result = self.run_command(['account', 'cancel-item', '12345']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier='12345') + self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier='12345') + + def test_acccount_order(self): + result = self.run_command(['account', 'orders']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order', 'getAllObjects') From 9302f97cb029417267da80a2cb8ad9cd5178af02 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Sep 2020 09:35:34 -0400 Subject: [PATCH 0255/1385] add documentation --- docs/cli/account.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/account.rst b/docs/cli/account.rst index c34f37d7d..27b4198d5 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -34,4 +34,8 @@ Account Commands .. click:: SoftLayer.CLI.account.cancel_item:cli :prog: account cancel-item + :show-nested: + +.. click:: SoftLayer.CLI.account.orders:cli + :prog: account orders :show-nested: \ No newline at end of file From ad131077fb6823c1e3f958fe44ce256876c28858 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 22 Sep 2020 09:59:14 -0400 Subject: [PATCH 0256/1385] Add to block and file storage volume lis order. --- SoftLayer/CLI/block/list.py | 4 +++- SoftLayer/CLI/file/list.py | 4 +++- SoftLayer/managers/block.py | 7 +++++- SoftLayer/managers/file.py | 7 +++++- tests/CLI/modules/block_tests.py | 21 +++++++++++++++++ tests/CLI/modules/file_tests.py | 20 ++++++++++++++++ tests/managers/block_tests.py | 40 ++++++++++++++++++++++++++++++++ tests/managers/file_tests.py | 40 ++++++++++++++++++++++++++++++++ 8 files changed, 139 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 6df25e13e..d9f4cf4d0 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -58,6 +58,7 @@ @click.command() @click.option('--username', '-u', help='Volume username') @click.option('--datacenter', '-d', help='Datacenter shortname') +@click.option('--order', '-o', help='Filter by ID of the order that purchased the block storage') @click.option('--storage-type', help='Type of storage volume', type=click.Choice(['performance', 'endurance'])) @@ -68,11 +69,12 @@ ', '.join(column.name for column in COLUMNS)), default=','.join(DEFAULT_COLUMNS)) @environment.pass_env -def cli(env, sortby, columns, datacenter, username, storage_type): +def cli(env, sortby, columns, datacenter, username, storage_type, order): """List block storage.""" block_manager = SoftLayer.BlockStorageManager(env.client) block_volumes = block_manager.list_block_volumes(datacenter=datacenter, username=username, + order=order, storage_type=storage_type, mask=columns.mask()) diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 8bb782884..5ae320c13 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -56,6 +56,7 @@ @click.command() @click.option('--username', '-u', help='Volume username') @click.option('--datacenter', '-d', help='Datacenter shortname') +@click.option('--order', '-o', help='Filter by ID of the order that purchased the block storage') @click.option('--storage-type', help='Type of storage volume', type=click.Choice(['performance', 'endurance'])) @@ -66,11 +67,12 @@ ', '.join(column.name for column in COLUMNS)), default=','.join(DEFAULT_COLUMNS)) @environment.pass_env -def cli(env, sortby, columns, datacenter, username, storage_type): +def cli(env, sortby, columns, datacenter, username, order, storage_type): """List file storage.""" file_manager = SoftLayer.FileStorageManager(env.client) file_volumes = file_manager.list_file_volumes(datacenter=datacenter, username=username, + order=order, storage_type=storage_type, mask=columns.mask()) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index a16500919..e4de585cf 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -25,9 +25,10 @@ def list_block_volume_limit(self): """ return self.get_volume_count_limits() - def list_block_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): + def list_block_volumes(self, datacenter=None, username=None, order=None, storage_type=None, **kwargs): """Returns a list of block volumes. + :param order: Volume order id. :param datacenter: Datacenter short name (e.g.: dal09) :param username: Name of volume. :param storage_type: Type of volume: Endurance or Performance @@ -67,6 +68,10 @@ def list_block_volumes(self, datacenter=None, username=None, storage_type=None, _filter['iscsiNetworkStorage']['username'] = \ (utils.query_filter(username)) + if order: + _filter['iscsiNetworkStorage']['billingItem']['orderItem'][ + 'order']['id'] = (utils.query_filter(order)) + kwargs['filter'] = _filter.to_dict() return self.client.call('Account', 'getIscsiNetworkStorage', **kwargs) diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 1810a90dc..30653821c 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -22,9 +22,10 @@ def list_file_volume_limit(self): """ return self.get_volume_count_limits() - def list_file_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): + def list_file_volumes(self, datacenter=None, username=None, order=None, storage_type=None, **kwargs): """Returns a list of file volumes. + :param order: Volume order id. :param datacenter: Datacenter short name (e.g.: dal09) :param username: Name of volume. :param storage_type: Type of volume: Endurance or Performance @@ -64,6 +65,10 @@ def list_file_volumes(self, datacenter=None, username=None, storage_type=None, * _filter['nasNetworkStorage']['username'] = \ (utils.query_filter(username)) + if order: + _filter['nasNetworkStorage']['billingItem']['orderItem'][ + 'order']['id'] = (utils.query_filter(order)) + kwargs['filter'] = _filter.to_dict() return self.client.call('Account', 'getNasNetworkStorage', **kwargs) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index a910f597e..196b4a51e 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -141,6 +141,27 @@ def test_volume_list(self): }], json.loads(result.output)) + def test_volume_list_order(self): + result = self.run_command(['block', 'volume-list', '--order=1234567']) + + self.assert_no_fail(result) + self.assertEqual([ + { + 'bytes_used': None, + 'capacity_gb': 20, + 'datacenter': 'dal05', + 'id': 100, + 'iops': None, + 'ip_addr': '10.1.2.3', + 'lunId': None, + 'notes': "{'status': 'availabl", + 'rep_partner_count': None, + 'storage_type': 'ENDURANCE', + 'username': 'username', + 'active_transactions': None + }], + json.loads(result.output)) + @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') def test_volume_count(self, list_mock): list_mock.return_value = [ diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 2e4839299..cc58e1df0 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -55,6 +55,26 @@ def test_volume_list(self): }], json.loads(result.output)) + def test_volume_list_order(self): + result = self.run_command(['file', 'volume-list', '--order=1234567']) + + self.assert_no_fail(result) + self.assertEqual([ + { + 'bytes_used': None, + 'capacity_gb': 10, + 'datacenter': 'Dallas', + 'id': 1, + 'ip_addr': '127.0.0.1', + 'storage_type': 'ENDURANCE', + 'username': 'user', + 'active_transactions': None, + 'mount_addr': '127.0.0.1:/TEST', + 'notes': None, + 'rep_partner_count': None + }], + json.loads(result.output)) + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') def test_volume_count(self, list_mock): list_mock.return_value = [ diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 54f78fb5e..e603ef7e3 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -157,6 +157,46 @@ def test_list_block_volumes(self): mask='mask[%s]' % expected_mask ) + def test_list_block_volumes_additional_filter_order(self): + result = self.block.list_block_volumes(order=1234567) + + self.assertEqual(SoftLayer_Account.getIscsiNetworkStorage, + result) + + expected_filter = { + 'iscsiNetworkStorage': { + 'storageType': { + 'keyName': {'operation': '*= BLOCK_STORAGE'} + }, + 'serviceResource': { + 'type': { + 'type': {'operation': '!~ ISCSI'} + } + }, + 'billingItem': { + 'orderItem': { + 'order': { + 'id': {'operation': 1234567}}}} + } + } + + expected_mask = 'id,' \ + 'username,' \ + 'lunId,' \ + 'capacityGb,' \ + 'bytesUsed,' \ + 'serviceResource.datacenter[name],' \ + 'serviceResourceBackendIpAddress,' \ + 'activeTransactionCount,' \ + 'replicationPartnerCount' + + self.assert_called_with( + 'SoftLayer_Account', + 'getIscsiNetworkStorage', + filter=expected_filter, + mask='mask[%s]' % expected_mask + ) + def test_list_block_volumes_with_additional_filters(self): result = self.block.list_block_volumes(datacenter="dal09", storage_type="Endurance", diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index ec50ee463..15d64d883 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -365,6 +365,46 @@ def test_list_file_volumes(self): mask='mask[%s]' % expected_mask ) + def test_list_file_volumes_additional_filter_order(self): + result = self.file.list_file_volumes(order=1234567) + + self.assertEqual(SoftLayer_Account.getNasNetworkStorage, + result) + + expected_filter = { + 'nasNetworkStorage': { + 'storageType': { + 'keyName': {'operation': '*= FILE_STORAGE'} + }, + 'serviceResource': { + 'type': { + 'type': {'operation': '!~ NAS'} + } + }, + 'billingItem': { + 'orderItem': { + 'order': { + 'id': {'operation': 1234567}}}} + } + } + + expected_mask = 'id,'\ + 'username,'\ + 'capacityGb,'\ + 'bytesUsed,'\ + 'serviceResource.datacenter[name],'\ + 'serviceResourceBackendIpAddress,'\ + 'activeTransactionCount,'\ + 'fileNetworkMountAddress,'\ + 'replicationPartnerCount' + + self.assert_called_with( + 'SoftLayer_Account', + 'getNasNetworkStorage', + filter=expected_filter, + mask='mask[%s]' % expected_mask + ) + def test_list_file_volumes_with_additional_filters(self): result = self.file.list_file_volumes(datacenter="dal09", storage_type="Endurance", From 43c64a041320fd9f08050d1764f8fd4d6b9cf3cf Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 22 Sep 2020 18:14:39 -0400 Subject: [PATCH 0257/1385] Add prices to vs create-options. --- SoftLayer/CLI/virt/create_options.py | 325 ++++++++++++++++++++++----- SoftLayer/managers/vs.py | 103 +++++++-- tests/CLI/modules/vs/vs_tests.py | 8 + tests/managers/hardware_tests.py | 3 - tests/managers/vs/vs_tests.py | 85 ++++++- 5 files changed, 443 insertions(+), 81 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 4e73d55be..c6bca1946 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -9,16 +9,21 @@ @click.command(short_help="Get options to use for creating virtual servers.") +@click.argument('location', required=False) @click.option('--vsi-type', required=False, show_default=True, default='PUBLIC_CLOUD_SERVER', - type=click.Choice(['TRANSIENT_CLOUD_SERVER', 'SUSPEND_CLOUD_SERVER', 'PUBLIC_CLOUD_SERVER']), + type=click.Choice(['PUBLIC_CLOUD_SERVER', 'TRANSIENT_CLOUD_SERVER', 'SUSPEND_CLOUD_SERVER', + 'CLOUD_SERVER']), help="Display options for a specific virtual server packages, for default is PUBLIC_CLOUD_SERVER, " - "choose between TRANSIENT_CLOUD_SERVER, SUSPEND_CLOUD_SERVER, PUBLIC_CLOUD_SERVER") + "choose between TRANSIENT_CLOUD_SERVER, SUSPEND_CLOUD_SERVER, CLOUD_SERVER") +@click.option('--prices', '-p', is_flag=True, + help='Use --prices to list the server item prices, and to list the Item Prices by location,' + 'add it to the --prices option using location short name, e.g. --prices dal13') @environment.pass_env -def cli(env, vsi_type): +def cli(env, vsi_type, prices, location=None): """Virtual server order options.""" vsi = SoftLayer.VSManager(env.client) - options = vsi.get_create_options(vsi_type) + options = vsi.get_create_options(vsi_type, location) tables = [] @@ -26,72 +31,290 @@ def cli(env, vsi_type): dc_table = formatting.Table(['datacenter', 'Value'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' - - for location in options['locations']: - dc_table.add_row([location['name'], location['key']]) + for location_info in options['locations']: + dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - # Operation system - os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") - os_table.sortby = 'Key' - os_table.align = 'l' + if prices: + preset_prices_table(options['sizes'], tables) + os_prices_table(options['operating_systems'], tables) + port_speed_prices_table(options['port_speed'], tables) + ram_prices_table(options['ram'], tables) + database_prices_table(options['database'], tables) + guest_core_prices_table(options['guest_core'], tables) + guest_disk_prices_table(options['guest_disk'], tables) + extras_prices_table(options['extras'], tables) + else: + # Operation system + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' + os_table.align = 'l' - for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) - tables.append(os_table) + for operating_system in options['operating_systems']: + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + tables.append(os_table) + + # Sizes + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + preset_table.sortby = 'Value' + preset_table.align = 'l' + + for size in options['sizes']: + preset_table.add_row([size['name'], size['key']]) + tables.append(preset_table) + + # RAM + ram_table = formatting.Table(['memory', 'Value'], title="RAM") + ram_table.sortby = 'Value' + ram_table.align = 'l' + + for ram in options['ram']: + ram_table.add_row([ram['name'], ram['key']]) + tables.append(ram_table) + + # Data base + database_table = formatting.Table(['database', 'Value'], title="Databases") + database_table.sortby = 'Value' + database_table.align = 'l' + + for database in options['database']: + database_table.add_row([database['name'], database['key']]) + tables.append(database_table) + + # Guest_core + guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") + guest_core_table.sortby = 'Value' + guest_core_table.align = 'l' + + for guest_core in options['guest_core']: + guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) + tables.append(guest_core_table) + + # Guest_core + guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") + guest_disk_table.sortby = 'Value' + guest_disk_table.align = 'l' + + for guest_disk in options['guest_disk']: + guest_disk_table.add_row( + [guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) + tables.append(guest_disk_table) - # Sizes - preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + # Port speed + port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") + port_speed_table.sortby = 'Key' + port_speed_table.align = 'l' + + for speed in options['port_speed']: + port_speed_table.add_row([speed['name'], speed['key']]) + tables.append(port_speed_table) + + env.fout(formatting.listing(tables, separator='\n')) + + +def preset_prices_table(sizes, tables): + """Shows Server Preset options prices. + + :param [] sizes: List of Hardware Server sizes. + :param tables: Table formatting. + """ + preset_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") preset_table.sortby = 'Value' preset_table.align = 'l' - - for size in options['sizes']: - preset_table.add_row([size['name'], size['key']]) + for size in sizes: + preset_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) tables.append(preset_table) - # RAM - ram_table = formatting.Table(['memory', 'Value'], title="RAM") - ram_table.sortby = 'Value' - ram_table.align = 'l' - for ram in options['ram']: - ram_table.add_row([ram['name'], ram['key']]) +def os_prices_table(operating_systems, tables): + """Shows Server Operating Systems prices cost and capacity restriction. + + :param [] operating_systems: List of Hardware Server operating systems. + :param tables: Table formatting. + """ + os_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], + title="Operating Systems Prices") + os_table.sortby = 'OS Key' + os_table.align = 'l' + for operating_system in operating_systems: + for price in operating_system['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + os_table.add_row( + [operating_system['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(os_table) + + +def port_speed_prices_table(port_speeds, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] port_speeds: List of Hardware Server Port Speeds. + :param tables: Table formatting. + """ + port_speed_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', 'Restriction'], + title="Network Options Prices") + port_speed_table.sortby = 'Speed' + port_speed_table.align = 'l' + for speed in port_speeds: + for price in speed['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + port_speed_table.add_row( + [speed['key'], speed['speed'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(port_speed_table) + + +def extras_prices_table(extras, tables): + """Shows Server extras prices cost and capacity restriction. + + :param [] extras: List of Hardware Server Extras. + :param tables: Table formatting. + """ + extras_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', 'Restriction'], + title="Extras Prices") + extras_table.align = 'l' + for extra in extras: + for price in extra['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + extras_table.add_row( + [extra['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(extras_table) + + +def _location_item_prices(location_prices, tables): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction']) + location_prices_table.sortby = 'keyName' + location_prices_table.align = 'l' + for price in location_prices: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + location_prices_table.add_row( + [price['item']['keyName'], price['id'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(location_prices_table) + + +def ram_prices_table(ram_list, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] ram_list: List of Virtual Server Ram. + :param tables: Table formatting. + """ + ram_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Ram Prices") + ram_table.sortby = 'Key' + ram_table.align = 'l' + for ram in ram_list: + for price in ram['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + ram_table.add_row( + [ram['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(ram_table) - # Data base - database_table = formatting.Table(['database', 'Value'], title="Databases") - database_table.sortby = 'Value' - database_table.align = 'l' - for database in options['database']: - database_table.add_row([database['name'], database['key']]) +def database_prices_table(database_list, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] database_list: List of Virtual Server database. + :param tables: Table formatting. + """ + database_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Data Base Prices") + database_table.sortby = 'Key' + database_table.align = 'l' + for database in database_list: + for price in database['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + database_table.add_row( + [database['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(database_table) - # Guest_core - guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") - guest_core_table.sortby = 'Value' - guest_core_table.align = 'l' - for guest_core in options['guest_core']: - guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) +def guest_core_prices_table(guest_core_list, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] guest_core_list: List of Virtual Server guest_core. + :param tables: Table formatting. + """ + guest_core_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Guest Core Prices") + guest_core_table.sortby = 'Key' + guest_core_table.align = 'l' + for guest_core in guest_core_list: + for price in guest_core['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + guest_core_table.add_row( + [guest_core['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(guest_core_table) - # Guest_core - guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") - guest_disk_table.sortby = 'Value' - guest_disk_table.align = 'l' - for guest_disk in options['guest_disk']: - guest_disk_table.add_row([guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) +def guest_disk_prices_table(guest_disk_list, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] guest_disk_list: List of Virtual Server guest_disk. + :param tables: Table formatting. + """ + guest_disk_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Guest Disk Prices") + guest_disk_table.sortby = 'Key' + guest_disk_table.align = 'l' + for guest_disk in guest_disk_list: + for price in guest_disk['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + guest_disk_table.add_row( + [guest_disk['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(guest_disk_table) - # Port speed - port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") - port_speed_table.sortby = 'Key' - port_speed_table.align = 'l' - for speed in options['port_speed']: - port_speed_table.add_row([speed['name'], speed['key']]) - tables.append(port_speed_table) +def _get_price_data(price, item): + """Get a specific data from HS price. - env.fout(formatting.listing(tables, separator='\n')) + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + result = '-' + if item in price: + result = price[item] + return result diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 1ddbdc3e1..1a03db1c6 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -13,11 +13,20 @@ from SoftLayer.decoration import retry from SoftLayer import exceptions +from SoftLayer.exceptions import SoftLayerError +from SoftLayer.managers.hardware import _get_preset_cost +from SoftLayer.managers.hardware import get_item_price from SoftLayer.managers import ordering from SoftLayer import utils LOGGER = logging.getLogger(__name__) +EXTRA_CATEGORIES = ['pri_ipv6_addresses', + 'static_ipv6_addresses', + 'sec_ip_addresses', + 'trusted_platform_module', + 'software_guard_extensions'] + # pylint: disable=no-self-use,too-many-lines @@ -249,9 +258,11 @@ def get_instance(self, instance_id, **kwargs): return self.guest.getObject(id=instance_id, **kwargs) @retry(logger=LOGGER) - def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): + def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER", datacenter=None): """Retrieves the available options for creating a VS. + :param string vsi_type: vs keyName. + :param string datacenter: short name, like dal09 :returns: A dictionary of creation options. Example:: @@ -265,26 +276,45 @@ def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): # SUSPEND_CLOUD_SERVER package = self._get_package(vsi_type) + location_group_id = None + if datacenter: + _filter = {"name": {"operation": datacenter}} + _mask = "mask[priceGroups]" + dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', mask=_mask, filter=_filter, limit=1) + if not dc_details: + raise SoftLayerError("Unable to find a datacenter named {}".format(datacenter)) + # A DC will have several price groups, no good way to deal with this other than checking each. + # An item should only belong to one type of price group. + for group in dc_details[0].get('priceGroups', []): + # We only care about SOME of the priceGroups, which all SHOULD start with `Location Group X` + # Hopefully this never changes.... + if group.get('description').startswith('Location'): + location_group_id = group.get('id') + # Locations locations = [] for region in package['regions']: - locations.append({ - 'name': region['location']['location']['longName'], - 'key': region['location']['location']['name'], - }) + if datacenter is None or datacenter == region['location']['location']['name']: + locations.append({ + 'name': region['location']['location']['longName'], + 'key': region['location']['location']['name'], + }) operating_systems = [] database = [] port_speeds = [] guest_core = [] local_disk = [] + extras = [] ram = [] sizes = [] for preset in package['activePresets'] + package['accountRestrictedActivePresets']: sizes.append({ 'name': preset['description'], - 'key': preset['keyName'] + 'key': preset['keyName'], + 'hourlyRecurringFee': _get_preset_cost(preset, package['items'], 'hourly', location_group_id), + 'recurringFee': _get_preset_cost(preset, package['items'], 'monthly', location_group_id) }) for item in package['items']: @@ -294,33 +324,39 @@ def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): operating_systems.append({ 'name': item['softwareDescription']['longDescription'], 'key': item['keyName'], - 'referenceCode': item['softwareDescription']['referenceCode'] + 'referenceCode': item['softwareDescription']['referenceCode'], + 'prices': get_item_price(item['prices'], location_group_id) }) # database elif category == 'database': database.append({ 'name': item['description'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) }) elif category == 'port_speed': port_speeds.append({ 'name': item['description'], - 'key': item['keyName'] + 'speed': item['capacity'], + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) }) elif category == 'guest_core': guest_core.append({ 'name': item['description'], 'capacity': item['capacity'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) }) elif category == 'ram': ram.append({ 'name': item['description'], 'capacity': item['capacity'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) }) elif category.__contains__('guest_disk'): @@ -328,9 +364,16 @@ def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): 'name': item['description'], 'capacity': item['capacity'], 'key': item['keyName'], - 'disk': category + 'disk': category, + 'prices': get_item_price(item['prices'], location_group_id) }) # Extras + elif category in EXTRA_CATEGORIES: + extras.append({ + 'name': item['description'], + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) + }) return { 'locations': locations, @@ -340,22 +383,34 @@ def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): 'guest_core': guest_core, 'port_speed': port_speeds, 'guest_disk': local_disk, - 'sizes': sizes + 'sizes': sizes, + 'extras': extras, } @retry(logger=LOGGER) def _get_package(self, package_keyname): - """Get the package related to simple hardware ordering.""" - mask = ''' - activePresets, accountRestrictedActivePresets, - items[description, keyName, capacity, - attributes[id, attributeTypeKeyName], - itemCategory[id, categoryCode], - softwareDescription[id, referenceCode, longDescription], prices], - regions[location[location[priceGroups]]]''' - - package_id = self.ordering_manager.get_package_by_key(package_keyname, mask="mask[id]")['id'] - package = self.client.call('Product_Package', 'getObject', id=package_id, mask=mask) + """Get the package related to simple vs ordering. + + :param string package_keyname: Virtual Server package keyName. + """ + items_mask = 'mask[id,keyName,capacity,description,attributes[id,attributeTypeKeyName],' \ + 'itemCategory[id,categoryCode],softwareDescription[id,referenceCode,longDescription],' \ + 'prices[categories]]' + # The preset prices list will only have default prices. The prices->item->prices will have location specific + presets_mask = 'mask[prices]' + region_mask = 'location[location[priceGroups]]' + package = {'items': [], 'activePresets': [], 'accountRestrictedActivePresets': [], 'regions': []} + package_info = self.ordering_manager.get_package_by_key(package_keyname, mask="mask[id]") + + package['items'] = self.client.call('SoftLayer_Product_Package', 'getItems', + id=package_info.get('id'), mask=items_mask) + package['activePresets'] = self.client.call('SoftLayer_Product_Package', 'getActivePresets', + id=package_info.get('id'), mask=presets_mask) + package['accountRestrictedActivePresets'] = self.client.call('SoftLayer_Product_Package', + 'getAccountRestrictedActivePresets', + id=package_info.get('id'), mask=presets_mask) + package['regions'] = self.client.call('SoftLayer_Product_Package', 'getRegions', + id=package_info.get('id'), mask=region_mask) return package def cancel_instance(self, instance_id): diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a680e3c72..7fb23b97b 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -317,6 +317,14 @@ def test_create_options(self): result = self.run_command(['vs', 'create-options', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) + def test_create_options_prices(self): + result = self.run_command(['vs', 'create-options', '--prices', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + self.assert_no_fail(result) + + def test_create_options_prices_location(self): + result = self.run_command(['vs', 'create-options', '--prices', 'dal13', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): confirm_mock.return_value = True diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e5b532647..062cafc30 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -247,9 +247,6 @@ def test_get_create_options_prices_by_location(self): 'recurringFee': 0.0 } - print("---------") - print(options) - self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], extras['prices'][0]['hourlyRecurringFee']) self.assertEqual(options['locations'][0], locations) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index cf701d21f..e858b2577 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -116,9 +116,88 @@ def test_get_instance(self): identifier=100) def test_get_create_options(self): - self.vs.get_create_options() - self.assert_called_with('SoftLayer_Product_Package', 'getObject', - identifier=200) + options = self.vs.get_create_options() + + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} + locations = {'key': 'wdc07', 'name': 'Washington 7'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64' + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks' + } + sizes = { + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 + } + + self.assertEqual(options['extras'][0]['key'], extras['key']) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0]['referenceCode'], + operating_systems['referenceCode']) + self.assertEqual(options['port_speed'][0]['name'], port_speeds['name']) + self.assertEqual(options['sizes'][0], sizes) + + def test_get_create_options_prices_by_location(self): + options = self.vs.get_create_options('wdc07') + + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + locations = {'key': 'wdc07', 'name': 'Washington 7'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + sizes = { + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 + } + + self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], + extras['prices'][0]['hourlyRecurringFee']) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0]['prices'][0]['locationGroupId'], + operating_systems['prices'][0]['locationGroupId']) + self.assertEqual(options['port_speed'][0]['prices'][0]['id'], port_speeds['prices'][0]['id']) + self.assertEqual(options['sizes'][0], sizes) def test_cancel_instance(self): result = self.vs.cancel_instance(1) From a8bead5a115ed1142526b5744d08e6c2c4e82cda Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Sep 2020 16:39:26 -0400 Subject: [PATCH 0258/1385] remove the prices equals a zero(0) --- SoftLayer/CLI/hardware/create_options.py | 76 +++++++++++++----------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index d61695be3..d7486ef6a 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -81,8 +81,9 @@ def _preset_prices_table(sizes, tables): preset_prices_table.sortby = 'Value' preset_prices_table.align = 'l' for size in sizes: - preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) + if ("%.4f" % size['hourlyRecurringFee'] != 0.00) or ("%.4f" % size['recurringFee'] != 0.00): + preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) tables.append(preset_prices_table) @@ -98,14 +99,16 @@ def _os_prices_table(operating_systems, tables): os_prices_table.align = 'l' for operating_system in operating_systems: for price in operating_system['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - os_prices_table.add_row( - [operating_system['key'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) + if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( + _get_price_data(price, 'recurringFee') != '0'): + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + os_prices_table.add_row( + [operating_system['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(os_prices_table) @@ -121,14 +124,16 @@ def _port_speed_prices_table(port_speeds, tables): port_speed_prices_table.align = 'l' for speed in port_speeds: for price in speed['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - port_speed_prices_table.add_row( - [speed['key'], speed['speed'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) + if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( + _get_price_data(price, 'recurringFee') != '0'): + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + port_speed_prices_table.add_row( + [speed['key'], speed['speed'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(port_speed_prices_table) @@ -143,14 +148,16 @@ def _extras_prices_table(extras, tables): extras_prices_table.align = 'l' for extra in extras: for price in extra['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - extras_prices_table.add_row( - [extra['key'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) + if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( + _get_price_data(price, 'recurringFee') != '0'): + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + extras_prices_table.add_row( + [extra['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(extras_prices_table) @@ -176,12 +183,13 @@ def _location_item_prices(location_prices, tables): location_prices_table.sortby = 'keyName' location_prices_table.align = 'l' for price in location_prices: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - location_prices_table.add_row( - [price['item']['keyName'], price['id'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) + if (_get_price_data(price, 'hourlyRecurringFee') != '0') or (_get_price_data(price, 'recurringFee') != '0'): + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + location_prices_table.add_row( + [price['item']['keyName'], price['id'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(location_prices_table) From 51775ba04ca8831ca195751351b147864500bf70 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Sep 2020 16:44:47 -0400 Subject: [PATCH 0259/1385] remove the prices equals a zero(0) --- SoftLayer/CLI/hardware/create_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index d7486ef6a..98f27b3a5 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -81,7 +81,7 @@ def _preset_prices_table(sizes, tables): preset_prices_table.sortby = 'Value' preset_prices_table.align = 'l' for size in sizes: - if ("%.4f" % size['hourlyRecurringFee'] != 0.00) or ("%.4f" % size['recurringFee'] != 0.00): + if ("%.4f" % size['hourlyRecurringFee'] != '0.0000') or ("%.4f" % size['recurringFee'] != '0.0000'): preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], "%.4f" % size['recurringFee']]) tables.append(preset_prices_table) From d68cef3723fa0faae0831884462f557f0170d592 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 25 Sep 2020 09:27:45 -0400 Subject: [PATCH 0260/1385] fix the unit test --- .../fixtures/SoftLayer_Product_Package.py | 30 +++++++++---------- tests/CLI/modules/server_tests.py | 11 ++++--- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 79a36e325..f705b0edb 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -866,8 +866,8 @@ 'referenceCode': 'REDHAT_5_64' }, 'prices': [{'id': 1122, - 'hourlyRecurringFee': 0.0, - 'recurringFee': 0.0, + 'hourlyRecurringFee': 0.10, + 'recurringFee': 0.10, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], @@ -879,8 +879,8 @@ 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, 'prices': [{'id': 4477, - 'hourlyRecurringFee': 0.0, - 'recurringFee': 0.0, + 'hourlyRecurringFee': 0.10, + 'recurringFee': 0.10, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], @@ -921,8 +921,8 @@ 'itemCategory': {'categoryCode': 'Computing Instance'}, 'prices': [{'id': 1144, 'locationGroupId': None, - 'hourlyRecurringFee': 0.0, - 'recurringFee': 0.0, + 'hourlyRecurringFee': 0.10, + 'recurringFee': 0.10, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], @@ -948,7 +948,7 @@ 'capacity': '1', 'description': '1 GB iSCSI Storage', 'itemCategory': {'categoryCode': 'iscsi'}, - 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, { 'id': 1121, @@ -956,7 +956,7 @@ 'capacity': '20', 'description': '20 GB iSCSI snapshot', 'itemCategory': {'categoryCode': 'iscsi_snapshot_space'}, - 'prices': [{'id': 2014, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 2014, 'hourlyRecurringFee': 0.10}], }, { 'id': 4440, @@ -964,7 +964,7 @@ 'capacity': '4', 'description': '4 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, { 'id': 8880, @@ -972,7 +972,7 @@ 'capacity': '8', 'description': '8 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, { 'id': 44400, @@ -980,7 +980,7 @@ 'capacity': '4', 'description': '4 Portable Private IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_priv'}, - 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, { 'id': 88800, @@ -1012,7 +1012,7 @@ 'capacity': '0', 'description': 'Global IPv6', 'itemCategory': {'categoryCode': 'global_ipv6'}, - 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 611, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, {'attributes': [], 'capacity': '0', @@ -1056,7 +1056,7 @@ 'keyName': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'prices': [{'accountRestrictions': [], 'currentPriceFlag': '', - 'hourlyRecurringFee': '0', + 'hourlyRecurringFee': '0.10', 'id': 37650, "locationGroupId": '', 'itemId': 4702, @@ -1064,8 +1064,8 @@ 'onSaleFlag': '', 'oneTimeFee': '0', 'quantity': '', - 'recurringFee': '0', - 'setupFee': '0', + 'recurringFee': '0.1', + 'setupFee': '0.1', 'sort': 9}], 'softwareDescription': {'id': 1362, 'longDescription': 'Ubuntu / 14.04-64', diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index dc9d70726..75b70c7c2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -367,18 +367,17 @@ def test_create_options_prices(self): self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) - self.assertEqual(output[1][0]['Value'], 'M1_64X512X25') - self.assertEqual(output[1][0]['Size'], 'M1.64x512x25') + self.assertEqual(output[2][0]['Monthly'], str(0.1)) + self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') def test_create_options_location(self): result = self.run_command(['server', 'create-options', '--prices', 'dal13']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Monthly'], "%.4f" % 0.0) - self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) - self.assertEqual(output[1][0]['Value'], 'M1_64X512X25') + print(output) + self.assertEqual(output[2][0]['Monthly'], str(0.1)) + self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): From 7f410f6ed2855db53a0812615d80da4158bc734f Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Sep 2020 16:47:00 -0400 Subject: [PATCH 0261/1385] fix Christopher code review comments --- SoftLayer/CLI/account/orders.py | 8 +++----- SoftLayer/managers/account.py | 14 +++----------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/account/orders.py b/SoftLayer/CLI/account/orders.py index 7129fddb9..7777f2379 100644 --- a/SoftLayer/CLI/account/orders.py +++ b/SoftLayer/CLI/account/orders.py @@ -20,10 +20,9 @@ def cli(env, limit): manager = AccountManager(env.client) orders = manager.get_account_all_billing_orders(limit) - table = [] - order_table = formatting.Table(['id', 'State', 'user', 'PurchaseDate', 'orderTotalAmount', 'Items'], + order_table = formatting.Table(['Id', 'State', 'User', 'Date', 'Amount', 'Item'], title="orders") - order_table.sortby = 'orderTotalAmount' + order_table.sortby = 'Amount' order_table.align = 'l' for order in orders: @@ -34,5 +33,4 @@ def cli(env, limit): order_table.add_row([order['id'], order['status'], order['userRecord']['username'], create_date, order['orderTotalAmount'], utils.trim_to(' '.join(map(str, items)), 50)]) - table.append(order_table) - env.fout(formatting.listing(table, separator='\n')) + env.fout(order_table) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index ad89f3558..e12539161 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -234,7 +234,7 @@ def cancel_item(self, identifier, reason="No longer needed", note=None): return self.client.call('Billing_Item', 'cancelItem', False, True, reason, note, id=identifier) - def get_account_all_billing_orders(self, limit, mask=None): + def get_account_all_billing_orders(self, limit=100, mask=None): """Gets all the topLevelBillingItems currently active on the account :param string mask: Object Mask @@ -247,14 +247,6 @@ def get_account_all_billing_orders(self, limit, mask=None): initialInvoice[id,amount,invoiceTotalAmount], items[description] """ - object_filter = { - 'createDate': { - 'operation': 'orderBy', - 'options': [{ - 'name': 'sort', - 'value': ['DESC'] - }]} - } - + print(limit) return self.client.call('Billing_Order', 'getAllObjects', - limit=limit, mask=mask, filter=object_filter) + limit=limit, mask=mask) From 5dc7e95b3e76245831d23baf21d8984d8678de70 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Sep 2020 17:57:24 -0400 Subject: [PATCH 0262/1385] fix the last Christopher code review comments --- SoftLayer/CLI/account/orders.py | 3 +-- SoftLayer/managers/account.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/SoftLayer/CLI/account/orders.py b/SoftLayer/CLI/account/orders.py index 7777f2379..570f77816 100644 --- a/SoftLayer/CLI/account/orders.py +++ b/SoftLayer/CLI/account/orders.py @@ -11,7 +11,7 @@ @click.command() @click.option('--limit', '-l', - help='How many results to get in one api call, default is 100', + help='How many results to get in one api call', default=100, show_default=True) @environment.pass_env @@ -22,7 +22,6 @@ def cli(env, limit): order_table = formatting.Table(['Id', 'State', 'User', 'Date', 'Amount', 'Item'], title="orders") - order_table.sortby = 'Amount' order_table.align = 'l' for order in orders: diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index e12539161..c46872e01 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -247,6 +247,5 @@ def get_account_all_billing_orders(self, limit=100, mask=None): initialInvoice[id,amount,invoiceTotalAmount], items[description] """ - print(limit) return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) From a61b4beea2b73785bac3dc779d38227a33c34041 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 24 Sep 2020 15:55:28 -0400 Subject: [PATCH 0263/1385] #1340 add order lookup --- SoftLayer/CLI/account/invoice_detail.py | 24 ++++++--- SoftLayer/CLI/order/lookup.py | 67 +++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/ordering.py | 23 ++++++++- 4 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 SoftLayer/CLI/order/lookup.py diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py index b840f3f60..1fd7e2d5f 100644 --- a/SoftLayer/CLI/account/invoice_detail.py +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -19,6 +19,22 @@ def cli(env, identifier, details): manager = AccountManager(env.client) top_items = manager.get_billing_items(identifier) + table = get_invoice_table(identifier, top_items, details) + env.fout(table) + + +def nice_string(ugly_string, limit=100): + """Format and trims strings""" + return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string + + +def get_invoice_table(identifier, top_items, details): + """Formats a table for invoice top level items. + + :param int identifier: Invoice identifier. + :param list top_items: invoiceTopLevelItems. + :param bool details: To add very detailed list of charges. + """ title = "Invoice %s" % identifier table = formatting.Table(["Item Id", "Category", "Description", "Single", @@ -52,10 +68,4 @@ def cli(env, identifier, details): '---', '---' ]) - - env.fout(table) - - -def nice_string(ugly_string, limit=100): - """Format and trims strings""" - return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string + return table diff --git a/SoftLayer/CLI/order/lookup.py b/SoftLayer/CLI/order/lookup.py new file mode 100644 index 000000000..423872ab7 --- /dev/null +++ b/SoftLayer/CLI/order/lookup.py @@ -0,0 +1,67 @@ +"""Provides some details related to the order.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI.account.invoice_detail import get_invoice_table + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@click.option('--details', is_flag=True, default=False, show_default=True, + help="Shows a very detailed list of charges") +@environment.pass_env +def cli(env, identifier, details): + """Provides some details related to order owner, date order, cost information, initial invoice.""" + + manager = ordering.OrderingManager(env.client) + order = manager.get_order_detail(identifier) + order_table = get_order_table(order) + + invoice = order.get('initialInvoice', {}) + top_items = invoice.get('invoiceTopLevelItems', []) + invoice_id = invoice.get('id') + invoice_table = get_invoice_table(invoice_id, top_items, details) + + order_table.add_row(['Initial Invoice', invoice_table]) + + env.fout(order_table) + + +def get_order_table(order): + """Formats a table for billing order""" + + title = "Order {id}".format(id=order.get('id')) + date_format = '%Y-%m-%d' + table = formatting.Table(["Key", "Value"], title=title) + table.align = 'l' + + ordered_by = "IBM" + user = order.get('userRecord', None) + if user: + ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + table.add_row(['Ordered By', ordered_by]) + + table.add_row(['Create Date', utils.clean_time(order.get('createDate'), date_format, date_format)]) + table.add_row(['Modify Date', utils.clean_time(order.get('modifyDate'), date_format, date_format)]) + table.add_row(['Order Approval Date', utils.clean_time(order.get('orderApprovalDate'), date_format, date_format)]) + table.add_row(['status', order.get('status')]) + table.add_row(['Order Total Amount', "{price:.2f}".format(price=float(order.get('orderTotalAmount', '0')))]) + table.add_row(['Invoice Total Amount', "{price:.2f}". + format(price=float(order.get('initialInvoice', {}).get('invoiceTotalAmount', '0')))]) + + items = order.get('items', []) + item_table = formatting.Table(["Item Description"]) + item_table.align['description'] = 'l' + + for item in items: + item_table.add_row([item.get('description')]) + + table.add_row(['items', item_table]) + + return table diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2633dc2cd..40c4bd6d1 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -226,6 +226,7 @@ ('order:quote-list', 'SoftLayer.CLI.order.quote_list:cli'), ('order:quote-detail', 'SoftLayer.CLI.order.quote_detail:cli'), ('order:quote', 'SoftLayer.CLI.order.quote:cli'), + ('order:lookup', 'SoftLayer.CLI.order.lookup:cli'), ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 866ee8b43..ad814aff3 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -11,7 +11,6 @@ from SoftLayer import exceptions - CATEGORY_MASK = '''id, isRequired, itemCategory[id, name, categoryCode]''' ITEM_MASK = '''id, keyName, description, itemCategory, categories, prices''' @@ -61,6 +60,28 @@ def get_packages_of_type(self, package_types, mask=None): packages = self.filter_outlet_packages(packages) return packages + def get_order_detail(self, order_id, mask=None): + """Get order details. + + :param int order_id: to specify the order that we want to retrieve. + :param string mask: Mask to specify the properties we want to retrieve. + """ + _default_mask = ( + 'mask[orderTotalAmount,orderApprovalDate,' + 'initialInvoice[id,amount,invoiceTotalAmount,' + 'invoiceTopLevelItems[id, description, hostName, domainName, oneTimeAfterTaxAmount,' + 'recurringAfterTaxAmount, createDate,' + 'categoryCode,' + 'category[name],' + 'location[name],' + 'children[id, category[name], description, oneTimeAfterTaxAmount,recurringAfterTaxAmount]]],' + 'items[description],userRecord[displayName,userStatus]]') + + mask = _default_mask if mask is None else mask + + order = self.billing_svc.getObject(mask=mask, id=order_id) + return order + @staticmethod def filter_outlet_packages(packages): """Remove packages designated as OUTLET. From 273463594179cd05b296a0cb87086ce9c2dd0833 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 24 Sep 2020 15:55:51 -0400 Subject: [PATCH 0264/1385] update from origin --- SoftLayer/fixtures/SoftLayer_Billing_Order.py | 25 +++++++++++++++++ tests/CLI/modules/order_tests.py | 7 +++++ tests/managers/ordering_tests.py | 27 ++++++++++++++++--- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order.py b/SoftLayer/fixtures/SoftLayer_Billing_Order.py index f8c5505bc..ae35280ea 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order.py @@ -45,3 +45,28 @@ 'orderApprovalDate': '2019-09-15T13:13:13-06:00', 'orderTotalAmount': '0' }] + +getObject = { + 'accountId': 1234, + 'createDate': '2020-09-23T16:22:30-06:00', + 'id': 6543210, + 'impersonatingUserRecordId': None, + 'initialInvoice': { + 'amount': '0', + 'id': 60012345, + 'invoiceTotalAmount': '0' + }, + 'items': [{ + 'description': 'Dual Intel Xeon Silver 4210 (20 Cores, 2.20 GHz)' + }], + 'modifyDate': '2020-09-23T16:22:32-06:00', + 'orderQuoteId': None, + 'orderTypeId': 11, + 'presaleEventId': None, + 'privateCloudOrderFlag': False, + 'status': 'APPROVED', + 'userRecord': { + 'displayName': 'testUser' + }, + 'userRecordId': 7654321, +} diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 13ccb0f80..279f49f5a 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -406,6 +406,13 @@ def _get_verified_order_return(self): 'recurringFee': '150'} return {'orderContainers': [{'prices': [price1, price2]}]} + def test_order_lookup(self): + result = self.run_command(['order', 'lookup', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order', 'getObject', identifier='12345') + self.assertIn('Ordered By', result.output) + self.assertIn('Initial Invoice', result.output) + def _get_all_packages(): package_type = {'keyName': 'BARE_METAL_CPU'} diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 675ac290d..8b6dc8beb 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -731,7 +731,7 @@ def test_get_item_capacity_core(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) @@ -748,7 +748,7 @@ def test_get_item_capacity_storage(self): "capacity": "1", "id": 10201, "keyName": "READHEAVY_TIER", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) @@ -766,7 +766,7 @@ def test_get_item_capacity_intel(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) @@ -805,3 +805,24 @@ def test_get_item_prices_by_location(self): self.assertEqual(options[0]['item']['keyName'], item_prices[0]['item']['keyName']) self.assertEqual(options[0]['hourlyRecurringFee'], item_prices[0]['hourlyRecurringFee']) + + def test_get_oder_detail_mask(self): + order_id = 12345 + test_mask = 'mask[id]' + self.ordering.get_order_detail(order_id, mask=test_mask) + self.assert_called_with('SoftLayer_Billing_Order', 'getObject', identifier=order_id, mask=test_mask) + + def test_get_oder_detail_default_mask(self): + order_id = 12345 + _default_mask = ( + 'mask[orderTotalAmount,orderApprovalDate,' + 'initialInvoice[id,amount,invoiceTotalAmount,' + 'invoiceTopLevelItems[id, description, hostName, domainName, oneTimeAfterTaxAmount,' + 'recurringAfterTaxAmount, createDate,' + 'categoryCode,' + 'category[name],' + 'location[name],' + 'children[id, category[name], description, oneTimeAfterTaxAmount,recurringAfterTaxAmount]]],' + 'items[description],userRecord[displayName,userStatus]]') + self.ordering.get_order_detail(order_id) + self.assert_called_with('SoftLayer_Billing_Order', 'getObject', identifier=order_id, mask=_default_mask) From c9b38745cb1733e867aee4cbb4b57f83d1e51768 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 24 Sep 2020 16:31:29 -0400 Subject: [PATCH 0265/1385] #1340 add order lookup doc --- docs/cli/ordering.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index acaf3a07e..1351cfd9c 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -135,3 +135,9 @@ Quotes .. click:: SoftLayer.CLI.order.place_quote:cli :prog: order place-quote :show-nested: + +Lookup +====== +.. click:: SoftLayer.CLI.order.lookup:cli + :prog: order lookup + :show-nested: From e00b921116674b4f09489c2c03adc8fdc16a23f4 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Sep 2020 12:23:16 -0400 Subject: [PATCH 0266/1385] fix Christopher code review --- SoftLayer/CLI/hardware/create_options.py | 30 +++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 98f27b3a5..b1bcf9a21 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -81,7 +81,7 @@ def _preset_prices_table(sizes, tables): preset_prices_table.sortby = 'Value' preset_prices_table.align = 'l' for size in sizes: - if ("%.4f" % size['hourlyRecurringFee'] != '0.0000') or ("%.4f" % size['recurringFee'] != '0.0000'): + if (_verify_prices("%.4f" % size['hourlyRecurringFee'])) or (_verify_prices("%.4f" % size['recurringFee'])): preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], "%.4f" % size['recurringFee']]) tables.append(preset_prices_table) @@ -99,8 +99,8 @@ def _os_prices_table(operating_systems, tables): os_prices_table.align = 'l' for operating_system in operating_systems: for price in operating_system['prices']: - if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( - _get_price_data(price, 'recurringFee') != '0'): + if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( + _verify_prices(_get_price_data(price, 'recurringFee'))): cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') @@ -124,8 +124,8 @@ def _port_speed_prices_table(port_speeds, tables): port_speed_prices_table.align = 'l' for speed in port_speeds: for price in speed['prices']: - if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( - _get_price_data(price, 'recurringFee') != '0'): + if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( + _verify_prices(_get_price_data(price, 'recurringFee'))): cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') @@ -137,6 +137,19 @@ def _port_speed_prices_table(port_speeds, tables): tables.append(port_speed_prices_table) +def _verify_prices(prices): + """Verify the prices is higher to zero(0) or is '-'. + + param prices: value to verify. + Returns: true false. + + """ + if prices == '-': + return True + else: + return float(prices) > 0 + + def _extras_prices_table(extras, tables): """Shows Server extras prices cost and capacity restriction. @@ -148,8 +161,8 @@ def _extras_prices_table(extras, tables): extras_prices_table.align = 'l' for extra in extras: for price in extra['prices']: - if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( - _get_price_data(price, 'recurringFee') != '0'): + if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( + _verify_prices(_get_price_data(price, 'recurringFee'))): cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') @@ -183,7 +196,8 @@ def _location_item_prices(location_prices, tables): location_prices_table.sortby = 'keyName' location_prices_table.align = 'l' for price in location_prices: - if (_get_price_data(price, 'hourlyRecurringFee') != '0') or (_get_price_data(price, 'recurringFee') != '0'): + if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( + _verify_prices(_get_price_data(price, 'recurringFee'))): cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') From e3a13379ff48affaaad1be72785ce58ce6b42103 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 30 Sep 2020 17:14:30 -0500 Subject: [PATCH 0267/1385] fixing a few documentation warnings --- SoftLayer/CLI/block/refresh.py | 5 ++++- SoftLayer/CLI/ticket/create.py | 9 ++++----- SoftLayer/CLI/ticket/update.py | 8 +++----- docs/cli/block.rst | 4 ++-- docs/cli/file.rst | 4 ++-- docs/cli/tickets.rst | 11 ++++++----- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 369a6c815..11d21fe3f 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -11,7 +11,10 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """Refresh a duplicate volume with a snapshot from its parent.""" + """Refresh a duplicate volume with a snapshot from its parent. + + + """ block_manager = SoftLayer.BlockStorageManager(env.client) resp = block_manager.refresh_dupe(volume_id, snapshot_id) diff --git a/SoftLayer/CLI/ticket/create.py b/SoftLayer/CLI/ticket/create.py index 17667d74f..143c5f7b1 100644 --- a/SoftLayer/CLI/ticket/create.py +++ b/SoftLayer/CLI/ticket/create.py @@ -25,19 +25,18 @@ def cli(env, title, subject_id, body, hardware_identifier, virtual_identifier, priority): """Create a Infrastructure support ticket. - Example:: - - Will create the ticket with `Some text`. + Will create the ticket with `Some text`.:: slcli ticket create --body="Some text" --subject-id 1522 --hardware 12345 --title "My New Ticket" - Will create the ticket with text from STDIN + Will create the ticket with text from STDIN:: cat sometfile.txt | slcli ticket create --subject-id 1003 --virtual 111111 --title "Reboot Me" - Will open the default text editor, and once closed, use that text to create the ticket + Will open the default text editor, and once closed, use that text to create the ticket:: slcli ticket create --subject-id 1482 --title "Vyatta Questions..." + """ ticket_mgr = SoftLayer.TicketManager(env.client) if body is None: diff --git a/SoftLayer/CLI/ticket/update.py b/SoftLayer/CLI/ticket/update.py index f04d36f94..d7c0e7bc7 100644 --- a/SoftLayer/CLI/ticket/update.py +++ b/SoftLayer/CLI/ticket/update.py @@ -16,17 +16,15 @@ def cli(env, identifier, body): """Adds an update to an existing ticket. - Example:: - - Will update the ticket with `Some text`. +Will update the ticket with `Some text`.:: slcli ticket update 123456 --body="Some text" - Will update the ticket with text from STDIN +Will update the ticket with text from STDIN:: cat sometfile.txt | slcli ticket update 123456 - Will open the default text editor, and once closed, use that text to update the ticket +Will open the default text editor, and once closed, use that text to update the ticket:: slcli ticket update 123456 """ diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 10ec8e6a6..5684b5623 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -112,11 +112,11 @@ Block Commands :show-nested: .. click:: SoftLayer.CLI.block.refresh:cli - :prog block volume-refresh + :prog: block volume-refresh :show-nested: .. click:: SoftLayer.CLI.block.convert:cli - :prog block volume-convert + :prog: block volume-convert :show-nested: .. click:: SoftLayer.CLI.block.subnets.list:cli diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 5af9b65cc..31ceb0332 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -100,11 +100,11 @@ File Commands :show-nested: .. click:: SoftLayer.CLI.file.refresh:cli - :prog file volume-refresh + :prog: file volume-refresh :show-nested: .. click:: SoftLayer.CLI.file.convert:cli - :prog file volume-convert + :prog: file volume-convert :show-nested: .. click:: SoftLayer.CLI.file.snapshot.schedule_list:cli diff --git a/docs/cli/tickets.rst b/docs/cli/tickets.rst index 71ef3192c..bb56aad86 100644 --- a/docs/cli/tickets.rst +++ b/docs/cli/tickets.rst @@ -1,13 +1,14 @@ .. _cli_tickets: Support Tickets -=============== +================= -The SoftLayer ticket API is used to create "classic" or Infrastructure Support cases. -These tickets will still show up in your web portal, but for the more unified case management API, -see the `Case Management API `_ +The SoftLayer ticket API is used to create "classic" or Infrastructure Support cases. These tickets will still show up in your web portal, but for the more unified case management API, see the `Case Management API `_ + +.. note:: + + Windows Git-Bash users might run into issues with `ticket create` and `ticket update` if --body isn't used, as it doesn't report that it is a real TTY to python, so the default editor can not be launched. -.. note:: Windows Git-Bash users might run into issues with `ticket create` and `ticket update` if --body isn't used, as it doesn't report that it is a real TTY to python, so the default editor can not be launched. .. click:: SoftLayer.CLI.ticket.create:cli :prog: ticket create From 51f786935d8d7966414922344a6d1f5b6f081424 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 30 Sep 2020 17:14:50 -0500 Subject: [PATCH 0268/1385] #1038 allowed 'NONE' to be used as a location if needed --- SoftLayer/CLI/order/place.py | 49 ++++++++++++-------------------- SoftLayer/managers/ordering.py | 3 ++ tests/managers/ordering_tests.py | 4 +++ 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 6b66fb110..6bf0d437c 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -42,40 +42,26 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, quantity, extras, order_items): """Place or verify an order. - This CLI command is used for placing/verifying an order of the specified package in - the given location (denoted by a datacenter's long name). Orders made via the CLI - can then be converted to be made programmatically by calling - SoftLayer.OrderingManager.place_order() with the same keynames. - - Packages for ordering can be retrieved from `slcli order package-list` - Presets for ordering can be retrieved from `slcli order preset-list` (not all packages - have presets) - - Items can be retrieved from `slcli order item-list`. In order to find required - items for the order, use `slcli order category-list`, and then provide the - --category option for each category code in `slcli order item-list`. - +\b +1. Find the package keyName from `slcli order package-list` +2. Find the location from `slcli order package-locations PUBLIC_CLOUD_SERVER` + If the package does not require a location, use 'NONE' instead. +3. Find the needed items `slcli order item-list PUBLIC_CLOUD_SERVER` + Some packages, like PUBLIC_CLOUD_SERVER need presets, `slcli order preset-list PUBLIC_CLOUD_SERVER` +4. Find the complex type from https://sldn.softlayer.com/reference +5. Use that complex type to fill out any --extras Example:: - # Order an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, - # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 - slcli order place --billing hourly CLOUD_SERVER DALLAS13 \\ - GUEST_CORES_4 \\ - RAM_16_GB \\ - REBOOT_REMOTE_CONSOLE \\ - 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS \\ - BANDWIDTH_0_GB_2 \\ - 1_IP_ADDRESS \\ - GUEST_DISK_100_GB_SAN \\ - OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI \\ - MONITORING_HOST_PING \\ - NOTIFICATION_EMAIL_AND_TICKET \\ - AUTOMATED_NOTIFICATION \\ - UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \\ - NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \\ - --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \\ - --complex-type SoftLayer_Container_Product_Order_Virtual_Guest + slcli order place --verify --preset B1_2X8X100 --billing hourly + --complex-type SoftLayer_Container_Product_Order_Virtual_Guest + --extras '{"virtualGuests": [{"hostname": "test", "domain": "ibm.com"}]}' + PUBLIC_CLOUD_SERVER DALLAS13 + BANDWIDTH_0_GB_2 MONITORING_HOST_PING NOTIFICATION_EMAIL_AND_TICKET + OS_DEBIAN_9_X_STRETCH_LAMP_64_BIT 1_IP_ADDRESS 1_IPV6_ADDRESS + 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS REBOOT_REMOTE_CONSOLE + AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT + NESSUS_VULNERABILITY_ASSESSMENT_REPORTING """ manager = ordering.OrderingManager(env.client) @@ -118,3 +104,4 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, table.add_row(['created', order['orderDate']]) table.add_row(['status', order['placedOrder']['status']]) env.fout(table) + diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 866ee8b43..847148a08 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -634,6 +634,9 @@ def get_location_id(self, location): if isinstance(location, int): return location + # Some orders dont require a location, just use 0 + if location.upper() == "NONE": + return 0 mask = "mask[id,name,regions[keyname]]" if match(r'[a-zA-Z]{3}[0-9]{2}', location) is not None: search = {'name': {'operation': location}} diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 675ac290d..0471893f8 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -594,6 +594,10 @@ def test_get_location_id_int(self): dc_id = self.ordering.get_location_id(1234) self.assertEqual(1234, dc_id) + def test_get_location_id_NONE(self): + dc_id = self.ordering.get_location_id("NONE") + self.assertEqual(0, dc_id) + def test_location_group_id_none(self): # RestTransport uses None for empty locationGroupId category1 = {'categoryCode': 'cat1'} From e72ee794cf3c46f86423777d62f0f632d7dc8e41 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 1 Oct 2020 13:33:30 -0500 Subject: [PATCH 0269/1385] fixed tox errors --- SoftLayer/CLI/order/place.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 6bf0d437c..f0ceed350 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -43,7 +43,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, """Place or verify an order. \b -1. Find the package keyName from `slcli order package-list` +1. Find the package keyName from `slcli order package-list` 2. Find the location from `slcli order package-locations PUBLIC_CLOUD_SERVER` If the package does not require a location, use 'NONE' instead. 3. Find the needed items `slcli order item-list PUBLIC_CLOUD_SERVER` @@ -53,7 +53,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, Example:: - slcli order place --verify --preset B1_2X8X100 --billing hourly + slcli order place --verify --preset B1_2X8X100 --billing hourly --complex-type SoftLayer_Container_Product_Order_Virtual_Guest --extras '{"virtualGuests": [{"hostname": "test", "domain": "ibm.com"}]}' PUBLIC_CLOUD_SERVER DALLAS13 @@ -104,4 +104,3 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, table.add_row(['created', order['orderDate']]) table.add_row(['status', order['placedOrder']['status']]) env.fout(table) - From 481c9e5254449e3d6735bfaccf38645761279749 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 1 Oct 2020 13:48:01 -0500 Subject: [PATCH 0270/1385] Update refresh.py fixed typo --- SoftLayer/CLI/block/refresh.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 11d21fe3f..369a6c815 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -11,10 +11,7 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """Refresh a duplicate volume with a snapshot from its parent. - - - """ + """Refresh a duplicate volume with a snapshot from its parent.""" block_manager = SoftLayer.BlockStorageManager(env.client) resp = block_manager.refresh_dupe(volume_id, snapshot_id) From 157f6241a98528fb866325852a0f832a227b3129 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 1 Oct 2020 16:22:58 -0400 Subject: [PATCH 0271/1385] Refactor block and file volume list and volume order. --- SoftLayer/CLI/block/list.py | 4 ++-- SoftLayer/CLI/block/order.py | 4 +++- SoftLayer/CLI/file/list.py | 6 +++--- SoftLayer/CLI/file/order.py | 3 +++ SoftLayer/managers/block.py | 4 ++-- SoftLayer/managers/file.py | 4 ++-- tests/CLI/modules/block_tests.py | 30 +++++++++++------------------- tests/CLI/modules/file_tests.py | 29 +++++++++++------------------ 8 files changed, 37 insertions(+), 47 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index d9f4cf4d0..44489f928 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -58,7 +58,7 @@ @click.command() @click.option('--username', '-u', help='Volume username') @click.option('--datacenter', '-d', help='Datacenter shortname') -@click.option('--order', '-o', help='Filter by ID of the order that purchased the block storage') +@click.option('--order', '-o', type=int, help='Filter by ID of the order that purchased the block storage') @click.option('--storage-type', help='Type of storage volume', type=click.Choice(['performance', 'endurance'])) @@ -74,8 +74,8 @@ def cli(env, sortby, columns, datacenter, username, storage_type, order): block_manager = SoftLayer.BlockStorageManager(env.client) block_volumes = block_manager.list_block_volumes(datacenter=datacenter, username=username, - order=order, storage_type=storage_type, + order=order, mask=columns.mask()) table = formatting.Table(columns.columns) diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index 738080fd0..fa7c6bcf6 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -6,7 +6,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions - CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @@ -128,5 +127,8 @@ def cli(env, storage_type, size, iops, tier, os_type, order['placedOrder']['id'])) for item in order['placedOrder']['items']: click.echo(" > %s" % item['description']) + click.echo( + '\nYou may run "slcli block volume-list --order {0}" to find this block volume after it ' + 'is ready.'.format(order['placedOrder']['id'])) else: click.echo("Order could not be placed! Please verify your options and try again.") diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 5ae320c13..f7c08fe18 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -56,7 +56,7 @@ @click.command() @click.option('--username', '-u', help='Volume username') @click.option('--datacenter', '-d', help='Datacenter shortname') -@click.option('--order', '-o', help='Filter by ID of the order that purchased the block storage') +@click.option('--order', '-o', type=int, help='Filter by ID of the order that purchased the block storage') @click.option('--storage-type', help='Type of storage volume', type=click.Choice(['performance', 'endurance'])) @@ -67,13 +67,13 @@ ', '.join(column.name for column in COLUMNS)), default=','.join(DEFAULT_COLUMNS)) @environment.pass_env -def cli(env, sortby, columns, datacenter, username, order, storage_type): +def cli(env, sortby, columns, datacenter, username, storage_type, order): """List file storage.""" file_manager = SoftLayer.FileStorageManager(env.client) file_volumes = file_manager.list_file_volumes(datacenter=datacenter, username=username, - order=order, storage_type=storage_type, + order=order, mask=columns.mask()) table = formatting.Table(columns.columns) diff --git a/SoftLayer/CLI/file/order.py b/SoftLayer/CLI/file/order.py index e665a088b..1c7584961 100644 --- a/SoftLayer/CLI/file/order.py +++ b/SoftLayer/CLI/file/order.py @@ -115,5 +115,8 @@ def cli(env, storage_type, size, iops, tier, order['placedOrder']['id'])) for item in order['placedOrder']['items']: click.echo(" > %s" % item['description']) + click.echo( + '\nYou may run "slcli file volume-list --order {0}" to find this file volume after it ' + 'is ready.'.format(order['placedOrder']['id'])) else: click.echo("Order could not be placed! Please verify your options and try again.") diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index e4de585cf..10327211c 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -25,13 +25,13 @@ def list_block_volume_limit(self): """ return self.get_volume_count_limits() - def list_block_volumes(self, datacenter=None, username=None, order=None, storage_type=None, **kwargs): + def list_block_volumes(self, datacenter=None, username=None, storage_type=None, order=None, **kwargs): """Returns a list of block volumes. - :param order: Volume order id. :param datacenter: Datacenter short name (e.g.: dal09) :param username: Name of volume. :param storage_type: Type of volume: Endurance or Performance + :param order: Volume order id. :param kwargs: :return: Returns a list of block volumes. """ diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 30653821c..d4d34f002 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -22,13 +22,13 @@ def list_file_volume_limit(self): """ return self.get_volume_count_limits() - def list_file_volumes(self, datacenter=None, username=None, order=None, storage_type=None, **kwargs): + def list_file_volumes(self, datacenter=None, username=None, storage_type=None, order=None, **kwargs): """Returns a list of file volumes. - :param order: Volume order id. :param datacenter: Datacenter short name (e.g.: dal09) :param username: Name of volume. :param storage_type: Type of volume: Endurance or Performance + :param order: Volume order id. :param kwargs: :return: Returns a list of file volumes. """ diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 196b4a51e..f061d36a2 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -145,22 +145,8 @@ def test_volume_list_order(self): result = self.run_command(['block', 'volume-list', '--order=1234567']) self.assert_no_fail(result) - self.assertEqual([ - { - 'bytes_used': None, - 'capacity_gb': 20, - 'datacenter': 'dal05', - 'id': 100, - 'iops': None, - 'ip_addr': '10.1.2.3', - 'lunId': None, - 'notes': "{'status': 'availabl", - 'rep_partner_count': None, - 'storage_type': 'ENDURANCE', - 'username': 'username', - 'active_transactions': None - }], - json.loads(result.output)) + json_result = json.loads(result.output) + self.assertEqual(json_result[0]['id'], 100) @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') def test_volume_count(self, list_mock): @@ -220,7 +206,9 @@ def test_volume_order_performance(self, order_mock): 'Order #478 placed successfully!\n' ' > Performance Storage\n > Block Storage\n' ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli block volume-list --order 478" to find this block volume ' + 'after it is ready.\n') def test_volume_order_endurance_tier_not_given(self): result = self.run_command(['block', 'volume-order', @@ -253,7 +241,9 @@ def test_volume_order_endurance(self, order_mock): 'Order #478 placed successfully!\n' ' > Endurance Storage\n > Block Storage\n' ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli block volume-list --order 478" to find this block volume ' + 'after it is ready.\n') @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') def test_volume_order_order_not_placed(self, order_mock): @@ -302,7 +292,9 @@ def test_volume_order_hourly_billing(self, order_mock): ' > Storage as a Service\n' ' > Block Storage\n' ' > 20 GB Storage Space\n' - ' > 200 IOPS\n') + ' > 200 IOPS\n' + '\nYou may run "slcli block volume-list --order 10983647" to find this block volume ' + 'after it is ready.\n') @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') def test_volume_order_performance_manager_error(self, order_mock): diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index cc58e1df0..dac95e0d8 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -59,21 +59,8 @@ def test_volume_list_order(self): result = self.run_command(['file', 'volume-list', '--order=1234567']) self.assert_no_fail(result) - self.assertEqual([ - { - 'bytes_used': None, - 'capacity_gb': 10, - 'datacenter': 'Dallas', - 'id': 1, - 'ip_addr': '127.0.0.1', - 'storage_type': 'ENDURANCE', - 'username': 'user', - 'active_transactions': None, - 'mount_addr': '127.0.0.1:/TEST', - 'notes': None, - 'rep_partner_count': None - }], - json.loads(result.output)) + json_result = json.loads(result.output) + self.assertEqual(json_result[0]['id'], 1) @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') def test_volume_count(self, list_mock): @@ -243,7 +230,9 @@ def test_volume_order_performance(self, order_mock): 'Order #478 placed successfully!\n' ' > Performance Storage\n > File Storage\n' ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli file volume-list --order 478" to find this file volume after it is ' + 'ready.\n') def test_volume_order_endurance_tier_not_given(self): result = self.run_command(['file', 'volume-order', @@ -276,7 +265,9 @@ def test_volume_order_endurance(self, order_mock): 'Order #478 placed successfully!\n' ' > Endurance Storage\n > File Storage\n' ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli file volume-list --order 478" to find this file volume after it is ' + 'ready.\n') @mock.patch('SoftLayer.FileStorageManager.order_file_volume') def test_volume_order_order_not_placed(self, order_mock): @@ -327,7 +318,9 @@ def test_volume_order_hourly_billing(self, order_mock): ' > File Storage\n' ' > 20 GB Storage Space\n' ' > 0.25 IOPS per GB\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli file volume-list --order 479" to find this file volume after it is ' + 'ready.\n') @mock.patch('SoftLayer.FileStorageManager.order_file_volume') def test_volume_order_performance_manager_error(self, order_mock): From 8d8102c11da852fdd0b9a871eb998cbf788b39b4 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 1 Oct 2020 17:40:33 -0400 Subject: [PATCH 0272/1385] Refactor file and block commands to use the username resolver --- SoftLayer/CLI/block/access/list.py | 4 +++- SoftLayer/CLI/block/snapshot/list.py | 4 +++- SoftLayer/CLI/block/subnets/list.py | 4 +++- SoftLayer/CLI/file/access/list.py | 4 +++- SoftLayer/CLI/file/snapshot/list.py | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/block/access/list.py b/SoftLayer/CLI/block/access/list.py index b011e263a..4ff77ef25 100644 --- a/SoftLayer/CLI/block/access/list.py +++ b/SoftLayer/CLI/block/access/list.py @@ -6,6 +6,7 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer.CLI import storage_utils @@ -21,8 +22,9 @@ def cli(env, columns, sortby, volume_id): """List ACLs.""" block_manager = SoftLayer.BlockStorageManager(env.client) + resolved_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Volume Id') access_list = block_manager.get_block_volume_access_list( - volume_id=volume_id) + volume_id=resolved_id) table = formatting.Table(columns.columns) table.sortby = sortby diff --git a/SoftLayer/CLI/block/snapshot/list.py b/SoftLayer/CLI/block/snapshot/list.py index b47f5949f..1c2f5c7f8 100644 --- a/SoftLayer/CLI/block/snapshot/list.py +++ b/SoftLayer/CLI/block/snapshot/list.py @@ -6,6 +6,7 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers COLUMNS = [ @@ -38,8 +39,9 @@ def cli(env, volume_id, sortby, columns): """List block storage snapshots.""" block_manager = SoftLayer.BlockStorageManager(env.client) + resolved_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Volume Id') snapshots = block_manager.get_block_volume_snapshot_list( - volume_id, + resolved_id, mask=columns.mask() ) diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py index e111de1b3..d7576971a 100644 --- a/SoftLayer/CLI/block/subnets/list.py +++ b/SoftLayer/CLI/block/subnets/list.py @@ -5,6 +5,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers COLUMNS = [ @@ -26,7 +27,8 @@ def cli(env, access_id): try: block_manager = SoftLayer.BlockStorageManager(env.client) - subnets = block_manager.get_subnets_in_acl(access_id) + resolved_id = helpers.resolve_id(block_manager.resolve_ids, access_id, 'Volume Id') + subnets = block_manager.get_subnets_in_acl(resolved_id) table = formatting.Table(COLUMNS) for subnet in subnets: diff --git a/SoftLayer/CLI/file/access/list.py b/SoftLayer/CLI/file/access/list.py index cc9997a05..34dd6fa8f 100644 --- a/SoftLayer/CLI/file/access/list.py +++ b/SoftLayer/CLI/file/access/list.py @@ -6,6 +6,7 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer.CLI import storage_utils @@ -21,8 +22,9 @@ def cli(env, columns, sortby, volume_id): """List ACLs.""" file_manager = SoftLayer.FileStorageManager(env.client) + resolved_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'Volume Id') access_list = file_manager.get_file_volume_access_list( - volume_id=volume_id) + volume_id=resolved_id) table = formatting.Table(columns.columns) table.sortby = sortby diff --git a/SoftLayer/CLI/file/snapshot/list.py b/SoftLayer/CLI/file/snapshot/list.py index 494900f06..aa9284014 100644 --- a/SoftLayer/CLI/file/snapshot/list.py +++ b/SoftLayer/CLI/file/snapshot/list.py @@ -6,6 +6,7 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers COLUMNS = [ @@ -38,8 +39,9 @@ def cli(env, volume_id, sortby, columns): """List file storage snapshots.""" file_manager = SoftLayer.FileStorageManager(env.client) + resolved_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'Volume Id') snapshots = file_manager.get_file_volume_snapshot_list( - volume_id, + resolved_id, mask=columns.mask() ) From c709a55685aee5b9173403999c822107dee42f3a Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 1 Oct 2020 18:24:52 -0400 Subject: [PATCH 0273/1385] fix tox analysis --- SoftLayer/CLI/file/snapshot/list.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/file/snapshot/list.py b/SoftLayer/CLI/file/snapshot/list.py index aa9284014..3a02a6b56 100644 --- a/SoftLayer/CLI/file/snapshot/list.py +++ b/SoftLayer/CLI/file/snapshot/list.py @@ -8,7 +8,6 @@ from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers - COLUMNS = [ column_helper.Column('id', ('id',), mask='id'), column_helper.Column('name', ('notes',), mask='notes'), From f1abe1a522693d4445a82f9a1a93c2f382886bf9 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 1 Oct 2020 18:59:21 -0400 Subject: [PATCH 0274/1385] Refactor code review. --- SoftLayer/CLI/virt/create_options.py | 310 ++++++++++++--------------- 1 file changed, 133 insertions(+), 177 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index c6bca1946..462090e13 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -13,8 +13,7 @@ @click.option('--vsi-type', required=False, show_default=True, default='PUBLIC_CLOUD_SERVER', type=click.Choice(['PUBLIC_CLOUD_SERVER', 'TRANSIENT_CLOUD_SERVER', 'SUSPEND_CLOUD_SERVER', 'CLOUD_SERVER']), - help="Display options for a specific virtual server packages, for default is PUBLIC_CLOUD_SERVER, " - "choose between TRANSIENT_CLOUD_SERVER, SUSPEND_CLOUD_SERVER, CLOUD_SERVER") + help="VS keyName type.") @click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and to list the Item Prices by location,' 'add it to the --prices option using location short name, e.g. --prices dal13') @@ -35,277 +34,234 @@ def cli(env, vsi_type, prices, location=None): dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - if prices: - preset_prices_table(options['sizes'], tables) - os_prices_table(options['operating_systems'], tables) - port_speed_prices_table(options['port_speed'], tables) - ram_prices_table(options['ram'], tables) - database_prices_table(options['database'], tables) - guest_core_prices_table(options['guest_core'], tables) - guest_disk_prices_table(options['guest_disk'], tables) - extras_prices_table(options['extras'], tables) + if vsi_type == 'CLOUD_SERVER': + tables.append(guest_core_prices_table(options['guest_core'], prices)) + tables.append(ram_prices_table(options['ram'], prices)) else: - # Operation system - os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") - os_table.sortby = 'Key' - os_table.align = 'l' - - for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) - tables.append(os_table) - - # Sizes - preset_table = formatting.Table(['Size', 'Value'], title="Sizes") - preset_table.sortby = 'Value' - preset_table.align = 'l' - - for size in options['sizes']: - preset_table.add_row([size['name'], size['key']]) - tables.append(preset_table) - - # RAM - ram_table = formatting.Table(['memory', 'Value'], title="RAM") - ram_table.sortby = 'Value' - ram_table.align = 'l' - - for ram in options['ram']: - ram_table.add_row([ram['name'], ram['key']]) - tables.append(ram_table) - - # Data base - database_table = formatting.Table(['database', 'Value'], title="Databases") - database_table.sortby = 'Value' - database_table.align = 'l' - - for database in options['database']: - database_table.add_row([database['name'], database['key']]) - tables.append(database_table) - - # Guest_core - guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") - guest_core_table.sortby = 'Value' - guest_core_table.align = 'l' - - for guest_core in options['guest_core']: - guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) - tables.append(guest_core_table) - - # Guest_core - guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") - guest_disk_table.sortby = 'Value' - guest_disk_table.align = 'l' - - for guest_disk in options['guest_disk']: - guest_disk_table.add_row( - [guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) - tables.append(guest_disk_table) - - # Port speed - port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") - port_speed_table.sortby = 'Key' - port_speed_table.align = 'l' - - for speed in options['port_speed']: - port_speed_table.add_row([speed['name'], speed['key']]) - tables.append(port_speed_table) - - env.fout(formatting.listing(tables, separator='\n')) - - -def preset_prices_table(sizes, tables): + tables.append(preset_prices_table(options['sizes'], prices)) + tables.append(os_prices_table(options['operating_systems'], prices)) + tables.append(port_speed_prices_table(options['port_speed'], prices)) + tables.append(database_prices_table(options['database'], prices)) + tables.append(guest_disk_prices_table(options['guest_disk'], prices)) + tables.append(extras_prices_table(options['extras'], prices)) + + env.fout(tables) + + +def preset_prices_table(sizes, prices=False): """Shows Server Preset options prices. :param [] sizes: List of Hardware Server sizes. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - preset_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") + preset_price_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") + preset_price_table.sortby = 'Value' + preset_price_table.align = 'l' + + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") preset_table.sortby = 'Value' preset_table.align = 'l' + for size in sizes: - preset_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) - tables.append(preset_table) + if (size['hourlyRecurringFee'] > 0) or (size['recurringFee'] > 0): + preset_price_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) + preset_table.add_row([size['name'], size['key']]) + if prices: + return preset_price_table + return preset_table -def os_prices_table(operating_systems, tables): +def os_prices_table(operating_systems, prices=False): """Shows Server Operating Systems prices cost and capacity restriction. :param [] operating_systems: List of Hardware Server operating systems. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - os_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], - title="Operating Systems Prices") - os_table.sortby = 'OS Key' + os_price_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], + title="Operating Systems Prices") + os_price_table.sortby = 'OS Key' + os_price_table.align = 'l' + + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' os_table.align = 'l' + for operating_system in operating_systems: for price in operating_system['prices']: cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') - os_table.add_row( + os_price_table.add_row( [operating_system['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(os_table) + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + if prices: + return os_price_table + return os_table -def port_speed_prices_table(port_speeds, tables): +def port_speed_prices_table(port_speeds, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] port_speeds: List of Hardware Server Port Speeds. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - port_speed_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', 'Restriction'], - title="Network Options Prices") - port_speed_table.sortby = 'Speed' + port_speed_price_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly'], title="Network Options Prices") + port_speed_price_table.sortby = 'Speed' + port_speed_price_table.align = 'l' + + port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") + port_speed_table.sortby = 'Key' port_speed_table.align = 'l' + for speed in port_speeds: for price in speed['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - port_speed_table.add_row( + port_speed_price_table.add_row( [speed['key'], speed['speed'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(port_speed_table) + _get_price_data(price, 'recurringFee')]) + port_speed_table.add_row([speed['name'], speed['key']]) + if prices: + return port_speed_price_table + return port_speed_table -def extras_prices_table(extras, tables): +def extras_prices_table(extras, prices=False): """Shows Server extras prices cost and capacity restriction. :param [] extras: List of Hardware Server Extras. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - extras_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', 'Restriction'], - title="Extras Prices") + extras_price_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly'], title="Extras Prices") + extras_price_table.align = 'l' + + extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") + extras_table.sortby = 'Value' extras_table.align = 'l' + for extra in extras: for price in extra['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - extras_table.add_row( + extras_price_table.add_row( [extra['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(extras_table) - + _get_price_data(price, 'recurringFee')]) + extras_table.add_row([extra['name'], extra['key']]) + if prices: + return extras_price_table + return extras_table -def _location_item_prices(location_prices, tables): - """Get a specific data from HS price. - :param price: Hardware Server price. - :param string item: Hardware Server price data. - """ - location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction']) - location_prices_table.sortby = 'keyName' - location_prices_table.align = 'l' - for price in location_prices: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - location_prices_table.add_row( - [price['item']['keyName'], price['id'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(location_prices_table) - - -def ram_prices_table(ram_list, tables): +def ram_prices_table(ram_list, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] ram_list: List of Virtual Server Ram. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - ram_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], - title="Ram Prices") - ram_table.sortby = 'Key' + ram_price_table = formatting.Table(['Key', 'Hourly', 'Monthly'], title="Ram Prices") + ram_price_table.sortby = 'Key' + ram_price_table.align = 'l' + + ram_table = formatting.Table(['memory', 'Value'], title="RAM") + ram_table.sortby = 'Value' ram_table.align = 'l' + for ram in ram_list: for price in ram['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - ram_table.add_row( + ram_price_table.add_row( [ram['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(ram_table) + _get_price_data(price, 'recurringFee')]) + ram_table.add_row([ram['name'], ram['key']]) + if prices: + return ram_price_table + return ram_table -def database_prices_table(database_list, tables): +def database_prices_table(database_list, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] database_list: List of Virtual Server database. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - database_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + database_price_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], title="Data Base Prices") - database_table.sortby = 'Key' + database_price_table.sortby = 'Key' + database_price_table.align = 'l' + + database_table = formatting.Table(['database', 'Value'], title="Databases") + database_table.sortby = 'Value' database_table.align = 'l' + for database in database_list: for price in database['prices']: cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') - database_table.add_row( + database_price_table.add_row( [database['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(database_table) + database_table.add_row([database['name'], database['key']]) + if prices: + return database_price_table + return database_table -def guest_core_prices_table(guest_core_list, tables): +def guest_core_prices_table(guest_core_list, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] guest_core_list: List of Virtual Server guest_core. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - guest_core_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], - title="Guest Core Prices") - guest_core_table.sortby = 'Key' + guest_core_price_table = formatting.Table(['Key', 'Hourly', 'Monthly'], title="Guest Core Prices") + guest_core_price_table.sortby = 'Key' + guest_core_price_table.align = 'l' + + guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") + guest_core_table.sortby = 'Value' guest_core_table.align = 'l' + for guest_core in guest_core_list: for price in guest_core['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - guest_core_table.add_row( + guest_core_price_table.add_row( [guest_core['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(guest_core_table) + _get_price_data(price, 'recurringFee')]) + guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) + if prices: + return guest_core_price_table + return guest_core_table -def guest_disk_prices_table(guest_disk_list, tables): +def guest_disk_prices_table(guest_disk_list, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] guest_disk_list: List of Virtual Server guest_disk. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - guest_disk_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], - title="Guest Disk Prices") - guest_disk_table.sortby = 'Key' + guest_disk_price_table = formatting.Table(['Key', 'Hourly', 'Monthly'], title="Guest Disk Prices") + guest_disk_price_table.sortby = 'Key' + guest_disk_price_table.align = 'l' + + guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") + guest_disk_table.sortby = 'Value' guest_disk_table.align = 'l' + for guest_disk in guest_disk_list: for price in guest_disk['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - guest_disk_table.add_row( + guest_disk_price_table.add_row( [guest_disk['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(guest_disk_table) + _get_price_data(price, 'recurringFee')]) + guest_disk_table.add_row( + [guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) + if prices: + return guest_disk_price_table + return guest_disk_table def _get_price_data(price, item): From 2d1e2bc6e2525771bed11b0a9b82a57e32340a19 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 1 Oct 2020 19:05:21 -0400 Subject: [PATCH 0275/1385] Fix tox analysis. --- SoftLayer/CLI/virt/create_options.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 462090e13..a3ee24314 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -186,8 +186,7 @@ def database_prices_table(database_list, prices=False): :param [] database_list: List of Virtual Server database. :param prices: Include pricing information or not. """ - database_price_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], - title="Data Base Prices") + database_price_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], title="Data Base Prices") database_price_table.sortby = 'Key' database_price_table.align = 'l' From d1c02450c56c1c24dbd13da8e6e76aa744a287ee Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 2 Oct 2020 14:56:55 -0400 Subject: [PATCH 0276/1385] Fix create subnet static for ipv4 price. --- SoftLayer/managers/network.py | 5 ++++- tests/managers/network_tests.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 1e9296cac..9b713479c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -171,7 +171,10 @@ def add_subnet(self, subnet_type, quantity=None, endpoint_id=None, version=4, item.get('capacity') == quantity_str, version == 4 or (version == 6 and desc in item['description'])]): - price_id = item['prices'][0]['id'] + if version == 4 and subnet_type == 'static': + price_id = item['prices'][1]['id'] + else: + price_id = item['prices'][0]['id'] break order = { diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 57106ecee..361ea1a61 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -100,6 +100,14 @@ def test_add_subnet_for_ipv4(self): self.assertEqual(fixtures.SoftLayer_Product_Order.verifyOrder, result) + result = self.network.add_subnet('static', + quantity=8, + endpoint_id=1234, + version=4, + test_order=True) + + self.assertEqual(fixtures.SoftLayer_Product_Order.verifyOrder, result) + def test_add_subnet_for_ipv6(self): # Test a public IPv6 order result = self.network.add_subnet('public', From d7254a0d5dc526b0b69c64359e86fc5a2617c78c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Oct 2020 16:51:32 -0500 Subject: [PATCH 0277/1385] #1345 refactored create-options a bit to have less code duplication --- SoftLayer/CLI/hardware/create_options.py | 193 ++++++++--------------- tests/CLI/modules/server_tests.py | 5 +- 2 files changed, 68 insertions(+), 130 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index b1bcf9a21..4c7723fb9 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -30,148 +30,110 @@ def cli(env, prices, location=None): dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - if prices: - _preset_prices_table(options['sizes'], tables) - _os_prices_table(options['operating_systems'], tables) - _port_speed_prices_table(options['port_speeds'], tables) - _extras_prices_table(options['extras'], tables) - else: - # Presets - preset_table = formatting.Table(['Size', 'Value'], title="Sizes") - preset_table.sortby = 'Value' - preset_table.align = 'l' - for size in options['sizes']: - preset_table.add_row([size['name'], size['key']]) - tables.append(preset_table) - - # Operating systems - os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") - os_table.sortby = 'Key' - os_table.align = 'l' - for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) - tables.append(os_table) - - # Port speed - port_speed_table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") - port_speed_table.sortby = 'Speed' - port_speed_table.align = 'l' - for speed in options['port_speeds']: - port_speed_table.add_row([speed['name'], speed['speed'], speed['key']]) - tables.append(port_speed_table) - - # Extras - extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") - extras_table.sortby = 'Value' - extras_table.align = 'l' - for extra in options['extras']: - extras_table.add_row([extra['name'], extra['key']]) - tables.append(extras_table) + tables.append(_preset_prices_table(options['sizes'], prices)) + tables.append(_os_prices_table(options['operating_systems'], prices)) + tables.append(_port_speed_prices_table(options['port_speeds'], prices)) + tables.append(_extras_prices_table(options['extras'], prices)) + # since this is multiple tables, this is required for a valid JSON object to be rendered. env.fout(formatting.listing(tables, separator='\n')) -def _preset_prices_table(sizes, tables): +def _preset_prices_table(sizes, prices=False): """Shows Server Preset options prices. :param [] sizes: List of Hardware Server sizes. - :param tables: Table formatting. + :param prices: Create a price table or not """ - preset_prices_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") - preset_prices_table.sortby = 'Value' - preset_prices_table.align = 'l' - for size in sizes: - if (_verify_prices("%.4f" % size['hourlyRecurringFee'])) or (_verify_prices("%.4f" % size['recurringFee'])): - preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) - tables.append(preset_prices_table) + if prices: + table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes") + for size in sizes: + if size.get('hourlyRecurringFee', 0) + size.get('recurringFee', 0) + 1 > 0: + table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) + else: + table = formatting.Table(['Size', 'Value'], title="Sizes") + for size in sizes: + table.add_row([size['name'], size['key']]) + table.sortby = 'Value' + table.align = 'l' + return table -def _os_prices_table(operating_systems, tables): +def _os_prices_table(operating_systems, prices=False): """Shows Server Operating Systems prices cost and capacity restriction. :param [] operating_systems: List of Hardware Server operating systems. - :param tables: Table formatting. + :param prices: Create a price table or not """ - os_prices_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], - title="Operating Systems Prices") - os_prices_table.sortby = 'OS Key' - os_prices_table.align = 'l' - for operating_system in operating_systems: - for price in operating_system['prices']: - if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( - _verify_prices(_get_price_data(price, 'recurringFee'))): + if prices: + table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Operating Systems") + for operating_system in operating_systems: + for price in operating_system['prices']: cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') - os_prices_table.add_row( + table.add_row( [operating_system['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(os_prices_table) + else: + table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + for operating_system in operating_systems: + table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + + table.sortby = 'Key' + table.align = 'l' + return table -def _port_speed_prices_table(port_speeds, tables): +def _port_speed_prices_table(port_speeds, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] port_speeds: List of Hardware Server Port Speeds. - :param tables: Table formatting. + :param prices: Create a price table or not """ - port_speed_prices_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', 'Restriction'], - title="Network Options Prices") - port_speed_prices_table.sortby = 'Speed' - port_speed_prices_table.align = 'l' - for speed in port_speeds: - for price in speed['prices']: - if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( - _verify_prices(_get_price_data(price, 'recurringFee'))): - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - port_speed_prices_table.add_row( + if prices: + table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly'], title="Network Options") + for speed in port_speeds: + for price in speed['prices']: + table.add_row( [speed['key'], speed['speed'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(port_speed_prices_table) - - -def _verify_prices(prices): - """Verify the prices is higher to zero(0) or is '-'. - - param prices: value to verify. - Returns: true false. - - """ - if prices == '-': - return True + _get_price_data(price, 'recurringFee')]) else: - return float(prices) > 0 + table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") + for speed in port_speeds: + table.add_row([speed['name'], speed['speed'], speed['key']]) + table.sortby = 'Speed' + table.align = 'l' + return table -def _extras_prices_table(extras, tables): +def _extras_prices_table(extras, prices=False): """Shows Server extras prices cost and capacity restriction. :param [] extras: List of Hardware Server Extras. - :param tables: Table formatting. + :param prices: Create a price table or not """ - extras_prices_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', 'Restriction'], - title="Extras Prices") - extras_prices_table.align = 'l' - for extra in extras: - for price in extra['prices']: - if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( - _verify_prices(_get_price_data(price, 'recurringFee'))): - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - extras_prices_table.add_row( + if prices: + table = formatting.Table(['Key', 'Hourly', 'Monthly'], title="Extras") + + for extra in extras: + for price in extra['prices']: + table.add_row( [extra['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(extras_prices_table) + _get_price_data(price, 'recurringFee')]) + else: + table = formatting.Table(['Extra Option', 'Key'], title="Extras") + for extra in extras: + table.add_row([extra['name'], extra['key']]) + table.sortby = 'Key' + table.align = 'l' + return table def _get_price_data(price, item): @@ -184,26 +146,3 @@ def _get_price_data(price, item): if item in price: result = price[item] return result - - -def _location_item_prices(location_prices, tables): - """Get a specific data from HS price. - - :param price: Hardware Server price. - :param string item: Hardware Server price data. - """ - location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction']) - location_prices_table.sortby = 'keyName' - location_prices_table.align = 'l' - for price in location_prices: - if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( - _verify_prices(_get_price_data(price, 'recurringFee'))): - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - location_prices_table.add_row( - [price['item']['keyName'], price['id'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(location_prices_table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 75b70c7c2..9b88c81f2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -368,16 +368,15 @@ def test_create_options_prices(self): self.assert_no_fail(result) output = json.loads(result.output) self.assertEqual(output[2][0]['Monthly'], str(0.1)) - self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') + self.assertEqual(output[2][0]['Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') def test_create_options_location(self): result = self.run_command(['server', 'create-options', '--prices', 'dal13']) self.assert_no_fail(result) output = json.loads(result.output) - print(output) self.assertEqual(output[2][0]['Monthly'], str(0.1)) - self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') + self.assertEqual(output[2][0]['Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): From 01064aaba136c968fbb64bdbe6e4630ad5db48ec Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 7 Oct 2020 11:05:12 -0400 Subject: [PATCH 0278/1385] Refactor create subnet static for ipv4 price. --- SoftLayer/managers/network.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 9b713479c..10d462f10 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -164,17 +164,14 @@ def add_subnet(self, subnet_type, quantity=None, endpoint_id=None, version=4, # item description. price_id = None quantity_str = str(quantity) - package_items = package.getItems(id=0) + package_items = package.getItems(id=0, mask='mask[prices[packageReferences[package[keyName]]]]') for item in package_items: category_code = utils.lookup(item, 'itemCategory', 'categoryCode') if all([category_code == category, item.get('capacity') == quantity_str, version == 4 or (version == 6 and desc in item['description'])]): - if version == 4 and subnet_type == 'static': - price_id = item['prices'][1]['id'] - else: - price_id = item['prices'][0]['id'] + price_id = self.get_subnet_item_price(item, subnet_type, version) break order = { @@ -195,6 +192,24 @@ def add_subnet(self, subnet_type, quantity=None, endpoint_id=None, version=4, else: return self.client['Product_Order'].placeOrder(order) + @staticmethod + def get_subnet_item_price(item, subnet_type, version): + """Get the subnet specific item price id. + + :param version: 4 for IPv4, 6 for IPv6. + :param subnet_type: Type of subnet to add: private, public, global,static. + :param item: Subnet item. + """ + price_id = None + if version == 4 and subnet_type == 'static': + for item_price in item['prices']: + for package_reference in item_price['packageReferences']: + if subnet_type.upper() in package_reference['package']['keyName']: + price_id = item_price['id'] + else: + price_id = item['prices'][0]['id'] + return price_id + def assign_global_ip(self, global_ip_id, target): """Assigns a global IP address to a specified target. From e75b0271b87c2d072af95b3dd1e23e5f58e64b28 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 7 Oct 2020 12:44:37 -0400 Subject: [PATCH 0279/1385] Capitalizing State and Items rows names for order detail table in lookup.py #1340 --- SoftLayer/CLI/order/lookup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/order/lookup.py b/SoftLayer/CLI/order/lookup.py index 423872ab7..6f25953d0 100644 --- a/SoftLayer/CLI/order/lookup.py +++ b/SoftLayer/CLI/order/lookup.py @@ -50,7 +50,7 @@ def get_order_table(order): table.add_row(['Create Date', utils.clean_time(order.get('createDate'), date_format, date_format)]) table.add_row(['Modify Date', utils.clean_time(order.get('modifyDate'), date_format, date_format)]) table.add_row(['Order Approval Date', utils.clean_time(order.get('orderApprovalDate'), date_format, date_format)]) - table.add_row(['status', order.get('status')]) + table.add_row(['Status', order.get('status')]) table.add_row(['Order Total Amount', "{price:.2f}".format(price=float(order.get('orderTotalAmount', '0')))]) table.add_row(['Invoice Total Amount', "{price:.2f}". format(price=float(order.get('initialInvoice', {}).get('invoiceTotalAmount', '0')))]) @@ -62,6 +62,6 @@ def get_order_table(order): for item in items: item_table.add_row([item.get('description')]) - table.add_row(['items', item_table]) + table.add_row(['Items', item_table]) return table From afb20da13977bb09465994756386ef027f03c399 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 7 Oct 2020 15:29:32 -0400 Subject: [PATCH 0280/1385] mentioning slcli order lookup in account orders doc block. #1340 --- SoftLayer/CLI/account/orders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/account/orders.py b/SoftLayer/CLI/account/orders.py index 570f77816..f1fb5eeb6 100644 --- a/SoftLayer/CLI/account/orders.py +++ b/SoftLayer/CLI/account/orders.py @@ -1,4 +1,4 @@ -"""Order list account""" +"""Lists account orders.""" # :license: MIT, see LICENSE for more details. import click @@ -16,7 +16,7 @@ show_default=True) @environment.pass_env def cli(env, limit): - """Order list account.""" + """Lists account orders. Use `slcli order lookup ` to find more details about a specific order.""" manager = AccountManager(env.client) orders = manager.get_account_all_billing_orders(limit) From ee670202ceb1d2a55d5c0c114878cee7f32b0ee4 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Thu, 8 Oct 2020 15:34:45 -0500 Subject: [PATCH 0281/1385] Update snapcraft.yaml Rebase to Core18 Adopt-info for auto versioning --- snap/snapcraft.yaml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c05b79e30..769a03614 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,10 +1,14 @@ -name: slcli # check to see if it's available -version: '5.5.1+git' # check versioning -summary: Python based SoftLayer API Tool. # 79 char long summary +name: slcli +adopt-info: slcli +summary: Python based SoftLayer API Tool. description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. -grade: stable # must be 'stable' to release into candidate/stable channels -confinement: strict # use 'strict' once you have the right plugs + +license: MIT + +base: core18 +grade: stable +confinement: strict apps: slcli: @@ -17,11 +21,14 @@ apps: - network-bind parts: - my-part: + slcli: source: https://github.com/softlayer/softlayer-python source-type: git plugin: python python-version: python3 + override-pull: | + snapcraftctl pull + snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" build-packages: - python3 From 8cd8846944ef759bbd135a1e543fa925208190dd Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Mon, 12 Oct 2020 21:16:57 -0500 Subject: [PATCH 0282/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index bd4f57dc3..80feba5f3 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,5 @@ name: slcli adopt-info: slcli -version: 'git' # will be replaced by a `git describe` based version string description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 5ef877c3dcaf607302441e1054de6c46395a012c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Oct 2020 10:05:33 -0500 Subject: [PATCH 0283/1385] Update snapcraft.yaml adding summary to snapcraft --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 80feba5f3..d3bafdd57 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -23,6 +23,7 @@ parts: slcli: source: https://github.com/softlayer/softlayer-python source-type: git + summary: A CLI tool to interact with the SoftLayer API. plugin: python python-version: python3 override-pull: | From 8cd344d09af4dc5a6e41b419ffa4ec22122bdf14 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Oct 2020 10:27:54 -0500 Subject: [PATCH 0284/1385] moved snapcraft readme because apparently you can't build a snap with it in the snap directory for some reason now #1362 --- snap/README.md => README-snapcraft.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename snap/README.md => README-snapcraft.md (100%) diff --git a/snap/README.md b/README-snapcraft.md similarity index 100% rename from snap/README.md rename to README-snapcraft.md From 8b72304e21ad6287932ada2c718c4629a04a3d65 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Oct 2020 10:48:50 -0500 Subject: [PATCH 0285/1385] Update snapcraft.yaml #1326 getting snap to build again --- snap/snapcraft.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d3bafdd57..59f2156b7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,4 +1,6 @@ name: slcli +summary: A CLI tool to interact with the SoftLayer API. +version: 'git' adopt-info: slcli description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. @@ -23,13 +25,9 @@ parts: slcli: source: https://github.com/softlayer/softlayer-python source-type: git - summary: A CLI tool to interact with the SoftLayer API. plugin: python python-version: python3 - override-pull: | - snapcraftctl pull - snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" - + build-packages: - python3 From 03c450477b079ca833985a87d33fcd4e9450d0b9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Oct 2020 11:45:31 -0500 Subject: [PATCH 0286/1385] #1326 fixing up snapcraft file so it builds this time --- snap/snapcraft.yaml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d3bafdd57..593b92020 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,13 +1,12 @@ -name: slcli -adopt-info: slcli +name: slcli # check to see if it's available +version: 'git' # will be replaced by a `git describe` based version string +summary: Python based SoftLayer API Tool. description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. - -license: MIT - -base: core18 -grade: stable +grade: stable confinement: strict +base: core18 +license: MIT apps: slcli: @@ -23,15 +22,11 @@ parts: slcli: source: https://github.com/softlayer/softlayer-python source-type: git - summary: A CLI tool to interact with the SoftLayer API. plugin: python python-version: python3 - override-pull: | - snapcraftctl pull - snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" build-packages: - python3 stage-packages: - - python3 + - python3 \ No newline at end of file From e60f5a199218372b56ba10f9ed71078fd0ff7097 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Tue, 13 Oct 2020 15:22:19 -0500 Subject: [PATCH 0287/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 80feba5f3..98c96111d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,6 @@ name: slcli adopt-info: slcli +summary: A snap for slcli for SoftLayer products and services description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 1ae2c95dd0c69da6315bd9c069b6a4879d188735 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 14 Oct 2020 17:13:48 -0500 Subject: [PATCH 0288/1385] 1057 ended up just writing some improved documentation for how to deal with KeyErrors. Due nested results from the API can be, I felt writing a custom result data structure would end up causing more bugs than we would solve. --- docs/api/client.rst | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index c798ac71d..f1692eb6a 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -202,6 +202,42 @@ If you ever need to figure out what exact API call the client is making, you can print(client.transport.print_reproduceable(call)) +Dealing with KeyError Exceptions +-------------------------------- + +One of the pain points in dealing with the SoftLayer API can be handling issues where you expected a property to be returned, but none was. + +The hostname property of a `SoftLayer_Billing_Item `_ is a good example of this. + +For example. + +:: + + # Uses default username and apikey from ~/.softlayer + client = SoftLayer.create_client_from_env() + # iter_call returns a python generator, and only makes another API call when the loop runs out of items. + result = client.iter_call('Account', 'getAllBillingItems', iter=True, mask="mask[id,hostName]") + print("Id, hostname") + for item in result: + # will throw a KeyError: 'hostName' exception on certain billing items that do not have a hostName + print("{}, {}".format(item['id'], item['hostName'])) + +The Solution +^^^^^^^^^^^^ + +Using the python dictionary's `.get() `_ is great for non-nested items. + +:: + print("{}, {}".format(item.get('id'), item.get('hostName'))) + +Otherwise, this SDK provides a util function to do something similar. Each additional argument passed into `utils.lookup` will go one level deeper into the nested dictionary to find the item requested, returning `None` if a KeyError shows up. + +:: + itemId = SoftLayer.utils.lookup(item, 'id') + itemHostname = SoftLayer.utils.lookup(item, 'hostName') + print("{}, {}".format(itemId, itemHostname)) + + API Reference ------------- From 1c951215362d4d602a4572e975cf40e19763a8af Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 15 Oct 2020 16:13:57 -0400 Subject: [PATCH 0289/1385] update from origin #1346 --- SoftLayer/CLI/order/item_list.py | 54 +++++++++------------ SoftLayer/managers/ordering.py | 33 +++++++++++-- tests/CLI/modules/order_tests.py | 26 +++++++++-- tests/managers/ordering_tests.py | 80 ++++++++++++++++++-------------- 4 files changed, 119 insertions(+), 74 deletions(-) diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index d22be8b56..45323f4bd 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -13,15 +13,15 @@ @click.command() -@click.argument('location', required=False, nargs=-1, type=click.UNPROCESSED) @click.argument('package_keyname') -@click.option('--keyword', help="A word (or string) used to filter item names.") -@click.option('--category', help="Category code to filter items by") +@click.option('--keyword', '-k', help="A word (or string) used to filter item names.") +@click.option('--category', '-c', help="Category code to filter items by") @click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and to list the ' 'Item Prices by location, add it to the --prices option using ' 'location KeyName, e.g. --prices AMSTERDAM02') +@click.argument('location', required=False) @environment.pass_env -def cli(env, location, package_keyname, keyword, category, prices): +def cli(env, package_keyname, keyword, category, prices, location=None): """List package items used for ordering. The item keyNames listed can be used with `slcli order place` to specify @@ -57,14 +57,13 @@ def cli(env, location, package_keyname, keyword, category, prices): if prices: _item_list_prices(categories, sorted_items, tables) if location: - location = location[0] location_prices = manager.get_item_prices_by_location(location, package_keyname) _location_item_prices(location_prices, location, tables) else: table_items_detail = formatting.Table(COLUMNS) - for catname in sorted(categories): - for item in sorted_items[catname]: - table_items_detail.add_row([catname, item['keyName'], item['description'], get_price(item)]) + for category_name in sorted(categories): + for item in sorted_items[category_name]: + table_items_detail.add_row([category_name, item['keyName'], item['description'], get_price(item)]) tables.append(table_items_detail) env.fout(formatting.listing(tables, separator='\n')) @@ -94,13 +93,13 @@ def get_price(item): def _item_list_prices(categories, sorted_items, tables): """Add the item prices cost and capacity restriction to the table""" table_prices = formatting.Table(COLUMNS_ITEM_PRICES) - for catname in sorted(categories): - for item in sorted_items[catname]: + for category in sorted(categories): + for item in sorted_items[category]: for price in item['prices']: if not price.get('locationGroupId'): - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') + cr_max = get_item_price_data(price, 'capacityRestrictionMaximum') + cr_min = get_item_price_data(price, 'capacityRestrictionMinimum') + cr_type = get_item_price_data(price, 'capacityRestrictionType') table_prices.add_row([item['keyName'], price['id'], get_item_price_data(price, 'hourlyRecurringFee'), get_item_price_data(price, 'recurringFee'), @@ -117,33 +116,22 @@ def get_item_price_data(price, item_attribute): def _location_item_prices(location_prices, location, tables): - """Get a specific data from HS price. + """Add a location prices table to tables. - :param price: Hardware Server price. - :param string item: Hardware Server price data. + :param list location_prices : Location prices. + :param string location : Location. + :param list tables: Table list to add location prices table. """ location_prices_table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="Item Prices for %s" % location) location_prices_table.sortby = 'keyName' location_prices_table.align = 'l' for price in location_prices: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') + cr_max = get_item_price_data(price, 'capacityRestrictionMaximum') + cr_min = get_item_price_data(price, 'capacityRestrictionMinimum') + cr_type = get_item_price_data(price, 'capacityRestrictionType') location_prices_table.add_row( [price['item']['keyName'], price['id'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), + get_item_price_data(price, 'hourlyRecurringFee'), + get_item_price_data(price, 'recurringFee'), "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(location_prices_table) - - -def _get_price_data(price, item): - """Get a specific data from HS price. - - :param price: Hardware Server price. - :param string item: Hardware Server price data. - """ - result = '-' - if item in price: - result = price[item] - return result diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 7a34c7720..e1cbb6f31 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -669,14 +669,39 @@ def get_location_id(self, location): return datacenter[0]['id'] def get_item_prices_by_location(self, location, package_keyname): - """Returns the hardware server item prices by location. + """Returns the item prices by location. :param string package_keyname: The package for which to get the items. - :param string location: location to get the item prices. + :param string location: location name or keyname to get the item prices. """ - object_mask = "filteredMask[pricingLocationGroup[locations[regions]]]" + object_mask = "filteredMask[pricingLocationGroup[locations]]" + location_name = self.resolve_location_name(location) object_filter = { - "itemPrices": {"pricingLocationGroup": {"locations": {"regions": {"keyname": {"operation": location}}}}}} + "itemPrices": {"pricingLocationGroup": {"locations": {"name": {"operation": location_name}}}}} package = self.get_package_by_key(package_keyname) + return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, id=package['id']) + + def resolve_location_name(self, location_key): + """Resolves a location name using a string location key. + + :param string location_key: A string location used to resolve the location name. + :return: An location name. + """ + + default_region_keyname = 'unknown' + if not location_key or location_key == default_region_keyname: + raise exceptions.SoftLayerError("Invalid location {}".format(location_key)) + + default_regions = [{'keyname': default_region_keyname}] + index_first = 0 + object_mask = "mask[regions]" + locations = self.client.call('SoftLayer_Location', 'getDatacenters', mask=object_mask) + for location in locations: + location_name = location.get('name') + if location_name == location_key: + return location_key + if location.get('regions', default_regions)[index_first].get('keyname') == location_key: + return location_name + raise exceptions.SoftLayerError("Location {} does not exist".format(location_key)) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 279f49f5a..f09b5aaea 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -46,7 +46,7 @@ def test_item_list(self): self.assertIn('item2', result.output) def test_item_list_prices(self): - result = self.run_command(['order', 'item-list', '--prices', 'package']) + result = self.run_command(['order', 'item-list', 'package', '--prices']) self.assert_no_fail(result) output = json.loads(result.output) @@ -55,8 +55,28 @@ def test_item_list_prices(self): self.assertEqual(output[0][1]['keyName'], 'KeyName015') self.assert_called_with('SoftLayer_Product_Package', 'getItems') - def test_item_list_location(self): - result = self.run_command(['order', 'item-list', '--prices', 'AMSTERDAM02', 'package']) + def test_item_list_location_keyname(self): + result = self.run_command(['order', 'item-list', 'package', '--prices', 'DALLAS13', ]) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[0][0]['Hourly'], 0.0) + self.assertEqual(output[0][1]['keyName'], 'KeyName015') + self.assertEqual(output[0][1]['priceId'], 1144) + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + + def test_item_list_location_name(self): + result = self.run_command(['order', 'item-list', 'package', '--prices', 'dal13', ]) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[0][0]['Hourly'], 0.0) + self.assertEqual(output[0][1]['keyName'], 'KeyName015') + self.assertEqual(output[0][1]['priceId'], 1144) + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + + def test_item_list_category_keyword(self): + result = self.run_command(['order', 'item-list', 'package', '--prices', 'dal13', '-c', 'os', '-k' 'test']) self.assert_no_fail(result) output = json.loads(result.output) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 182438849..ac4ec70fc 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -776,40 +776,6 @@ def test_get_item_capacity_intel(self): self.assertEqual(24, int(item_capacity)) - def test_get_item_prices_by_location(self): - options = self.ordering.get_item_prices_by_location("MONTREAL", "MONTREAL") - item_prices = [ - { - "hourlyRecurringFee": ".093", - "id": 204015, - "recurringFee": "62", - "item": { - "description": "4 x 2.0 GHz or higher Cores", - "id": 859, - "keyName": "GUEST_CORES_4", - }, - "pricingLocationGroup": { - "id": 503, - "locations": [ - { - "id": 449610, - "longName": "Montreal 1", - "name": "mon01", - "regions": [ - { - "description": "MON01 - Montreal", - "keyname": "MONTREAL", - } - ] - } - ] - } - } - ] - - self.assertEqual(options[0]['item']['keyName'], item_prices[0]['item']['keyName']) - self.assertEqual(options[0]['hourlyRecurringFee'], item_prices[0]['hourlyRecurringFee']) - def test_get_oder_detail_mask(self): order_id = 12345 test_mask = 'mask[id]' @@ -830,3 +796,49 @@ def test_get_oder_detail_default_mask(self): 'items[description],userRecord[displayName,userStatus]]') self.ordering.get_order_detail(order_id) self.assert_called_with('SoftLayer_Billing_Order', 'getObject', identifier=order_id, mask=_default_mask) + + def test_get_item_prices_by_location_name(self): + object_mask = "filteredMask[pricingLocationGroup[locations]]" + object_filter = { + "itemPrices": {"pricingLocationGroup": {"locations": {"name": {"operation": 'dal13'}}}}} + self.ordering.get_item_prices_by_location('dal13', 'TEST') + + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter) + + def test_get_item_prices_by_location_keyname(self): + object_mask = "filteredMask[pricingLocationGroup[locations]]" + object_filter = { + "itemPrices": {"pricingLocationGroup": {"locations": {"name": {"operation": 'dal13'}}}}} + self.ordering.get_item_prices_by_location('DALLAS13', 'TEST') + + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter) + + def test_resolve_location_name(self): + location_name_expected = 'dal13' + object_mask = "mask[regions]" + location_name = self.ordering.resolve_location_name('DALLAS13') + self.assertEqual(location_name, location_name_expected) + self.assert_called_with('SoftLayer_Location', 'getDatacenters', mask=object_mask) + + def test_resolve_location_name_by_keyname(self): + location_name_expected = 'dal13' + object_mask = "mask[regions]" + location_name = self.ordering.resolve_location_name('DALLAS13') + self.assertEqual(location_name, location_name_expected) + self.assert_called_with('SoftLayer_Location', 'getDatacenters', mask=object_mask) + + def test_resolve_location_name_by_name(self): + location_name_expected = 'dal13' + object_mask = "mask[regions]" + location_name = self.ordering.resolve_location_name('dal13') + self.assertEqual(location_name, location_name_expected) + self.assert_called_with('SoftLayer_Location', 'getDatacenters', mask=object_mask) + + def test_resolve_location_name_invalid(self): + exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, None) + self.assertIn("Invalid location", str(exc)) + + def test_resolve_location_name_not_exist(self): + exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") + self.assertIn("does not exist", str(exc)) + From 6464fb889a2484d20654ef0b877ebcf8c3baa4bf Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 15 Oct 2020 16:20:53 -0400 Subject: [PATCH 0290/1385] fix tox removing blank line #1346 --- tests/managers/ordering_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index ac4ec70fc..f42532c7b 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -841,4 +841,3 @@ def test_resolve_location_name_invalid(self): def test_resolve_location_name_not_exist(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") self.assertIn("does not exist", str(exc)) - From 0423459f9cab1145e909e22acdd32c30efee66cf Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 22 Oct 2020 14:15:21 -0500 Subject: [PATCH 0291/1385] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 299f1b8ac..89cabe59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 + dns zone-list: added resourceRecordCount, added automatic pagination for large zones + dns record-list: fixed an issue where a record (like SRV types) that don't have a host would cause the command to fail - Renamed managers.storage.refresh_dep_dupe to SoftLayer.managers.storage.refresh_dupe #1342 to support the new API method. CLI commands now use this method. +- #1295 added disk upgrade options for virtual guests ## [5.9.0] - 2020-08-03 https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 From 9fa2e40e80167962bcaefed2ea17054be6a9bd88 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 26 Oct 2020 16:30:05 -0400 Subject: [PATCH 0292/1385] #1367 removing NESSUS_VULNERABILITY_ASSESSMENT_REPORTING from examples and docs --- SoftLayer/CLI/order/place.py | 1 - SoftLayer/CLI/order/place_quote.py | 1 - docs/cli/ordering.rst | 1 - 3 files changed, 3 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index f0ceed350..531bacee5 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -61,7 +61,6 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, OS_DEBIAN_9_X_STRETCH_LAMP_64_BIT 1_IP_ADDRESS 1_IPV6_ADDRESS 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS REBOOT_REMOTE_CONSOLE AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT - NESSUS_VULNERABILITY_ASSESSMENT_REPORTING """ manager = ordering.OrderingManager(env.client) diff --git a/SoftLayer/CLI/order/place_quote.py b/SoftLayer/CLI/order/place_quote.py index 28865ff70..351e9427f 100644 --- a/SoftLayer/CLI/order/place_quote.py +++ b/SoftLayer/CLI/order/place_quote.py @@ -62,7 +62,6 @@ def cli(env, package_keyname, location, preset, name, send_email, complex_type, NOTIFICATION_EMAIL_AND_TICKET \\ AUTOMATED_NOTIFICATION \\ UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \\ - NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \\ --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \\ --complex-type SoftLayer_Container_Product_Order_Virtual_Guest diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index 1351cfd9c..7405bdb0e 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -111,7 +111,6 @@ order place NOTIFICATION_EMAIL_AND_TICKET \ AUTOMATED_NOTIFICATION \ UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \ - NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \ --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \ --complex-type SoftLayer_Container_Product_Order_Virtual_Guest From 04817cd5baa73c360f72c551ba731b16856cde30 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 26 Oct 2020 16:40:56 -0400 Subject: [PATCH 0293/1385] #1367 removing NESSUS_VULNERABILITY_ASSESSMENT_REPORTING from fixtures and docs vs --- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 8 -------- docs/cli/vs.rst | 1 - 2 files changed, 9 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 987fd12d7..ca72350c1 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -606,14 +606,6 @@ "description": "Unlimited SSL VPN Users & 1 PPTP VPN User per account" } }, - { - "hourlyRecurringFee": "0", - "id": 418, - "recurringFee": "0", - "item": { - "description": "Nessus Vulnerability Assessment & Reporting" - } - } ], "quantity": 1, "sourceVirtualGuestId": None, diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 09539a72b..811e38c98 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -104,7 +104,6 @@ datacenter using the command `slcli vs create`. : 0.0 : Email and Ticket : : 0.0 : Automated Reboot from Monitoring : : 0.0 : Unlimited SSL VPN Users & 1 PPTP VPN User per account : - : 0.0 : Nessus Vulnerability Assessment & Reporting : : 0.0 : 2 GB : : 0.0 : 1 x 2.0 GHz or higher Core : : 0.000 : Total hourly cost : From de317d4b8f2b8f55af16014c9f387627a4c047ea Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 12 Nov 2020 17:44:52 -0400 Subject: [PATCH 0294/1385] Remove the `-a` option from `slcli user create` --- SoftLayer/CLI/user/create.py | 16 ++++++++-------- tests/CLI/modules/user_tests.py | 3 +-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/SoftLayer/CLI/user/create.py b/SoftLayer/CLI/user/create.py index ccfe55955..988496a17 100644 --- a/SoftLayer/CLI/user/create.py +++ b/SoftLayer/CLI/user/create.py @@ -29,9 +29,9 @@ "supersedes this template.") @click.option('--template', '-t', default=None, help="A json string describing https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/") -@click.option('--api-key', '-a', default=False, is_flag=True, help="Create an API key for this user.") +# @click.option('--api-key', '-a', default=False, is_flag=True, help="Create an API key for this user.") @environment.pass_env -def cli(env, username, email, password, from_user, template, api_key): +def cli(env, username, email, password, from_user, template): """Creates a user Users. Remember to set the permissions and access for this new user. @@ -81,13 +81,13 @@ def cli(env, username, email, password, from_user, template, api_key): raise exceptions.CLIAbort("Canceling creation!") result = mgr.create_user(user_template, password) - new_api_key = None - if api_key: - click.secho("Adding API key...", fg='green') - new_api_key = mgr.add_api_authentication_key(result['id']) + # new_api_key = None + # if api_key11: + # click.secho("Adding API key...", fg='green') + # new_api_key = mgr.add_api_authentication_key(result['id']) - table = formatting.Table(['Username', 'Email', 'Password', 'API Key']) - table.add_row([result['username'], result['email'], password, new_api_key]) + table = formatting.Table(['Username', 'Email', 'Password']) + table.add_row([result['username'], result['email'], password]) env.fout(table) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index f16ef1843..b6723a2b2 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -194,9 +194,8 @@ def test_create_user_generate_password_2(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_and_apikey(self, confirm_mock): confirm_mock.return_value = True - result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-a']) + result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_User_Customer', 'addApiAuthenticationKey') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_with_template(self, confirm_mock): From e1d1857a2aace67a26c208f8d2c103625d748027 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 13 Nov 2020 08:44:09 -0400 Subject: [PATCH 0295/1385] Remove the comments lines --- SoftLayer/CLI/user/create.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/SoftLayer/CLI/user/create.py b/SoftLayer/CLI/user/create.py index 988496a17..74e30b6e6 100644 --- a/SoftLayer/CLI/user/create.py +++ b/SoftLayer/CLI/user/create.py @@ -29,7 +29,6 @@ "supersedes this template.") @click.option('--template', '-t', default=None, help="A json string describing https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/") -# @click.option('--api-key', '-a', default=False, is_flag=True, help="Create an API key for this user.") @environment.pass_env def cli(env, username, email, password, from_user, template): """Creates a user Users. @@ -81,10 +80,6 @@ def cli(env, username, email, password, from_user, template): raise exceptions.CLIAbort("Canceling creation!") result = mgr.create_user(user_template, password) - # new_api_key = None - # if api_key11: - # click.secho("Adding API key...", fg='green') - # new_api_key = mgr.add_api_authentication_key(result['id']) table = formatting.Table(['Username', 'Email', 'Password']) table.add_row([result['username'], result['email'], password]) From fd082957a8d1e61c9b068dd68b5830b292b938ea Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 13 Nov 2020 11:12:13 -0400 Subject: [PATCH 0296/1385] Fix subnet list. --- SoftLayer/managers/network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 10d462f10..11ed9733e 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -22,6 +22,7 @@ DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', + 'networkVlanId', 'ipAddressCount', 'virtualGuests', 'id', From cda1d46073eac16a2f110f8bc5c5261aff77c716 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 16 Nov 2020 15:15:24 -0600 Subject: [PATCH 0297/1385] #1378 fixed analysis/flake8 tests --- tests/CLI/modules/config_tests.py | 10 +++++----- tests/CLI/modules/vs/vs_create_tests.py | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index ec018a53c..33c82520a 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -67,13 +67,13 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) self.assert_no_fail(result) - self.assertTrue('Configuration Updated Successfully' in result.output) + self.assertIn('Configuration Updated Successfully', result.output) contents = config_file.read().decode("utf-8") - self.assertTrue('[softlayer]' in contents) - self.assertTrue('username = user' in contents) - self.assertTrue('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' in contents) - self.assertTrue('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT in contents) + self.assertIn('[softlayer]', contents) + self.assertIn('username = user', contents) + self.assertIn('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', contents) + self.assertIn('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT, contents) @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 413bb6c18..53c3bdc97 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -666,8 +666,7 @@ def test_create_vs_export(self): '--domain', 'TESTING', '--flavor', 'B1_2X8X25', '--datacenter', 'TEST00', '--os', 'UBUNTU_LATEST']) self.assert_no_fail(result) - self.assertTrue('Successfully exported options to a template file.' - in result.output) + self.assertIn('Successfully exported options to a template file.', result.output) contents = config_file.read().decode("utf-8") self.assertIn('hostname=TEST', contents) self.assertIn('flavor=B1_2X8X25', contents) From 826c5949089c941332642c99b54bb2daa5b197e4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 18 Nov 2020 09:45:13 -0400 Subject: [PATCH 0298/1385] Add pagination to block and file storage. --- SoftLayer/managers/block.py | 2 +- SoftLayer/managers/file.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 10327211c..871c9cb27 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -73,7 +73,7 @@ def list_block_volumes(self, datacenter=None, username=None, storage_type=None, 'order']['id'] = (utils.query_filter(order)) kwargs['filter'] = _filter.to_dict() - return self.client.call('Account', 'getIscsiNetworkStorage', **kwargs) + return self.client.call('Account', 'getIscsiNetworkStorage', iter=True, **kwargs) def get_block_volume_details(self, volume_id, **kwargs): """Returns details about the specified volume. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index d4d34f002..734e54081 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -70,7 +70,7 @@ def list_file_volumes(self, datacenter=None, username=None, storage_type=None, o 'order']['id'] = (utils.query_filter(order)) kwargs['filter'] = _filter.to_dict() - return self.client.call('Account', 'getNasNetworkStorage', **kwargs) + return self.client.call('Account', 'getNasNetworkStorage', iter=True, **kwargs) def get_file_volume_details(self, volume_id, **kwargs): """Returns details about the specified volume. From 9cba8fbe82544ab74c6e9ef258be4155d52f8a23 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 3 Dec 2020 13:24:57 -0600 Subject: [PATCH 0299/1385] Version to 5.9.2 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89cabe59d..efa26f9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Change Log +## [5.9.2] - 2020-12-03 +https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 + +## New Commands +- `slcli account orders` #1349 +- `slcli order lookup` #1354 + +## Improvements +- Ordering price information improvements. #1319 +- refactor vsi create-option #1337 +- Add Invoice Item id as parameter in `slcli account item-detail` command +- Added order lookup command to block and file orders. #1350 +- Add prices to vs create-options. #1351 +- Allow orders without a location if needed #1356 +- Refactor file and block commands to use the username resolver #1357 +- Fix create subnet static for ipv4 price. #1358 +- moved snapcraft readme #1363 +- Update snapcraft.yaml #1365 +- Updated documentation on how to deal with KeyError #1366 +- Fix order item-list --prices location #1360 +- Removed Nessus scanner from docs and examples #1368 +- Fix subnet list. #1379 +- Fixed analysis/flake8 tests #1381 +- Remove the `-a` option from `slcli user create`. Only the user themselves can create an API key now. #1377 + ## [5.9.1] - 2020-09-15 https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 23ee96975..a09e85706 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.1' +VERSION = 'v5.9.2' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 60147ff00..2cb688c44 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.9.1', + version='5.9.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 2c69e9217b90e0a35c70270b86c3b070b08329d0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 3 Dec 2020 13:25:53 -0600 Subject: [PATCH 0300/1385] Update CHANGELOG.md Fixed some styling on the changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efa26f9c0..abff35816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 - `slcli account orders` #1349 - `slcli order lookup` #1354 -## Improvements +#### Improvements - Ordering price information improvements. #1319 - refactor vsi create-option #1337 - Add Invoice Item id as parameter in `slcli account item-detail` command @@ -25,7 +25,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 - Fixed analysis/flake8 tests #1381 - Remove the `-a` option from `slcli user create`. Only the user themselves can create an API key now. #1377 -## [5.9.1] - 2020-09-15 +#### [5.9.1] - 2020-09-15 https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 - Fix the ha option for firewalls, add and implement unit test #1327 From 367a82e8758d52f0f09909010754865050860eea Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 3 Dec 2020 13:31:14 -0600 Subject: [PATCH 0301/1385] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abff35816..d86588d91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## [5.9.2] - 2020-12-03 https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 -## New Commands +#### New Commands - `slcli account orders` #1349 - `slcli order lookup` #1354 @@ -25,7 +25,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 - Fixed analysis/flake8 tests #1381 - Remove the `-a` option from `slcli user create`. Only the user themselves can create an API key now. #1377 -#### [5.9.1] - 2020-09-15 +## [5.9.1] - 2020-09-15 https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 - Fix the ha option for firewalls, add and implement unit test #1327 From 1ba743f938ebb159303b22fc47cc065cd46faf0a Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 18 Dec 2020 15:11:04 -0600 Subject: [PATCH 0302/1385] Added a unit test for large ints --- ..._Network_Storage_Hub_Cleversafe_Account.py | 9 +++++ tests/transport_tests.py | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py index 4bc3f4fc7..4d066cf7e 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py @@ -37,3 +37,12 @@ } } ] + +getBuckets = [ + { + "bytesUsed": 40540117, + "name": "normal-bucket", + "objectCount": 4, + "storageLocation": "us-standard" + } +] diff --git a/tests/transport_tests.py b/tests/transport_tests.py index a2e500bd8..27f892098 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -326,6 +326,40 @@ def test_ibm_id_call(self, auth, request): self.assertIsInstance(resp, transports.SoftLayerListResult) self.assertEqual(resp.total_count, 10) + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_call_large_number_response(self, request): + response = requests.Response() + body = b''' + + + + + + + + + bytesUsed + 2666148982056 + + + + + + + + + ''' + response.raw = io.BytesIO(body) + response.headers['SoftLayer-Total-Items'] = 1 + response.status_code = 200 + request.return_value = response + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp[0]['bytesUsed'], 2666148982056) + @mock.patch('SoftLayer.transports.requests.Session.request') @pytest.mark.parametrize( From 0f48d23996b7f7239470f730132c353f686afa3e Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 6 Jan 2021 15:10:56 +0530 Subject: [PATCH 0303/1385] Adding disaster recovery failover api --- .../replication/disaster_recovery_failover.py | 34 ++++++++++++++++ .../replication/disaster_recovery_failover.py | 34 ++++++++++++++++ SoftLayer/CLI/routes.py | 2 + .../fixtures/SoftLayer_Network_Storage.py | 1 + SoftLayer/managers/storage.py | 11 ++++- docs/cli/block.rst | 4 ++ docs/cli/file.rst | 4 ++ tests/CLI/modules/block_tests.py | 39 +++++++++++++++++- tests/CLI/modules/file_tests.py | 40 ++++++++++++++++++- tests/managers/block_tests.py | 12 ++++++ tests/managers/file_tests.py | 12 ++++++ 11 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 SoftLayer/CLI/block/replication/disaster_recovery_failover.py create mode 100644 SoftLayer/CLI/file/replication/disaster_recovery_failover.py diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py new file mode 100644 index 000000000..cc29fc0ac --- /dev/null +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -0,0 +1,34 @@ +"""Failover an inaccessible file volume to its available replicant volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume-id') +@click.option('--replicant-id', help="ID of the replicant volume") +@environment.pass_env +def cli(env, volume_id, replicant_id): + """Failover an inaccessible file volume to its available replicant volume.""" + block_storage_manager = SoftLayer.BlockStorageManager(env.client) + + click.secho("""WARNING:Disaster Recovery Failover a block volume to the given replicant volume.\n""" + """* This action cannot be undone\n""" + """* You will not be able to perform failback to the original\n""" + """* You cannot failover without replica""",fg = 'red' ) + + if not (formatting.confirm('Are you sure you want to continue?')): + raise exceptions.CLIAbort('Aborted.') + + success = block_storage_manager.disaster_recovery_failover_to_replicant( + volume_id, + replicant_id + ) + if success: + click.echo("Disaster Recovery Failover to replicant is now in progress.") + else: + click.echo("Disaster Recovery Failover operation could not be initiated.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py new file mode 100644 index 000000000..7fa02322a --- /dev/null +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -0,0 +1,34 @@ +"""Failover an inaccessible file volume to its available replicant volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume-id') +@click.option('--replicant-id', help="ID of the replicant volume") +@environment.pass_env +def cli(env, volume_id, replicant_id): + """Failover an inaccessible file volume to its available replicant volume.""" + file_storage_manager = SoftLayer.FileStorageManager(env.client) + + click.secho("""WARNING : Disaster Recovery Failover should not be performed unless data center for the primary volume is unreachable.\n""" + """* This action cannot be undone\n""" + """* You will not be able to perform failback to the original without support intervention\n""" + """* You cannot failover without replica""",fg = 'red' ) + + if not (formatting.confirm('Are you sure you want to continue?')): + raise exceptions.CLIAbort('Aborted.') + + success = file_storage_manager.disaster_recovery_failover_to_replicant( + volume_id, + replicant_id + ) + if success: + click.echo("Disaster Recovery Failover to replicant is now in progress.") + else: + click.echo("Disaster Recovery Failover operation could not be initiated.") \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 40c4bd6d1..c223bfae2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -92,6 +92,7 @@ ('block:subnets-remove', 'SoftLayer.CLI.block.subnets.remove:cli'), ('block:replica-failback', 'SoftLayer.CLI.block.replication.failback:cli'), ('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'), + ('block:disaster-recovery-failover', 'SoftLayer.CLI.block.replication.disaster_recovery_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'), @@ -127,6 +128,7 @@ ('file:access-revoke', 'SoftLayer.CLI.file.access.revoke:cli'), ('file:replica-failback', 'SoftLayer.CLI.file.replication.failback:cli'), ('file:replica-failover', 'SoftLayer.CLI.file.replication.failover:cli'), + ('file:disaster-recovery-failover', 'SoftLayer.CLI.file.replication.disaster_recovery_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'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 37297e4d4..bf1f7adc4 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -221,6 +221,7 @@ failoverToReplicant = True failbackFromReplicant = True restoreFromSnapshot = True +disasterRecoveryFailoverToReplicant = True createSnapshot = { 'id': 449 diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 6aceb6f46..89955569c 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -398,13 +398,22 @@ def failover_to_replicant(self, volume_id, replicant_id): """ return self.client.call('Network_Storage', 'failoverToReplicant', replicant_id, id=volume_id) + def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): + """Disaster Recovery Failover to a volume replicant. + + :param integer volume_id: The id of the volume + :param integer replicant: ID of replicant to failover to + :return: Returns whether failover to successful or not + """ + return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) + def failback_from_replicant(self, volume_id): """Failback from a volume replicant. :param integer volume_id: The id of the volume :return: Returns whether failback was successful or not """ - + return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 5684b5623..18a324397 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -142,3 +142,7 @@ Block Commands .. click:: SoftLayer.CLI.block.set_note:cli :prog: block volume-set-note :show-nested: + +.. click:: SoftLayer.CLI.block.replication.disaster_recovery_failover:cli + :prog: block disaster-recovery-failover + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 31ceb0332..6c914b1a4 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -121,4 +121,8 @@ File Commands .. click:: SoftLayer.CLI.file.set_note:cli :prog: file volume-set-note + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.disaster_recovery_failover:cli + :prog: file disaster-recovery-failover :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f061d36a2..88ad1480b 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -4,8 +4,9 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions +from SoftLayer import SoftLayerAPIError from SoftLayer import testing +from SoftLayer.CLI import exceptions import json import mock @@ -48,7 +49,7 @@ def test_volume_set_lun_id_in_range_missing_value(self): 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( + lun_mock.side_effect = 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()) @@ -498,6 +499,18 @@ def test_replicant_failover(self): self.assert_no_fail(result) self.assertEqual('Failover to replicant is now in progress.\n', result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = True + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assert_no_fail(result) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', + result.output) def test_replication_locations(self): result = self.run_command(['block', 'replica-locations', '1234']) @@ -558,6 +571,28 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = False + + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertIn('Disaster Recovery Failover operation could not be initiated.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_disaster_recovery_failover_aborted(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + def test_replicant_failback(self): result = self.run_command(['block', 'replica-failback', '12345678']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index dac95e0d8..dc50cd68c 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -4,8 +4,9 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions +from SoftLayer import SoftLayerError from SoftLayer import testing +from SoftLayer.CLI import exceptions import json import mock @@ -126,7 +127,7 @@ def test_volume_cancel_without_billing_item(self): result = self.run_command([ '--really', 'file', 'volume-cancel', '1234']) - self.assertIsInstance(result.exception, exceptions.SoftLayerError) + self.assertIsInstance(result.exception, SoftLayerError) def test_volume_detail(self): result = self.run_command(['file', 'volume-detail', '1234']) @@ -493,6 +494,41 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = True + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assert_no_fail(result) + self.assertEqual('Disaster Recovery Failover to replicant is now in progress.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = False + + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual('Disaster Recovery Failover operation could not be initiated.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_disaster_recovery_failover_aborted(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_replicant_failback(self): result = self.run_command(['file', 'replica-failback', '12345678']) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index e603ef7e3..159553db7 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -384,6 +384,18 @@ def test_replicant_failover(self): identifier=1234, ) + def test_disaster_recovery_failover(self): + result = self.block.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) + def test_replicant_failback(self): result = self.block.failback_from_replicant(1234) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 15d64d883..91f9325d5 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -289,6 +289,18 @@ def test_replicant_failover(self): identifier=1234, ) + def test_disaster_recovery_failover(self): + result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) + def test_replicant_failback(self): result = self.file.failback_from_replicant(1234) From bf8c308eee0239accbccdfa1ea8221acb9ebe035 Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 6 Jan 2021 22:38:21 +0530 Subject: [PATCH 0304/1385] Refactoring the disaster recovery method --- .../block/replication/disaster_recovery_failover.py | 8 +++----- .../file/replication/disaster_recovery_failover.py | 8 +++----- tests/CLI/modules/block_tests.py | 12 ------------ 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index cc29fc0ac..77b04c65b 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -24,11 +24,9 @@ def cli(env, volume_id, replicant_id): if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') - success = block_storage_manager.disaster_recovery_failover_to_replicant( + block_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - if success: - click.echo("Disaster Recovery Failover to replicant is now in progress.") - else: - click.echo("Disaster Recovery Failover operation could not be initiated.") + + click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 7fa02322a..2e2a946a1 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -24,11 +24,9 @@ def cli(env, volume_id, replicant_id): if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') - success = file_storage_manager.disaster_recovery_failover_to_replicant( + file_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - if success: - click.echo("Disaster Recovery Failover to replicant is now in progress.") - else: - click.echo("Disaster Recovery Failover operation could not be initiated.") \ No newline at end of file + + click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 88ad1480b..6ccebf66f 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -571,18 +571,6 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) - @mock.patch('SoftLayer.CLI.formatting.confirm') - @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') - def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): - confirm_mock.return_value = True - disaster_recovery_failover_mock.return_value = False - - result = self.run_command(['block', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) - - self.assertIn('Disaster Recovery Failover operation could not be initiated.\n', - result.output) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_disaster_recovery_failover_aborted(self, confirm_mock): confirm_mock.return_value = False From 47e49e56f4b8f0f9e65dad138145a75fa810350e Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Thu, 7 Jan 2021 22:22:49 +0530 Subject: [PATCH 0305/1385] added warning message and summary for disaster-recover-failover --- .../replication/disaster_recovery_failover.py | 16 ++++++++++------ .../replication/disaster_recovery_failover.py | 14 +++++++++----- tests/CLI/modules/file_tests.py | 14 +------------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index 77b04c65b..cf79cd1a3 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -8,7 +8,10 @@ from SoftLayer.CLI import exceptions -@click.command() +@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. +This method does not allow for fail back via the API. To fail back to the original volume after using this method, open a support ticket. +To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -16,10 +19,11 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" block_storage_manager = SoftLayer.BlockStorageManager(env.client) - click.secho("""WARNING:Disaster Recovery Failover a block volume to the given replicant volume.\n""" - """* This action cannot be undone\n""" - """* You will not be able to perform failback to the original\n""" - """* You cannot failover without replica""",fg = 'red' ) + click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for fail back via the API.""" + """To fail back to the original volume after using this method, open a support ticket.""" + """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') @@ -29,4 +33,4 @@ def cli(env, volume_id, replicant_id): replicant_id ) - click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 2e2a946a1..3d175d909 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -8,7 +8,10 @@ from SoftLayer.CLI import exceptions -@click.command() +@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. +This method does not allow for fail back via API. If you wish to test failover, please use SoftLayer_Network_Storage::failoverToReplicant. +After using this method, to fail back to the original volume, please open a support ticket""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -16,10 +19,11 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" file_storage_manager = SoftLayer.FileStorageManager(env.client) - click.secho("""WARNING : Disaster Recovery Failover should not be performed unless data center for the primary volume is unreachable.\n""" - """* This action cannot be undone\n""" - """* You will not be able to perform failback to the original without support intervention\n""" - """* You cannot failover without replica""",fg = 'red' ) + click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for fail back via the API.""" + """To fail back to the original volume after using this method, open a support ticket.""" + """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index dc50cd68c..b13596355 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -503,19 +503,7 @@ def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confi '--replicant-id=5678']) self.assert_no_fail(result) - self.assertEqual('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) - - @mock.patch('SoftLayer.CLI.formatting.confirm') - @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') - def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): - confirm_mock.return_value = True - disaster_recovery_failover_mock.return_value = False - - result = self.run_command(['file', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) - - self.assertEqual('Disaster Recovery Failover operation could not be initiated.\n', + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) @mock.patch('SoftLayer.CLI.formatting.confirm') From 9c5af6283ba14766857ddcf4c703775c51fc4bfa Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 7 Jan 2021 18:49:16 -0400 Subject: [PATCH 0306/1385] #1400 get externalBinding and apiAuthenticationKey data to user list --- SoftLayer/managers/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 0948df8b3..56acf163e 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -53,7 +53,7 @@ def list_users(self, objectmask=None, objectfilter=None): if objectmask is None: objectmask = """mask[id, username, displayName, userStatus[name], hardwareCount, virtualGuestCount, - email, roles]""" + email, roles, externalBindingCount,apiAuthenticationKeyCount]""" return self.account_service.getUsers(mask=objectmask, filter=objectfilter) From fd13d77afe4788734d24f3aff8aee547c7bbe15b Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 7 Jan 2021 19:00:15 -0400 Subject: [PATCH 0307/1385] #1400 show 2FA and Classic APIs in user list --- SoftLayer/CLI/user/list.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/user/list.py b/SoftLayer/CLI/user/list.py index 8e79fe6bb..142f824ab 100644 --- a/SoftLayer/CLI/user/list.py +++ b/SoftLayer/CLI/user/list.py @@ -16,7 +16,9 @@ column_helper.Column('displayName', ('displayName',)), column_helper.Column('status', ('userStatus', 'name')), column_helper.Column('hardwareCount', ('hardwareCount',)), - column_helper.Column('virtualGuestCount', ('virtualGuestCount',)) + column_helper.Column('virtualGuestCount', ('virtualGuestCount',)), + column_helper.Column('2FAs', ('externalBindingCount',)), + column_helper.Column('classicAPIKeys', ('apiAuthenticationKeyCount',)) ] DEFAULT_COLUMNS = [ From 687678c1f32235fc099ffdedba3ee030e4c05df7 Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 13 Jan 2021 18:28:06 +0530 Subject: [PATCH 0308/1385] addressing nitpicky --- .../replication/disaster_recovery_failover.py | 32 ++++++++++--------- .../replication/disaster_recovery_failover.py | 31 ++++++++++-------- SoftLayer/managers/storage.py | 3 +- tests/CLI/modules/block_tests.py | 13 ++++---- tests/CLI/modules/file_tests.py | 11 +++---- tests/managers/block_tests.py | 2 +- tests/managers/file_tests.py | 20 ++++++------ 7 files changed, 56 insertions(+), 56 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index cf79cd1a3..1a0c304d8 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -1,36 +1,38 @@ -"""Failover an inaccessible file volume to its available replicant volume.""" +"""Failover an inaccessible block volume to its available replicant volume.""" # :license: MIT, see LICENSE for more details. import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. -If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. -This method does not allow for fail back via the API. To fail back to the original volume after using this method, open a support ticket. -To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""") +@click.command(epilog="""Failover an inaccessible block volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately +failover to an available replica in another location. This method does not allow for failback via API. +After using this method, to failback to the original volume, please open a support ticket. +If you wish to test failover, please use replica-failover.""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env def cli(env, volume_id, replicant_id): - """Failover an inaccessible file volume to its available replicant volume.""" + """Failover an inaccessible block volume to its available replicant volume.""" block_storage_manager = SoftLayer.BlockStorageManager(env.client) - click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" - """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" - """This method does not allow for fail back via the API.""" - """To fail back to the original volume after using this method, open a support ticket.""" - """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) - - if not (formatting.confirm('Are you sure you want to continue?')): + click.secho("""WARNING : Failover an inaccessible block volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event,""" + """this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for failback via the API.""" + """To failback to the original volume after using this method, open a support ticket.""" + """If you wish to test failover, use replica-failover instead.""", fg='red') + + if not formatting.confirm('Are you sure you want to continue?'): raise exceptions.CLIAbort('Aborted.') block_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 3d175d909..a3f9373f7 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -4,14 +4,16 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. -If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. -This method does not allow for fail back via API. If you wish to test failover, please use SoftLayer_Network_Storage::failoverToReplicant. -After using this method, to fail back to the original volume, please open a support ticket""") +@click.command(epilog="""Failover an inaccessible file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately +failover to an available replica in another location. This method does not allow for failback via API. +After using this method, to failback to the original volume, please open a support ticket. +If you wish to test failover, please use replica-failover. +""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -19,18 +21,19 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" file_storage_manager = SoftLayer.FileStorageManager(env.client) - click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" - """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" - """This method does not allow for fail back via the API.""" - """To fail back to the original volume after using this method, open a support ticket.""" - """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) - - if not (formatting.confirm('Are you sure you want to continue?')): + click.secho("""WARNING : Failover an inaccessible file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event,""" + """this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for failback via the API.""" + """To failback to the original volume after using this method, open a support ticket.""" + """If you wish to test failover, use replica-failover instead.""", fg='red') + + if not formatting.confirm('Are you sure you want to continue?'): raise exceptions.CLIAbort('Aborted.') file_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - - click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file + + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 89955569c..44e76c138 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -405,7 +405,7 @@ def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): :param integer replicant: ID of replicant to failover to :return: Returns whether failover to successful or not """ - return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) + return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) def failback_from_replicant(self, volume_id): """Failback from a volume replicant. @@ -413,7 +413,6 @@ def failback_from_replicant(self, volume_id): :param integer volume_id: The id of the volume :return: Returns whether failback was successful or not """ - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 6ccebf66f..cbf1ba25f 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerAPIError from SoftLayer import testing -from SoftLayer.CLI import exceptions + import json import mock @@ -497,9 +498,8 @@ def test_replicant_failover(self): '--replicant-id=5678']) self.assert_no_fail(result) - self.assertEqual('Failover to replicant is now in progress.\n', - result.output) - + self.assertEqual('Failover to replicant is now in progress.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): @@ -509,8 +509,7 @@ def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confi '--replicant-id=5678']) self.assert_no_fail(result) - self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) def test_replication_locations(self): result = self.run_command(['block', 'replica-locations', '1234']) @@ -579,7 +578,7 @@ def test_disaster_recovery_failover_aborted(self, confirm_mock): '--replicant-id=5678']) self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_replicant_failback(self): result = self.run_command(['block', 'replica-failback', '12345678']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index b13596355..1bfe58e16 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -4,9 +4,9 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerError from SoftLayer import testing -from SoftLayer.CLI import exceptions import json import mock @@ -499,12 +499,10 @@ def test_replicant_failover_unsuccessful(self, failover_mock): def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): confirm_mock.return_value = True disaster_recovery_failover_mock.return_value = True - result = self.run_command(['file', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', '--replicant-id=5678']) self.assert_no_fail(result) - self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_disaster_recovery_failover_aborted(self, confirm_mock): @@ -514,8 +512,7 @@ def test_disaster_recovery_failover_aborted(self, confirm_mock): '--replicant-id=5678']) self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - + self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_replicant_failback(self): result = self.run_command(['file', 'replica-failback', '12345678']) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 159553db7..8dc1cd83b 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -394,7 +394,7 @@ def test_disaster_recovery_failover(self): 'disasterRecoveryFailoverToReplicant', args=(5678,), identifier=1234, - ) + ) def test_replicant_failback(self): result = self.block.failback_from_replicant(1234) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 91f9325d5..11e35c001 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -290,16 +290,16 @@ def test_replicant_failover(self): ) def test_disaster_recovery_failover(self): - result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) - - self.assertEqual( - SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) - self.assert_called_with( - 'SoftLayer_Network_Storage', - 'disasterRecoveryFailoverToReplicant', - args=(5678,), - identifier=1234, - ) + result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) def test_replicant_failback(self): result = self.file.failback_from_replicant(1234) From 5ad1d04bc5f4afe2618c709ce1cb067940d4dc8a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 1 Feb 2021 16:04:43 -0400 Subject: [PATCH 0309/1385] Add pagination to object storage list accounts. --- SoftLayer/managers/object_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index 2560d26c8..731d48d13 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -30,7 +30,7 @@ def __init__(self, client): def list_accounts(self): """Lists your object storage accounts.""" return self.client.call('Account', 'getHubNetworkStorage', - mask=LIST_ACCOUNTS_MASK) + mask=LIST_ACCOUNTS_MASK, iter=True, limit=10) def list_endpoints(self): """Lists the known object storage endpoints.""" From 09f3ce5f99a89e3093b4e627c4eac6e8a9267dfc Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 3 Feb 2021 15:53:21 -0400 Subject: [PATCH 0310/1385] Refactor object storage list accounts. --- SoftLayer/CLI/object_storage/list_accounts.py | 8 ++++++-- SoftLayer/managers/object_storage.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/object_storage/list_accounts.py b/SoftLayer/CLI/object_storage/list_accounts.py index c49aecc67..e7862711c 100644 --- a/SoftLayer/CLI/object_storage/list_accounts.py +++ b/SoftLayer/CLI/object_storage/list_accounts.py @@ -9,12 +9,16 @@ @click.command() +@click.option('--limit', + type=int, + default=10, + help="Result limit") @environment.pass_env -def cli(env): +def cli(env, limit): """List object storage accounts.""" mgr = SoftLayer.ObjectStorageManager(env.client) - accounts = mgr.list_accounts() + accounts = mgr.list_accounts(limit=limit) table = formatting.Table(['id', 'name', 'apiType']) table.sortby = 'id' api_type = None diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index 731d48d13..f9d37440e 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -27,10 +27,10 @@ class ObjectStorageManager(object): def __init__(self, client): self.client = client - def list_accounts(self): + def list_accounts(self, limit=10): """Lists your object storage accounts.""" return self.client.call('Account', 'getHubNetworkStorage', - mask=LIST_ACCOUNTS_MASK, iter=True, limit=10) + mask=LIST_ACCOUNTS_MASK, iter=True, limit=limit) def list_endpoints(self): """Lists the known object storage endpoints.""" From 461cee479e078ed7b10094ee6758b438944a6188 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 4 Feb 2021 18:28:11 -0400 Subject: [PATCH 0311/1385] Add slcli vs create by router data. --- SoftLayer/CLI/virt/create.py | 6 ++++ SoftLayer/managers/vs.py | 39 +++++++++++++++++++-- tests/CLI/modules/vs/vs_create_tests.py | 43 +++++++++++++++++++++++ tests/managers/vs/vs_tests.py | 45 +++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index d9864a890..23a18457d 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -95,6 +95,8 @@ def _parse_create_args(client, args): "private_vlan": args.get('vlan_private', None), "public_subnet": args.get('subnet_public', None), "private_subnet": args.get('subnet_private', None), + "public_router": args.get('router_public', None), + "private_router": args.get('router_private', None), } # The primary disk is included in the flavor and the local_disk flag is not needed @@ -192,6 +194,10 @@ def _parse_create_args(client, args): help="The ID of the public SUBNET on which you want the virtual server placed") @click.option('--subnet-private', type=click.INT, help="The ID of the private SUBNET on which you want the virtual server placed") +@click.option('--router-public', type=click.INT, + help="The ID of the public ROUTER on which you want the virtual server placed") +@click.option('--router-private', type=click.INT, + help="The ID of the private ROUTER on which you want the virtual server placed") @helpers.multi_option('--public-security-group', '-S', help=('Security group ID to associate with the public interface')) @helpers.multi_option('--private-security-group', '-s', diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 1a03db1c6..3c3eab3a2 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -470,6 +470,7 @@ def _generate_create_dict( datacenter=None, os_code=None, image_id=None, dedicated=False, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, + public_router=None, private_router=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None, public_security_groups=None, private_security_groups=None, boot_mode=None, transient=False, **kwargs): @@ -533,6 +534,15 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} + if private_router or public_router: + if private_vlan or public_vlan or private_subnet or public_subnet: + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + private_router, public_router) + data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) @@ -581,7 +591,8 @@ def _generate_create_dict( def _create_network_components( self, public_vlan=None, private_vlan=None, - private_subnet=None, public_subnet=None): + private_subnet=None, public_subnet=None, + private_router=None, public_router=None): parameters = {} if private_vlan: parameters['primaryBackendNetworkComponent'] = {"networkVlan": {"id": int(private_vlan)}} @@ -598,6 +609,12 @@ def _create_network_components( parameters['primaryBackendNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(private_subnet)} + if private_router: + parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + + if public_router: + parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} + return parameters @retry(logger=LOGGER) @@ -685,7 +702,21 @@ def verify_create_instance(self, **kwargs): kwargs.pop('tags', None) create_options = self._generate_create_dict(**kwargs) template = self.guest.generateOrderTemplate(create_options) - if 'private_subnet' in kwargs or 'public_subnet' in kwargs: + if kwargs.get('public_router') or kwargs.get('private_router'): + if kwargs.get('private_vlan') or kwargs.get('public_vlan') or kwargs.get('private_subnet') \ + or kwargs.get('public_subnet'): + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + vsi = template['virtualGuests'][0] + network_components = self._create_network_components(kwargs.get('public_vlan', None), + kwargs.get('private_vlan', None), + kwargs.get('private_subnet', None), + kwargs.get('public_subnet', None), + kwargs.get('private_router', None), + kwargs.get('public_router', None)) + vsi.update(network_components) + + if kwargs.get('private_subnet') or kwargs.get('public_subnet'): vsi = template['virtualGuests'][0] network_components = self._create_network_components(kwargs.get('public_vlan', None), kwargs.get('private_vlan', None), @@ -693,6 +724,9 @@ def verify_create_instance(self, **kwargs): kwargs.get('public_subnet', None)) vsi.update(network_components) + print("template") + print(template) + return template def create_instance(self, **kwargs): @@ -1121,6 +1155,7 @@ def order_guest(self, guest_object, test=False): if guest_object.get('placement_id'): template['virtualGuests'][0]['placementGroupId'] = guest_object.get('placement_id') + print(template) if test: result = self.client.call('Product_Order', 'verifyOrder', template) else: diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 53c3bdc97..2ad0f8647 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -93,6 +93,49 @@ def test_create_vlan_subnet(self, confirm_mock): self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_by_router(self, confirm_mock): + confirm_mock.return_value = True + + result = self.run_command(['vs', 'create', + '--cpu=2', + '--domain=example.com', + '--hostname=host', + '--os=UBUNTU_LATEST', + '--memory=1', + '--billing=hourly', + '--datacenter=dal05', + '--router-private=577940', + '--router-public=1639255', + '--tag=dev', + '--tag=green']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + args = ({ + 'startCpus': 2, + 'maxMemory': 1024, + 'hostname': 'host', + 'domain': 'example.com', + 'localDiskFlag': True, + 'hourlyBillingFlag': True, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'datacenter': {'name': 'dal05'}, + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 577940 + } + }, + 'primaryNetworkComponent': { + 'router': { + 'id': 1639255 + } + } + },) + + self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_with_wait_ready(self, confirm_mock): mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index e858b2577..db8bb4ed8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -557,6 +557,38 @@ def test_generate_private_vlan(self): self.assertEqual(data, assert_data) + def test_generate_by_router_and_vlan(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._generate_create_dict, + cpus=1, + memory=1, + hostname='test', + domain='example.com', + os_code="STRING", + private_router=1, + private_vlan=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + + def test_generate_by_router_and_subnet(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._generate_create_dict, + cpus=1, + memory=1, + hostname='test', + domain='example.com', + os_code="STRING", + private_router=1, + private_subnet=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + def test_generate_sec_group(self): data = self.vs._generate_create_dict( cpus=1, @@ -596,6 +628,19 @@ def test_create_network_components_vlan_subnet_private_vlan_subnet_public(self): self.assertEqual(data, assert_data) + def test_create_network_components_by_routers(self): + data = self.vs._create_network_components( + private_router=1, + public_router=1 + ) + + assert_data = { + 'primaryBackendNetworkComponent': {'router': {'id': 1}}, + 'primaryNetworkComponent': {'router': {'id': 1}}, + } + + self.assertEqual(data, assert_data) + def test_create_network_components_vlan_subnet_private(self): data = self.vs._create_network_components( private_vlan=1, From 485b231d95f00c6251bf779086044e8cf136c963 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 5 Feb 2021 11:58:49 -0400 Subject: [PATCH 0312/1385] #1410 fix conflicts after updating from origin --- SoftLayer/managers/object_storage.py | 32 ++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index e27b8975f..5efadeabc 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -6,6 +6,9 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.exceptions import SoftLayerError +from SoftLayer import utils + LIST_ACCOUNTS_MASK = '''mask[ id,username,notes,vendorName,serviceResource ]''' @@ -15,7 +18,7 @@ ]''' -class ObjectStorageManager(object): +class ObjectStorageManager(utils.IdentifierMixin, object): """Manager for SoftLayer Object Storage accounts. See product information here: https://www.ibm.com/cloud/object-storage @@ -26,11 +29,16 @@ class ObjectStorageManager(object): def __init__(self, client): self.client = client + self.resolvers = [self._get_id_from_username] - def list_accounts(self, limit=10): + def list_accounts(self, object_mask=None, object_filter=None, limit=10): """Lists your object storage accounts.""" - return self.client.call('Account', 'getHubNetworkStorage', - mask=LIST_ACCOUNTS_MASK, iter=True, limit=limit) + object_mask = object_mask if object_mask else LIST_ACCOUNTS_MASK + return self.client.call('Account', + 'getHubNetworkStorage', + mask=object_mask, + filter=object_filter, + limit=limit) def list_endpoints(self): """Lists the known object storage endpoints.""" @@ -96,3 +104,19 @@ def list_credential(self, identifier): return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials', id=identifier) + + def _get_id_from_username(self, username): + """Looks up a username's id + + :param string username: Username to lookup + :returns: The id that matches username. + """ + _mask = "mask[id,username]" + _filter = {'hubNetworkStorage': {'username': utils.query_filter(username)}} + account = self.list_accounts(_mask, _filter) + if len(account) == 1: + return [account[0]['id']] + elif len(account) > 1: + raise SoftLayerError("Multiple object storage accounts found with the name: {}".format(username)) + else: + raise SoftLayerError("Unable to find object storage account id for: {}".format(username)) From b0f7933e1c32cd09a952ce66eeea0aa19f2d0cbe Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Feb 2021 17:11:56 -0400 Subject: [PATCH 0313/1385] #1410 add resolve id test to object storage --- tests/managers/object_storage_tests.py | 52 ++++++-------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index e5042080d..73558a29b 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -79,43 +79,15 @@ def test_limit_credential(self): self.assertEqual(credential, 2) def test_list_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials') - accounts.return_value = [ - { - "id": 1103123, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXsf4sf", - "username": "XfHhBNBPlPdlWyaP3fsd", - "type": { - "name": "S3 Compatible Signature" - } - }, - { - "id": 1102341, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", - "username": "XfHhBNBPlPdlWyaP", - "type": { - "name": "S3 Compatible Signature" - } - } - ] - credential = self.object_storage.list_credential(100) - self.assertEqual(credential, - [ - { - "id": 1103123, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXsf4sf", - "username": "XfHhBNBPlPdlWyaP3fsd", - "type": { - "name": "S3 Compatible Signature" - } - }, - { - "id": 1102341, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", - "username": "XfHhBNBPlPdlWyaP", - "type": { - "name": "S3 Compatible Signature" - } - } - ] - ) + credentials = self.object_storage.list_credential(100) + self.assertIsInstance(credentials, list) + self.assert_called_with('SoftLayer_Network_Storage_Hub_Cleversafe_Account', + 'getCredentials', + identifier=100) + + def test_resolve_ids(self): + accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') + accounts.return_value = [{'id': 12345, 'username': 'test'}] + identifier = self.object_storage.resolve_ids('test') + self.assertEqual(identifier, [12345]) + self.assert_called_with('SoftLayer_Account', 'getHubNetworkStorage') From 35f7a0ca64e3012c4f204c1cac1536ef42b57f02 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Feb 2021 17:20:46 -0400 Subject: [PATCH 0314/1385] #1410 add username lookup to slcli object-storage credential list --- SoftLayer/CLI/object_storage/credential/list.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/object_storage/credential/list.py b/SoftLayer/CLI/object_storage/credential/list.py index 647e4224c..53f878fce 100644 --- a/SoftLayer/CLI/object_storage/credential/list.py +++ b/SoftLayer/CLI/object_storage/credential/list.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() @@ -15,15 +16,17 @@ def cli(env, identifier): """Retrieve credentials used for generating an AWS signature. Max of 2.""" mgr = SoftLayer.ObjectStorageManager(env.client) - credential_list = mgr.list_credential(identifier) + storage_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Object Storage') + credential_list = mgr.list_credential(storage_id) + table = formatting.Table(['id', 'password', 'username', 'type_name']) for credential in credential_list: table.add_row([ - credential['id'], - credential['password'], - credential['username'], - credential['type']['name'] + credential.get('id'), + credential.get('password'), + credential.get('username'), + credential.get('type', {}).get('name') ]) env.fout(table) From d833f77452a0261622f93492cd6ec6bfae2c6a40 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Feb 2021 17:22:22 -0400 Subject: [PATCH 0315/1385] #1410 add username lookup test to slcli object-storage credential list --- tests/CLI/modules/object_storage_tests.py | 25 ++++++----------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 74d70152e..ee5218fa4 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json +from unittest import mock from SoftLayer import testing @@ -82,25 +83,11 @@ def test_limit_credential(self): self.assertEqual(json.loads(result.output), [{'limit': 2}]) def test_list_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials') - accounts.return_value = [{'id': 1103123, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', - 'type': {'name': 'S3 Compatible Signature'}, - 'username': 'XfHhBNBPlPdlWya'}, - {'id': 1103333, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9', - 'type': {'name': 'S3 Compatible Signature'}, - 'username': 'XfHhBNBPlPd'}] - result = self.run_command(['object-storage', 'credential', 'list', '100']) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.helpers.resolve_id') + def test_list_credential_by_username(self, resolve_id_mock): + resolve_id_mock.return_value = 100 + result = self.run_command(['object-storage', 'credential', 'list', 'test']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - [{'id': 1103123, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', - 'type_name': 'S3 Compatible Signature', - 'username': 'XfHhBNBPlPdlWya'}, - {'id': 1103333, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9', - 'type_name': 'S3 Compatible Signature', - 'username': 'XfHhBNBPlPd'}]) From 8b438469494fef39fe413ad4fb8bc39d9caaf25b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 5 Feb 2021 11:28:37 -0400 Subject: [PATCH 0316/1385] #1410 add resolve id tests to object storage --- tests/managers/object_storage_tests.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index 73558a29b..16081cc83 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -6,6 +6,7 @@ """ import SoftLayer from SoftLayer import fixtures +from SoftLayer import SoftLayerError from SoftLayer import testing @@ -91,3 +92,14 @@ def test_resolve_ids(self): identifier = self.object_storage.resolve_ids('test') self.assertEqual(identifier, [12345]) self.assert_called_with('SoftLayer_Account', 'getHubNetworkStorage') + + def test_resolve_ids_fail_multiple(self): + accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') + accounts.return_value = [{'id': 12345, 'username': 'test'}, + {'id': 12345, 'username': 'test'}] + self.assertRaises(SoftLayerError, self.object_storage.resolve_ids, 'test') + + def test_resolve_ids_fail_no_found(self): + accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') + accounts.return_value = [] + self.assertRaises(SoftLayerError, self.object_storage.resolve_ids, 'test') From e51759d6d4d08bafc44d9ff2428133ee64d6b827 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 5 Feb 2021 11:43:24 -0400 Subject: [PATCH 0317/1385] #1410 add username lookup to slcli object-storage credential limit, delete, create --- .../CLI/object_storage/credential/create.py | 12 +++-- .../CLI/object_storage/credential/delete.py | 4 +- .../CLI/object_storage/credential/limit.py | 4 +- ..._Network_Storage_Hub_Cleversafe_Account.py | 4 ++ tests/CLI/modules/object_storage_tests.py | 46 ++++++++----------- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/SoftLayer/CLI/object_storage/credential/create.py b/SoftLayer/CLI/object_storage/credential/create.py index 934ac7651..a7ce36755 100644 --- a/SoftLayer/CLI/object_storage/credential/create.py +++ b/SoftLayer/CLI/object_storage/credential/create.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() @@ -15,14 +16,15 @@ def cli(env, identifier): """Create credentials for an IBM Cloud Object Storage Account""" mgr = SoftLayer.ObjectStorageManager(env.client) - credential = mgr.create_credential(identifier) + storage_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Object Storage') + credential = mgr.create_credential(storage_id) table = formatting.Table(['id', 'password', 'username', 'type_name']) table.sortby = 'id' table.add_row([ - credential['id'], - credential['password'], - credential['username'], - credential['type']['name'] + credential.get('id'), + credential.get('password'), + credential.get('username'), + credential.get('type', {}).get('name') ]) env.fout(table) diff --git a/SoftLayer/CLI/object_storage/credential/delete.py b/SoftLayer/CLI/object_storage/credential/delete.py index 7b066ba59..da1ddaeb9 100644 --- a/SoftLayer/CLI/object_storage/credential/delete.py +++ b/SoftLayer/CLI/object_storage/credential/delete.py @@ -5,6 +5,7 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers @click.command() @@ -16,6 +17,7 @@ def cli(env, identifier, credential_id): """Delete the credential of an Object Storage Account.""" mgr = SoftLayer.ObjectStorageManager(env.client) - credential = mgr.delete_credential(identifier, credential_id=credential_id) + storage_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Object Storage') + credential = mgr.delete_credential(storage_id, credential_id=credential_id) env.fout(credential) diff --git a/SoftLayer/CLI/object_storage/credential/limit.py b/SoftLayer/CLI/object_storage/credential/limit.py index cc3ad115c..689a8cef4 100644 --- a/SoftLayer/CLI/object_storage/credential/limit.py +++ b/SoftLayer/CLI/object_storage/credential/limit.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() @@ -15,7 +16,8 @@ def cli(env, identifier): """Credential limits for this IBM Cloud Object Storage account.""" mgr = SoftLayer.ObjectStorageManager(env.client) - credential_limit = mgr.limit_credential(identifier) + storage_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Object Storage') + credential_limit = mgr.limit_credential(storage_id) table = formatting.Table(['limit']) table.add_row([ credential_limit, diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py index 4d066cf7e..ab72576e4 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py @@ -46,3 +46,7 @@ "storageLocation": "us-standard" } ] + +getCredentialLimit = 2 + +credentialDelete = True diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index ee5218fa4..2e843906d 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -40,47 +40,37 @@ def test_list_endpoints(self): 'public': 'https://dal05/auth/v1.0/'}]) def test_create_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate') - accounts.return_value = { - "accountId": "12345", - "createDate": "2019-04-05T13:25:25-06:00", - "id": 11111, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCy", - "username": "XfHhBNBPlPdl", - "type": { - "description": "A credential for generating S3 Compatible Signatures.", - "keyName": "S3_COMPATIBLE_SIGNATURE", - "name": "S3 Compatible Signature" - } - } - result = self.run_command(['object-storage', 'credential', 'create', '100']) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.helpers.resolve_id') + def test_create_credential_by_username(self, resolve_id_mock): + resolve_id_mock.return_value = 100 + result = self.run_command(['object-storage', 'credential', 'create', 'test']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - [{'id': 11111, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCy', - 'type_name': 'S3 Compatible Signature', - 'username': 'XfHhBNBPlPdl'}] - ) def test_delete_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') - accounts.return_value = True - result = self.run_command(['object-storage', 'credential', 'delete', '-c', 100, '100']) - self.assert_no_fail(result) self.assertEqual(result.output, 'True\n') - def test_limit_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') - accounts.return_value = 2 + @mock.patch('SoftLayer.CLI.helpers.resolve_id') + def test_delete_credential_by_username(self, resolve_id_mock): + resolve_id_mock.return_value = 100 + result = self.run_command(['object-storage', 'credential', 'delete', 'test']) + self.assert_no_fail(result) + def test_limit_credential(self): result = self.run_command(['object-storage', 'credential', 'limit', '100']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), [{'limit': 2}]) + self.assertIn('limit', result.output) + + @mock.patch('SoftLayer.CLI.helpers.resolve_id') + def test_limit_credential_by_username(self, resolve_id_mock): + resolve_id_mock.return_value = 100 + result = self.run_command(['object-storage', 'credential', 'limit', 'test']) + self.assert_no_fail(result) def test_list_credential(self): result = self.run_command(['object-storage', 'credential', 'list', '100']) From b66b563e0dfb14bebb1ab4497493babd507e9fb4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 14:47:37 -0400 Subject: [PATCH 0318/1385] Fix tox analysis. --- SoftLayer/managers/vs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 3c3eab3a2..20068ef61 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -470,7 +470,6 @@ def _generate_create_dict( datacenter=None, os_code=None, image_id=None, dedicated=False, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, - public_router=None, private_router=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None, public_security_groups=None, private_security_groups=None, boot_mode=None, transient=False, **kwargs): @@ -534,13 +533,14 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - if private_router or public_router: + if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " "only router, not all options") network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet, - private_router, public_router) + kwargs.get('private_router'), + kwargs.get('public_router')) data.update(network_components) if private_vlan or public_vlan or private_subnet or public_subnet: From 9a0f48a264a293df2233eae4e88878954d8de432 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 15:04:05 -0400 Subject: [PATCH 0319/1385] Fix tox analysis. --- SoftLayer/managers/vs.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 20068ef61..6de17d0dd 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,20 +533,7 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - if kwargs.get('private_router') or kwargs.get('public_router'): - if private_vlan or public_vlan or private_subnet or public_subnet: - raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " - "only router, not all options") - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet, - kwargs.get('private_router'), - kwargs.get('public_router')) - data.update(network_components) - - if private_vlan or public_vlan or private_subnet or public_subnet: - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet) - data.update(network_components) + self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -589,6 +576,21 @@ def _generate_create_dict( return data + def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + if kwargs.get('private_router') or kwargs.get('public_router'): + if private_vlan or public_vlan or private_subnet or public_subnet: + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + kwargs.get('private_router'), + kwargs.get('public_router')) + data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet) + data.update(network_components) + def _create_network_components( self, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, @@ -724,9 +726,6 @@ def verify_create_instance(self, **kwargs): kwargs.get('public_subnet', None)) vsi.update(network_components) - print("template") - print(template) - return template def create_instance(self, **kwargs): @@ -1155,7 +1154,6 @@ def order_guest(self, guest_object, test=False): if guest_object.get('placement_id'): template['virtualGuests'][0]['placementGroupId'] = guest_object.get('placement_id') - print(template) if test: result = self.client.call('Product_Order', 'verifyOrder', template) else: From fde0cf6c0566f5e4f5a3e364bf53c257c58f3cbc Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 15:37:51 -0400 Subject: [PATCH 0320/1385] Add method docstring. --- SoftLayer/managers/vs.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6de17d0dd..01c2b05e5 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,7 +533,10 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) + network_components = self.get_network_components(kwargs, private_subnet, private_vlan, public_subnet, + public_vlan) + + data.update(network_components) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -576,7 +579,16 @@ def _generate_create_dict( return data - def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + def get_network_components(self, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + """Get the network components structure. + + :param kwargs: Vs item list. + :param int private_subnet: Private subnet id. + :param int private_vlan: Private vlan id. + :param int public_subnet: Public subnet id. + :param int public_vlan: Public vlan id. + """ + network_components = None if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " @@ -585,11 +597,12 @@ def get_network_components(self, data, kwargs, private_subnet, private_vlan, pub private_subnet, public_subnet, kwargs.get('private_router'), kwargs.get('public_router')) - data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) - data.update(network_components) + + return network_components def _create_network_components( self, public_vlan=None, private_vlan=None, From 6d354ce3de711ca0bab1443bcb87662220caeb21 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 16:09:10 -0400 Subject: [PATCH 0321/1385] Fix tox test issues. --- SoftLayer/managers/vs.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 01c2b05e5..0994530b3 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,10 +533,7 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - network_components = self.get_network_components(kwargs, private_subnet, private_vlan, public_subnet, - public_vlan) - - data.update(network_components) + self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -579,16 +576,16 @@ def _generate_create_dict( return data - def get_network_components(self, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): """Get the network components structure. + :param data: Array variable to add the network structure. :param kwargs: Vs item list. :param int private_subnet: Private subnet id. :param int private_vlan: Private vlan id. :param int public_subnet: Public subnet id. :param int public_vlan: Public vlan id. """ - network_components = None if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " @@ -597,12 +594,11 @@ def get_network_components(self, kwargs, private_subnet, private_vlan, public_su private_subnet, public_subnet, kwargs.get('private_router'), kwargs.get('public_router')) - + data.update(network_components) if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) - - return network_components + data.update(network_components) def _create_network_components( self, public_vlan=None, private_vlan=None, From b2f1af63973a4e79995337b020369cbfd6770aaf Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 9 Feb 2021 14:23:14 -0400 Subject: [PATCH 0322/1385] Refactor network components method. --- SoftLayer/managers/vs.py | 44 +++++++++++------------------------ tests/managers/vs/vs_tests.py | 12 ++++++++++ 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 0994530b3..b247e8be5 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,7 +533,11 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + kwargs.get('private_router'), + kwargs.get('public_router')) + data.update(network_components) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -576,35 +580,19 @@ def _generate_create_dict( return data - def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): - """Get the network components structure. - - :param data: Array variable to add the network structure. - :param kwargs: Vs item list. - :param int private_subnet: Private subnet id. - :param int private_vlan: Private vlan id. - :param int public_subnet: Public subnet id. - :param int public_vlan: Public vlan id. - """ - if kwargs.get('private_router') or kwargs.get('public_router'): - if private_vlan or public_vlan or private_subnet or public_subnet: - raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " - "only router, not all options") - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet, - kwargs.get('private_router'), - kwargs.get('public_router')) - data.update(network_components) - if private_vlan or public_vlan or private_subnet or public_subnet: - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet) - data.update(network_components) - def _create_network_components( self, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, private_router=None, public_router=None): parameters = {} + if any([private_router, public_router]) and any([private_vlan, public_vlan, private_subnet, public_subnet]): + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + + if private_router: + parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + if public_router: + parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} if private_vlan: parameters['primaryBackendNetworkComponent'] = {"networkVlan": {"id": int(private_vlan)}} if public_vlan: @@ -620,12 +608,6 @@ def _create_network_components( parameters['primaryBackendNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(private_subnet)} - if private_router: - parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} - - if public_router: - parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} - return parameters @retry(logger=LOGGER) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index db8bb4ed8..c75e5e3b9 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -641,6 +641,18 @@ def test_create_network_components_by_routers(self): self.assertEqual(data, assert_data) + def test_create_network_components_by_routers_and_vlan(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._create_network_components, + private_router=1, + public_router=1, + private_vlan=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + def test_create_network_components_vlan_subnet_private(self): data = self.vs._create_network_components( private_vlan=1, From fc1f67ea245ce08ebfbb9bffd9293271ea07841f Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 11 Feb 2021 15:02:25 -0400 Subject: [PATCH 0323/1385] Add IOPs data to block volume list. --- SoftLayer/CLI/block/list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 44489f928..769aad233 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -23,7 +23,7 @@ mask="storageType.keyName"), column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"), column_helper.Column('bytes_used', ('bytesUsed',), mask="bytesUsed"), - column_helper.Column('iops', ('iops',), mask="iops"), + column_helper.Column('IOPs', ('provisionedIops',), mask="provisionedIops"), column_helper.Column('ip_addr', ('serviceResourceBackendIpAddress',), mask="serviceResourceBackendIpAddress"), column_helper.Column('lunId', ('lunId',), mask="lunId"), @@ -44,7 +44,7 @@ 'storage_type', 'capacity_gb', 'bytes_used', - 'iops', + 'IOPs', 'ip_addr', 'lunId', 'active_transactions', From e30066a08419ee9c9f38a4bdaaad2422771189eb Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 11 Feb 2021 15:09:23 -0400 Subject: [PATCH 0324/1385] Add IOPs data to block volume-list unit test. --- tests/CLI/modules/block_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f061d36a2..97feb58be 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -130,7 +130,7 @@ def test_volume_list(self): 'capacity_gb': 20, 'datacenter': 'dal05', 'id': 100, - 'iops': None, + 'IOPs': None, 'ip_addr': '10.1.2.3', 'lunId': None, 'notes': "{'status': 'availabl", From 2fae9ff48be806122ae79d85f62182582d622765 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Feb 2021 18:18:48 -0400 Subject: [PATCH 0325/1385] add a flags in the report bandwidth --- SoftLayer/CLI/report/bandwidth.py | 66 +++++--- tests/CLI/modules/report_tests.py | 250 ++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 23d1a157c..bd651c90a 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -176,7 +176,7 @@ def _get_virtual_bandwidth(env, start, end): '--start', callback=_validate_datetime, default=( - datetime.datetime.now() - datetime.timedelta(days=30) + datetime.datetime.now() - datetime.timedelta(days=30) ).strftime('%Y-%m-%d'), help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") @click.option( @@ -187,8 +187,12 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) +@click.option('--virtual', is_flag=True, help='show the all bandwidth summary virtual', + default=False) +@click.option('--server', is_flag=True, help='show the all bandwidth summary bare metal', + default=False) @environment.pass_env -def cli(env, start, end, sortby): +def cli(env, start, end, sortby, virtual, server): """Bandwidth report for every pool/server. This reports on the total data transfered for each virtual sever, hardware @@ -213,24 +217,46 @@ def f_type(key, results): return (result['counter'] for result in results if result['type'] == key) - try: + def _input_to_table(item): + "Input metric data to table" + pub_in = int(sum(f_type('publicIn_net_octet', item['data']))) + pub_out = int(sum(f_type('publicOut_net_octet', item['data']))) + pri_in = int(sum(f_type('privateIn_net_octet', item['data']))) + pri_out = int(sum(f_type('privateOut_net_octet', item['data']))) + table.add_row([ + item['type'], + item['name'], + formatting.b_to_gb(pub_in), + formatting.b_to_gb(pub_out), + formatting.b_to_gb(pri_in), + formatting.b_to_gb(pri_out), + item.get('pool') or formatting.blank(), + ]) + + if virtual: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + try: + pass + except KeyboardInterrupt: + env.err("Printing virtual collected results and then aborting.") + + elif server: + try: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_hardware_bandwidth(env, start, end)): + _input_to_table(item) + except KeyboardInterrupt: + env.err("Printing server collected results and then aborting.") + else: for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end), - _get_hardware_bandwidth(env, start, end)): - pub_in = int(sum(f_type('publicIn_net_octet', item['data']))) - pub_out = int(sum(f_type('publicOut_net_octet', item['data']))) - pri_in = int(sum(f_type('privateIn_net_octet', item['data']))) - pri_out = int(sum(f_type('privateOut_net_octet', item['data']))) - table.add_row([ - item['type'], - item['name'], - formatting.b_to_gb(pub_in), - formatting.b_to_gb(pub_out), - formatting.b_to_gb(pri_in), - formatting.b_to_gb(pri_out), - item.get('pool') or formatting.blank(), - ]) - except KeyboardInterrupt: - env.err("Printing collected results and then aborting.") + _get_hardware_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + try: + pass + except KeyboardInterrupt: + env.err("Printing collected results and then aborting.") env.out(env.fmt(table)) diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 3d580edad..896e61e20 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -194,3 +194,253 @@ def test_bandwidth_report(self): 300, ) self.assertEqual(expected_args, call.args) + + def test_virtual_bandwidth_report(self): + racks = self.set_mock('SoftLayer_Account', 'getVirtualDedicatedRacks') + racks.return_value = [{ + 'id': 1, + 'name': 'pool1', + 'metricTrackingObjectId': 1, + }, { + 'id': 2, + 'name': 'pool2', + }, { + 'id': 3, + 'name': 'pool3', + 'metricTrackingObjectId': 3, + }] + guests = self.set_mock('SoftLayer_Account', 'getVirtualGuests') + guests.return_value = [{ + 'id': 201, + 'metricTrackingObjectId': 201, + 'hostname': 'host1', + }, { + 'id': 202, + 'hostname': 'host2', + 'virtualRack': {'id': 2, 'bandwidthAllotmentTypeId': 2}, + }, { + 'id': 203, + 'metricTrackingObjectId': 203, + 'hostname': 'host3', + 'virtualRack': {'id': 2, 'bandwidthAllotmentTypeId': 2}, + }] + summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', + 'getSummaryData') + summary_data.return_value = [ + {'type': 'publicIn_net_octet', 'counter': 10}, + {'type': 'publicOut_net_octet', 'counter': 20}, + {'type': 'privateIn_net_octet', 'counter': 30}, + {'type': 'privateOut_net_octet', 'counter': 40}, + ] + + result = self.run_command([ + 'report', + 'bandwidth', + '--start=2016-02-04', + '--end=2016-03-04 12:34:56', + '--virtual', + ]) + + self.assert_no_fail(result) + stripped_output = '[' + result.output.split('[', 1)[1] + self.assertEqual([ + { + 'hostname': 'pool1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'pool3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'host1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'virtual', + }, { + 'hostname': 'host3', + 'pool': 2, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'virtual', + }], + json.loads(stripped_output), + ) + self.assertEqual( + 4, + len(self.calls('SoftLayer_Metric_Tracking_Object', + 'getSummaryData')), + ) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=1) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=3) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=201) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=203) + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', + identifier=1)[0] + expected_args = ( + '2016-02-04 00:00:00 ', + '2016-03-04 12:34:56 ', + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) + self.assertEqual(expected_args, call.args) + + def test_server_bandwidth_report(self): + racks = self.set_mock('SoftLayer_Account', 'getVirtualDedicatedRacks') + racks.return_value = [{ + 'id': 1, + 'name': 'pool1', + 'metricTrackingObjectId': 1, + }, { + 'id': 2, + 'name': 'pool2', + }, { + 'id': 3, + 'name': 'pool3', + 'metricTrackingObjectId': 3, + }] + hardware = self.set_mock('SoftLayer_Account', 'getHardware') + hardware.return_value = [{ + 'id': 101, + 'metricTrackingObject': {'id': 101}, + 'hostname': 'host1', + }, { + 'id': 102, + 'hostname': 'host2', + 'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2}, + }, { + 'id': 103, + 'metricTrackingObject': {'id': 103}, + 'hostname': 'host3', + 'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2}, + }] + + summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', + 'getSummaryData') + summary_data.return_value = [ + {'type': 'publicIn_net_octet', 'counter': 10}, + {'type': 'publicOut_net_octet', 'counter': 20}, + {'type': 'privateIn_net_octet', 'counter': 30}, + {'type': 'privateOut_net_octet', 'counter': 40}, + ] + + result = self.run_command([ + 'report', + 'bandwidth', + '--start=2016-02-04', + '--end=2016-03-04 12:34:56', + '--server', + ]) + + self.assert_no_fail(result) + stripped_output = '[' + result.output.split('[', 1)[1] + self.assertEqual([ + { + 'hostname': 'pool1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'pool3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'host1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'hardware', + }, { + 'hostname': 'host3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'hardware', + }, ], + json.loads(stripped_output), + ) + self.assertEqual( + 4, + len(self.calls('SoftLayer_Metric_Tracking_Object', + 'getSummaryData')), + ) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=101) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=103) + + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', + identifier=1)[0] + expected_args = ( + '2016-02-04 00:00:00 ', + '2016-03-04 12:34:56 ', + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) + self.assertEqual(expected_args, call.args) From ecb46a514f1c295ff6595bdd7edc5e3650dc0e92 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Feb 2021 18:40:33 -0400 Subject: [PATCH 0326/1385] fix the tox tool --- tests/CLI/modules/report_tests.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 896e61e20..8489aeeab 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -102,7 +102,7 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'pool', + 'type': 'pool' }, { 'hostname': 'pool3', 'pool': None, @@ -110,7 +110,7 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'pool', + 'type': 'pool' }, { 'hostname': 'host1', 'pool': None, @@ -118,15 +118,15 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'virtual', + 'type': 'hardware' }, { 'hostname': 'host3', - 'pool': 2, + 'pool': None, 'private_in': 30, 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'virtual', + 'type': 'hardware' }, { 'hostname': 'host1', 'pool': None, @@ -134,16 +134,15 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'hardware', + 'type': 'virtual' }, { 'hostname': 'host3', - 'pool': None, + 'pool': 2, 'private_in': 30, 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'hardware', - }], + 'type': 'virtual'}], json.loads(stripped_output), ) self.assertEqual( From ea5c5c9f31c1b6be116a6c1ae10c35414b2c5ee9 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 12 Feb 2021 20:29:26 -0400 Subject: [PATCH 0327/1385] #1400 add 2FA and classic APIKeys fields to user list as default values --- SoftLayer/CLI/user/list.py | 19 ++++++++++++++++--- SoftLayer/fixtures/SoftLayer_Account.py | 8 ++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/user/list.py b/SoftLayer/CLI/user/list.py index 142f824ab..10ba388a2 100644 --- a/SoftLayer/CLI/user/list.py +++ b/SoftLayer/CLI/user/list.py @@ -8,6 +8,8 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +TWO_FACTO_AUTH = 'externalBindingCount' +CLASSIC_API_KEYS = 'apiAuthenticationKeyCount' COLUMNS = [ column_helper.Column('id', ('id',)), @@ -17,15 +19,17 @@ column_helper.Column('status', ('userStatus', 'name')), column_helper.Column('hardwareCount', ('hardwareCount',)), column_helper.Column('virtualGuestCount', ('virtualGuestCount',)), - column_helper.Column('2FAs', ('externalBindingCount',)), - column_helper.Column('classicAPIKeys', ('apiAuthenticationKeyCount',)) + column_helper.Column('2FA', (TWO_FACTO_AUTH,)), + column_helper.Column('classicAPIKey', (CLASSIC_API_KEYS,)) ] DEFAULT_COLUMNS = [ 'id', 'username', 'email', - 'displayName' + 'displayName', + '2FA', + 'classicAPIKey', ] @@ -44,7 +48,16 @@ def cli(env, columns): table = formatting.Table(columns.columns) for user in users: + user = _yes_format(user, [TWO_FACTO_AUTH, CLASSIC_API_KEYS]) table.add_row([value or formatting.blank() for value in columns.row(user)]) env.fout(table) + + +def _yes_format(user, keys): + """Changes all dictionary values to yes whose keys are in the list. """ + for key in keys: + if user.get(key): + user[key] = 'yes' + return user diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index a1c667cf1..123e0bc47 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -691,13 +691,17 @@ 'id': 11100, 'userStatus': {'name': 'Active'}, 'username': 'SL1234', - 'virtualGuestCount': 99}, + 'virtualGuestCount': 99, + 'externalBindingCount': 1, + 'apiAuthenticationKeyCount': 1, + }, {'displayName': 'PulseL', 'hardwareCount': 100, 'id': 11111, 'userStatus': {'name': 'Active'}, 'username': 'sl1234-abob', - 'virtualGuestCount': 99} + 'virtualGuestCount': 99, + } ] getReservedCapacityGroups = [ From ac7465fe94e35dee8f256f2aa97eb08669336fd5 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 17 Feb 2021 15:35:12 -0400 Subject: [PATCH 0328/1385] Add the option network component by router to slcli hw create. --- SoftLayer/CLI/hardware/create.py | 8 ++++++- SoftLayer/managers/hardware.py | 8 ++++++- tests/CLI/modules/server_tests.py | 20 ++++++++++++++++ tests/managers/hardware_tests.py | 38 +++++++++++-------------------- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index 40fa871bc..a1d373e14 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -31,6 +31,10 @@ help="Exports options to a template file") @click.option('--wait', type=click.INT, help="Wait until the server is finished provisioning for up to X seconds before returning") +@click.option('--router-public', type=click.INT, + help="The ID of the public ROUTER on which you want the virtual server placed") +@click.option('--router-private', type=click.INT, + help="The ID of the private ROUTER on which you want the virtual server placed") @helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") @helpers.multi_option('--extra', '-e', help="Extra option Key Names") @environment.pass_env @@ -57,7 +61,9 @@ def cli(env, **args): 'port_speed': args.get('port_speed'), 'no_public': args.get('no_public') or False, 'extras': args.get('extra'), - 'network': args.get('network') + 'network': args.get('network'), + 'public_router': args.get('router_public', None), + 'private_router': args.get('router_private', None) } # Do not create hardware server with --test or --export diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 53939f2e1..ecf1a89c2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -492,7 +492,9 @@ def _generate_create_dict(self, hourly=True, no_public=False, extras=None, - network=None): + network=None, + public_router=None, + private_router=None): """Translates arguments into a dictionary for creating a server.""" extras = extras or [] @@ -535,6 +537,10 @@ def _generate_create_dict(self, 'domain': domain, }] } + if private_router: + extras['hardware'][0]['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + if public_router: + extras['hardware'][0]['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} if post_uri: extras['provisionScripts'] = [post_uri] diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9b88c81f2..5a2db4fc1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -418,6 +418,26 @@ def test_create_server_with_export(self, export_mock): self.assertIn("Successfully exported options to a template file.", result.output) export_mock.assert_called_once() + @mock.patch('SoftLayer.HardwareManager.place_order') + def test_create_server_with_router(self, order_mock): + order_mock.return_value = { + 'orderId': 98765, + 'orderDate': '2013-08-02 15:23:47' + } + + result = self.run_command(['--really', 'server', 'create', + '--size=S1270_8GB_2X1TBSATA_NORAID', + '--hostname=test', + '--domain=example.com', + '--datacenter=TEST00', + '--port-speed=100', + '--os=OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + '--router-private=123', + '--router-public=1234' + ]) + + self.assert_no_fail(result) + def test_edit_server_userdata_and_file(self): # Test both userdata and userfile at once with tempfile.NamedTemporaryFile() as userfile: diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 062cafc30..fb8b734ea 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -318,41 +318,29 @@ def test_generate_create_dict(self): 'port_speed': 10, 'hourly': True, 'extras': ['1_IPV6_ADDRESS'], - 'post_uri': 'http://example.com/script.php', - 'ssh_keys': [10], + 'public_router': 1111, + 'private_router': 1234 } - package = 'BARE_METAL_SERVER' - location = 'wdc07' - item_keynames = [ - '1_IP_ADDRESS', - 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', - 'REBOOT_KVM_OVER_IP', - 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'BANDWIDTH_0_GB_2', - '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', - '1_IPV6_ADDRESS' - ] - hourly = True - preset_keyname = 'S1270_8GB_2X1TBSATA_NORAID' extras = { 'hardware': [{ 'domain': 'giggles.woo', 'hostname': 'unicorn', - }], - 'provisionScripts': ['http://example.com/script.php'], - 'sshKeys': [{'sshKeyIds': [10]}] + 'primaryNetworkComponent': { + "router": { + "id": 1111 + } + }, + 'primaryBackendNetworkComponent': { + "router": { + "id": 1234 + } + } + }] } data = self.hardware._generate_create_dict(**args) - - self.assertEqual(package, data['package_keyname']) - self.assertEqual(location, data['location']) - for keyname in item_keynames: - self.assertIn(keyname, data['item_keynames']) self.assertEqual(extras, data['extras']) - self.assertEqual(preset_keyname, data['preset_keyname']) - self.assertEqual(hourly, data['hourly']) def test_generate_create_dict_network_key(self): args = { From 5a046cbea66e640e3fadd8811b00170ab6708f2c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 17 Feb 2021 16:01:50 -0400 Subject: [PATCH 0329/1385] Add unit test. --- tests/managers/hardware_tests.py | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index fb8b734ea..c709994b4 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -309,6 +309,52 @@ def test_generate_create_dict_no_regions(self): self.assertIn("Could not find valid location for: 'wdc01'", str(ex)) def test_generate_create_dict(self): + args = { + 'size': 'S1270_8GB_2X1TBSATA_NORAID', + 'hostname': 'unicorn', + 'domain': 'giggles.woo', + 'location': 'wdc07', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'port_speed': 10, + 'hourly': True, + 'extras': ['1_IPV6_ADDRESS'], + 'post_uri': 'http://example.com/script.php', + 'ssh_keys': [10], + } + + package = 'BARE_METAL_SERVER' + location = 'wdc07' + item_keynames = [ + '1_IP_ADDRESS', + 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', + 'REBOOT_KVM_OVER_IP', + 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'BANDWIDTH_0_GB_2', + '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', + '1_IPV6_ADDRESS' + ] + hourly = True + preset_keyname = 'S1270_8GB_2X1TBSATA_NORAID' + extras = { + 'hardware': [{ + 'domain': 'giggles.woo', + 'hostname': 'unicorn', + }], + 'provisionScripts': ['http://example.com/script.php'], + 'sshKeys': [{'sshKeyIds': [10]}] + } + + data = self.hardware._generate_create_dict(**args) + + self.assertEqual(package, data['package_keyname']) + self.assertEqual(location, data['location']) + for keyname in item_keynames: + self.assertIn(keyname, data['item_keynames']) + self.assertEqual(extras, data['extras']) + self.assertEqual(preset_keyname, data['preset_keyname']) + self.assertEqual(hourly, data['hourly']) + + def test_generate_create_dict_by_router_network_component(self): args = { 'size': 'S1270_8GB_2X1TBSATA_NORAID', 'hostname': 'unicorn', From f20508e44c3dd771be294df2aa2ef8709038c131 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Feb 2021 18:13:46 -0400 Subject: [PATCH 0330/1385] Fix team code review comments --- SoftLayer/CLI/report/bandwidth.py | 39 ++++++++++++------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index bd651c90a..5a29bf192 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -187,9 +187,9 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) -@click.option('--virtual', is_flag=True, help='show the all bandwidth summary virtual', +@click.option('--virtual', is_flag=True, help='Show the all bandwidth summary for each virtual server', default=False) -@click.option('--server', is_flag=True, help='show the all bandwidth summary bare metal', +@click.option('--server', is_flag=True, help='show the all bandwidth summary for each hardware server', default=False) @environment.pass_env def cli(env, start, end, sortby, virtual, server): @@ -233,30 +233,21 @@ def _input_to_table(item): item.get('pool') or formatting.blank(), ]) - if virtual: - for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end)): - _input_to_table(item) - try: - pass - except KeyboardInterrupt: - env.err("Printing virtual collected results and then aborting.") - - elif server: - try: + try: + if virtual: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + elif server: for item in itertools.chain(_get_pooled_bandwidth(env, start, end), _get_hardware_bandwidth(env, start, end)): _input_to_table(item) - except KeyboardInterrupt: - env.err("Printing server collected results and then aborting.") - else: - for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_hardware_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end)): - _input_to_table(item) - try: - pass - except KeyboardInterrupt: - env.err("Printing collected results and then aborting.") + else: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_hardware_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + except KeyboardInterrupt: + env.err("Printing collected results and then aborting.") env.out(env.fmt(table)) From 81afeafacb574682798b80b1a3a4b9761857cc51 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 23 Feb 2021 22:15:03 -0400 Subject: [PATCH 0331/1385] fix the tox tool --- SoftLayer/CLI/custom_types.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/custom_types.py b/SoftLayer/CLI/custom_types.py index 66167b68a..c5a2102a2 100644 --- a/SoftLayer/CLI/custom_types.py +++ b/SoftLayer/CLI/custom_types.py @@ -18,7 +18,7 @@ class NetworkParamType(click.ParamType): """ name = 'network' - def convert(self, value, param, ctx): + def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements try: # Inlined from python standard ipaddress module # https://docs.python.org/3/library/ipaddress.html diff --git a/setup.py b/setup.py index 2cb688c44..ad934b774 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ LONG_DESCRIPTION = readme_file.read() else: LONG_DESCRIPTION = DESCRIPTION - +# pylint: disable=inconsistent-return-statements setup( name='SoftLayer', version='5.9.2', From 8bf29e4e0e9c869853c778a8b09fa9715d181b06 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 09:53:08 -0400 Subject: [PATCH 0332/1385] fix tox tool --- SoftLayer/CLI/custom_types.py | 3 ++- SoftLayer/CLI/report/bandwidth.py | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/custom_types.py b/SoftLayer/CLI/custom_types.py index c5a2102a2..e0f27b042 100644 --- a/SoftLayer/CLI/custom_types.py +++ b/SoftLayer/CLI/custom_types.py @@ -9,6 +9,7 @@ import click +# pylint: disable=inconsistent-return-statements class NetworkParamType(click.ParamType): """Validates a network parameter type and converts to a tuple. @@ -18,7 +19,7 @@ class NetworkParamType(click.ParamType): """ name = 'network' - def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements + def convert(self, value, param, ctx): try: # Inlined from python standard ipaddress module # https://docs.python.org/3/library/ipaddress.html diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 5a29bf192..4ae2d0f68 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -175,9 +175,8 @@ def _get_virtual_bandwidth(env, start, end): @click.option( '--start', callback=_validate_datetime, - default=( - datetime.datetime.now() - datetime.timedelta(days=30) - ).strftime('%Y-%m-%d'), + default=(datetime.datetime.now() - datetime.timedelta(days=30) + ).strftime('%Y-%m-%d'), help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") @click.option( '--end', @@ -187,9 +186,11 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) -@click.option('--virtual', is_flag=True, help='Show the all bandwidth summary for each virtual server', +@click.option('--virtual', is_flag=True, + help='Show the all bandwidth summary for each virtual server', default=False) -@click.option('--server', is_flag=True, help='show the all bandwidth summary for each hardware server', +@click.option('--server', is_flag=True, + help='show the all bandwidth summary for each hardware server', default=False) @environment.pass_env def cli(env, start, end, sortby, virtual, server): From a172c908d4485ade7320f230769750aaa6afd780 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 11:10:37 -0400 Subject: [PATCH 0333/1385] fix tox tool --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ad934b774..ef532573a 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ LONG_DESCRIPTION = readme_file.read() else: LONG_DESCRIPTION = DESCRIPTION -# pylint: disable=inconsistent-return-statements + setup( name='SoftLayer', version='5.9.2', @@ -55,4 +55,4 @@ 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], -) +) # pylint: disable=inconsistent-return-statements From ab6962465aa11abc1cda02c0bb326858fad2c3db Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 11:29:24 -0400 Subject: [PATCH 0334/1385] fix tox tool --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ef532573a..2591b055a 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,8 @@ from setuptools import setup, find_packages +# pylint: disable=inconsistent-return-statements + DESCRIPTION = "A library for SoftLayer's API" if os.path.exists('README.rst'): @@ -55,4 +57,4 @@ 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], -) # pylint: disable=inconsistent-return-statements +) From 427294d8e2cc8a5ea6ee8abb544f088734ea25d3 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 16:57:38 -0400 Subject: [PATCH 0335/1385] fix tox tool --- SoftLayer/CLI/config/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 9b1259891..7f1833a4a 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -12,7 +12,7 @@ from SoftLayer.CLI import formatting -def get_api_key(client, username, secret): +def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. This will also generate an API key if one doesn't exist From 2afcd9d26901c20e7d60be97fd48d73e4f1c34f0 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 26 Feb 2021 10:36:29 -0400 Subject: [PATCH 0336/1385] Allow modifying Timeout for LoadBalancers --- SoftLayer/CLI/loadbal/pools.py | 3 +++ SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 ++ tests/CLI/modules/loadbal_tests.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index 148395cd2..bfd1d48f7 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -84,6 +84,8 @@ def add(env, identifier, **args): @click.option('--method', '-m', help="Balancing Method", type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) @click.option('--connections', '-c', type=int, help="Maximum number of connections to allow.") +@click.option('--clientTimeout', '-t', type=int, + help="maximum idle time in seconds(Range: 1 to 7200).") @click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") @click.option('--sslCert', '-x', help="SSL certificate ID. See `slcli ssl list`") @environment.pass_env @@ -108,6 +110,7 @@ def edit(env, identifier, listener, **args): 'method': 'loadBalancingMethod', 'connections': 'maxConn', 'sticky': 'sessionType', + 'clienttimeout': 'clientTimeout', 'sslcert': 'tlsCertificateId' } diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 94220cdea..52158f620 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -48,6 +48,7 @@ ], 'listeners': [ { + 'clientTimeout': 15, 'defaultPool': { 'healthMonitor': { 'uuid': '222222ab-bbcc-4f32-9b31-1b6d3a1959c8' @@ -97,6 +98,7 @@ 'protocolPort': 110, 'provisioningStatus': 'ACTIVE', 'tlsCertificateId': None, + 'clientTimeout': 25, 'uuid': 'a509723d-a3cb-4ae4-bc5b-5ecf04f890ff'} ], 'members': [ diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 84866d3f5..b2da4c374 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -49,7 +49,7 @@ def test_delete_pool(self): def test_edit_pool(self): result = self.run_command(['loadbal', 'pool-edit', '111111', '370a9f12-b3be-47b3-bfa5-8e460010000', '-f 510', - '-b 256', '-c 5']) + '-b 256', '-c 5', '-t 10']) self.assert_no_fail(result) def test_add_7p(self): From 5d88288c712e2eb494665c26aee828f8103ca809 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 26 Feb 2021 17:37:59 -0400 Subject: [PATCH 0337/1385] updating --- SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 52158f620..70e8b64cb 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -98,7 +98,7 @@ 'protocolPort': 110, 'provisioningStatus': 'ACTIVE', 'tlsCertificateId': None, - 'clientTimeout': 25, + 'clientTimeout': 30, 'uuid': 'a509723d-a3cb-4ae4-bc5b-5ecf04f890ff'} ], 'members': [ From 956d7fa6baef8b5c584663b488f29ff750d6dbfc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 26 Feb 2021 16:55:39 -0600 Subject: [PATCH 0338/1385] #1425 checking termLength when ordering --- SoftLayer/managers/ordering.py | 11 ++++++--- tests/managers/ordering_tests.py | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e1cbb6f31..dcfb4e186 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -406,11 +406,16 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): return prices @staticmethod - def get_item_price_id(core, prices): - """get item price id""" + def get_item_price_id(core, prices, term=0): + """get item price id + + core: None or a number to match against capacityRestrictionType + prices: list of SoftLayer_Product_Item_Price + term: int to match against SoftLayer_Product_Item_Price.termLength + """ price_id = None for price in prices: - if not price['locationGroupId']: + if not price['locationGroupId'] and price.get('termLength', 0) in {term, '', None}: restriction = price.get('capacityRestrictionType', False) # There is a price restriction. Make sure the price is within the restriction if restriction and core is not None: diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f42532c7b..48cf38735 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -841,3 +841,45 @@ def test_resolve_location_name_invalid(self): def test_resolve_location_name_not_exist(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") self.assertIn("does not exist", str(exc)) + + # https://github.com/softlayer/softlayer-python/issues/1425 + # Issues relating to checking prices based of the price.term relationship + def test_issues1425_zeroterm(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} + price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} + + # Test 0 termLength + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + # Test None termLength + price2['termLength'] = None + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + # Test '' termLength + price2['termLength'] = '' + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + def test_issues1425_nonzeroterm(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} + price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} + + # Test 36 termLength + price_id = self.ordering.get_item_price_id("8", [price2, price1], 36) + self.assertEqual(1234, price_id) + + # Test None-existing price for term + price_id = self.ordering.get_item_price_id("8", [price2, price1], 37) + self.assertEqual(None, price_id) \ No newline at end of file From 3b6aae8ca03a8378cb3a0697480f0fde24e0d6be Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 26 Feb 2021 17:01:07 -0600 Subject: [PATCH 0339/1385] tox fixes --- tests/managers/ordering_tests.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 48cf38735..7adcd684f 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -847,11 +847,11 @@ def test_resolve_location_name_not_exist(self): def test_issues1425_zeroterm(self): category1 = {'categoryCode': 'cat1'} price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 36} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 0} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} # Test 0 termLength price_id = self.ordering.get_item_price_id("8", [price2, price1]) @@ -870,11 +870,11 @@ def test_issues1425_zeroterm(self): def test_issues1425_nonzeroterm(self): category1 = {'categoryCode': 'cat1'} price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 36} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 0} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} # Test 36 termLength price_id = self.ordering.get_item_price_id("8", [price2, price1], 36) @@ -882,4 +882,4 @@ def test_issues1425_nonzeroterm(self): # Test None-existing price for term price_id = self.ordering.get_item_price_id("8", [price2, price1], 37) - self.assertEqual(None, price_id) \ No newline at end of file + self.assertEqual(None, price_id) From b9c759566b2ecddafc4730b47dfa3fc885f00a85 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 19:51:20 -0600 Subject: [PATCH 0340/1385] Add testing and support for python 3.9. Remove testtools as a test dependency. It's not used except for a version skip that is easy to replace. testtools still relies on unittest2, a python 2-ism that generates warnings in python 3.9. Fix the snapcraft release by updating to a newer snapcraft action that fixes the 'add-path' error. --- .github/workflows/release.yml | 46 +++++++------- .github/workflows/tests.yml | 108 ++++++++++++++++---------------- README.rst | 4 +- setup.py | 1 + tests/CLI/modules/user_tests.py | 5 +- tools/test-requirements.txt | 1 - tox.ini | 2 +- 7 files changed, 84 insertions(+), 83 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36ef10414..9b244a86b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,23 +1,23 @@ -name: Release - -on: - release: - types: [published] - -jobs: - release: - runs-on: ubuntu-latest - strategy: - matrix: - arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] - steps: - - name: Install Snapcraft - uses: samuelmeuli/action-snapcraft@v1.1.1 - with: - snapcraft_token: ${{ secrets.snapcraft_token }} - - name: Push to stable - run: | - VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` - echo Publishing $VERSION on ${{ matrix.arch }} - snapcraft release slcli $VERSION stable - +name: Release + +on: + release: + types: [published] + +jobs: + release: + runs-on: ubuntu-18.04 + strategy: + matrix: + arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] + steps: + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v1.2.0 + with: + snapcraft_token: ${{ secrets.snapcraft_token }} + - name: Push to stable + run: | + VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` + echo Publishing $VERSION on ${{ matrix.arch }} + snapcraft release slcli $VERSION stable + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7aa408ed8..7bc787791 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,54 +1,54 @@ -name: Tests - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.5,3.6,3.7,3.8] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Test - run: tox -e py - coverage: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Coverage - run: tox -e coverage - analysis: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Analysis - run: tox -e analysis \ No newline at end of file +name: Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5,3.6,3.7,3.8,3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Test + run: tox -e py + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Coverage + run: tox -e coverage + analysis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Analysis + run: tox -e analysis diff --git a/README.rst b/README.rst index 4f741a5d0..75e5d6f54 100644 --- a/README.rst +++ b/README.rst @@ -127,7 +127,7 @@ If you are using the library directly in python, you can do something like this. System Requirements ------------------- -* Python 3.5, 3.6, 3.7, or 3.8. +* Python 3.5, 3.6, 3.7, 3.8, or 3.9. * A valid SoftLayer API username and key. * A connection to SoftLayer's private network is required to use our private network API endpoints. @@ -150,6 +150,6 @@ Python Packages Copyright --------- -This software is Copyright (c) 2016-2019 SoftLayer Technologies, Inc. +This software is Copyright (c) 2016-2021 SoftLayer Technologies, Inc. See the bundled LICENSE file for more information. diff --git a/setup.py b/setup.py index 2591b055a..7bbc3ebe5 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index b6723a2b2..246a5b0a2 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -8,7 +8,6 @@ import sys import mock -import testtools from SoftLayer import testing @@ -168,10 +167,12 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) - @testtools.skipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): + if sys.version_info < (3, 6): + self.skipTest("Secrets module only exists in version 3.6+") + secrets.return_value = 'Q' confirm_mock.return_value = True result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'generate']) diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index d417e04c1..0f1ec684c 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -4,7 +4,6 @@ pytest pytest-cov mock sphinx -testtools ptable >= 0.9.2 click >= 7 requests >= 2.20.0 diff --git a/tox.ini b/tox.ini index c9b3b5e72..b1fa2c870 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38,pypy3,analysis,coverage,docs +envlist = py35,py36,py37,py38,py39,pypy3,analysis,coverage,docs [flake8] From f019f3dd68447b95aacce6978b86f21d2b72ddb3 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 20:12:20 -0600 Subject: [PATCH 0341/1385] Use testtools.SkipIf instead so that secrets mocking isn't broken for python 3.5. --- tests/CLI/modules/user_tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 246a5b0a2..3693d32b5 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -6,6 +6,7 @@ """ import json import sys +import unittest import mock @@ -167,12 +168,10 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) + @unittest.SkipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): - if sys.version_info < (3, 6): - self.skipTest("Secrets module only exists in version 3.6+") - secrets.return_value = 'Q' confirm_mock.return_value = True result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'generate']) From 8d73349a740a9887ad7cc97acf2057b32aff58ea Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 20:16:17 -0600 Subject: [PATCH 0342/1385] Fix casing --- tests/CLI/modules/user_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 3693d32b5..2f4c1c978 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -168,7 +168,7 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) - @unittest.SkipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") + @unittest.skipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): From fb138c6cc95a4c0eefcf0f393d67b04fb68c895d Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 2 Mar 2021 18:35:29 -0400 Subject: [PATCH 0343/1385] #1430 refactor Ordering verify_quote cleaning empty or None fields logic --- SoftLayer/managers/ordering.py | 6 +++--- tests/managers/ordering_tests.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e1cbb6f31..5faf8af60 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -218,11 +218,11 @@ def verify_quote(self, quote_id, extra): container = self.generate_order_template(quote_id, extra) clean_container = {} - # There are a few fields that wil cause exceptions in the XML endpoing if you send in '' - # reservedCapacityId and hostId specifically. But we clean all just to be safe. + # There are a few fields that wil cause exceptions in the XML endpoint if you send in '', + # or None in Rest endpoint (e.g. reservedCapacityId, hostId). But we clean all just to be safe. # This for some reason is only a problem on verify_quote. for key in container.keys(): - if container.get(key) != '': + if container.get(key): clean_container[key] = container[key] return self.client.call('SoftLayer_Billing_Order_Quote', 'verifyOrder', clean_container, id=quote_id) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f42532c7b..e790028ba 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -713,7 +713,8 @@ def test_clean_quote_verify(self): 'hostname': 'test1', 'domain': 'example.com' }], - 'testProperty': '' + 'testPropertyEmpty': '', + 'testPropertyNone': None } result = self.ordering.verify_quote(1234, extras) @@ -721,7 +722,8 @@ def test_clean_quote_verify(self): self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder') call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder')[0] order_container = call.args[0] - self.assertNotIn('testProperty', order_container) + self.assertNotIn('testPropertyEmpty', order_container) + self.assertNotIn('testPropertyNone', order_container) self.assertNotIn('reservedCapacityId', order_container) def test_get_item_capacity_core(self): From e4c72b8562c8a82c3fa3adf0a26aa6ff8f8f0b8c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:24:42 -0400 Subject: [PATCH 0344/1385] Add routers for each DC in `slcli hw create-options` --- SoftLayer/CLI/hardware/create_options.py | 29 +++++++++++++++++++++++- SoftLayer/fixtures/SoftLayer_Account.py | 18 +++++++++++++++ SoftLayer/managers/account.py | 10 ++++++++ tests/managers/account_tests.py | 4 ++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 4c7723fb9..5231904cd 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -5,6 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.managers import account from SoftLayer.managers import hardware @@ -18,8 +19,18 @@ def cli(env, prices, location=None): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) + account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) + _filter = '' + if location: + _filter = { + 'routers': { + 'topLevelLocation': {'name': {'operation': location}} + } + } + + routers = account_manager.get_routers(_filter) tables = [] # Datacenters @@ -34,6 +45,7 @@ def cli(env, prices, location=None): tables.append(_os_prices_table(options['operating_systems'], prices)) tables.append(_port_speed_prices_table(options['port_speeds'], prices)) tables.append(_extras_prices_table(options['extras'], prices)) + tables.append(_get_routers(routers)) # since this is multiple tables, this is required for a valid JSON object to be rendered. env.fout(formatting.listing(tables, separator='\n')) @@ -50,7 +62,7 @@ def _preset_prices_table(sizes, prices=False): for size in sizes: if size.get('hourlyRecurringFee', 0) + size.get('recurringFee', 0) + 1 > 0: table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) + "%.4f" % size['recurringFee']]) else: table = formatting.Table(['Size', 'Value'], title="Sizes") for size in sizes: @@ -146,3 +158,18 @@ def _get_price_data(price, item): if item in price: result = price[item] return result + + +def _get_routers(routers): + """Get all routers information + + :param routers: Routers data + """ + + table = formatting.Table(["id", "hostname", "name"], title='Routers') + for router in routers: + table.add_row([router['id'], + router['hostname'], + router['topLevelLocation']['longName'], ]) + table.align = 'l' + return table diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 123e0bc47..3c82b3da3 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1020,3 +1020,21 @@ "resourceTableId": 777777 } ] + +getRouters = [ + { + "accountId": 1, + "bareMetalInstanceFlag": 0, + "domain": "softlayer.com", + "fullyQualifiedDomainName": "fcr01a.ams01.softlayer.com", + "hardwareStatusId": 5, + "hostname": "fcr01a.ams01", + "id": 123456, + "serviceProviderId": 1, + "topLevelLocation": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + } + }] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 45ac3ad52..6b8c9f031 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -276,3 +276,13 @@ def get_account_all_billing_orders(self, limit=100, mask=None): """ return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) + + def get_routers(self, mask=None, _filter=None): + """Gets all the routers currently active on the account + + :param string mask: Object Mask + :param string _filter: Object filter + :returns: Routers + """ + + return self.client['SoftLayer_Account'].getRouters(filter=_filter, mask=mask) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index b051e5ee7..c5d2edf95 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -149,3 +149,7 @@ def test_get_item_details_with_invoice_item_id(self): self.manager.get_item_detail(123456) self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=123456) self.assert_called_with('SoftLayer_Billing_Invoice_Item', 'getBillingItem', identifier=123456) + + def test_get_routers(self): + self.manager.get_routers() + self.assert_called_with("SoftLayer_Account", "getRouters") From 78400f19bf15cbdb1cab499ff65548cb6d59629a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:47:52 -0400 Subject: [PATCH 0345/1385] fix tox tool --- SoftLayer/CLI/hardware/create_options.py | 12 +++--------- SoftLayer/managers/account.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 5231904cd..186a6fe44 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -22,15 +22,9 @@ def cli(env, prices, location=None): account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - _filter = '' - if location: - _filter = { - 'routers': { - 'topLevelLocation': {'name': {'operation': location}} - } - } - - routers = account_manager.get_routers(_filter) + + + routers = account_manager.get_routers(location) tables = [] # Datacenters diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 6b8c9f031..d008b92a3 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,12 +277,19 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, _filter=None): + def get_routers(self, mask=None, location=None): """Gets all the routers currently active on the account :param string mask: Object Mask - :param string _filter: Object filter + :param string location: location string :returns: Routers """ + object_filter = '' + if location: + object_filter = { + 'routers': { + 'topLevelLocation': {'name': {'operation': location}} + } + } - return self.client['SoftLayer_Account'].getRouters(filter=_filter, mask=mask) + return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) From 5d338a529322364c526e5ed9993214bf025439bc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 3 Mar 2021 15:50:21 -0600 Subject: [PATCH 0346/1385] v5.9.3 Release notes and updates --- CHANGELOG.md | 22 ++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d86588d91..70ab66753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Change Log + +## [5.9.3] - 2021-03-03 +https://github.com/softlayer/softlayer-python/compare/v5.9.2...v5.9.3 + +#### New Commands +- `slcli file|block disaster-recovery-failover` #1407 + +#### Improvements +- Unit testing for large integers #1403 +- Add Multi factor authentication to users list #1408 +- Add pagination to object storage list accounts. #1411 +- Add username lookup to slcli object-storage credential #1415 +- Add IOPs data to slcli block volume-list. #1418 +- Add 2FA and classic APIKeys fields to slcli user list as default values #1421 +- Add a flags in the report bandwidth #1420 +- Add the option network component by router to slcli hw create. #1422 +- Add slcli vs create by router data. #1414 +- Add testing and support for python 3.9. #1429 +- Checking for TermLength on prices #1428 + + + ## [5.9.2] - 2020-12-03 https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index a09e85706..b10b164d0 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.2' +VERSION = 'v5.9.3' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 7bbc3ebe5..6d51d38ce 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.2', + version='5.9.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From fa9da6a1dbd884212e65f8ba3a3564070e086b2c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:56:49 -0400 Subject: [PATCH 0347/1385] fix tox tool --- SoftLayer/CLI/hardware/create_options.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 186a6fe44..7d104d877 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -21,9 +21,6 @@ def cli(env, prices, location=None): hardware_manager = hardware.HardwareManager(env.client) account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - - - routers = account_manager.get_routers(location) tables = [] From f4fee95a9a83dd746388c9e37c9df1bc521febe7 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 9 Mar 2021 15:14:54 -0400 Subject: [PATCH 0348/1385] Add preset datatype in `slcli virtual detail` --- SoftLayer/CLI/virt/detail.py | 1 + SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 94c0c7994..731df19ea 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,6 +69,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) + table.add_row(['preset', result['billingItem']['orderItem']['preset']['keyName']]) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index ca72350c1..5a3bcc105 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -28,7 +28,8 @@ 'userRecord': { 'username': 'chechu', } - } + }, + 'preset': {'keyName': 'B1_8X16X100'} } }, 'datacenter': {'id': 50, 'name': 'TEST00', @@ -480,16 +481,16 @@ "categoryCode": "guest_disk0", "id": 81 }}, { - "description": "250 GB (SAN)", - "attributes": [ - { - "id": 198, - "attributeTypeKeyName": "SAN_DISK" - }], - "itemCategory": { - "categoryCode": "guest_disk0", - "id": 89 - }}], + "description": "250 GB (SAN)", + "attributes": [ + { + "id": 198, + "attributeTypeKeyName": "SAN_DISK" + }], + "itemCategory": { + "categoryCode": "guest_disk0", + "id": 89 + }}], 'guest_core': [{ "description": "4 x 2.0 GHz or higher Cores (Dedicated)", "attributes": [], @@ -497,13 +498,13 @@ "categoryCode": "guest_core", "id": 80 }}, - { - "description": "8 x 2.0 GHz or higher Cores", - "attributes": [], - "itemCategory": { - "categoryCode": "guest_core", - "id": 90 - }}] + { + "description": "8 x 2.0 GHz or higher Cores", + "attributes": [], + "itemCategory": { + "categoryCode": "guest_core", + "id": 90 + }}] } getReverseDomainRecords = [{ From c400ab560033f9282634197bbc3fb1dd52633757 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:02:54 -0400 Subject: [PATCH 0349/1385] Adding upgrade option to slcli hw. Adding cli unit test. --- SoftLayer/CLI/hardware/upgrade.py | 47 ++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Hardware_Server.py | 76 ++++++++++ SoftLayer/managers/hardware.py | 139 ++++++++++++++++++ tests/CLI/modules/server_tests.py | 27 ++++ 5 files changed, 290 insertions(+) create mode 100644 SoftLayer/CLI/hardware/upgrade.py diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py new file mode 100644 index 000000000..d766000ab --- /dev/null +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -0,0 +1,47 @@ +"""Upgrade a hardware server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--memory', type=click.INT, help="Memory Size in GB") +@click.option('--network', help="Network port speed in Mbps", + default=None, + type=click.Choice(['100', '100 Redundant', '100 Dual', + '1000', '1000 Redundant', '1000 Dual', + '10000', '10000 Redundant', '10000 Dual']) + ) +@click.option('--drive-controller', + help="Drive Controller", + default=None, + type=click.Choice(['Non-RAID', 'RAID'])) +@click.option('--public-bandwidth', type=click.INT, help="Public Bandwidth in GB") +@click.option('--test', is_flag=True, default=False, help="Do not actually upgrade the hardware server") +@environment.pass_env +def cli(env, identifier, memory, network, drive_controller, public_bandwidth, test): + """Upgrade a Hardware Server.""" + + mgr = SoftLayer.HardwareManager(env.client) + + if not any([memory, network, drive_controller, public_bandwidth]): + raise exceptions.ArgumentError("Must provide " + " [--memory], [--network], [--drive-controller], or [--public-bandwidth]") + + hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Hardware') + if not test: + if not (env.skip_confirmations or formatting.confirm( + "This action will incur charges on your account. Continue?")): + raise exceptions.CLIAbort('Aborted') + + if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, + public_bandwidth=public_bandwidth, test=test): + raise exceptions.CLIAbort('Hardware Server Upgrade Failed') + env.fout('Successfully Upgraded.') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 40c4bd6d1..eee8fe314 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -256,6 +256,7 @@ ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), + ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 5c26d20da..a28d0fc13 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -6,6 +6,9 @@ 'billingItem': { 'id': 6327, 'recurringFee': 1.54, + 'package': { + 'id': 911 + }, 'nextInvoiceTotalRecurringAmount': 16.08, 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, @@ -262,3 +265,76 @@ } } } + +getUpgradeItemPrices = [ + { + "id": 21525, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + } + ], + "item": { + "capacity": "10000", + "description": "10 Gbps Redundant Public & Private Network Uplinks", + "id": 4342, + "keyName": "10_GBPS_REDUNDANT_PUBLIC_PRIVATE_NETWORK_UPLINKS" + } + }, + { + "hourlyRecurringFee": ".247", + "id": 209391, + "recurringFee": "164", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "keyName": "RAM_32_GB_DDR4_2133_ECC_NON_REG" + } + }, + { + "hourlyRecurringFee": ".068", + "id": 22482, + "recurringFee": "50", + "categories": [ + { + "categoryCode": "disk_controller", + "id": 11, + "name": "Disk Controller", + } + ], + "item": { + "capacity": "0", + "description": "RAID", + "id": 4478, + "keyName": "DISK_CONTROLLER_RAID", + } + }, + { + "id": 50357, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "bandwidth", + "id": 10, + "name": "Public Bandwidth", + } + ], + "item": { + "capacity": "500", + "description": "500 GB Bandwidth Allotment", + "id": 6177, + "keyName": "BANDWIDTH_500_GB" + } + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 53939f2e1..5eeac0709 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -5,11 +5,13 @@ :license: MIT, see LICENSE for more details. """ +import datetime import logging import socket import time from SoftLayer.decoration import retry +from SoftLayer import exceptions from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager @@ -780,6 +782,143 @@ def get_hardware_item_prices(self, location): return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, id=package['id']) + def upgrade(self, instance_id, memory=None, + nic_speed=None, drive_controller=None, + public_bandwidth=None, test=False): + """Upgrades a hardware server instance. + + :param int instance_id: Instance id of the hardware server to be upgraded. + :param string memory: Memory size. + :param string nic_speed: Network Port Speed data. + :param string drive_controller: Drive Controller data. + :param string public_bandwidth: Public keyName data. + :param bool test: Test option to verify the request. + + :returns: bool + """ + upgrade_prices = self._get_upgrade_prices(instance_id) + prices = [] + data = {} + + if memory: + data['memory'] = memory + if nic_speed: + data['nic_speed'] = nic_speed + if drive_controller: + data['disk_controller'] = drive_controller + if public_bandwidth: + data['bandwidth'] = public_bandwidth + + server_response = self.get_instance(instance_id) + package_id = server_response['billingItem']['package']['id'] + + maintenance_window = datetime.datetime.now(utils.UTC()) + order = { + 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', + 'properties': [{ + 'name': 'MAINTENANCE_WINDOW', + 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") + }], + 'hardware': [{'id': int(instance_id)}], + 'packageId': package_id + } + + for option, value in data.items(): + price_id = self._get_prices_for_upgrade_option(upgrade_prices, option, value) + if not price_id: + # Every option provided is expected to have a price + raise exceptions.SoftLayerError( + "Unable to find %s option with value %s" % (option, value)) + + prices.append({'id': price_id}) + + order['prices'] = prices + + if prices: + if test: + self.client['Product_Order'].verifyOrder(order) + else: + self.client['Product_Order'].placeOrder(order) + return True + return False + + @retry(logger=LOGGER) + def get_instance(self, instance_id): + """Get details about a hardware server instance. + + :param int instance_id: the instance ID + :returns: A dictionary containing a large amount of information about + the specified instance. + """ + mask = [ + 'billingItem[id,package[id,keyName]]' + ] + mask = "mask[%s]" % ','.join(mask) + + return self.hardware.getObject(id=instance_id, mask=mask) + + def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): + """Following Method gets all the price ids related to upgrading a VS. + + :param int instance_id: Instance id of the VS to be upgraded + + :returns: list + """ + mask = [ + 'id', + 'locationGroupId', + 'categories[name,id,categoryCode]', + 'item[keyName,description,capacity,units]' + ] + mask = "mask[%s]" % ','.join(mask) + return self.hardware.getUpgradeItemPrices(include_downgrade_options, id=instance_id, mask=mask) + + @staticmethod + def _get_prices_for_upgrade_option(upgrade_prices, option, value): + """Find the price id for the option and value to upgrade. This + + :param list upgrade_prices: Contains all the prices related to a + hardware server upgrade. + :param string option: Describes type of parameter to be upgraded + :param value: The value of the parameter to be upgraded + """ + + option_category = { + 'memory': 'ram', + 'nic_speed': 'port_speed', + 'disk_controller': 'disk_controller', + 'bandwidth': 'bandwidth' + } + category_code = option_category.get(option) + + for price in upgrade_prices: + if price.get('categories') is None or price.get('item') is None: + continue + + product = price.get('item') + for category in price.get('categories'): + if not (category.get('categoryCode') == category_code): + continue + + if option == 'disk_controller': + if value == product.get('description'): + return price.get('id') + elif option == 'nic_speed': + if value.isdigit(): + if str(product.get('capacity')) == str(value): + return price.get('id') + else: + split_nic_speed = value.split(" ") + if str(product.get('capacity')) == str(split_nic_speed[0]) and \ + split_nic_speed[1] in product.get("description"): + return price.get('id') + elif option == 'bandwidth': + if str(product.get('capacity')) == str(value): + return price.get('id') + else: + if str(product.get('capacity')) == str(value): + return price.get('id') + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9b88c81f2..efd7eb32b 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -883,3 +883,30 @@ def test_hardware_guests_empty(self): result = self.run_command(['hw', 'guests', '123456']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_upgrade_no_options(self, ): + result = self.run_command(['hw', 'upgrade', '100']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.ArgumentError) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_aborted(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--memory=1']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_test(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--test', '--memory=32', '--public-bandwidth=500', + '--drive-controller=RAID', '--network=10000 Redundant']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', + '--drive-controller=RAID', '--network=10000 Redundant']) + self.assert_no_fail(result) + From 8addedbf3225593590f205b559378ea1d6472554 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:38:20 -0400 Subject: [PATCH 0350/1385] Adding unit test. --- SoftLayer/managers/hardware.py | 4 ++-- tests/managers/hardware_tests.py | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5eeac0709..fc083995c 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -788,10 +788,10 @@ def upgrade(self, instance_id, memory=None, """Upgrades a hardware server instance. :param int instance_id: Instance id of the hardware server to be upgraded. - :param string memory: Memory size. + :param int memory: Memory size. :param string nic_speed: Network Port Speed data. :param string drive_controller: Drive Controller data. - :param string public_bandwidth: Public keyName data. + :param int public_bandwidth: Public keyName data. :param bool test: Test option to verify the request. :returns: bool diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 062cafc30..e44301c73 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -807,6 +807,47 @@ def test_get_hardware_guests(self): self.assertEqual("NSX-T Manager", result[0]['hostname']) + def test_get_price_id_memory_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 1}, 'id': 99} + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(99, result) + + def test_get_price_id_mismatch_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'ram1'}], 'item': {'capacity': 1}, 'id': 90}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 2}, 'id': 91}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 1}, 'id': 92}, + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(92, result) + + def test_upgrade(self): + result = self.hardware.upgrade(1, memory=32) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'], [{'id': 209391}]) + + def test_upgrade_blank(self): + result = self.hardware.upgrade(1) + + self.assertEqual(result, False) + self.assertEqual(self.calls('SoftLayer_Product_Order', 'placeOrder'), []) + + def test_upgrade_full(self): + result = self.hardware.upgrade(1, memory=32, nic_speed="10000 Redundant", drive_controller="RAID", + public_bandwidth=500, test=False) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual([{'id': 209391}, {'id': 21525}, {'id': 22482}, {'id': 50357}], order_container['prices']) + class HardwareHelperTests(testing.TestCase): From d7289bb61843e8f3a4aade783fd701fb3f3a921f Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:45:26 -0400 Subject: [PATCH 0351/1385] Adding documentation. --- docs/cli/hardware.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index f7691e700..e4d99998c 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -119,3 +119,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.guests:cli :prog: hardware guests :show-nested: + +.. click:: SoftLayer.CLI.hardware.upgrade:cli + :prog: hardware upgrade + :show-nested: From 03d032933a31dbe41dfb0d83cbaa043820e98c07 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 20:10:53 -0400 Subject: [PATCH 0352/1385] Adding documentation. Fix tests. --- tests/CLI/modules/server_tests.py | 1 - tests/managers/hardware_tests.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index efd7eb32b..803d4aab1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -909,4 +909,3 @@ def test_upgrade(self, confirm_mock): result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) - diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e44301c73..4aa3410b8 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -846,7 +846,10 @@ def test_upgrade_full(self): self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] - self.assertEqual([{'id': 209391}, {'id': 21525}, {'id': 22482}, {'id': 50357}], order_container['prices']) + self.assertIn({'id': 209391}, order_container['prices']) + self.assertIn({'id': 21525}, order_container['prices']) + self.assertIn({'id': 22482}, order_container['prices']) + self.assertIn({'id': 50357}, order_container['prices']) class HardwareHelperTests(testing.TestCase): From 556b1cc1d9290a5fbd67d3ffb97aaf9562d532bf Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 20:41:00 -0400 Subject: [PATCH 0353/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fc083995c..d0436e54f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -20,7 +20,7 @@ LOGGER = logging.getLogger(__name__) # Invalid names are ignored due to long method names and short argument names -# pylint: disable=invalid-name, no-self-use +# pylint: disable=invalid-name, no-self-use, too-many-lines EXTRA_CATEGORIES = ['pri_ipv6_addresses', 'static_ipv6_addresses', @@ -881,6 +881,8 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded :param value: The value of the parameter to be upgraded + + :returns: int """ option_category = { @@ -897,7 +899,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): product = price.get('item') for category in price.get('categories'): - if not (category.get('categoryCode') == category_code): + if not category.get('categoryCode') == category_code: continue if option == 'disk_controller': From 9741084bbf95d4397815baad71df5838342561b4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:22:31 -0400 Subject: [PATCH 0354/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d0436e54f..5baedd314 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -880,7 +880,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): :param list upgrade_prices: Contains all the prices related to a hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :param value: The value of the parameter to be upgraded + :param value value: The value of the parameter to be upgraded :returns: int """ From f8a418af42bb1d7c4b399fadc3a02e65e3ff5b20 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:29:36 -0400 Subject: [PATCH 0355/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5baedd314..b303fb5f6 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -880,7 +880,6 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): :param list upgrade_prices: Contains all the prices related to a hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :param value value: The value of the parameter to be upgraded :returns: int """ From 208693481aeaa18daf8887831f1d89f6e6a9379d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:40:55 -0400 Subject: [PATCH 0356/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index b303fb5f6..7aef731f8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -881,9 +881,9 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :returns: int + :returns: A item price id. """ - + price_id = None option_category = { 'memory': 'ram', 'nic_speed': 'port_speed', @@ -903,22 +903,24 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): if option == 'disk_controller': if value == product.get('description'): - return price.get('id') + price_id = price.get('id') elif option == 'nic_speed': if value.isdigit(): if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') else: split_nic_speed = value.split(" ") if str(product.get('capacity')) == str(split_nic_speed[0]) and \ split_nic_speed[1] in product.get("description"): - return price.get('id') + price_id = price.get('id') elif option == 'bandwidth': if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') else: if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') + + return price_id def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): From 21a5db744e5e3b239d6fafb1c50b7acdc4301b4a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:42:26 -0400 Subject: [PATCH 0357/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 7aef731f8..ba4494a3e 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -881,7 +881,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :returns: A item price id. + :return: int. """ price_id = None option_category = { From 4443f0b3d796e07f44b55cec7d64b11aff31bc68 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 15 Mar 2021 09:33:37 -0400 Subject: [PATCH 0358/1385] Fix Teams code review comments --- SoftLayer/CLI/virt/detail.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 731df19ea..0829f782f 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,7 +69,10 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) - table.add_row(['preset', result['billingItem']['orderItem']['preset']['keyName']]) + table.add_row(['preset', utils.lookup(result, 'billingItem', + 'orderItem', + 'preset', + 'keyName') or {}]) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) From 60a6febaa00bf86f370bf76da7f7a2a0454f4b16 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 16 Mar 2021 09:07:09 -0400 Subject: [PATCH 0359/1385] Fix Teams code review comments --- SoftLayer/CLI/virt/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 0829f782f..01a66cc9f 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -72,7 +72,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', - 'keyName') or {}]) + 'keyName') or '-']) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) From 9c9d4cf1cb62ffa763d3d830477d7d352fa90818 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 17 Mar 2021 17:08:51 -0400 Subject: [PATCH 0360/1385] Add the authorize block and file storage to a hw. --- SoftLayer/CLI/hardware/authorize_storage.py | 25 +++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Account.py | 19 ++++++++++++++++ SoftLayer/fixtures/SoftLayer_Hardware.py | 2 ++ SoftLayer/managers/hardware.py | 24 ++++++++++++++++++++ tests/CLI/modules/server_tests.py | 11 +++++++++ tests/managers/hardware_tests.py | 5 +++++ 7 files changed, 87 insertions(+) create mode 100644 SoftLayer/CLI/hardware/authorize_storage.py diff --git a/SoftLayer/CLI/hardware/authorize_storage.py b/SoftLayer/CLI/hardware/authorize_storage.py new file mode 100644 index 000000000..013896883 --- /dev/null +++ b/SoftLayer/CLI/hardware/authorize_storage.py @@ -0,0 +1,25 @@ +"""Authorize File or Block Storage to a Hardware Server""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--username-storage', '-u', type=click.STRING, + help="The storage username to be added to the hardware server") +@environment.pass_env +def cli(env, identifier, username_storage): + """Authorize File or Block Storage to a Hardware Server. + """ + hardware = SoftLayer.HardwareManager(env.client) + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') + + if not hardware.authorize_storage(hardware_id, username_storage): + raise exceptions.CLIAbort('Authorize Storage Failed') + env.fout('Successfully Storage Added.') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..74a87c10b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -256,6 +256,7 @@ ('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'), ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'), ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), + ('hardware:authorize-storage', 'SoftLayer.CLI.hardware.authorize_storage:cli'), ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 123e0bc47..75e565004 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1020,3 +1020,22 @@ "resourceTableId": 777777 } ] + +getNetworkStorage = [ + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2016-01-21T12:11:07-06:00", + "id": 1234567, + "nasType": "ISCSI", + "username": "SL01SEL301234-11", + }, + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2015-04-29T07:55:55-06:00", + "id": 4917123, + "nasType": "NAS", + "username": "SL01SEV1234567_111" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index cb902b556..fd6bf9535 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -57,3 +57,5 @@ {'tag': {'name': 'a tag'}} ], } + +allowAccessToNetworkStorageList = True diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ecf1a89c2..34a6c16cf 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -786,6 +786,30 @@ def get_hardware_item_prices(self, location): return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, id=package['id']) + def authorize_storage(self, hardware_id, username_storage): + """Authorize File or Block Storage to a Hardware Server. + + :param int hardware_id: Hardware server id. + :param string username_storage: Storage username. + + :return: bool. + """ + _filter = {"networkStorage": {"username": {"operation": username_storage}}} + + storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + storage_template = [ + { + "id": storage_result[0]['id'], + "username": username_storage + } + ] + + result = self.client.call('Hardware', 'allowAccessToNetworkStorageList', + storage_template, id=hardware_id) + + return result + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 5a2db4fc1..3434643a2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -903,3 +903,14 @@ def test_hardware_guests_empty(self): result = self.run_command(['hw', 'guests', '123456']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_hw_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) + + self.assertEqual(result.exit_code, 2) + + def test_authorize_hw(self): + result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index c709994b4..e1e3f8cfd 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -841,6 +841,11 @@ def test_get_hardware_guests(self): self.assertEqual("NSX-T Manager", result[0]['hostname']) + def test_authorize_storage(self): + options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") + + self.assertEqual(True, options) + class HardwareHelperTests(testing.TestCase): From f4b835dfc340f068646db18dbb19fb76008fefa6 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 18 Mar 2021 10:45:01 -0400 Subject: [PATCH 0361/1385] Add documentation. --- docs/cli/hardware.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index f7691e700..490a6608c 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -119,3 +119,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.guests:cli :prog: hardware guests :show-nested: + +.. click:: SoftLayer.CLI.hardware.authorize_storage:cli + :prog: hardware authorize-storage + :show-nested: From ac8a4b492080567c6fa938b28379fde8fa4776c5 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 18 Mar 2021 14:54:32 -0400 Subject: [PATCH 0362/1385] Fix method documentation. --- SoftLayer/managers/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ba4494a3e..42fd25ba0 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -858,9 +858,9 @@ def get_instance(self, instance_id): return self.hardware.getObject(id=instance_id, mask=mask) def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): - """Following Method gets all the price ids related to upgrading a VS. + """Following Method gets all the price ids related to upgrading a Hardware Server. - :param int instance_id: Instance id of the VS to be upgraded + :param int instance_id: Instance id of the Hardware Server to be upgraded. :returns: list """ From 1392433cb27e4bdb3d9581ddef08b2f6e23db3c6 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Thu, 18 Mar 2021 18:46:54 -0400 Subject: [PATCH 0363/1385] Update authorize_storage.py Fix docstring. --- SoftLayer/CLI/hardware/authorize_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/authorize_storage.py b/SoftLayer/CLI/hardware/authorize_storage.py index 013896883..69b4ba273 100644 --- a/SoftLayer/CLI/hardware/authorize_storage.py +++ b/SoftLayer/CLI/hardware/authorize_storage.py @@ -15,8 +15,7 @@ help="The storage username to be added to the hardware server") @environment.pass_env def cli(env, identifier, username_storage): - """Authorize File or Block Storage to a Hardware Server. - """ + """Authorize File or Block Storage to a Hardware Server.""" hardware = SoftLayer.HardwareManager(env.client) hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') From 605cab1eb1464919582c0e7fd33a33e8e7e93184 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 12:59:43 -0400 Subject: [PATCH 0364/1385] Fix exception storage username. --- SoftLayer/managers/hardware.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 34a6c16cf..309a019b4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -797,6 +797,10 @@ def authorize_storage(self, hardware_id, username_storage): _filter = {"networkStorage": {"username": {"operation": username_storage}}} storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + if len(storage_result) == 0: + raise SoftLayerError("The Storage with username: %s was not found, please" + " enter a valid storage username" % username_storage) storage_template = [ { From acde527815b9955a8a3969e8a366b3e06c8ab125 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 13:02:42 -0400 Subject: [PATCH 0365/1385] Add a unit test to avoid storage username ex. --- tests/CLI/modules/server_tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 3434643a2..6c746c9d9 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -910,6 +910,16 @@ def test_authorize_hw_no_confirm(self, confirm_mock): result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) self.assertEqual(result.exit_code, 2) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_hw_empty(self, confirm_mock): + confirm_mock.return_value = True + storage_result = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + storage_result.return_value = [] + result = self.run_command(['hw', 'authorize-storage', '--username-storage=#', '1234']) + + self.assertEqual(str(result.exception), "The Storage with username: # was not found, " + "please enter a valid storage username") def test_authorize_hw(self): result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) From 68f2a99f7e92b03392b9da3c05ec46d998184884 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 13:06:49 -0400 Subject: [PATCH 0366/1385] Add a unit test to manager for storage username. --- tests/managers/hardware_tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e1e3f8cfd..7a008e4fd 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -845,6 +845,13 @@ def test_authorize_storage(self): options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") self.assertEqual(True, options) + + def test_authorize_storage_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + mock.return_value = [] + self.assertRaises(SoftLayer.exceptions.SoftLayerError, + self.hardware.authorize_storage, + 1234, "#") class HardwareHelperTests(testing.TestCase): From 239452a9c1c24511e5c7c6f175835da3331d1dff Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 19 Mar 2021 13:19:55 -0400 Subject: [PATCH 0367/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- tests/CLI/modules/server_tests.py | 2 +- tests/managers/hardware_tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 309a019b4..7f0ba1baf 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -797,7 +797,7 @@ def authorize_storage(self, hardware_id, username_storage): _filter = {"networkStorage": {"username": {"operation": username_storage}}} storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) - + if len(storage_result) == 0: raise SoftLayerError("The Storage with username: %s was not found, please" " enter a valid storage username" % username_storage) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 6c746c9d9..3d3b440f5 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -910,7 +910,7 @@ def test_authorize_hw_no_confirm(self, confirm_mock): result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) self.assertEqual(result.exit_code, 2) - + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_authorize_hw_empty(self, confirm_mock): confirm_mock.return_value = True diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 7a008e4fd..e39846e31 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -845,7 +845,7 @@ def test_authorize_storage(self): options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") self.assertEqual(True, options) - + def test_authorize_storage_empty(self): mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') mock.return_value = [] From 3853a1f45d7da5480247685b6ae7c2996882951e Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 19 Mar 2021 15:43:34 -0400 Subject: [PATCH 0368/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 1 + tests/CLI/modules/server_tests.py | 1 + 2 files changed, 2 insertions(+) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8cb4f7acb..e13394355 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -956,6 +956,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): return price_id + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f92fdf9d7..de7ccd95e 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -923,6 +923,7 @@ def test_authorize_hw_empty(self, confirm_mock): def test_authorize_hw(self): result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) def test_upgrade_no_options(self, ): result = self.run_command(['hw', 'upgrade', '100']) From fb00f9dd62314fcd6999b9b3e6b7598e6ba5a33c Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 22 Mar 2021 15:45:29 -0400 Subject: [PATCH 0369/1385] Add authorize block, file and portable storage to a vs. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/authorize_storage.py | 41 ++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Account.py | 19 +++++++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 6 +++ SoftLayer/managers/vs.py | 42 +++++++++++++++++++ docs/cli/vs.rst | 4 ++ tests/CLI/modules/vs/vs_tests.py | 35 ++++++++++++++++ tests/managers/vs/vs_tests.py | 16 +++++++ 8 files changed, 164 insertions(+) create mode 100644 SoftLayer/CLI/virt/authorize_storage.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..1e1a2db17 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -45,6 +45,7 @@ ('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'), ('virtual:usage', 'SoftLayer.CLI.virt.usage:cli'), ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), + ('virtual:authorize-storage', 'SoftLayer.CLI.virt.authorize_storage:cli'), ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py new file mode 100644 index 000000000..3929e863f --- /dev/null +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -0,0 +1,41 @@ +"""Authorize File, Block and Portable Storage to a Virtual Server""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--username-storage', '-u', type=click.STRING, + help="The storage username to be added to the virtual server") +@click.option('--portable-id', type=click.INT, + help="The portable storage id to be added to the virtual server") +@environment.pass_env +def cli(env, identifier, username_storage, portable_id): + """Authorize File, Block and Portable Storage to a Virtual Server. + """ + vs = SoftLayer.VSManager(env.client) + vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') + table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") + table.align['name'] = 'r' + table.align['value'] = 'l' + + if username_storage: + if not vs.authorize_storage(vs_id, username_storage): + raise exceptions.CLIAbort('Authorize Volume Failed') + env.fout('Successfully Volume: %s was Added.' % username_storage) + if portable_id: + portable_id = helpers.resolve_id(vs.resolve_ids, portable_id, 'storage') + portable_result = vs.attach_portable_storage(vs_id, portable_id) + + env.fout('Successfully Portable Storage: %i was Added.' % portable_id) + + table.add_row(['Id', portable_result['id']]) + table.add_row(['createDate', portable_result['createDate']]) + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 3c82b3da3..502c6b295 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1038,3 +1038,22 @@ "statusId": 2 } }] + +getNetworkStorage = [ + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2016-01-21T12:11:07-06:00", + "id": 1234567, + "nasType": "ISCSI", + "username": "SL01SEL301234-11", + }, + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2015-04-29T07:55:55-06:00", + "id": 4917123, + "nasType": "NAS", + "username": "SL01SEV1234567_111" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index ca72350c1..76d95dde0 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -875,3 +875,9 @@ migrate = True migrateDedicatedHost = True +allowAccessToNetworkStorageList = True + +attachDiskImage = { + "createDate": "2021-03-22T13:15:31-06:00", + "id": 1234567 + } diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index b247e8be5..c0eb91b9a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1404,3 +1404,45 @@ def get_hardware_guests(self): object_filter = {"hardware": {"virtualHost": {"id": {"operation": "not null"}}}} mask = "mask[virtualHost[guests[powerState]]]" return self.client.call('SoftLayer_Account', 'getHardware', mask=mask, filter=object_filter) + + def authorize_storage(self, vs_id, username_storage): + """Authorize File or Block Storage to a Virtual Server. + + :param int vs_id: Virtual server id. + :param string username_storage: Storage username. + + :return: bool. + """ + _filter = {"networkStorage": {"username": {"operation": username_storage}}} + + storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + if len(storage_result) == 0: + raise SoftLayerError("The Storage with username: %s was not found, please" + " enter a valid storage username" % username_storage) + + storage_template = [ + { + "id": storage_result[0]['id'], + "username": username_storage + } + ] + + result = self.client.call('SoftLayer_Virtual_Guest', 'allowAccessToNetworkStorageList', + storage_template, id=vs_id) + + return result + + def attach_portable_storage(self, vs_id, portable_id): + """Attach portal storage to a Virtual Server. + + :param int vs_id: Virtual server id. + :param int portable_id: Portal storage id. + + :return: SoftLayer_Provisioning_Version1_Transaction. + """ + disk_id = portable_id + result = self.client.call('SoftLayer_Virtual_Guest', 'attachDiskImage', + disk_id, id=vs_id) + + return result diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 811e38c98..6227e0570 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -267,6 +267,10 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual migrate :show-nested: +.. click:: SoftLayer.CLI.virt.authorize_storage:cli + :prog: virtual authorize-storage + :show-nested: + Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 7fb23b97b..de3a310b9 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -892,3 +892,38 @@ def test_credentail(self): "username": "user", "password": "pass" }]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_storage_vs_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['vs', 'authorize-storage', '-u', '1234']) + + self.assertEqual(result.exit_code, 2) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_vs_empty(self, confirm_mock): + confirm_mock.return_value = True + storage_result = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + storage_result.return_value = [] + result = self.run_command(['vs', 'authorize-storage', '--username-storage=#', '1234']) + + self.assertEqual(str(result.exception), "The Storage with username: # was not found, " + "please enter a valid storage username") + + def test_authorize_storage_vs(self): + result = self.run_command(['vs', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) + + def test_authorize_portable_storage_vs(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'attachDiskImage') + mock.return_value = { + "createDate": "2021-03-22T13:15:31-06:00", + "id": 1234567 + } + result = self.run_command(['vs', 'authorize-storage', '--portable-id=12345', '1234']) + self.assert_no_fail(result) + + def test_authorize_volume_and_portable_storage_vs(self): + result = self.run_command(['vs', 'authorize-storage', '--username-storage=SL01SEL301234-11', + '--portable-id=12345', '1234']) + self.assert_no_fail(result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index c75e5e3b9..d5202bbe8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1309,3 +1309,19 @@ def test_get_hardware_guests(self): result = self.vs.get_hardware_guests() self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname']) + + def test_authorize_storage(self): + options = self.vs.authorize_storage(1234, "SL01SEL301234-11") + + self.assertEqual(True, options) + + def test_authorize_storage_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + mock.return_value = [] + self.assertRaises(SoftLayer.exceptions.SoftLayerError, + self.vs.authorize_storage, + 1234, "#") + + def test_authorize_portable_storage(self): + options = self.vs.attach_portable_storage(1234, 1234567) + self.assertEqual(1234567, options['id']) From 75444c4ea9635ac40082c672115a4e55f6a610a1 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Mon, 22 Mar 2021 16:03:46 -0400 Subject: [PATCH 0370/1385] Fix tox analysis. --- SoftLayer/CLI/virt/authorize_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py index 3929e863f..d2080eb52 100644 --- a/SoftLayer/CLI/virt/authorize_storage.py +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -18,8 +18,7 @@ help="The portable storage id to be added to the virtual server") @environment.pass_env def cli(env, identifier, username_storage, portable_id): - """Authorize File, Block and Portable Storage to a Virtual Server. - """ + """Authorize File, Block and Portable Storage to a Virtual Server.""" vs = SoftLayer.VSManager(env.client) vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") From 4ff8abaae4508f74b19702199ed8f05ac9ac1e5a Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Mon, 22 Mar 2021 16:12:31 -0400 Subject: [PATCH 0371/1385] Fix tox analysis. --- SoftLayer/CLI/virt/authorize_storage.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py index d2080eb52..3228e8800 100644 --- a/SoftLayer/CLI/virt/authorize_storage.py +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -19,19 +19,19 @@ @environment.pass_env def cli(env, identifier, username_storage, portable_id): """Authorize File, Block and Portable Storage to a Virtual Server.""" - vs = SoftLayer.VSManager(env.client) - vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') + virtual = SoftLayer.VSManager(env.client) + virtual_id = helpers.resolve_id(virtual.resolve_ids, identifier, 'virtual') table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") table.align['name'] = 'r' table.align['value'] = 'l' if username_storage: - if not vs.authorize_storage(vs_id, username_storage): + if not virtual.authorize_storage(virtual_id, username_storage): raise exceptions.CLIAbort('Authorize Volume Failed') env.fout('Successfully Volume: %s was Added.' % username_storage) if portable_id: - portable_id = helpers.resolve_id(vs.resolve_ids, portable_id, 'storage') - portable_result = vs.attach_portable_storage(vs_id, portable_id) + portable_id = helpers.resolve_id(virtual.resolve_ids, portable_id, 'storage') + portable_result = virtual.attach_portable_storage(virtual_id, portable_id) env.fout('Successfully Portable Storage: %i was Added.' % portable_id) From 9f5abad79d7b5de168cc8a0c67bb439d47ff2384 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 22 Mar 2021 17:16:27 -0500 Subject: [PATCH 0372/1385] #1315 basic IAM authentication support, and slcli login function --- SoftLayer/API.py | 163 +++++++++++++++++++++++++++++++++- SoftLayer/CLI/config/login.py | 96 ++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/__init__.py | 1 + SoftLayer/auth.py | 29 ++++++ SoftLayer/config.py | 18 ++++ SoftLayer/consts.py | 1 + 7 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/CLI/config/login.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 430ed2d14..81e667817 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,16 +6,24 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name +import json +import logging +import requests import warnings from SoftLayer import auth as slauth from SoftLayer import config from SoftLayer import consts +from SoftLayer import exceptions from SoftLayer import transports + +LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT +CONFIG_FILE = consts.CONFIG_FILE + __all__ = [ 'create_client_from_env', 'Client', @@ -80,6 +88,8 @@ def create_client_from_env(username=None, 'Your Company' """ + if config_file is None: + config_file = CONFIG_FILE settings = config.get_client_settings(username=username, api_key=api_key, endpoint_url=endpoint_url, @@ -127,7 +137,7 @@ def create_client_from_env(username=None, settings.get('api_key'), ) - return BaseClient(auth=auth, transport=transport) + return BaseClient(auth=auth, transport=transport, config_file=config_file) def Client(**kwargs): @@ -150,9 +160,35 @@ class BaseClient(object): _prefix = "SoftLayer_" - def __init__(self, auth=None, transport=None): + def __init__(self, auth=None, transport=None, config_file=None): + if config_file is None: + config_file = CONFIG_FILE self.auth = auth - self.transport = transport + self.config_file = config_file + self.settings = config.get_config(self.config_file) + + if transport is None: + url = self.settings['softlayer'].get('endpoint_url') + if url is not None and '/rest' in url: + # If this looks like a rest endpoint, use the rest transport + transport = transports.RestTransport( + endpoint_url=url, + proxy=self.settings['softlayer'].get('proxy'), + timeout=self.settings['softlayer'].getint('timeout'), + user_agent=consts.USER_AGENT, + verify=self.settings['softlayer'].getboolean('verify'), + ) + else: + # Default the transport to use XMLRPC + transport = transports.XmlRpcTransport( + endpoint_url=url, + proxy=self.settings['softlayer'].get('proxy'), + timeout=self.settings['softlayer'].getint('timeout'), + user_agent=consts.USER_AGENT, + verify=self.settings['softlayer'].getboolean('verify'), + ) + + self.transport = transport def authenticate_with_password(self, username, password, security_question_id=None, @@ -321,6 +357,127 @@ def __repr__(self): def __len__(self): return 0 +class IAMClient(BaseClient): + """IBM ID Client for using IAM authentication + + :param auth: auth driver that looks like SoftLayer.auth.AuthenticationBase + :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) + """ + + + def authenticate_with_password(self, username, password): + """Performs IBM IAM Username/Password Authentication + + :param string username: your IBMid username + :param string password: your IBMid password + """ + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'password', + 'password': password, + 'response_type': 'cloud_iam', + 'username': username + } + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: {}".format(response.text)) + + response.raise_for_status() + + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + + config.write_config(self.settings) + self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + return tokens + + def authenticate_with_iam_token(self, a_token, r_token): + """Authenticates to the SL API with an IAM Token + + :param string a_token: Access token + :param string r_token: Refresh Token, to be used if Access token is expired. + """ + self.auth = slauth.BearerAuthentication('', a_token) + user = None + try: + user = self.call('Account', 'getCurrentUser') + except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: + LOGGER.warning("Token has expired, trying to refresh.") + # self.refresh_iam_token(r_token) + else: + raise ex + return user + + def refresh_iam_token(self, r_token): + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'refresh_token', + 'refresh_token': r_token, + 'response_type': 'cloud_iam' + } + + config = self.settings.get('softlayer') + if config.get('account', False): + data['account'] = account + if config.get('ims_account', False): + data['ims_account'] = ims_account + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + response.raise_for_status() + + LOGGER.warning("Successfully refreshed Tokens, saving to config") + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + config.write_config(self.settings) + return tokens + + + + def call(self, service, method, *args, **kwargs): + """Handles refreshing IAM tokens in case of a HTTP 401 error""" + try: + return super().call(service, method, *args, **kwargs) + except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: + LOGGER.warning("Token has expired, trying to refresh.") + self.refresh_iam_token(r_token) + return super().call(service, method, *args, **kwargs) + else: + raise ex + + + def __repr__(self): + return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) + class Service(object): """A SoftLayer Service. diff --git a/SoftLayer/CLI/config/login.py b/SoftLayer/CLI/config/login.py new file mode 100644 index 000000000..546ce6fdb --- /dev/null +++ b/SoftLayer/CLI/config/login.py @@ -0,0 +1,96 @@ +"""Gets a temporary token for a user""" +# :license: MIT, see LICENSE for more details. +import configparser +import os.path + + +import click +import json +import requests + +import SoftLayer +from SoftLayer import config +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.consts import USER_AGENT +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + + email = env.input("Email:") + password = env.getpass("Password:") + + account_id = '' + ims_id = '' + print("ENV CONFIG FILE IS {}".format(env.config_file)) + sl_config = config.get_config(env.config_file) + tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} + client = SoftLayer.API.IAMClient(config_file=env.config_file) + user = client.authenticate_with_iam_token(tokens['access_token'], tokens['refresh_token']) + print(user) + # tokens = client.authenticate_with_password(email, password) + + # tokens = login(email, password) + # print(tokens) + + + + accounts = get_accounts(tokens['access_token']) + print(accounts) + + # if accounts.get('total_results', 0) == 1: + # selected = accounts['resources'][0] + # account_id = utils.lookup(selected, 'metadata', 'guid') + # ims_id = None + # for links in utils.lookup(selected, 'metadata', 'linked_accounts'): + # if links.get('origin') == "IMS": + # ims_id = links.get('id') + + # print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) + # tokens = refresh_token(tokens['refresh_token'], account_id, ims_id) + # print(tokens) + + # print("Saving Tokens...") + + + for key in sl_config['softlayer']: + print("{} = {} ".format(key, sl_config['softlayer'][key])) + + # sl_config['softlayer']['access_token'] = tokens['access_token'] + # sl_config['softlayer']['refresh_token'] = tokens['refresh_token'] + # sl_config['softlayer']['ims_account'] = ims_id + # sl_config['softlayer']['account_id'] = account_id + # config.write_config(sl_config, env.config_file) + # print(sl_config) + + # print("Email: {}, Password: {}".format(email, password)) + + print("Checking for an API key") + + user = client.call('SoftLayer_Account', 'getCurrentUser') + print(user) + + + +def get_accounts(a_token): + """Gets account list from accounts.cloud.ibm.com/v1/accounts""" + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + headers['Authorization'] = 'Bearer {}'.format(a_token) + response = iam_client.request( + 'GET', + 'https://accounts.cloud.ibm.com/v1/accounts', + headers=headers + ) + + response.raise_for_status() + return json.loads(response.text) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..070f32c42 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -70,6 +70,7 @@ ('config:setup', 'SoftLayer.CLI.config.setup:cli'), ('config:show', 'SoftLayer.CLI.config.show:cli'), ('setup', 'SoftLayer.CLI.config.setup:cli'), + ('login', 'SoftLayer.CLI.config.login:cli'), ('dns', 'SoftLayer.CLI.dns'), ('dns:import', 'SoftLayer.CLI.dns.zone_import:cli'), diff --git a/SoftLayer/__init__.py b/SoftLayer/__init__.py index 9e14ea38e..30dd65774 100644 --- a/SoftLayer/__init__.py +++ b/SoftLayer/__init__.py @@ -31,6 +31,7 @@ __copyright__ = 'Copyright 2016 SoftLayer Technologies, Inc.' __all__ = [ # noqa: F405 'BaseClient', + 'IAMClient', 'create_client_from_env', 'Client', 'BasicAuthentication', diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index 4046937e6..c2d22168e 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -7,6 +7,8 @@ """ # pylint: disable=no-self-use +from SoftLayer import config + __all__ = [ 'BasicAuthentication', 'TokenAuthentication', @@ -109,3 +111,30 @@ def get_request(self, request): def __repr__(self): return "BasicHTTPAuthentication(username=%r)" % self.username + +class BearerAuthentication(AuthenticationBase): + """Bearer Token authentication class. + + :param username str: a user's username, not really needed but all the others use it. + :param api_key str: a user's IAM Token + """ + + def __init__(self, username, token, r_token=None): + """For using IBM IAM authentication + + :param username str: Not really needed, will be set to their current username though for logging + :param token str: the IAM Token + :param r_token str: The refresh Token, optional + """ + self.username = username + self.api_key = token + self.r_token = r_token + + def get_request(self, request): + """Sets token-based auth headers.""" + request.transport_headers['Authorization'] = 'Bearer {}'.format(self.api_key) + request.transport_user = self.username + return request + + def __repr__(self): + return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) \ No newline at end of file diff --git a/SoftLayer/config.py b/SoftLayer/config.py index d008893f0..3caebde0f 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -6,9 +6,11 @@ :license: MIT, see LICENSE for more details. """ import configparser +import logging import os import os.path +LOGGER = logging.getLogger(__name__) def get_client_settings_args(**kwargs): """Retrieve client settings from user-supplied arguments. @@ -91,3 +93,19 @@ def get_client_settings(**kwargs): all_settings = settings return all_settings + + +def get_config(config_file=None): + if config_file is None: + config_file = '~/.softlayer' + config = configparser.ConfigParser() + config.read(os.path.expanduser(config_file)) + return config + +def write_config(configuration, config_file=None): + if config_file is None: + config_file = '~/.softlayer' + config_file = os.path.expanduser(config_file) + LOGGER.warning("Updating config file {} with new access tokens".format(config_file)) + with open(config_file, 'w') as file: + configuration.write(file) \ No newline at end of file diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index b10b164d0..71c52be75 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -11,3 +11,4 @@ API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' API_PRIVATE_ENDPOINT_REST = 'https://api.service.softlayer.com/rest/v3.1/' USER_AGENT = "softlayer-python/%s" % VERSION +CONFIG_FILE = "~/.softlayer" From a51e9cf917458dc8a77ddccaeadb320abb75010d Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 23 Mar 2021 15:04:28 -0400 Subject: [PATCH 0373/1385] Refactor hw detail prices. --- SoftLayer/CLI/hardware/detail.py | 10 ++++++---- SoftLayer/managers/hardware.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..95410fc49 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -78,11 +78,13 @@ def cli(env, identifier, passwords, price): if price: total_price = utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount') or 0 - price_table = formatting.Table(['Item', 'Recurring Price']) - price_table.add_row(['Total', total_price]) + price_table = formatting.Table(['Item', 'CategoryCode', 'Recurring Price']) + price_table.align['Item'] = 'l' - for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) + price_table.add_row(['Total', '-', total_price]) + + for item in utils.lookup(result, 'billingItem', 'nextInvoiceChildren') or []: + price_table.add_row([item['description'], item['categoryCode'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..dd1dedf68 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -260,7 +260,7 @@ def get_hardware(self, hardware_id, **kwargs): passwords[username,password]],''' 'billingItem[' 'id,nextInvoiceTotalRecurringAmount,' - 'children[nextInvoiceTotalRecurringAmount],' + 'nextInvoiceChildren[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' 'hourlyBillingFlag,' From 1db96f6f26c72223bbe10a7eff60e065cc969db1 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Mar 2021 17:33:57 -0400 Subject: [PATCH 0374/1385] add billing and lastTransaction on hardware detail --- SoftLayer/CLI/hardware/detail.py | 2 ++ SoftLayer/managers/hardware.py | 1 + 2 files changed, 3 insertions(+) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..f87f5206d 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,6 +61,8 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) + table.add_row(['LastTransaction', result['lastTransaction']['transactionGroup']['name']]) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..9045d9bda 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -263,6 +263,7 @@ def get_hardware(self, hardware_id, **kwargs): 'children[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' + 'lastTransaction[transactionGroup[name]],' 'hourlyBillingFlag,' 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' From 20630befee1efbaa74f520195fd22235f3f423f8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 24 Mar 2021 17:23:19 -0500 Subject: [PATCH 0375/1385] #1315 moved the IBMID login stuff to config setup instead of its own command --- SoftLayer/API.py | 105 ++++++++++----- SoftLayer/CLI/config/login.py | 96 -------------- SoftLayer/CLI/config/setup.py | 232 +++++++++++++++++++++++++++++----- SoftLayer/CLI/routes.py | 1 - 4 files changed, 275 insertions(+), 159 deletions(-) delete mode 100644 SoftLayer/CLI/config/login.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 81e667817..2ef237d6c 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -10,7 +10,7 @@ import logging import requests import warnings - +import time from SoftLayer import auth as slauth from SoftLayer import config @@ -18,7 +18,7 @@ from SoftLayer import exceptions from SoftLayer import transports - +from pprint import pprint as pp LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT @@ -402,29 +402,63 @@ def authenticate_with_password(self, username, password): self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - config.write_config(self.settings) + config.write_config(self.settings, self.config_file) + self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + + return tokens + + def authenticate_with_passcode(self, passcode): + """Performs IBM IAM SSO Authentication + + :param string passcode: your IBMid password + """ + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'urn:ibm:params:oauth:grant-type:passcode', + 'passcode': passcode, + 'response_type': 'cloud_iam' + } + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: {}".format(response.text)) + + response.raise_for_status() + + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) + r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) + LOGGER.warning("Tokens retrieved, expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + return tokens - def authenticate_with_iam_token(self, a_token, r_token): + def authenticate_with_iam_token(self, a_token, r_token=None): """Authenticates to the SL API with an IAM Token :param string a_token: Access token :param string r_token: Refresh Token, to be used if Access token is expired. """ - self.auth = slauth.BearerAuthentication('', a_token) - user = None - try: - user = self.call('Account', 'getCurrentUser') - except exceptions.SoftLayerAPIError as ex: - if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh.") - # self.refresh_iam_token(r_token) - else: - raise ex - return user + self.auth = slauth.BearerAuthentication('', a_token, r_token) - def refresh_iam_token(self, r_token): + def refresh_iam_token(self, r_token, account_id=None, ims_account=None): + """Refreshes the IAM Token, will default to values in the config file""" iam_client = requests.Session() headers = { @@ -438,11 +472,15 @@ def refresh_iam_token(self, r_token): 'response_type': 'cloud_iam' } - config = self.settings.get('softlayer') - if config.get('account', False): - data['account'] = account - if config.get('ims_account', False): - data['ims_account'] = ims_account + sl_config = self.settings['softlayer'] + + if account_id is None and sl_config.get('account_id', False): + account_id = sl_config.get('account_id') + if ims_account is None and sl_config.get('ims_account', False): + ims_account = sl_config.get('ims_account') + + data['account'] = account_id + data['ims_account'] = ims_account response = iam_client.request( 'POST', @@ -451,30 +489,37 @@ def refresh_iam_token(self, r_token): headers=headers, auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) + + if response.status_code != 200: + LOGGER.warning("Unable to refresh IAM Token. {}".format(response.text)) + response.raise_for_status() - - LOGGER.warning("Successfully refreshed Tokens, saving to config") + tokens = json.loads(response.text) + a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) + r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) + LOGGER.warning("Successfully refreshed Tokens. Expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - config.write_config(self.settings) + config.write_config(self.settings, self.config_file) + self.auth = slauth.BearerAuthentication('', tokens['access_token']) return tokens - - def call(self, service, method, *args, **kwargs): """Handles refreshing IAM tokens in case of a HTTP 401 error""" try: return super().call(service, method, *args, **kwargs) except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh.") - self.refresh_iam_token(r_token) - return super().call(service, method, *args, **kwargs) + LOGGER.warning("Token has expired, trying to refresh. {}".format(ex.faultString)) + # self.refresh_iam_token(r_token) + # return super().call(service, method, *args, **kwargs) + return ex else: raise ex - def __repr__(self): return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) diff --git a/SoftLayer/CLI/config/login.py b/SoftLayer/CLI/config/login.py deleted file mode 100644 index 546ce6fdb..000000000 --- a/SoftLayer/CLI/config/login.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Gets a temporary token for a user""" -# :license: MIT, see LICENSE for more details. -import configparser -import os.path - - -import click -import json -import requests - -import SoftLayer -from SoftLayer import config -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.consts import USER_AGENT -from SoftLayer import utils - - -@click.command() -@environment.pass_env -def cli(env): - - email = env.input("Email:") - password = env.getpass("Password:") - - account_id = '' - ims_id = '' - print("ENV CONFIG FILE IS {}".format(env.config_file)) - sl_config = config.get_config(env.config_file) - tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} - client = SoftLayer.API.IAMClient(config_file=env.config_file) - user = client.authenticate_with_iam_token(tokens['access_token'], tokens['refresh_token']) - print(user) - # tokens = client.authenticate_with_password(email, password) - - # tokens = login(email, password) - # print(tokens) - - - - accounts = get_accounts(tokens['access_token']) - print(accounts) - - # if accounts.get('total_results', 0) == 1: - # selected = accounts['resources'][0] - # account_id = utils.lookup(selected, 'metadata', 'guid') - # ims_id = None - # for links in utils.lookup(selected, 'metadata', 'linked_accounts'): - # if links.get('origin') == "IMS": - # ims_id = links.get('id') - - # print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) - # tokens = refresh_token(tokens['refresh_token'], account_id, ims_id) - # print(tokens) - - # print("Saving Tokens...") - - - for key in sl_config['softlayer']: - print("{} = {} ".format(key, sl_config['softlayer'][key])) - - # sl_config['softlayer']['access_token'] = tokens['access_token'] - # sl_config['softlayer']['refresh_token'] = tokens['refresh_token'] - # sl_config['softlayer']['ims_account'] = ims_id - # sl_config['softlayer']['account_id'] = account_id - # config.write_config(sl_config, env.config_file) - # print(sl_config) - - # print("Email: {}, Password: {}".format(email, password)) - - print("Checking for an API key") - - user = client.call('SoftLayer_Account', 'getCurrentUser') - print(user) - - - -def get_accounts(a_token): - """Gets account list from accounts.cloud.ibm.com/v1/accounts""" - iam_client = requests.Session() - - headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': USER_AGENT, - 'Accept': 'application/json' - } - headers['Authorization'] = 'Bearer {}'.format(a_token) - response = iam_client.request( - 'GET', - 'https://accounts.cloud.ibm.com/v1/accounts', - headers=headers - ) - - response.raise_for_status() - return json.loads(response.text) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 7f1833a4a..26d7805ee 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -1,7 +1,10 @@ """Setup CLI configuration.""" # :license: MIT, see LICENSE for more details. import configparser +import json +import requests import os.path +import webbrowser import click @@ -10,7 +13,11 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting +from SoftLayer import config as base_config +from SoftLayer.consts import USER_AGENT +from SoftLayer import utils +from pprint import pprint as pp def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. @@ -27,27 +34,68 @@ def get_api_key(client, username, secret): # pylint: disable=inconsistent-retur if 'invalid api token' not in ex.faultString.lower(): raise else: - # Try to use a client with username/password - client.authenticate_with_password(username, secret) + if isinstance(client, SoftLayer.API.IAMClient): + client.authenticate_with_iam_token(secret) + else: + # Try to use a client with username/password + client.authenticate_with_password(username, secret) - user_record = client['Account'].getCurrentUser(mask='id, apiAuthenticationKeys') + user_record = client.call('Account', 'getCurrentUser', mask='id, apiAuthenticationKeys') api_keys = user_record['apiAuthenticationKeys'] if len(api_keys) == 0: - return client['User_Customer'].addApiAuthenticationKey(id=user_record['id']) + return client.call('User_Customer', 'addApiAuthenticationKey', id=user_record['id']) return api_keys[0]['authenticationKey'] @click.command() +@click.option('--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), + help="Select a method of authentication.", default='classic_key', show_default=True) @environment.pass_env -def cli(env): +def cli(env, auth): """Setup the ~/.softlayer file with username and apikey. - Set the username to 'apikey' for cloud.ibm.com accounts. + [Auth Types] + + ibmid: Requires your cloud.ibm.com username and password, and generates a classic infrastructure API key. + + cloud_key: A 32 character API key. Username will be 'apikey' + + classic_key: A 64 character API key used in the Softlayer/Classic Infrastructure systems. + + sso: For users with @ibm.com email addresses. """ + username = None + api_key = None + + timeout = 0 + defaults = config.get_settings_from_client(env.client) + endpoint_url = defaults.get('endpoint_url', 'public') + # endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) + # Get ths username and API key + if auth == 'ibmid': + print("Logging in with IBMid") + username, api_key = ibmid_login(env) + + elif auth == 'cloud_key': + username = 'apikey' + secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) + api_key = get_api_key(new_client, username, secret) - username, secret, endpoint_url, timeout = get_user_input(env) - new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) - api_key = get_api_key(new_client, username, secret) + elif auth =='sso': + print("Using SSO for login") + username, api_key = sso_login(env) + else: + print("Using a Classic Infrastructure API key") + + username = env.input('Classic Infrastructue Username', default=defaults['username']) + secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + + new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) + api_key = get_api_key(new_client, username, secret) + + # Ask for timeout + timeout = env.input('Timeout', default=defaults['timeout'] or 0) path = '~/.softlayer' if env.config_file: @@ -59,8 +107,7 @@ def cli(env): 'endpoint_url': endpoint_url, 'timeout': timeout}))) - if not formatting.confirm('Are you sure you want to write settings ' - 'to "%s"?' % config_path, default=True): + if not formatting.confirm('Are you sure you want to write settings to "%s"?' % config_path, default=True): raise exceptions.CLIAbort('Aborted.') # Persist the config file. Read the target config file in before @@ -77,10 +124,7 @@ def cli(env): parsed_config.set('softlayer', 'endpoint_url', endpoint_url) parsed_config.set('softlayer', 'timeout', timeout) - config_fd = os.fdopen(os.open(config_path, - (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), - 0o600), - 'w') + config_fd = os.fdopen(os.open(config_path, (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), 0o600), 'w') try: parsed_config.write(config_fd) finally: @@ -89,21 +133,10 @@ def cli(env): env.fout("Configuration Updated Successfully") -def get_user_input(env): - """Ask for username, secret (api_key or password) and endpoint_url.""" +def get_endpoint_url(env, endpoint='public'): + """Gets the Endpoint to use.""" - defaults = config.get_settings_from_client(env.client) - - # Ask for username - username = env.input('Username', default=defaults['username']) - - # Ask for 'secret' which can be api_key or their password - secret = env.getpass('API Key or Password', default=defaults['api_key']) - - # Ask for which endpoint they want to use - endpoint = defaults.get('endpoint_url', 'public') - endpoint_type = env.input( - 'Endpoint (public|private|custom)', default=endpoint) + endpoint_type = env.input('Endpoint (public|private|custom)', default=endpoint) endpoint_type = endpoint_type.lower() if endpoint_type == 'public': @@ -116,7 +149,142 @@ def get_user_input(env): else: endpoint_url = endpoint_type - # Ask for timeout - timeout = env.input('Timeout', default=defaults['timeout'] or 0) - return username, secret, endpoint_url, timeout + return endpoint_url + +def ibmid_login(env): + """Uses an IBMid and Password to get an access token, and that access token to get an API key""" + email = env.input("Email").strip() + password = env.getpass("Password").strip() + + account_id = '' + ims_id = '' + sl_config = base_config.get_config(env.config_file) + # tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} + client = SoftLayer.API.IAMClient(config_file=env.config_file) + + # STEP 1: Get the base IAM Token with a username/password + tokens = client.authenticate_with_password(email, password) + + # STEP 2: Figure out which account we want to use + account = get_accounts(env, tokens['access_token']) + + # STEP 3: Refresh Token, using a specific account this time. + tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) + + # STEP 4: Get or create the Classic Infrastructure API key + # client.authenticate_with_iam_token(tokens['access_token']) + user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") + + if len(user.get('apiAuthenticationKeys', [])) == 0: + env.fout("Creating a Classic Infrastrucutre API key for {}".format(user['username'])) + api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) + else: + api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] + + return user.get('username'), api_key + + +def get_accounts(env, a_token): + """Gets account list from accounts.cloud.ibm.com/v1/accounts""" + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + headers['Authorization'] = 'Bearer {}'.format(a_token) + response = iam_client.request( + 'GET', + 'https://accounts.cloud.ibm.com/v1/accounts', + headers=headers + ) + + response.raise_for_status() + + accounts = json.loads(response.text) + selected = None + ims_id = None + if accounts.get('total_results', 0) == 1: + selected = accounts['resources'][0] + else: + env.fout("Select an Account...") + counter = 1 + for selected in accounts.get('resources', []): + links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] + for link in links: + if link.get('origin') == "IMS": + ims_id = link.get('id') + if ims_id is None: + ims_id = "Unlinked" + env.fout("{}: {} ({})".format(counter, utils.lookup(selected, 'entity', 'name'), ims_id)) + counter = counter + 1 + ims_id = None # Reset ims_id to avoid any mix-match or something. + choice = click.prompt('Enter a number', type=int) + # Test to make sure choice is not out of bounds... + selected = accounts['resources'][choice - 1] + + + account_id = utils.lookup(selected, 'metadata', 'guid') + links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] + for link in links: + if link.get('origin') == "IMS": + ims_id = link.get('id') + + print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) + return {"account_id": account_id, "ims_id": ims_id} + + +def get_sso_url(): + """Gets the URL for using SSO Tokens""" + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/json', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + response = iam_client.request( + 'GET', + 'https://iam.cloud.ibm.com/identity/.well-known/openid-configuration', + headers=headers + ) + + response.raise_for_status() + data = json.loads(response.text) + return data.get('passcode_endpoint') + +def sso_login(env): + """Uses a SSO token to get a SL apikey""" + account_id = '' + ims_id = '' + + passcode_url = get_sso_url() + env.fout("Get a one-time code from {} to proceed.".format(passcode_url)) + open_browser = env.input("Open the URL in the default browser? [Y/n]", default='Y') + if open_browser.lower() == 'y': + webbrowser.open(passcode_url) + passcode = env.input("One-time code") + client = SoftLayer.API.IAMClient(config_file=env.config_file) + + # STEP 1: Get the base IAM Token with a username/password + tokens = client.authenticate_with_passcode(passcode) + + # STEP 2: Figure out which account we want to use + account = get_accounts(env, tokens['access_token']) + + # STEP 3: Refresh Token, using a specific account this time. + tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) + + # STEP 4: Get or create the Classic Infrastructure API key + # client.authenticate_with_iam_token(tokens['access_token']) + user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") + + if len(user.get('apiAuthenticationKeys', [])) == 0: + env.fout("Creating a Classic Infrastrucutre API key for {}".format(user['username'])) + api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) + else: + api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] + return user.get('username'), api_key \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 070f32c42..c223bfae2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -70,7 +70,6 @@ ('config:setup', 'SoftLayer.CLI.config.setup:cli'), ('config:show', 'SoftLayer.CLI.config.show:cli'), ('setup', 'SoftLayer.CLI.config.setup:cli'), - ('login', 'SoftLayer.CLI.config.login:cli'), ('dns', 'SoftLayer.CLI.dns'), ('dns:import', 'SoftLayer.CLI.dns.zone_import:cli'), From 7ae7588bd5ee098de67be601f35b3a74439acb1c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Mar 2021 17:22:08 -0500 Subject: [PATCH 0376/1385] Tox fixes --- SoftLayer/API.py | 35 ++++++++--------- SoftLayer/CLI/config/setup.py | 47 ++++++++--------------- SoftLayer/auth.py | 7 ++-- SoftLayer/config.py | 7 +++- tests/CLI/modules/config_tests.py | 64 +++++++++++-------------------- 5 files changed, 64 insertions(+), 96 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 2ef237d6c..5d15681ce 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,11 +6,13 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name +import time +import warnings + import json import logging import requests -import warnings -import time + from SoftLayer import auth as slauth from SoftLayer import config @@ -18,7 +20,6 @@ from SoftLayer import exceptions from SoftLayer import transports -from pprint import pprint as pp LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT @@ -188,7 +189,7 @@ def __init__(self, auth=None, transport=None, config_file=None): verify=self.settings['softlayer'].getboolean('verify'), ) - self.transport = transport + self.transport = transport def authenticate_with_password(self, username, password, security_question_id=None, @@ -357,6 +358,7 @@ def __repr__(self): def __len__(self): return 0 + class IAMClient(BaseClient): """IBM ID Client for using IAM authentication @@ -364,8 +366,7 @@ class IAMClient(BaseClient): :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) """ - - def authenticate_with_password(self, username, password): + def authenticate_with_password(self, username, password, security_question_id=None, security_question_answer=None): """Performs IBM IAM Username/Password Authentication :param string username: your IBMid username @@ -394,14 +395,14 @@ def authenticate_with_password(self, username, password): auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) if response.status_code != 200: - LOGGER.error("Unable to login: {}".format(response.text)) + LOGGER.error("Unable to login: %s", response.text) response.raise_for_status() tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - + config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) @@ -434,7 +435,7 @@ def authenticate_with_passcode(self, passcode): auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) if response.status_code != 200: - LOGGER.error("Unable to login: {}".format(response.text)) + LOGGER.error("Unable to login: %s", response.text) response.raise_for_status() @@ -443,7 +444,7 @@ def authenticate_with_passcode(self, passcode): self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) - LOGGER.warning("Tokens retrieved, expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) @@ -489,16 +490,16 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): headers=headers, auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) - + if response.status_code != 200: - LOGGER.warning("Unable to refresh IAM Token. {}".format(response.text)) - + LOGGER.warning("Unable to refresh IAM Token. %s", response.text) + response.raise_for_status() - + tokens = json.loads(response.text) a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) - LOGGER.warning("Successfully refreshed Tokens. Expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -513,9 +514,7 @@ def call(self, service, method, *args, **kwargs): except exceptions.SoftLayerAPIError as ex: if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh. {}".format(ex.faultString)) - # self.refresh_iam_token(r_token) - # return super().call(service, method, *args, **kwargs) + LOGGER.warning("Token has expired, trying to refresh. %s", ex.faultString) return ex else: raise ex diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 26d7805ee..476ab64b3 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -1,10 +1,11 @@ """Setup CLI configuration.""" # :license: MIT, see LICENSE for more details. +import webbrowser + import configparser import json -import requests import os.path -import webbrowser +import requests import click @@ -13,11 +14,9 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer import config as base_config from SoftLayer.consts import USER_AGENT from SoftLayer import utils -from pprint import pprint as pp def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. @@ -66,31 +65,26 @@ def cli(env, auth): """ username = None api_key = None - + timeout = 0 defaults = config.get_settings_from_client(env.client) - endpoint_url = defaults.get('endpoint_url', 'public') - # endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) + endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) # Get ths username and API key if auth == 'ibmid': - print("Logging in with IBMid") username, api_key = ibmid_login(env) - + elif auth == 'cloud_key': username = 'apikey' - secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + secret = env.getpass('Classic Infrastructue API Key', default=defaults['api_key']) new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) - elif auth =='sso': - print("Using SSO for login") + elif auth == 'sso': username, api_key = sso_login(env) - else: - print("Using a Classic Infrastructure API key") + else: username = env.input('Classic Infrastructue Username', default=defaults['username']) - secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) - + secret = env.getpass('Classic Infrastructue API Key', default=defaults['api_key']) new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) @@ -148,32 +142,26 @@ def get_endpoint_url(env, endpoint='public'): endpoint_url = env.input('Endpoint URL', default=endpoint) else: endpoint_url = endpoint_type - - return endpoint_url + def ibmid_login(env): """Uses an IBMid and Password to get an access token, and that access token to get an API key""" email = env.input("Email").strip() password = env.getpass("Password").strip() - account_id = '' - ims_id = '' - sl_config = base_config.get_config(env.config_file) - # tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} client = SoftLayer.API.IAMClient(config_file=env.config_file) - + # STEP 1: Get the base IAM Token with a username/password tokens = client.authenticate_with_password(email, password) # STEP 2: Figure out which account we want to use account = get_accounts(env, tokens['access_token']) - + # STEP 3: Refresh Token, using a specific account this time. tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) # STEP 4: Get or create the Classic Infrastructure API key - # client.authenticate_with_iam_token(tokens['access_token']) user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") if len(user.get('apiAuthenticationKeys', [])) == 0: @@ -220,12 +208,11 @@ def get_accounts(env, a_token): ims_id = "Unlinked" env.fout("{}: {} ({})".format(counter, utils.lookup(selected, 'entity', 'name'), ims_id)) counter = counter + 1 - ims_id = None # Reset ims_id to avoid any mix-match or something. + ims_id = None # Reset ims_id to avoid any mix-match or something. choice = click.prompt('Enter a number', type=int) # Test to make sure choice is not out of bounds... selected = accounts['resources'][choice - 1] - account_id = utils.lookup(selected, 'metadata', 'guid') links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] for link in links: @@ -256,11 +243,9 @@ def get_sso_url(): data = json.loads(response.text) return data.get('passcode_endpoint') + def sso_login(env): """Uses a SSO token to get a SL apikey""" - account_id = '' - ims_id = '' - passcode_url = get_sso_url() env.fout("Get a one-time code from {} to proceed.".format(passcode_url)) open_browser = env.input("Open the URL in the default browser? [Y/n]", default='Y') @@ -287,4 +272,4 @@ def sso_login(env): api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) else: api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] - return user.get('username'), api_key \ No newline at end of file + return user.get('username'), api_key diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index c2d22168e..18e0ebe96 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -7,8 +7,6 @@ """ # pylint: disable=no-self-use -from SoftLayer import config - __all__ = [ 'BasicAuthentication', 'TokenAuthentication', @@ -112,6 +110,7 @@ def get_request(self, request): def __repr__(self): return "BasicHTTPAuthentication(username=%r)" % self.username + class BearerAuthentication(AuthenticationBase): """Bearer Token authentication class. @@ -121,7 +120,7 @@ class BearerAuthentication(AuthenticationBase): def __init__(self, username, token, r_token=None): """For using IBM IAM authentication - + :param username str: Not really needed, will be set to their current username though for logging :param token str: the IAM Token :param r_token str: The refresh Token, optional @@ -137,4 +136,4 @@ def get_request(self, request): return request def __repr__(self): - return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) \ No newline at end of file + return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 3caebde0f..2f48aa221 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -12,6 +12,7 @@ LOGGER = logging.getLogger(__name__) + def get_client_settings_args(**kwargs): """Retrieve client settings from user-supplied arguments. @@ -96,16 +97,18 @@ def get_client_settings(**kwargs): def get_config(config_file=None): + """Returns a parsed config object""" if config_file is None: config_file = '~/.softlayer' config = configparser.ConfigParser() config.read(os.path.expanduser(config_file)) return config + def write_config(configuration, config_file=None): + """Writes a configuration to config_file""" if config_file is None: config_file = '~/.softlayer' config_file = os.path.expanduser(config_file) - LOGGER.warning("Updating config file {} with new access tokens".format(config_file)) with open(config_file, 'w') as file: - configuration.write(file) \ No newline at end of file + configuration.write(file) diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 33c82520a..fca30af1c 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -22,9 +22,7 @@ class TestHelpShow(testing.TestCase): def set_up(self): - transport = transports.XmlRpcTransport( - endpoint_url='http://endpoint-url', - ) + transport = transports.XmlRpcTransport(endpoint_url='http://endpoint-url',) self.env.client = SoftLayer.BaseClient( transport=transport, auth=auth.BasicAuthentication('username', 'api-key')) @@ -50,6 +48,7 @@ def set_up(self): # used. transport = testing.MockableTransport(SoftLayer.FixtureTransport()) self.env.client = SoftLayer.BaseClient(transport=transport) + self.config_file = "./test_config_file" @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -62,7 +61,7 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = True getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] + mocked_input.side_effect = ['public', 'user', 0] result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) @@ -84,55 +83,38 @@ def test_setup_cancel(self, mocked_input, getpass, confirm_mock, client): with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = False getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] - - result = self.run_command(['--config=%s' % config_file.name, - 'config', 'setup']) + mocked_input.side_effect = ['public', 'user', 0] + result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') - @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_private(self, mocked_input, getpass): - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'private', 0] - - username, secret, endpoint_url, timeout = ( - config.get_user_input(self.env)) - - self.assertEqual(username, 'user') - self.assertEqual(secret, 'A' * 64) - self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT) - self.assertEqual(timeout, 0) - - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') - @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_custom(self, mocked_input, getpass): - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'custom', 'custom-endpoint', 0] - - _, _, endpoint_url, _ = config.get_user_input(self.env) - - self.assertEqual(endpoint_url, 'custom-endpoint') - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') def test_github_1074(self, mocked_input, getpass): """Tests to make sure directly using an endpoint works""" - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'test-endpoint', 0] - - _, _, endpoint_url, _ = config.get_user_input(self.env) - + mocked_input.side_effect = ['test-endpoint'] + endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, 'test-endpoint') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_default(self, mocked_input, getpass): - self.env.getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] + def test_get_endpoint(self, mocked_input, getpass): + """Tests to make sure directly using an endpoint works""" + mocked_input.side_effect = ['private', 'custom', 'test.com', 'public', 'test-endpoint'] + + # private + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT) - _, _, endpoint_url, _ = config.get_user_input(self.env) + # custom - test.com + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, 'test.com') + # public + endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, consts.API_PUBLIC_ENDPOINT) + + # test-endpoint + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, 'test-endpoint') From d7b2d5990f004b7355ca759f34bafa30c56debbd Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:04:20 -0400 Subject: [PATCH 0377/1385] #1450 add slcli order quote-save tests --- tests/CLI/modules/order_tests.py | 5 +++++ tests/managers/ordering_tests.py | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index f09b5aaea..24495d0ff 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -401,6 +401,11 @@ def test_quote_detail(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Billing_Order_Quote', 'getObject', identifier='12345') + def test_quote_save(self): + result = self.run_command(['order', 'quote-save', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'saveQuote', identifier='12345') + def test_quote_list(self): result = self.run_command(['order', 'quote-list']) self.assert_no_fail(result) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f18d801a9..5f88d59d5 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -104,6 +104,13 @@ def test_get_quote_details(self): quote_fixture = quote_service.getObject(id=1234) self.assertEqual(quote, quote_fixture) + def test_save_quote(self): + saved_quote = self.ordering.save_quote(1234) + quote_service = self.ordering.client['Billing_Order_Quote'] + quote_fixture = quote_service.getObject(id=1234) + self.assertEqual(saved_quote, quote_fixture) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'saveQuote', identifier=1234) + def test_verify_quote(self): extras = { 'hardware': [{ From f46990ffe16f16c2be78862e4dd8d8d65cb36b6e Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:06:08 -0400 Subject: [PATCH 0378/1385] #1450 add slcli order quote-save feature --- SoftLayer/CLI/order/quote_save.py | 33 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Billing_Order_Quote.py | 2 ++ SoftLayer/managers/ordering.py | 7 ++++ 4 files changed, 43 insertions(+) create mode 100644 SoftLayer/CLI/order/quote_save.py diff --git a/SoftLayer/CLI/order/quote_save.py b/SoftLayer/CLI/order/quote_save.py new file mode 100644 index 000000000..64b695ca0 --- /dev/null +++ b/SoftLayer/CLI/order/quote_save.py @@ -0,0 +1,33 @@ +"""Save a quote""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering +from SoftLayer.utils import clean_time + + +@click.command() +@click.argument('quote') +@environment.pass_env +def cli(env, quote): + """Save a quote""" + + manager = ordering.OrderingManager(env.client) + result = manager.save_quote(quote) + + table = formatting.Table([ + 'Id', 'Name', 'Created', 'Modified', 'Status' + ]) + table.align['Name'] = 'l' + + table.add_row([ + result.get('id'), + result.get('name'), + clean_time(result.get('createDate')), + clean_time(result.get('modifyDate')), + result.get('status'), + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2e73aabf8..631d4b2be 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -227,6 +227,7 @@ ('order:place-quote', 'SoftLayer.CLI.order.place_quote:cli'), ('order:quote-list', 'SoftLayer.CLI.order.quote_list:cli'), ('order:quote-detail', 'SoftLayer.CLI.order.quote_detail:cli'), + ('order:quote-save', 'SoftLayer.CLI.order.quote_save:cli'), ('order:quote', 'SoftLayer.CLI.order.quote:cli'), ('order:lookup', 'SoftLayer.CLI.order.lookup:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py index f1ca8c497..9e7340e67 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py @@ -102,3 +102,5 @@ ] } } + +saveQuote = getObject diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 36bbb93ef..18513e1e4 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -169,6 +169,13 @@ def get_quote_details(self, quote_id): quote = self.client['Billing_Order_Quote'].getObject(id=quote_id, mask=mask) return quote + def save_quote(self, quote_id): + """Save a quote. + + :param quote_id: ID number of target quote + """ + return self.client['Billing_Order_Quote'].saveQuote(id=quote_id) + def get_order_container(self, quote_id): """Generate an order container from a quote object. From 68292e28474bd57df479cd398c8027f2f6a0f72b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:06:34 -0400 Subject: [PATCH 0379/1385] #1450 add slcli order quote-save docs --- docs/cli/ordering.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index 7405bdb0e..740438a69 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -131,6 +131,10 @@ Quotes :prog: order quote-detail :show-nested: +.. click:: SoftLayer.CLI.order.quote_save:cli + :prog: order quote-save + :show-nested: + .. click:: SoftLayer.CLI.order.place_quote:cli :prog: order place-quote :show-nested: From 5c3cd04515a21a2b522d7e776d1cb390155fe2fc Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 29 Mar 2021 10:02:45 -0400 Subject: [PATCH 0380/1385] fix team code review comments --- SoftLayer/CLI/hardware/detail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index f87f5206d..c5b1c2b9b 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,7 +61,8 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) - table.add_row(['LastTransaction', result['lastTransaction']['transactionGroup']['name']]) + table.add_row(['last_transaction', + utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name')]) table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) From 6a8a2d5f982e73d69d9ce2853e94177c8de90d29 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 29 Mar 2021 17:51:41 -0400 Subject: [PATCH 0381/1385] add the Hardware components on "slcli hardware detail" --- SoftLayer/CLI/hardware/detail.py | 12 ++++++++++++ SoftLayer/managers/hardware.py | 2 ++ 2 files changed, 14 insertions(+) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..a1e31e023 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -71,6 +71,8 @@ def cli(env, identifier, passwords, price): bandwidth = hardware.get_bandwidth_allocation(hardware_id) bw_table = _bw_table(bandwidth) table.add_row(['Bandwidth', bw_table]) + system_table = _system_table(result['activeComponents']) + table.add_row(['System_data', system_table]) if result.get('notes'): table.add_row(['notes', result['notes']]) @@ -117,3 +119,13 @@ def _bw_table(bw_data): table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) return table + + +def _system_table(system_data): + table = formatting.Table(['Type', 'name']) + for system in system_data: + table.add_row([utils.lookup(system, 'hardwareComponentModel', + 'hardwareGenericComponentModel', + 'hardwareComponentType', 'keyName'), + utils.lookup(system, 'hardwareComponentModel', 'longDescription')]) + return table diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..e7e2d24db 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -239,6 +239,8 @@ def get_hardware(self, hardware_id, **kwargs): 'primaryIpAddress,' 'networkManagementIpAddress,' 'userData,' + 'activeComponents[id,hardwareComponentModel[' + 'hardwareGenericComponentModel[id,hardwareComponentType[keyName]]]],' 'datacenter,' '''networkComponents[id, status, speed, maxSpeed, name, ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress, From 8aa65113dd3b9470bfa524c57e5fe0bedba6ba54 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 16:57:48 -0500 Subject: [PATCH 0382/1385] fixed code review comments, a few more unit tests --- SoftLayer/API.py | 91 +++++++++++++++++++------------ SoftLayer/CLI/config/setup.py | 4 +- SoftLayer/exceptions.py | 15 +++++ tests/CLI/modules/config_tests.py | 56 +++++++++++++++++++ 4 files changed, 129 insertions(+), 37 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 5d15681ce..a21f9f654 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -175,7 +175,8 @@ def __init__(self, auth=None, transport=None, config_file=None): transport = transports.RestTransport( endpoint_url=url, proxy=self.settings['softlayer'].get('proxy'), - timeout=self.settings['softlayer'].getint('timeout'), + # prevents an exception incase timeout is a float number. + timeout=int(self.settings['softlayer'].getfloat('timeout')), user_agent=consts.USER_AGENT, verify=self.settings['softlayer'].getboolean('verify'), ) @@ -184,7 +185,7 @@ def __init__(self, auth=None, transport=None, config_file=None): transport = transports.XmlRpcTransport( endpoint_url=url, proxy=self.settings['softlayer'].get('proxy'), - timeout=self.settings['softlayer'].getint('timeout'), + timeout=int(self.settings['softlayer'].getfloat('timeout')), user_agent=consts.USER_AGENT, verify=self.settings['softlayer'].getboolean('verify'), ) @@ -387,19 +388,25 @@ def authenticate_with_password(self, username, password, security_question_id=No 'username': username } - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) - if response.status_code != 200: - LOGGER.error("Unable to login: %s", response.text) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: %s", response.text) - response.raise_for_status() + response.raise_for_status() + tokens = json.loads(response.text) + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -427,19 +434,26 @@ def authenticate_with_passcode(self, passcode): 'response_type': 'cloud_iam' } - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) - if response.status_code != 200: - LOGGER.error("Unable to login: %s", response.text) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: %s", response.text) + + response.raise_for_status() + tokens = json.loads(response.text) - response.raise_for_status() + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) @@ -483,20 +497,27 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): data['account'] = account_id data['ims_account'] = ims_account - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + + if response.status_code != 200: + LOGGER.warning("Unable to refresh IAM Token. %s", response.text) - if response.status_code != 200: - LOGGER.warning("Unable to refresh IAM Token. %s", response.text) + response.raise_for_status() + tokens = json.loads(response.text) - response.raise_for_status() + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 476ab64b3..d5a8ee6f2 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -88,8 +88,8 @@ def cli(env, auth): new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) - # Ask for timeout - timeout = env.input('Timeout', default=defaults['timeout'] or 0) + # Ask for timeout, convert to float, then to int + timeout = int(float(env.input('Timeout', default=defaults['timeout'] or 0))) path = '~/.softlayer' if env.config_file: diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index b3530aa8c..f444b8389 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -15,6 +15,21 @@ class SoftLayerError(Exception): class Unauthenticated(SoftLayerError): """Unauthenticated.""" +class IAMError(SoftLayerError): + """Errors from iam.cloud.ibm.com""" + + def __init__(self, fault_code, fault_string, url=None): + SoftLayerError.__init__(self, fault_string) + self.faultCode = fault_code + self.faultString = fault_string + self.url = url + + def __repr__(self): + return "{} ({}): {}".format(self.url, self.faultCode, self.faultString) + + def __str__(self): + return "{} ({}): {}".format(self.url, self.faultCode, self.faultString) + class SoftLayerAPIError(SoftLayerError): """SoftLayerAPIError is an exception raised during API errors. diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index fca30af1c..7783ac2a2 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json +import os import sys import tempfile @@ -50,6 +51,12 @@ def set_up(self): self.env.client = SoftLayer.BaseClient(transport=transport) self.config_file = "./test_config_file" + def tearDown(self): + # Windows doesn't let you write and read from temp files + # So use a real file instead. + if os.path.exists(self.config_file): + os.remove(self.config_file) + @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @@ -74,6 +81,30 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): self.assertIn('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', contents) self.assertIn('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT, contents) + @mock.patch('SoftLayer.Client') + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.CLI.environment.Environment.getpass') + @mock.patch('SoftLayer.CLI.environment.Environment.input') + def test_setup_float_timeout(self, mocked_input, getpass, confirm_mock, client): + client.return_value = self.env.client + confirm_mock.return_value = True + getpass.return_value = 'A' * 64 + mocked_input.side_effect = ['public', 'user', 10.0] + + result = self.run_command(['--config=%s' % self.config_file, 'config', 'setup']) + + self.assert_no_fail(result) + self.assertIn('Configuration Updated Successfully', result.output) + + with open(self.config_file, 'r') as config_file: + contents = config_file.read() + self.assertIn('[softlayer]', contents) + self.assertIn('username = user', contents) + self.assertIn('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', contents) + self.assertIn('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT, contents) + self.assertNotIn('timeout = 10.0\n', contents) + self.assertIn('timeout = 10\n', contents) + @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @@ -118,3 +149,28 @@ def test_get_endpoint(self, mocked_input, getpass): # test-endpoint endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, 'test-endpoint') + + @mock.patch('SoftLayer.CLI.environment.Environment.input') + @mock.patch('SoftLayer.CLI.config.setup.get_sso_url') + @mock.patch('SoftLayer.CLI.config.setup.get_accounts') + @mock.patch('SoftLayer.API.IAMClient.authenticate_with_passcode') + @mock.patch('SoftLayer.API.IAMClient.refresh_iam_token') + @mock.patch('SoftLayer.API.IAMClient.call') + def test_sso_login(self, api_call, token, passcode, get_accounts, get_sso_url, mocked_input): + """Tests to make sure directly using an endpoint works""" + mocked_input.side_effect = ['n', '123qweasd'] + get_sso_url.return_value = "https://test.com/" + get_accounts.return_value = {"account_id": 12345, "ims_id": 5555} + passcode.return_value = {"access_token": "aassddffggh", "refresh_token": "qqqqqqq"} + token.return_value = {"access_token": "zzzzzz", "refresh_token": "fffffff"} + test_key = "zz112233" + user_object_1 = { + "apiAuthenticationKeys": [{"authenticationKey":test_key}], + "username":"testerson", + "id":99} + api_call.side_effect = [user_object_1] + + user, apikey = config.sso_login(self.env) + self.assertEqual("testerson", user) + self.assertEqual(test_key, apikey) + \ No newline at end of file From 485d9f94a0a41e705c53ff60ae48c26c23b28797 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 16:59:44 -0500 Subject: [PATCH 0383/1385] Added -a as an option Co-authored-by: ATGE <30413337+ATGE@users.noreply.github.com> --- SoftLayer/CLI/config/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index d5a8ee6f2..261139a26 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -47,7 +47,7 @@ def get_api_key(client, username, secret): # pylint: disable=inconsistent-retur @click.command() -@click.option('--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), +@click.option('-a', '--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), help="Select a method of authentication.", default='classic_key', show_default=True) @environment.pass_env def cli(env, auth): From 3e34733e76a4d9f0dbb07a7be1327418af313fd9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 17:24:26 -0500 Subject: [PATCH 0384/1385] tox fixes --- SoftLayer/API.py | 6 +++--- SoftLayer/config.py | 8 ++++++++ SoftLayer/exceptions.py | 1 + tests/CLI/modules/config_tests.py | 7 +++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index a21f9f654..a32491ba7 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -405,7 +405,7 @@ def authenticate_with_password(self, username, password, security_question_id=No error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -452,7 +452,7 @@ def authenticate_with_passcode(self, passcode): error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -516,7 +516,7 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 2f48aa221..291523d6c 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -102,6 +102,14 @@ def get_config(config_file=None): config_file = '~/.softlayer' config = configparser.ConfigParser() config.read(os.path.expanduser(config_file)) + # No configuration file found. + if not config.has_section('softlayer'): + config.add_section('softlayer') + config['softlayer']['username'] = '' + config['softlayer']['endpoint_url'] = '' + config['softlayer']['api_key'] = '' + config['softlayer']['timeout'] = 0 + return config diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index f444b8389..d7ce41bc3 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -15,6 +15,7 @@ class SoftLayerError(Exception): class Unauthenticated(SoftLayerError): """Unauthenticated.""" + class IAMError(SoftLayerError): """Errors from iam.cloud.ibm.com""" diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 7783ac2a2..5fe917c1c 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -165,12 +165,11 @@ def test_sso_login(self, api_call, token, passcode, get_accounts, get_sso_url, m token.return_value = {"access_token": "zzzzzz", "refresh_token": "fffffff"} test_key = "zz112233" user_object_1 = { - "apiAuthenticationKeys": [{"authenticationKey":test_key}], - "username":"testerson", - "id":99} + "apiAuthenticationKeys": [{"authenticationKey": test_key}], + "username": "testerson", + "id": 99} api_call.side_effect = [user_object_1] user, apikey = config.sso_login(self.env) self.assertEqual("testerson", user) self.assertEqual(test_key, apikey) - \ No newline at end of file From e20ceea73bb670183fe22a7f31c493d5e0d8a374 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 17:34:44 -0500 Subject: [PATCH 0385/1385] fixing a test --- SoftLayer/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 291523d6c..caa8def10 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -108,7 +108,7 @@ def get_config(config_file=None): config['softlayer']['username'] = '' config['softlayer']['endpoint_url'] = '' config['softlayer']['api_key'] = '' - config['softlayer']['timeout'] = 0 + config['softlayer']['timeout'] = '0' return config From 0f4b627ba74e303d6309cb5eb3df1ce0d211580f Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 31 Mar 2021 16:19:23 -0500 Subject: [PATCH 0386/1385] #1395 Forced reserved capacity guests to be monthly as hourly gets ordered as the wrong package --- SoftLayer/CLI/virt/capacity/create_guest.py | 5 ++++- SoftLayer/managers/vs_capacity.py | 3 +++ tests/managers/vs/vs_capacity_tests.py | 3 +-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/virt/capacity/create_guest.py b/SoftLayer/CLI/virt/capacity/create_guest.py index 4b8a983a6..07d215205 100644 --- a/SoftLayer/CLI/virt/capacity/create_guest.py +++ b/SoftLayer/CLI/virt/capacity/create_guest.py @@ -34,7 +34,10 @@ help="Test order, will return the order container, but not actually order a server.") @environment.pass_env def cli(env, **args): - """Allows for creating a virtual guest in a reserved capacity.""" + """Allows for creating a virtual guest in a reserved capacity. Only MONTHLY guests are supported at this time. + + If you would like support for hourly reserved capacity guests, please open an issue on the softlayer-python github. + """ create_args = _parse_create_args(env.client, args) create_args['primary_disk'] = args.get('primary_disk') diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 8ce5cf250..727a881b9 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -151,6 +151,9 @@ def create_guest(self, capacity_id, test, guest_object): # Reserved capacity only supports SAN as of 20181008 guest_object['local_disk'] = False + # Reserved capacity only supports monthly ordering via Virtual_Guest::generateOrderTemplate + # Hourly ordering would require building out the order manually. + guest_object['hourly'] = False template = vs_manager.verify_create_instance(**guest_object) template['reservedCapacityId'] = capacity_id if guest_object.get('ipv6'): diff --git a/tests/managers/vs/vs_capacity_tests.py b/tests/managers/vs/vs_capacity_tests.py index bb7178055..c6aad9f56 100644 --- a/tests/managers/vs/vs_capacity_tests.py +++ b/tests/managers/vs/vs_capacity_tests.py @@ -116,7 +116,6 @@ def test_create_guest(self): 'disks': (), 'domain': 'test.com', 'hostname': 'A1538172419', - 'hourly': True, 'ipv6': True, 'local_disk': None, 'os_code': 'UBUNTU_LATEST_64', @@ -132,7 +131,7 @@ def test_create_guest(self): 'maxMemory': None, 'hostname': 'A1538172419', 'domain': 'test.com', - 'hourlyBillingFlag': True, + 'hourlyBillingFlag': False, 'supplementalCreateObjectOptions': { 'bootMode': None, 'flavorKeyName': 'B1_1X2X25' From 848d27bb03a279d0758ccab2bb9dbd732b65ac4b Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 18:33:33 -0400 Subject: [PATCH 0387/1385] Add the option add and upgrade the hw disk. --- SoftLayer/CLI/hardware/upgrade.py | 24 +++++- .../fixtures/SoftLayer_Hardware_Server.py | 39 ++++++++++ SoftLayer/managers/hardware.py | 75 ++++++++++++++++++- tests/CLI/modules/server_tests.py | 35 +++++++++ tests/managers/hardware_tests.py | 31 ++++++++ 5 files changed, 197 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index d766000ab..037506df0 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -24,16 +24,22 @@ default=None, type=click.Choice(['Non-RAID', 'RAID'])) @click.option('--public-bandwidth', type=click.INT, help="Public Bandwidth in GB") +@click.option('--add-disk', nargs=2, multiple=True, type=(int, int), + help="Add a Hard disk in GB to a specific channel, e.g 1000 GB in disk2, it will be " + "--add-disk 1000 2") +@click.option('--resize-disk', nargs=2, multiple=True, type=(int, int), + help="Upgrade a specific disk size in GB, e.g --resize-disk 2000 2") @click.option('--test', is_flag=True, default=False, help="Do not actually upgrade the hardware server") @environment.pass_env -def cli(env, identifier, memory, network, drive_controller, public_bandwidth, test): +def cli(env, identifier, memory, network, drive_controller, public_bandwidth, add_disk, resize_disk, test): """Upgrade a Hardware Server.""" mgr = SoftLayer.HardwareManager(env.client) - if not any([memory, network, drive_controller, public_bandwidth]): + if not any([memory, network, drive_controller, public_bandwidth, add_disk, resize_disk]): raise exceptions.ArgumentError("Must provide " - " [--memory], [--network], [--drive-controller], or [--public-bandwidth]") + " [--memory], [--network], [--drive-controller], [--public-bandwidth]," + "[--add-disk] or [--resize-disk]") hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Hardware') if not test: @@ -41,7 +47,17 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, te "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') + disk_list = list() + if add_disk: + for guest_disk in add_disk: + disks = {'description': 'add_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} + disk_list.append(disks) + if resize_disk: + for guest_disk in resize_disk: + disks = {'description': 'resize_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} + disk_list.append(disks) + if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, - public_bandwidth=public_bandwidth, test=test): + public_bandwidth=public_bandwidth, disk=disk_list, test=test): raise exceptions.CLIAbort('Hardware Server Upgrade Failed') env.fout('Successfully Upgraded.') diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index a28d0fc13..0eacab158 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -13,6 +13,10 @@ 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], + 'nextInvoiceChildren': [ + {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1, 'categoryCode': 'disk1'}, + {'description': 'test2', 'nextInvoiceTotalRecurringAmount': 2, 'categoryCode': 'disk3'} + ], 'orderItem': { 'order': { 'userRecord': { @@ -336,5 +340,40 @@ "id": 6177, "keyName": "BANDWIDTH_500_GB" } + }, + { + "hourlyRecurringFee": ".023", + "id": 49759, + "recurringFee": "15", + "categories": [ + { + "categoryCode": "disk2", + "id": 6, + "name": "Third Hard Drive" + } + ], + "item": { + "capacity": "1000", + "description": "1.00 TB SATA", + "id": 6159, + "keyName": "HARD_DRIVE_1_00_TB_SATA_2", + } + }, + { + "id": 49759, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "disk1", + "id": 5, + "name": "Second Hard Drive" + } + ], + "item": { + "capacity": "1000", + "description": "1.00 TB SATA", + "id": 6159, + "keyName": "HARD_DRIVE_1_00_TB_SATA_2" + } } ] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d19a4c423..0ae4929b8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -818,7 +818,7 @@ def authorize_storage(self, hardware_id, username_storage): def upgrade(self, instance_id, memory=None, nic_speed=None, drive_controller=None, - public_bandwidth=None, test=False): + public_bandwidth=None, disk=None, test=False): """Upgrades a hardware server instance. :param int instance_id: Instance id of the hardware server to be upgraded. @@ -826,6 +826,7 @@ def upgrade(self, instance_id, memory=None, :param string nic_speed: Network Port Speed data. :param string drive_controller: Drive Controller data. :param int public_bandwidth: Public keyName data. + :param list disk: List of disks to add or upgrade Hardware Server. :param bool test: Test option to verify the request. :returns: bool @@ -857,6 +858,10 @@ def upgrade(self, instance_id, memory=None, 'packageId': package_id } + if disk: + prices = self._get_disk_price_list(instance_id, disk) + order['prices'] = prices + for option, value in data.items(): price_id = self._get_prices_for_upgrade_option(upgrade_prices, option, value) if not price_id: @@ -885,7 +890,7 @@ def get_instance(self, instance_id): the specified instance. """ mask = [ - 'billingItem[id,package[id,keyName]]' + 'billingItem[id,package[id,keyName],nextInvoiceChildren]' ] mask = "mask[%s]" % ','.join(mask) @@ -924,7 +929,10 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): 'disk_controller': 'disk_controller', 'bandwidth': 'bandwidth' } - category_code = option_category.get(option) + if 'disk' in option: + category_code = option + else: + category_code = option_category.get(option) for price in upgrade_prices: if price.get('categories') is None or price.get('item') is None: @@ -950,12 +958,73 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): elif option == 'bandwidth': if str(product.get('capacity')) == str(value): price_id = price.get('id') + elif 'disk' in option: + if str(product.get('capacity')) == str(value): + price_id = price else: if str(product.get('capacity')) == str(value): price_id = price.get('id') return price_id + def _get_disk_price_list(self, instance_id, disk): + """Get the disks prices to be added or upgraded. + + :param int instance_id: Hardware Server instance id. + :param list disk: List of disks to be added o upgraded to the HW. + + :return list. + """ + prices = [] + disk_exist = False + upgrade_prices = self._get_upgrade_prices(instance_id) + server_response = self.get_instance(instance_id) + for disk_data in disk: + disk_channel = 'disk' + str(disk_data.get('number')) + for item in utils.lookup(server_response, 'billingItem', 'nextInvoiceChildren'): + if disk_channel == item['categoryCode']: + disk_exist = True + break + if disk_exist: + disk_price_detail = self._get_disk_price_detail(disk_data, upgrade_prices, disk_channel, 'add_disk') + prices.append(disk_price_detail) + else: + disk_price_detail = self._get_disk_price_detail(disk_data, upgrade_prices, disk_channel, 'resize_disk') + prices.append(disk_price_detail) + + return prices + + def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_type): + """Get the disk price detail. + + :param disk_data: List of disks to be added or upgraded. + :param list upgrade_prices: List of item prices. + :param String disk_channel: Disk position. + :param String disk_type: Disk type. + + """ + if disk_data.get('description') == disk_type: + raise SoftLayerError("Unable to add the disk because this already exists." + if "add" in disk_type else "Unable to resize the disk because this does not exists.") + else: + price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, + disk_data.get('capacity')) + if not price_id: + raise SoftLayerError("The item price was not found for %s with 'capacity:' %i" % + (disk_channel, disk_data.get('capacity'))) + + disk_price = { + "id": price_id.get('id'), + "categories": [ + { + "categoryCode": price_id['categories'][0]['categoryCode'], + "id": price_id['categories'][0]['id'] + } + ] + } + + return disk_price + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index de7ccd95e..f378e9745 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -944,6 +944,41 @@ def test_upgrade_test(self, confirm_mock): '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_add_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '2']) + + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_resize_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '1']) + + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_not_price_found(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '3']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_already_exist(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '1']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_does_not_exist(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '3']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade(self, confirm_mock): confirm_mock.return_value = True diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 9f48ad2aa..f7f7c4ea2 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -869,6 +869,13 @@ def test_get_price_id_mismatch_capacity(self): result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) self.assertEqual(92, result) + def test_get_price_id_disk_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'disk1'}], 'item': {'capacity': 1}, 'id': 99} + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'disk1', 1) + self.assertEqual(99, result['id']) + def test_upgrade(self): result = self.hardware.upgrade(1, memory=32) @@ -878,6 +885,30 @@ def test_upgrade(self): order_container = call.args[0] self.assertEqual(order_container['prices'], [{'id': 209391}]) + def test_upgrade_add_disk(self): + disk_list = list() + disks = {'description': 'add_disk', 'capacity': 1000, 'number': 2} + disk_list.append(disks) + result = self.hardware.upgrade(1, disk=disk_list) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'][0]['id'], 49759) + + def test_upgrade_resize_disk(self): + disk_list = list() + disks = {'description': 'resize_disk', 'capacity': 1000, 'number': 1} + disk_list.append(disks) + result = self.hardware.upgrade(1, disk=disk_list) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'][0]['id'], 49759) + def test_upgrade_blank(self): result = self.hardware.upgrade(1) From 791c18dd4093d65e5051b2df96de3e68fa21947a Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 18:54:17 -0400 Subject: [PATCH 0388/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0ae4929b8..34a724710 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1004,8 +1004,12 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t """ if disk_data.get('description') == disk_type: - raise SoftLayerError("Unable to add the disk because this already exists." - if "add" in disk_type else "Unable to resize the disk because this does not exists.") + if "add" in disk_type: + disk_type_description = "Unable to add the disk because this already exists." + else: + disk_type_description = "Unable to resize the disk because this does not exists." + + raise SoftLayerError(disk_type_description) else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, disk_data.get('capacity')) From 6885a47d5513ddceed00fac2f5d71a3d6314ce6d Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 19:53:11 -0400 Subject: [PATCH 0389/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 34a724710..0c290696f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1005,11 +1005,9 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t """ if disk_data.get('description') == disk_type: if "add" in disk_type: - disk_type_description = "Unable to add the disk because this already exists." + raise SoftLayerError("Unable to add the disk because this already exists.") else: - disk_type_description = "Unable to resize the disk because this does not exists." - - raise SoftLayerError(disk_type_description) + raise SoftLayerError("Unable to resize the disk because this does not exists.") else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, disk_data.get('capacity')) From 24f7bb9e23c3d7d7153c9f4c1d71831a1c16a2a5 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 20:02:23 -0400 Subject: [PATCH 0390/1385] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0c290696f..e161f6ef7 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1006,7 +1006,7 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t if disk_data.get('description') == disk_type: if "add" in disk_type: raise SoftLayerError("Unable to add the disk because this already exists.") - else: + if "resize" in disk_type: raise SoftLayerError("Unable to resize the disk because this does not exists.") else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, From fb2b08ab826b64f34f739981dc173c3930e5131d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Apr 2021 15:23:27 -0500 Subject: [PATCH 0391/1385] #1352 removing the rwhois commands --- SoftLayer/CLI/routes.py | 4 -- SoftLayer/CLI/rwhois/__init__.py | 1 - SoftLayer/CLI/rwhois/edit.py | 55 --------------------- SoftLayer/CLI/rwhois/show.py | 34 ------------- SoftLayer/managers/network.py | 37 +------------- docs/cli/rwhois.rst | 12 ----- tests/CLI/modules/rwhois_tests.py | 81 ------------------------------- tests/managers/network_tests.py | 39 --------------- 8 files changed, 1 insertion(+), 262 deletions(-) delete mode 100644 SoftLayer/CLI/rwhois/__init__.py delete mode 100644 SoftLayer/CLI/rwhois/edit.py delete mode 100644 SoftLayer/CLI/rwhois/show.py delete mode 100644 docs/cli/rwhois.rst delete mode 100644 tests/CLI/modules/rwhois_tests.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 1bb6f5d9c..6307c3a5e 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -231,10 +231,6 @@ ('order:quote', 'SoftLayer.CLI.order.quote:cli'), ('order:lookup', 'SoftLayer.CLI.order.lookup:cli'), - ('rwhois', 'SoftLayer.CLI.rwhois'), - ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), - ('rwhois:show', 'SoftLayer.CLI.rwhois.show:cli'), - ('hardware', 'SoftLayer.CLI.hardware'), ('hardware:bandwidth', 'SoftLayer.CLI.hardware.bandwidth:cli'), ('hardware:cancel', 'SoftLayer.CLI.hardware.cancel:cli'), diff --git a/SoftLayer/CLI/rwhois/__init__.py b/SoftLayer/CLI/rwhois/__init__.py deleted file mode 100644 index ef14d6880..000000000 --- a/SoftLayer/CLI/rwhois/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Referral Whois.""" diff --git a/SoftLayer/CLI/rwhois/edit.py b/SoftLayer/CLI/rwhois/edit.py deleted file mode 100644 index 01854bdc9..000000000 --- a/SoftLayer/CLI/rwhois/edit.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Edit the RWhois data on the account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions - - -@click.command() -@click.option('--abuse', help='Set the abuse email address') -@click.option('--address1', help='Update the address 1 field') -@click.option('--address2', help='Update the address 2 field') -@click.option('--city', help='Set the city name') -@click.option('--company', help='Set the company name') -@click.option('--country', help='Set the two-letter country code') -@click.option('--firstname', help='Update the first name field') -@click.option('--lastname', help='Update the last name field') -@click.option('--postal', help='Set the postal code field') -@click.option('--public/--private', - default=None, - help='Flags the address as a public or private residence.') -@click.option('--state', help='Set the two-letter state code') -@environment.pass_env -def cli(env, abuse, address1, address2, city, company, country, firstname, - lastname, postal, public, state): - """Edit the RWhois data on the account.""" - mgr = SoftLayer.NetworkManager(env.client) - - update = { - 'abuse_email': abuse, - 'address1': address1, - 'address2': address2, - 'company_name': company, - 'city': city, - 'country': country, - 'first_name': firstname, - 'last_name': lastname, - 'postal_code': postal, - 'state': state, - 'private_residence': public, - } - - if public is True: - update['private_residence'] = False - elif public is False: - update['private_residence'] = True - - check = [x for x in update.values() if x is not None] - if not check: - raise exceptions.CLIAbort( - "You must specify at least one field to update.") - - mgr.edit_rwhois(**update) diff --git a/SoftLayer/CLI/rwhois/show.py b/SoftLayer/CLI/rwhois/show.py deleted file mode 100644 index 9e862b0f3..000000000 --- a/SoftLayer/CLI/rwhois/show.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Display the RWhois information for your account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """Display the RWhois information for your account.""" - - mgr = SoftLayer.NetworkManager(env.client) - result = mgr.get_rwhois() - - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' - table.add_row(['Name', result['firstName'] + ' ' + result['lastName']]) - table.add_row(['Company', result['companyName']]) - table.add_row(['Abuse Email', result['abuseEmail']]) - table.add_row(['Address 1', result['address1']]) - if result.get('address2'): - table.add_row(['Address 2', result['address2']]) - table.add_row(['City', result['city']]) - table.add_row(['State', result.get('state', '-')]) - table.add_row(['Postal Code', result.get('postalCode', '-')]) - table.add_row(['Country', result['country']]) - table.add_row(['Private Residence', result['privateResidenceFlag']]) - - env.fout(table) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 11ed9733e..d609de5d5 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -56,7 +56,7 @@ class NetworkManager(object): - """Manage SoftLayer network objects: VLANs, subnets, IPs and rwhois + """Manage SoftLayer network objects: VLANs, subnets and IPs See product information here: https://www.ibm.com/cloud/network @@ -311,34 +311,6 @@ def detach_securitygroup_components(self, group_id, component_ids): return self.security_group.detachNetworkComponents(component_ids, id=group_id) - def edit_rwhois(self, abuse_email=None, address1=None, address2=None, - city=None, company_name=None, country=None, - first_name=None, last_name=None, postal_code=None, - private_residence=None, state=None): - """Edit rwhois record.""" - update = {} - for key, value in [('abuseEmail', abuse_email), - ('address1', address1), - ('address2', address2), - ('city', city), - ('companyName', company_name), - ('country', country), - ('firstName', first_name), - ('lastName', last_name), - ('privateResidenceFlag', private_residence), - ('state', state), - ('postalCode', postal_code)]: - if value is not None: - update[key] = value - - # If there's anything to update, update it - if update: - rwhois = self.get_rwhois() - return self.client['Network_Subnet_Rwhois_Data'].editObject( - update, id=rwhois['id']) - - return True - def edit_securitygroup(self, group_id, name=None, description=None): """Edit security group details. @@ -408,13 +380,6 @@ def ip_lookup(self, ip_address): obj = self.client['Network_Subnet_IpAddress'] return obj.getByIpAddress(ip_address, mask='hardware, virtualGuest') - def get_rwhois(self): - """Returns the RWhois information about the current account. - - :returns: A dictionary containing the account's RWhois information. - """ - return self.account.getRwhoisData() - def get_securitygroup(self, group_id, **kwargs): """Returns the information about the given security group. diff --git a/docs/cli/rwhois.rst b/docs/cli/rwhois.rst deleted file mode 100644 index 10d2004c9..000000000 --- a/docs/cli/rwhois.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. _cli_rwhois: - -Reverse Whois Commands -====================== - -.. click:: SoftLayer.CLI.rwhois.edit:cli - :prog: rwhois edit - :show-nested: - -.. click:: SoftLayer.CLI.rwhois.show:cli - :prog: rwhois show - :show-nested: diff --git a/tests/CLI/modules/rwhois_tests.py b/tests/CLI/modules/rwhois_tests.py deleted file mode 100644 index 6409bf884..000000000 --- a/tests/CLI/modules/rwhois_tests.py +++ /dev/null @@ -1,81 +0,0 @@ -""" - SoftLayer.tests.CLI.modules.rwhois_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -from SoftLayer.CLI import exceptions -from SoftLayer import testing - -import json - - -class RWhoisTests(testing.TestCase): - def test_edit_nothing(self): - - result = self.run_command(['rwhois', 'edit']) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - - def test_edit(self): - - result = self.run_command(['rwhois', 'edit', - '--abuse=abuse@site.com', - '--address1=address line 1', - '--address2=address line 2', - '--company=Company, Inc', - '--city=Dallas', - '--country=United States', - '--firstname=John', - '--lastname=Smith', - '--postal=12345', - '--state=TX', - '--state=TX', - '--private']) - - self.assert_no_fail(result) - self.assertEqual(result.output, "") - - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - args=({'city': 'Dallas', - 'firstName': 'John', - 'companyName': 'Company, Inc', - 'address1': 'address line 1', - 'address2': 'address line 2', - 'lastName': 'Smith', - 'abuseEmail': 'abuse@site.com', - 'state': 'TX', - 'country': 'United States', - 'postalCode': '12345', - 'privateResidenceFlag': True},), - identifier='id') - - def test_edit_public(self): - result = self.run_command(['rwhois', 'edit', '--public']) - - self.assert_no_fail(result) - self.assertEqual(result.output, "") - - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - args=({'privateResidenceFlag': False},), - identifier='id') - - def test_show(self): - self.maxDiff = 100000 - result = self.run_command(['rwhois', 'show']) - - expected = {'Abuse Email': 'abuseEmail', - 'Address 1': 'address1', - 'Address 2': 'address2', - 'City': 'city', - 'Company': 'companyName', - 'Country': 'country', - 'Name': 'firstName lastName', - 'Postal Code': 'postalCode', - 'State': '-', - 'Private Residence': True} - self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), expected) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 361ea1a61..80d054f9d 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -222,39 +222,6 @@ def test_detach_securitygroup_components(self): 'detachNetworkComponents', identifier=100, args=([500, 600],)) - def test_edit_rwhois(self): - result = self.network.edit_rwhois( - abuse_email='abuse@test.foo', - address1='123 Test Street', - address2='Apt. #31', - city='Anywhere', - company_name='TestLayer', - country='US', - first_name='Bob', - last_name='Bobinson', - postal_code='9ba62', - private_residence=False, - state='TX') - - self.assertEqual(result, True) - expected = { - 'abuseEmail': 'abuse@test.foo', - 'address1': '123 Test Street', - 'address2': 'Apt. #31', - 'city': 'Anywhere', - 'companyName': 'TestLayer', - 'country': 'US', - 'firstName': 'Bob', - 'lastName': 'Bobinson', - 'postalCode': '9ba62', - 'privateResidenceFlag': False, - 'state': 'TX', - } - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - identifier='id', - args=(expected,)) - def test_edit_securitygroup(self): result = self.network.edit_securitygroup(100, name='foobar') @@ -290,12 +257,6 @@ def test_edit_securitygroup_rule_unset(self): 'portRangeMin': -1, 'portRangeMax': -1, 'ethertype': '', 'remoteIp': ''}],)) - def test_get_rwhois(self): - result = self.network.get_rwhois() - - self.assertEqual(result, fixtures.SoftLayer_Account.getRwhoisData) - self.assert_called_with('SoftLayer_Account', 'getRwhoisData') - def test_get_securitygroup(self): result = self.network.get_securitygroup(100) From 262507e07c861ee249f959d2301cdc1d899789a1 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Apr 2021 16:07:00 -0500 Subject: [PATCH 0392/1385] #1457 added contributing guide --- CONTRIBUTING.md | 62 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1eed6d308..9182f802b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,14 +3,22 @@ We are happy to accept contributions to softlayer-python. Please follow the guidelines below. -* Sign our contributor agreement (CLA) You can find the [CLA here](./docs/dev/cla-individual.md). +## Procedural -* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/dev/cla-corporate.md). - -* Fork the repo, make your changes, and open a pull request. +1. All code changes require a corresponding issue. [Open an issue here](https://github.com/softlayer/softlayer-python/issues). +2. Fork the [softlayer-python](https://github.com/softlayer/softlayer-python) repository. +3. Make any changes required, commit messages should reference the issue number (include #1234 if the message if your issue is number 1234 for example). +4. Make a pull request from your fork/branch to origin/master +5. Requires 1 approval for merging * Additional infomration can be found in our [contribution guide](http://softlayer-python.readthedocs.org/en/latest/dev/index.html) +## Legal + +* See our [Contributor License Agreement](./docs/dev/cla-individual.md). Opening a pull request is acceptance of this agreement. + +* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/dev/cla-corporate.md). + ## Code style @@ -101,4 +109,48 @@ order_args = getattr(order_call[0], 'args')[0] # Test our specific argument value self.assertEqual(123, order_args['hostId']) -``` \ No newline at end of file +``` + + +## Project Management + +### Issues + +* ~~Title~~: Should contain quick highlight of the issue is about +* ~~Body~~: All the technical information goes here +* ~~Assignee~~: Should be the person who is actively working on an issue. +* ~~Label~~: All issues should have at least 1 Label. +* ~~Projects~~: Should be added to the quarerly Softlayer project when being worked on +* ~~Milestones~~: Not really used, can be left blank +* ~~Linked Pull Request~~: Should be linked to the relavent pull request when it is opened. + +### Pull Requests + +* ~~Title~~: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request +* ~~Body~~: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. +* ~~Reviewers~~: 1 Reviewer is required +* ~~Assignee~~: Should be the person who opened the pull request +* ~~Labels~~: Should match the issue +* ~~Projects~~: Should match the issue +* ~~Milestones~~: Not really used, can be left blank +* ~~Linked issues~~: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. + +### Code Reviews +All issues should be reviewed by at least 1 member of the SLDN team that is not the person opening the pull request. Time permitting, all members of the SLDN team should review the request. + +#### Things to look for while doing a review + +As a reviewer, these are some guidelines when doing a review, but not hard rules. + +* Code Style: Generally `tox -e analysis` will pick up most style violations, but anything that is wildly different from the normal code patters in this project should be changed to match, unless there is a strong reason to not do so. +* API Calls: Close attention should be made to any new API calls, to make sure they will work as expected, and errors are handled if needed. +* DocBlock comments: CLI and manager methods need to be documented well enough for users to easily understand what they do. +* Easy to read code: Code should generally be easy enough to understand at a glance. Confusing code is a sign that it should either be better documented, or refactored a bit to be clearer in design. + + +### Testing + +When doing testing of a code change, indicate this with a comment on the pull request like + +:heavy_check: `slcli vs list --new-feature` +:x: `slcli vs list --broken-feature` From 26d9162db780889617e0f6071b1b1bc1db0365d9 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 09:01:27 -0400 Subject: [PATCH 0393/1385] Add an --orderBy parameters to call-api --- SoftLayer/CLI/call_api.py | 8 +++++++- SoftLayer/utils.py | 16 ++++++++++++++++ tests/CLI/modules/call_api_tests.py | 14 ++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index cbce4eccb..cf0a2b871 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -112,6 +112,9 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument @click.option('--mask', help="String-based object mask") @click.option('--limit', type=click.INT, help="Result limit") @click.option('--offset', type=click.INT, help="Result offset") +@click.option('--orderBy', type=click.STRING, help="an object filter that adds an order by clause" + "E.G --orderBy subnets.id default DESC" + " --orderBy subnets.id=ASC") @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") @click.option('--json-filter', callback=_validate_filter, @@ -119,7 +122,7 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument "Remember to use double quotes (\") for variable names. Can NOT be used with --filter. " "Dont use whitespace outside of strings, or the slcli might have trouble parsing it.") @environment.pass_env -def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, +def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, orderby=None, output_python=False, json_filter=None): """Call arbitrary API endpoints with the given SERVICE and METHOD. @@ -147,6 +150,9 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, raise exceptions.CLIAbort("--filter and --json-filter cannot be used together.") object_filter = _build_filters(_filters) + if orderby: + _filters = utils.build_filter_orderby(orderby) + object_filter.update(_filters) if json_filter: object_filter.update(json_filter) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cc6d7bd4f..3900bb9dd 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -200,6 +200,22 @@ def event_log_filter_less_than_date(date, utc): } +def build_filter_orderby(orderby): + _filters = {} + aux = list(reversed(str(orderby).split('.'))) + for split in aux: + _aux_filter = {} + if str(split).__contains__('='): + _aux_filter[str(split).split('=')[0]] = query_filter_orderby(str(split).split('=')[1]) + _filters = _aux_filter + elif split == list(aux)[0]: + _aux_filter[split] = query_filter_orderby('DESC') + else: + _aux_filter[split] = _filters + _filters = _aux_filter + return _filters + + class IdentifierMixin(object): """Mixin used to resolve ids from other names of objects. diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index 8d3f19ab2..b98998f29 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -298,3 +298,17 @@ def test_json_filter(self): result = self.run_command(['call-api', 'Account', 'getObject', '--json-filter={"test":"something"}']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getObject', filter={"test": "something"}) + + def test_call_api_orderBy(self): + result = self.run_command(['call-api', 'Account', 'getVirtualGuests', + '--orderBy', 'virtualGuests.id=DESC']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', + 'getVirtualGuests', + filter={ + 'virtualGuests': + {'id': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC']}]}}}) From c232336bcf8e46f4181e3e5e85d62806b9f69453 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 11:16:46 -0400 Subject: [PATCH 0394/1385] Add method comment --- SoftLayer/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 3900bb9dd..f5cdce405 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -201,6 +201,12 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): + """ + Builds filters using the filter options passed into the CLI. + + only support fot create filter option orderBy, default value is DESC + + """ _filters = {} aux = list(reversed(str(orderby).split('.'))) for split in aux: From 447b3aa5303be9005706b667ba37488b61e7ff0c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 12:02:00 -0400 Subject: [PATCH 0395/1385] Add method comment --- SoftLayer/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index f5cdce405..ac2e34099 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -201,11 +201,9 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): - """ - Builds filters using the filter options passed into the CLI. - - only support fot create filter option orderBy, default value is DESC + """Builds filters using the filter options passed into the CLI. + Only support fot create filter option orderBy, default value is DESC. """ _filters = {} aux = list(reversed(str(orderby).split('.'))) From 19f28989606c30c404299659221066163111c591 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 18:23:04 -0400 Subject: [PATCH 0396/1385] fix Christopher code review comments --- SoftLayer/CLI/hardware/detail.py | 7 +++++-- SoftLayer/managers/hardware.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index c5b1c2b9b..512850dba 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,8 +61,11 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) - table.add_row(['last_transaction', - utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name')]) + + last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), + utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) + + table.add_row(['last_transaction', last_transaction]) table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 9045d9bda..ea2fb1a63 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -263,7 +263,7 @@ def get_hardware(self, hardware_id, **kwargs): 'children[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' - 'lastTransaction[transactionGroup[name]],' + 'lastTransaction[transactionGroup],' 'hourlyBillingFlag,' 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' From 10bfe14bb64f3e1ce189afc781387b0adfc3cced Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 14:34:44 -0500 Subject: [PATCH 0397/1385] #1436 added checking for a special character sequence for when windows users use shift+ins to paste into a password field --- SoftLayer/CLI/environment.py | 17 ++++++++++++++++- tests/CLI/environment_tests.py | 9 +++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index c73bc6385..b1670f949 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -67,7 +67,22 @@ def input(self, prompt, default=None, show_default=True): def getpass(self, prompt, default=None): """Provide a password prompt.""" - return click.prompt(prompt, hide_input=True, default=default) + password = click.prompt(prompt, hide_input=True, default=default) + + # https://github.com/softlayer/softlayer-python/issues/1436 + # click.prompt uses python's getpass() in the background + # https://github.com/python/cpython/blob/3.9/Lib/getpass.py#L97 + # In windows, shift+insert actually inputs the below 2 characters + # If we detect those 2 characters, need to manually read from the clipbaord instead + # https://stackoverflow.com/questions/101128/how-do-i-read-text-from-the-clipboard + if password == 'àR': + # tkinter is a built in python gui, but it has clipboard reading functions. + from tkinter import Tk + tk_manager = Tk() + password = tk_manager.clipboard_get() + # keep the window from showing + tk_manager.withdraw() + return password # Command loading methods def list_commands(self, *path): diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index fa90ba1db..f000819a6 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -55,6 +55,15 @@ def test_getpass(self, prompt_mock): prompt_mock.assert_called_with('input', default=None, hide_input=True) self.assertEqual(prompt_mock(), r) + @mock.patch('click.prompt') + @mock.patch('tkinter.Tk.clipboard_get') + def test_getpass_issues1436(self, tk, prompt_mock): + tk.return_value = 'test_from_clipboard' + prompt_mock.return_value = 'àR' + r = self.env.getpass('input') + prompt_mock.assert_called_with('input', default=None, hide_input=True) + self.assertEqual('test_from_clipboard', r) + def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} r = self.env.resolve_alias('aliasname') From 391bf8ccd7e2537d3d213c449d928f83a1882070 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 15:13:48 -0500 Subject: [PATCH 0398/1385] fixing a unit test --- tests/CLI/environment_tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index f000819a6..b6d275941 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -7,6 +7,7 @@ import click import mock +# from unittest.mock import MagicMock from SoftLayer.CLI import environment from SoftLayer import testing @@ -56,13 +57,13 @@ def test_getpass(self, prompt_mock): self.assertEqual(prompt_mock(), r) @mock.patch('click.prompt') - @mock.patch('tkinter.Tk.clipboard_get') + @mock.patch('tkinter.Tk') def test_getpass_issues1436(self, tk, prompt_mock): - tk.return_value = 'test_from_clipboard' prompt_mock.return_value = 'àR' r = self.env.getpass('input') prompt_mock.assert_called_with('input', default=None, hide_input=True) - self.assertEqual('test_from_clipboard', r) + tk.assert_called_with() + def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} From 07df909a4321b77211db521158ba11073852e6a3 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 15:32:58 -0500 Subject: [PATCH 0399/1385] tox fixes --- SoftLayer/CLI/environment.py | 1 + tests/CLI/environment_tests.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index b1670f949..e16c5cde9 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -77,6 +77,7 @@ def getpass(self, prompt, default=None): # https://stackoverflow.com/questions/101128/how-do-i-read-text-from-the-clipboard if password == 'àR': # tkinter is a built in python gui, but it has clipboard reading functions. + # pylint: disable=import-outside-toplevel from tkinter import Tk tk_manager = Tk() password = tk_manager.clipboard_get() diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index b6d275941..a7a91d0f2 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -60,11 +60,10 @@ def test_getpass(self, prompt_mock): @mock.patch('tkinter.Tk') def test_getpass_issues1436(self, tk, prompt_mock): prompt_mock.return_value = 'àR' - r = self.env.getpass('input') + self.env.getpass('input') prompt_mock.assert_called_with('input', default=None, hide_input=True) tk.assert_called_with() - def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} r = self.env.resolve_alias('aliasname') From c8bb4ff7c8157ee768c5cb4ac4c5975a1abbc530 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 9 Apr 2021 15:19:50 -0500 Subject: [PATCH 0400/1385] removed reference to rwhois in the documentation --- docs/cli.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index dc82da29f..a659b145c 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -92,7 +92,6 @@ To discover the available commands, simply type `slcli`. object-storage Object Storage. order View and order from the catalog. report Reports. - rwhois Referral Whois. securitygroup Network security groups. setup Edit configuration. shell Enters a shell for slcli. From 9c4f3ba229d29060a2960c6f224ea801e94207d9 Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 9 Apr 2021 19:32:17 -0400 Subject: [PATCH 0401/1385] Fix slcli hw upgrade disk to support with rest. --- SoftLayer/managers/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index e161f6ef7..2b6eb7f8f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -896,7 +896,7 @@ def get_instance(self, instance_id): return self.hardware.getObject(id=instance_id, mask=mask) - def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): + def _get_upgrade_prices(self, instance_id): """Following Method gets all the price ids related to upgrading a Hardware Server. :param int instance_id: Instance id of the Hardware Server to be upgraded. @@ -910,7 +910,7 @@ def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): 'item[keyName,description,capacity,units]' ] mask = "mask[%s]" % ','.join(mask) - return self.hardware.getUpgradeItemPrices(include_downgrade_options, id=instance_id, mask=mask) + return self.hardware.getUpgradeItemPrices(id=instance_id, mask=mask) @staticmethod def _get_prices_for_upgrade_option(upgrade_prices, option, value): From f39538a3066229a3667f1e1afb705320d1ab9177 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 13 Apr 2021 15:49:31 -0400 Subject: [PATCH 0402/1385] add Billing and lastTransaction on slcli virtual detail --- SoftLayer/CLI/virt/detail.py | 5 +++++ SoftLayer/managers/vs.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 01a66cc9f..ac9453ddd 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,6 +69,11 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) + last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), + utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) + + table.add_row(['last_transaction', last_transaction]) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index c0eb91b9a..77410ff4a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -227,7 +227,7 @@ def get_instance(self, instance_id, **kwargs): 'maxMemory,' 'datacenter,' 'activeTransaction[id, transactionStatus[friendlyName,name]],' - 'lastTransaction[transactionStatus],' + 'lastTransaction[transactionStatus,modifyDate,transactionGroup[name]],' 'lastOperatingSystemReload.id,' 'blockDevices,' 'blockDeviceTemplateGroup[id, name, globalIdentifier],' From 9e27d841e0b8512229839c45c29b19bb69545b5c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 15:34:13 -0500 Subject: [PATCH 0403/1385] #1462 Added automation to publish to test-pypi in preperation for fully automating the build process --- .github/workflows/test_pypi_release.yml | 37 ++++++++++++++ README.rst | 7 ++- RELEASE.md | 68 ++++++++++++++++++++++--- 3 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test_pypi_release.yml diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml new file mode 100644 index 000000000..5e7c6b683 --- /dev/null +++ b/.github/workflows/test_pypi_release.yml @@ -0,0 +1,37 @@ +# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ + +name: Publish 📦 to TestPyPI + +on: + push: + branches: [ master ] + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@master + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.CGALLO_TEST_PYPI }} + repository_url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/README.rst b/README.rst index 75e5d6f54..2ae928347 100644 --- a/README.rst +++ b/README.rst @@ -28,8 +28,7 @@ SoftLayer products and services. Documentation ------------- -Documentation for the Python client is available at -http://softlayer.github.io/softlayer-python/. +Documentation for the Python client is available at `Read the Docs `_ . Additional API documentation can be found on the SoftLayer Development Network: @@ -38,7 +37,7 @@ Additional API documentation can be found on the SoftLayer Development Network: * `Object mask information and examples `_ * `Code Examples - `_ + `_ Installation ------------ @@ -82,7 +81,7 @@ Issues with the Softlayer API itself should be addressed by opening a ticket. Examples -------- -A curated list of examples on how to use this library can be found at `softlayer.github.io `_ +A curated list of examples on how to use this library can be found at `SLDN `_ Debugging --------- diff --git a/RELEASE.md b/RELEASE.md index eb1cb6d47..962ee1663 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,18 +1,74 @@ -# Release steps -* Update version constants (find them by running `git grep [VERSION_NUMBER]`) -* Create changelog entry (edit CHANGELOG.md with a one-liner for each closed issue going in the release) -* Commit and push changes to master with the message: "Version Bump to v[VERSION_NUMBER]" -* Make sure your `upstream` repo is set + +# Versions + +This project follows the Major.Minor.Revision versioning system. Fixes, and minor additions would increment Revision. Large changes and additions would increment Minor, and anything that would be a "Breaking" change, or redesign would be an increment of Major. + +# Changelog + +When doing a release, the Changelog format should be as follows: + +```markdown + +## [Version] - YYYY-MM-DD +https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 + +#### New Command +- `slcli new command` #issueNumber + +#### Improvements +- List out improvements #issueNumber +- Something else that changed #issueNumber + +#### Deprecated +- List something that got removed #issueNumber + +``` + +# Normal Release steps + +A "release" of the softlayer-python project is the current state of the `master` branch. Any changes in the master branch should be considered releaseable. + + +1. Create the changelog entry, us this to update `CHANGELOG.md` and as the text for the release on github. +2. Update the version numbers in these files on the master branch. + - `SoftLayer/consts.py` + - `setup.py` +3. Make sure the tests for the build all pass +4. [Draft a new release](https://github.com/softlayer/softlayer-python/releases/new) + - Version should start with `v` followed by Major.Minor.Revision: `vM.m.r` + - Title should be `M.m.r` + - Description should be the release notes + - Target should be the `master` branch +5. The github automation should take care of publishing the release to [PyPi](https://pypi.org/project/SoftLayer/). This may take a few minutes to update. + +# Manual Release steps + +1. Create the changelog entry, us this to update `CHANGELOG.md` and as the text for the release on github. +2. Update the version numbers in these files on the master branch. + - `SoftLayer/consts.py` + - `setup.py` +3. Commit your changes to `master`, and make sure `softlayer/softlayer-python` repo is updated to reflect that +4. Make sure your `upstream` repo is set + ``` git remote -v upstream git@github.com:softlayer/softlayer-python.git (fetch) upstream git@github.com:softlayer/softlayer-python.git (push) ``` -* Push tag and PyPi `python fabfile.py 5.7.2`. Before you do this, make sure you have the organization repository set up as upstream remote, also make sure that you have pip set up with your PyPi user credentials. The easiest way to do that is to create a file at `~/.pypirc` with the following contents: + +5. Create and publish the package + - Make sure you have `twine` installed, this is what uploads the pacakge to PyPi. + - Before you do this, make sure you have the organization repository set up as upstream remote, also make sure that you have pip set up with your PyPi user credentials. The easiest way to do that is to create a file at `~/.pypirc` with the following contents: ``` [server-login] username:YOUR_USERNAME password:YOUR_PASSWORD ``` + + - Run `python fabfile.py 5.7.2`. Where `5.7.2` is the `M.m.r` version number. Don't use the `v` here in the version number. + + +*NOTE* PyPi doesn't let you reupload a version, if you upload a bad package for some reason, you have to create a new version. + From 53fc65625e25aa4206aa0712ce2811448836557c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 16:39:38 -0500 Subject: [PATCH 0404/1385] Added a utility to merge objectFilters, #1459 1461 --- SoftLayer/utils.py | 16 ++++++++++++++++ tests/basic_tests.py | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cc6d7bd4f..bd7f33c91 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +import collections import datetime import re import time @@ -57,6 +58,21 @@ def to_dict(self): for key, val in self.items()} +def dict_merge(dct1, dct2): + """Recursively merges dct2 into dct1, ideal for merging objectFilter together. + + :param dct1: dict onto which the merge is executed + :param dct2: dct merged into dct + :return: None + """ + + for k, v in dct2.items(): + if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): + dict_merge(dct1[k], dct2[k]) + else: + dct1[k] = dct2[k] + + def query_filter(query): """Translate a query-style string to a 'filter'. diff --git a/tests/basic_tests.py b/tests/basic_tests.py index b430a3d5e..f4dbe6085 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -79,6 +79,16 @@ def test_timezone(self): self.assertEqual(datetime.timedelta(0), time.dst()) self.assertEqual(datetime.timedelta(0), time.utcoffset()) + def test_dict_merge(self): + filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} + filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} + SoftLayer.utils.dict_merge(filter1, filter2) + + self.assertEqual(filter1['virtualGuests']['id']['operation'], 'orderBy') + self.assertEqual(filter1['virtualGuests']['hostname']['operation'], 'etst') + + + class TestNestedDict(testing.TestCase): From ffce9b3020437e99d46d850165c2d5a713dd7ad0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 16:48:06 -0500 Subject: [PATCH 0405/1385] changed dict_merge to return a merged dictionary --- SoftLayer/utils.py | 14 ++++++++------ tests/basic_tests.py | 7 ++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index bd7f33c91..c0851ec4a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -59,18 +59,20 @@ def to_dict(self): def dict_merge(dct1, dct2): - """Recursively merges dct2 into dct1, ideal for merging objectFilter together. + """Recursively merges dct2 and dct1, ideal for merging objectFilter together. - :param dct1: dict onto which the merge is executed - :param dct2: dct merged into dct - :return: None + :param dct1: A dictionary + :param dct2: A dictionary + :return: dct1 + dct2 """ + dct = dct1.copy() for k, v in dct2.items(): if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): - dict_merge(dct1[k], dct2[k]) + dct[k] = dict_merge(dct1[k], dct2[k]) else: - dct1[k] = dct2[k] + dct[k] = dct2[k] + return dct def query_filter(query): diff --git a/tests/basic_tests.py b/tests/basic_tests.py index f4dbe6085..dbbcbef0a 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -82,10 +82,11 @@ def test_timezone(self): def test_dict_merge(self): filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} - SoftLayer.utils.dict_merge(filter1, filter2) + result = SoftLayer.utils.dict_merge(filter1, filter2) - self.assertEqual(filter1['virtualGuests']['id']['operation'], 'orderBy') - self.assertEqual(filter1['virtualGuests']['hostname']['operation'], 'etst') + self.assertEqual(result['virtualGuests']['id']['operation'], 'orderBy') + self.assertNotIn('id', filter1['virtualGuests']) + self.assertEqual(result['virtualGuests']['hostname']['operation'], 'etst') From 554cbbd33fbc3c40c2751c8f2182e92c5d24a3ff Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 18:42:37 -0500 Subject: [PATCH 0406/1385] tox fixes --- SoftLayer/utils.py | 2 +- tests/basic_tests.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index c0851ec4a..83bd79eae 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -67,7 +67,7 @@ def dict_merge(dct1, dct2): """ dct = dct1.copy() - for k, v in dct2.items(): + for k, _ in dct2.items(): if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): dct[k] = dict_merge(dct1[k], dct2[k]) else: diff --git a/tests/basic_tests.py b/tests/basic_tests.py index dbbcbef0a..59bd76d86 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -80,8 +80,8 @@ def test_timezone(self): self.assertEqual(datetime.timedelta(0), time.utcoffset()) def test_dict_merge(self): - filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} - filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} + filter1 = {"virtualGuests": {"hostname": {"operation": "etst"}}} + filter2 = {"virtualGuests": {"id": {"operation": "orderBy", "options": [{"name": "sort", "value": ["DESC"]}]}}} result = SoftLayer.utils.dict_merge(filter1, filter2) self.assertEqual(result['virtualGuests']['id']['operation'], 'orderBy') @@ -89,8 +89,6 @@ def test_dict_merge(self): self.assertEqual(result['virtualGuests']['hostname']['operation'], 'etst') - - class TestNestedDict(testing.TestCase): def test_basic(self): From 5caa6ce09f477f6f4215702bc32c945fbb968a7e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 14 Apr 2021 14:23:37 -0500 Subject: [PATCH 0407/1385] Updating author_email to SLDN distro list --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6d51d38ce..a09cca0a4 100644 --- a/setup.py +++ b/setup.py @@ -19,8 +19,8 @@ version='5.9.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, - author='SoftLayer Technologies, Inc.', - author_email='sldn@softlayer.com', + author='SoftLayer, Inc., an IBM Company', + author_email='SLDNDeveloperRelations@wwpdl.vnet.ibm.com', packages=find_packages(exclude=['tests']), license='MIT', zip_safe=False, From 9a5f20ac0056cdbaa571395a05c1ad2ba0c5fb2c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 15 Apr 2021 14:47:16 -0500 Subject: [PATCH 0408/1385] fixed some style issues --- CONTRIBUTING.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9182f802b..2ec9136a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,24 +116,24 @@ self.assertEqual(123, order_args['hostId']) ### Issues -* ~~Title~~: Should contain quick highlight of the issue is about -* ~~Body~~: All the technical information goes here -* ~~Assignee~~: Should be the person who is actively working on an issue. -* ~~Label~~: All issues should have at least 1 Label. -* ~~Projects~~: Should be added to the quarerly Softlayer project when being worked on -* ~~Milestones~~: Not really used, can be left blank -* ~~Linked Pull Request~~: Should be linked to the relavent pull request when it is opened. +* _Title_: Should contain quick highlight of the issue is about +* _Body_: All the technical information goes here +* _Assignee_: Should be the person who is actively working on an issue. +* _Label_: All issues should have at least 1 Label. +* _Projects_: Should be added to the quarerly Softlayer project when being worked on +* _Milestones_: Not really used, can be left blank +* _Linked Pull Request_: Should be linked to the relavent pull request when it is opened. ### Pull Requests -* ~~Title~~: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request -* ~~Body~~: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. -* ~~Reviewers~~: 1 Reviewer is required -* ~~Assignee~~: Should be the person who opened the pull request -* ~~Labels~~: Should match the issue -* ~~Projects~~: Should match the issue -* ~~Milestones~~: Not really used, can be left blank -* ~~Linked issues~~: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. +* _Title_: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request +* _Body_: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. +* _Reviewers_: 1 Reviewer is required +* _Assignee_: Should be the person who opened the pull request +* _Labels_: Should match the issue +* _Projects_: Should match the issue +* _Milestones_: Not really used, can be left blank +* _Linked issues_: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. ### Code Reviews All issues should be reviewed by at least 1 member of the SLDN team that is not the person opening the pull request. Time permitting, all members of the SLDN team should review the request. From 1af447c63b33047591516c9ec89347754d6257e0 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 22 Apr 2021 15:51:34 -0400 Subject: [PATCH 0409/1385] add the firewall information on slcli firewall detail --- SoftLayer/CLI/firewall/detail.py | 16 ++++++++++++++-- SoftLayer/managers/firewall.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index 1beb1a32a..e3b61e088 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -19,12 +19,24 @@ def cli(env, identifier): mgr = SoftLayer.FirewallManager(env.client) firewall_type, firewall_id = firewall.parse_id(identifier) + result = mgr.get_instance(firewall_id) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['id', result['id']]) + table.add_row(['primaryIpAddress', result['primaryIpAddress']]) + table.add_row(['datacenter', result['datacenter']['longName']]) + table.add_row(['networkVlan', result['networkVlan']['name']]) + table.add_row(['networkVlaniD', result['networkVlan']['id']]) + if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) - - env.fout(get_rules_table(rules)) + table.add_row(['rules', get_rules_table(rules)]) + env.fout(table) def get_rules_table(rules): diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 34b197521..633eddfd8 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -290,3 +290,15 @@ def edit_standard_fwl_rules(self, firewall_id, rules): template = {'networkComponentFirewallId': firewall_id, 'rules': rules} return rule_svc.createObject(template) + + def get_instance(self, firewall_id, mask=None): + """Get the firewall information + + :param integer firewall_id: the instance ID of the standard firewall + """ + if not mask: + mask = ('mask[datacenter,networkVlan]') + + svc = self.client['Network_Vlan_Firewall'] + + return svc.getObject(id=firewall_id, mask=mask) From 319121eec26ca1402f92ea870f873dec26e6a309 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 22 Apr 2021 16:41:18 -0400 Subject: [PATCH 0410/1385] fix tox tool --- .../SoftLayer_Network_Vlan_Firewall.py | 7 +++ tests/CLI/modules/firewall_tests.py | 47 ++++++++++--------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py index 5d78cf53b..c2f4134f3 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py @@ -7,10 +7,17 @@ }, "id": 3130, "primaryIpAddress": "192.155.239.146", + "datacenter": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, "networkVlan": { "accountId": 307608, "id": 371028, "primarySubnetId": 536252, + "name": 'testvlan', "vlanNumber": 1489, "firewallInterfaces": [ { diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 7362f1557..3fe9c3214 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -59,27 +59,32 @@ def test_detail(self): result = self.run_command(['firewall', 'detail', 'vlan:1234']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - [{'#': 1, - 'action': 'permit', - 'dest': 'any on server:80-80', - 'dest_mask': '255.255.255.255', - 'protocol': 'tcp', - 'src_ip': '0.0.0.0', - 'src_mask': '0.0.0.0'}, - {'#': 2, - 'action': 'permit', - 'dest': 'any on server:1-65535', - 'dest_mask': '255.255.255.255', - 'protocol': 'tmp', - 'src_ip': '193.212.1.10', - 'src_mask': '255.255.255.255'}, - {'#': 3, - 'action': 'permit', - 'dest': 'any on server:80-800', - 'dest_mask': '255.255.255.255', - 'protocol': 'tcp', - 'src_ip': '0.0.0.0', - 'src_mask': '0.0.0.0'}]) + {'datacenter': 'Amsterdam 1', + 'id': 3130, + 'networkVlan': 'testvlan', + 'networkVlaniD': 371028, + 'primaryIpAddress': '192.155.239.146', + 'rules': [{'#': 1, + 'action': 'permit', + 'dest': 'any on server:80-80', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}, + {'#': 2, + 'action': 'permit', + 'dest': 'any on server:1-65535', + 'dest_mask': '255.255.255.255', + 'protocol': 'tmp', + 'src_ip': '193.212.1.10', + 'src_mask': '255.255.255.255'}, + {'#': 3, + 'action': 'permit', + 'dest': 'any on server:80-800', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}]}) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_cancel_firewall(self, confirm_mock): From 7b70badb55ed13961fbea639f08fe66fe432259e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 23 Apr 2021 10:40:30 -0400 Subject: [PATCH 0411/1385] update with Christopher method and fix the team code review comments --- SoftLayer/CLI/call_api.py | 17 +++++++++++------ SoftLayer/utils.py | 16 ++++++++-------- tests/CLI/modules/call_api_tests.py | 8 ++++++-- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index cf0a2b871..b07834ed6 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -112,9 +112,12 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument @click.option('--mask', help="String-based object mask") @click.option('--limit', type=click.INT, help="Result limit") @click.option('--offset', type=click.INT, help="Result offset") -@click.option('--orderBy', type=click.STRING, help="an object filter that adds an order by clause" - "E.G --orderBy subnets.id default DESC" - " --orderBy subnets.id=ASC") +@click.option('--orderBy', type=click.STRING, + help="To set the sort direction, ASC or DESC can be provided." + "This should be of the form: '--orderBy nested.property' default DESC or " + "'--orderBy nested.property=ASC', e.g. " + " --orderBy subnets.id default DESC" + " --orderBy subnets.id=ASC") @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") @click.option('--json-filter', callback=_validate_filter, @@ -144,6 +147,8 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, or --json-filter '{"virtualGuests":{"hostname":{"operation":"^= test"}}}' --limit=10 slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' + slcli call-api Account getVirtualGuests \\ + --orderBy virttualguests.id=ASC """ if _filters and json_filter: @@ -151,10 +156,10 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, or object_filter = _build_filters(_filters) if orderby: - _filters = utils.build_filter_orderby(orderby) - object_filter.update(_filters) + orderby = utils.build_filter_orderby(orderby) + object_filter = utils.dict_merge(object_filter, orderby) if json_filter: - object_filter.update(json_filter) + object_filter = utils.dict_merge(json_filter, object_filter) args = [service, method] + list(parameters) kwargs = { diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cce78839c..6b18b6570 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -221,19 +221,19 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): """Builds filters using the filter options passed into the CLI. - Only support fot create filter option orderBy, default value is DESC. + It only supports the orderBy option, the default value is DESC. """ _filters = {} - aux = list(reversed(str(orderby).split('.'))) - for split in aux: + reverse_filter = list(reversed(orderby.split('.'))) + for keyword in reverse_filter: _aux_filter = {} - if str(split).__contains__('='): - _aux_filter[str(split).split('=')[0]] = query_filter_orderby(str(split).split('=')[1]) + if '=' in keyword: + _aux_filter[str(keyword).split('=')[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter - elif split == list(aux)[0]: - _aux_filter[split] = query_filter_orderby('DESC') + elif keyword == list(reverse_filter)[0]: + _aux_filter[keyword] = query_filter_orderby('DESC') else: - _aux_filter[split] = _filters + _aux_filter[keyword] = _filters _filters = _aux_filter return _filters diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index b98998f29..d00eb4aaf 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -301,7 +301,9 @@ def test_json_filter(self): def test_call_api_orderBy(self): result = self.run_command(['call-api', 'Account', 'getVirtualGuests', - '--orderBy', 'virtualGuests.id=DESC']) + '--orderBy', 'virtualGuests.id=DESC', + '--mask=virtualGuests.typeId,maxCpu', + '-f', 'virtualGuests.typeId=1']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', @@ -311,4 +313,6 @@ def test_call_api_orderBy(self): 'operation': 'orderBy', 'options': [{ 'name': 'sort', - 'value': ['DESC']}]}}}) + 'value': ['DESC']}]}, + 'typeId': {'operation': 1}} + }) From fbe3b0387c22e0e24cce5424520dc8aa84d73b63 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:33:18 -0500 Subject: [PATCH 0412/1385] #1474 replaced using 'mock' with 'unitest.mock' --- SoftLayer/testing/__init__.py | 2 +- tests/CLI/core_tests.py | 2 +- tests/CLI/environment_tests.py | 2 +- tests/CLI/helper_tests.py | 2 +- tests/CLI/modules/autoscale_tests.py | 2 +- tests/CLI/modules/block_tests.py | 2 +- tests/CLI/modules/config_tests.py | 2 +- tests/CLI/modules/dedicatedhost_tests.py | 2 +- tests/CLI/modules/dns_tests.py | 2 +- tests/CLI/modules/file_tests.py | 2 +- tests/CLI/modules/firewall_tests.py | 2 +- tests/CLI/modules/globalip_tests.py | 2 +- tests/CLI/modules/loadbal_tests.py | 2 +- tests/CLI/modules/object_storage_tests.py | 2 +- tests/CLI/modules/securitygroup_tests.py | 2 +- tests/CLI/modules/server_tests.py | 2 +- tests/CLI/modules/sshkey_tests.py | 2 +- tests/CLI/modules/ssl_tests.py | 2 +- tests/CLI/modules/subnet_tests.py | 2 +- tests/CLI/modules/tag_tests.py | 2 +- tests/CLI/modules/ticket_tests.py | 2 +- tests/CLI/modules/user_tests.py | 2 +- tests/CLI/modules/vlan_tests.py | 2 +- tests/CLI/modules/vs/vs_create_tests.py | 2 +- tests/CLI/modules/vs/vs_placement_tests.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 2 +- tests/api_tests.py | 2 +- tests/config_tests.py | 2 +- tests/decoration_tests.py | 2 +- tests/managers/dedicated_host_tests.py | 2 +- tests/managers/hardware_tests.py | 2 +- tests/managers/network_tests.py | 2 +- tests/managers/ordering_tests.py | 2 +- tests/managers/user_tests.py | 2 +- tests/managers/vs/vs_capacity_tests.py | 2 +- tests/managers/vs/vs_order_tests.py | 2 +- tests/managers/vs/vs_placement_tests.py | 2 +- tests/managers/vs/vs_tests.py | 2 +- tests/managers/vs/vs_waiting_for_ready_tests.py | 2 +- tests/transport_tests.py | 2 +- 40 files changed, 40 insertions(+), 40 deletions(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index 563b02494..a9054e3bb 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -11,7 +11,7 @@ import unittest from click import testing -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI import core diff --git a/tests/CLI/core_tests.py b/tests/CLI/core_tests.py index f230a3513..e8720514d 100644 --- a/tests/CLI/core_tests.py +++ b/tests/CLI/core_tests.py @@ -8,7 +8,7 @@ import logging import click -import mock +from unittest import mock as mock from requests.models import Response import SoftLayer diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index fa90ba1db..ed393afde 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -6,7 +6,7 @@ """ import click -import mock +from unittest import mock as mock from SoftLayer.CLI import environment from SoftLayer import testing diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index c22278c34..e3a6218c2 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -11,7 +11,7 @@ import tempfile import click -import mock +from unittest import mock as mock from SoftLayer.CLI import core from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 6d0e543da..6a1e9e37c 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -7,7 +7,7 @@ Tests for the autoscale cli command """ -import mock +from unittest import mock as mock import sys from SoftLayer import fixtures diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 83cba03d0..c38a7544d 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -10,7 +10,7 @@ import json -import mock +from unittest import mock as mock class BlockTests(testing.TestCase): diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 5fe917c1c..ef16edf38 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -9,7 +9,7 @@ import sys import tempfile -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import auth diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 077c3f033..a3199a744 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 82403d1a9..8fb8714f0 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -8,7 +8,7 @@ import os.path import sys -import mock +from unittest import mock as mock from SoftLayer.CLI.dns import zone_import from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 1bfe58e16..442ca067d 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -9,7 +9,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock class FileTests(testing.TestCase): diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 7362f1557..d80605d81 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -from unittest import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 6f2ee40d5..97633c0b6 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import testing diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index b2da4c374..8576a8292 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -3,7 +3,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 2e843906d..b5a219f62 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -from unittest import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/securitygroup_tests.py b/tests/CLI/modules/securitygroup_tests.py index b6801fcc8..2c930df71 100644 --- a/tests/CLI/modules/securitygroup_tests.py +++ b/tests/CLI/modules/securitygroup_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import testing diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index de7ccd95e..198160000 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -8,7 +8,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/sshkey_tests.py b/tests/CLI/modules/sshkey_tests.py index 253309c08..61260a2f0 100644 --- a/tests/CLI/modules/sshkey_tests.py +++ b/tests/CLI/modules/sshkey_tests.py @@ -9,7 +9,7 @@ import sys import tempfile -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import testing diff --git a/tests/CLI/modules/ssl_tests.py b/tests/CLI/modules/ssl_tests.py index 79b04df41..2bcdff5ca 100644 --- a/tests/CLI/modules/ssl_tests.py +++ b/tests/CLI/modules/ssl_tests.py @@ -7,7 +7,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock class SslTests(testing.TestCase): diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 57e7dbbb4..6d7a9bbaa 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -9,7 +9,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock import SoftLayer diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index b2e29721e..364201181 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -4,7 +4,7 @@ Tests for the user cli command """ -import mock +from unittest import mock as mock from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import testing diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 7b2363eab..92bd848d6 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 2f4c1c978..a11a94838 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -8,7 +8,7 @@ import sys import unittest -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index ee606f513..73c1fab97 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 2ad0f8647..9d644aabd 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys import tempfile diff --git a/tests/CLI/modules/vs/vs_placement_tests.py b/tests/CLI/modules/vs/vs_placement_tests.py index 3b716a6cd..aadf20426 100644 --- a/tests/CLI/modules/vs/vs_placement_tests.py +++ b/tests/CLI/modules/vs/vs_placement_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index de3a310b9..608b9dd6f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -7,7 +7,7 @@ import json import sys -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Virtual_Guest as SoftLayer_Virtual_Guest diff --git a/tests/api_tests.py b/tests/api_tests.py index 39f596b3c..a83246858 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer import SoftLayer.API diff --git a/tests/config_tests.py b/tests/config_tests.py index f6adb1be6..c4abdb032 100644 --- a/tests/config_tests.py +++ b/tests/config_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import config from SoftLayer import testing diff --git a/tests/decoration_tests.py b/tests/decoration_tests.py index 9d230671c..d7a39d757 100644 --- a/tests/decoration_tests.py +++ b/tests/decoration_tests.py @@ -6,7 +6,7 @@ """ import logging -import mock +from unittest import mock as mock from SoftLayer.decoration import retry from SoftLayer import exceptions diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index 6888db3ce..ea0efeb42 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 9f48ad2aa..3fbdf5740 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -6,7 +6,7 @@ """ import copy -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 80d054f9d..735492b9b 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys import unittest diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 5f88d59d5..53995264d 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 61f5b4d0a..5ea4d2696 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -5,7 +5,7 @@ """ import datetime -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_capacity_tests.py b/tests/managers/vs/vs_capacity_tests.py index c6aad9f56..6fa6599e8 100644 --- a/tests/managers/vs/vs_capacity_tests.py +++ b/tests/managers/vs/vs_capacity_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/managers/vs/vs_order_tests.py b/tests/managers/vs/vs_order_tests.py index 7b54f5450..ae77df2cd 100644 --- a/tests/managers/vs/vs_order_tests.py +++ b/tests/managers/vs/vs_order_tests.py @@ -6,7 +6,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_placement_tests.py b/tests/managers/vs/vs_placement_tests.py index b492f69bf..7a6a69457 100644 --- a/tests/managers/vs/vs_placement_tests.py +++ b/tests/managers/vs/vs_placement_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer.managers.vs_placement import PlacementManager from SoftLayer import testing diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index d5202bbe8..976a66fd8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_waiting_for_ready_tests.py b/tests/managers/vs/vs_waiting_for_ready_tests.py index 4308bd55d..08b3071c6 100644 --- a/tests/managers/vs/vs_waiting_for_ready_tests.py +++ b/tests/managers/vs/vs_waiting_for_ready_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 27f892098..854ee6b2e 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -8,7 +8,7 @@ import warnings import json -import mock +from unittest import mock as mock import pytest import requests From f8e88bc83190548020479acd4f046c1214a00a27 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:34:24 -0500 Subject: [PATCH 0413/1385] autopep8 changes --- tests/CLI/modules/globalip_tests.py | 2 +- tests/CLI/modules/server_tests.py | 36 +++++++++++------------ tests/CLI/modules/subnet_tests.py | 2 +- tests/CLI/modules/ticket_tests.py | 1 + tests/CLI/modules/vs/vs_create_tests.py | 34 ++++++++++----------- tests/CLI/modules/vs/vs_tests.py | 39 +++++++++++++------------ tests/managers/hardware_tests.py | 8 ++--- tests/managers/ordering_tests.py | 9 +++--- 8 files changed, 67 insertions(+), 64 deletions(-) diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 97633c0b6..e12b7c3f6 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -79,7 +79,7 @@ def test_create(self, confirm_mock): { "item": "Total monthly cost", "cost": "2.00" - }]) + }]) def test_ip_unassign(self): result = self.run_command(['globalip', 'unassign', '1']) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 198160000..d39af4571 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -692,19 +692,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -747,12 +747,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 6d7a9bbaa..b73529a4f 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -173,7 +173,7 @@ def test_lookup(self): "netmask": "255.255.255.192", "gateway": "10.47.16.129", "type": "PRIMARY" - }}) + }}) @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_cancel(self, confirm_mock): diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 92bd848d6..4fbdbff0c 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -15,6 +15,7 @@ class FakeTTY(): """A fake object to fake STD input""" + def __init__(self, isatty=False, read="Default Output"): """Sets isatty and read""" self._isatty = isatty diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 9d644aabd..52625d337 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -113,26 +113,26 @@ def test_create_by_router(self, confirm_mock): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') args = ({ - 'startCpus': 2, - 'maxMemory': 1024, - 'hostname': 'host', - 'domain': 'example.com', - 'localDiskFlag': True, - 'hourlyBillingFlag': True, - 'supplementalCreateObjectOptions': {'bootMode': None}, - 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'datacenter': {'name': 'dal05'}, - 'primaryBackendNetworkComponent': { + 'startCpus': 2, + 'maxMemory': 1024, + 'hostname': 'host', + 'domain': 'example.com', + 'localDiskFlag': True, + 'hourlyBillingFlag': True, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'datacenter': {'name': 'dal05'}, + 'primaryBackendNetworkComponent': { 'router': { 'id': 577940 } - }, - 'primaryNetworkComponent': { - 'router': { - 'id': 1639255 - } - } - },) + }, + 'primaryNetworkComponent': { + 'router': { + 'id': 1639255 + } + } + },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 608b9dd6f..97dc52ea4 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -322,7 +322,8 @@ def test_create_options_prices(self): self.assert_no_fail(result) def test_create_options_prices_location(self): - result = self.run_command(['vs', 'create-options', '--prices', 'dal13', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + result = self.run_command(['vs', 'create-options', '--prices', 'dal13', + '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -344,19 +345,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -399,12 +400,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 3fbdf5740..fa5459cd1 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 53995264d..b25c42494 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -744,7 +744,7 @@ def test_get_item_capacity_core(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) @@ -761,7 +761,7 @@ def test_get_item_capacity_storage(self): "capacity": "1", "id": 10201, "keyName": "READHEAVY_TIER", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) @@ -779,7 +779,7 @@ def test_get_item_capacity_intel(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) @@ -848,7 +848,8 @@ def test_resolve_location_name_invalid(self): self.assertIn("Invalid location", str(exc)) def test_resolve_location_name_not_exist(self): - exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") + exc = self.assertRaises(exceptions.SoftLayerError, + self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") self.assertIn("does not exist", str(exc)) # https://github.com/softlayer/softlayer-python/issues/1425 From 09f86f435277bdee5379fe2586a3e97905d850e2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:54:02 -0500 Subject: [PATCH 0414/1385] fixing import order stuff --- SoftLayer/testing/__init__.py | 2 +- tests/CLI/modules/autoscale_tests.py | 3 ++- tests/CLI/modules/dedicatedhost_tests.py | 2 +- tests/CLI/modules/server_tests.py | 7 +++---- tests/CLI/modules/subnet_tests.py | 8 ++++---- tests/CLI/modules/vs/vs_create_tests.py | 3 ++- tests/managers/dedicated_host_tests.py | 2 +- tests/managers/ipsec_tests.py | 3 +-- tests/managers/network_tests.py | 3 ++- tests/transport_tests.py | 4 ++-- 10 files changed, 19 insertions(+), 18 deletions(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index a9054e3bb..6eff9851c 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -9,9 +9,9 @@ import logging import os.path import unittest +from unittest import mock as mock from click import testing -from unittest import mock as mock import SoftLayer from SoftLayer.CLI import core diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 6a1e9e37c..cb3cdfdb9 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -7,8 +7,9 @@ Tests for the autoscale cli command """ -from unittest import mock as mock + import sys +from unittest import mock as mock from SoftLayer import fixtures from SoftLayer import testing diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index a3199a744..a015fa70d 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -6,8 +6,8 @@ """ import json from unittest import mock as mock -import SoftLayer +import SoftLayer from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer.fixtures import SoftLayer_Virtual_DedicatedHost diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index d39af4571..e6cb2b18a 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -8,16 +8,15 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock +import json import sys +import tempfile +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerError from SoftLayer import testing -import json -import tempfile - class ServerCLITests(testing.TestCase): diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index b73529a4f..65a4cc5c8 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.fixtures import SoftLayer_Product_Order -from SoftLayer.fixtures import SoftLayer_Product_Package -from SoftLayer import testing - import json from unittest import mock as mock + import SoftLayer +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer import testing class SubnetTests(testing.TestCase): diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 52625d337..e9bb2fd74 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock + import sys import tempfile +from unittest import mock as mock from SoftLayer.fixtures import SoftLayer_Product_Package as SoftLayer_Product_Package from SoftLayer import testing diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index ea0efeb42..afe3df3a2 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -5,8 +5,8 @@ :license: MIT, see LICENSE for more details. """ from unittest import mock as mock -import SoftLayer +import SoftLayer from SoftLayer import exceptions from SoftLayer import fixtures from SoftLayer import testing diff --git a/tests/managers/ipsec_tests.py b/tests/managers/ipsec_tests.py index aaebc9f7f..f88e33ed5 100644 --- a/tests/managers/ipsec_tests.py +++ b/tests/managers/ipsec_tests.py @@ -4,8 +4,7 @@ :license: MIT, see LICENSE for more details. """ - -from mock import MagicMock +from unittest.mock import MagicMock as MagicMock import SoftLayer from SoftLayer.exceptions import SoftLayerAPIError diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 735492b9b..2463ed999 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock + import sys import unittest +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 854ee6b2e..ca3dcc73b 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -5,10 +5,10 @@ :license: MIT, see LICENSE for more details. """ import io -import warnings - import json from unittest import mock as mock +import warnings + import pytest import requests From dceab111688fe6bccd3b04a96d1db6d400829c41 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 15:17:57 -0500 Subject: [PATCH 0415/1385] dropping support for py3.5 as it is EOL https://www.python.org/downloads/release/python-3510/ --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b1fa2c870..73b9a0007 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38,py39,pypy3,analysis,coverage,docs +envlist = py36,py37,py38,py39,pypy3,analysis,coverage,docs [flake8] From 1039f158b13fd5a9f5725e68285e5e5b651188cf Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 15:28:35 -0500 Subject: [PATCH 0416/1385] dropping support for py3.5 as it is EOL https://www.python.org/downloads/release/python-3510/ --- .github/workflows/tests.yml | 2 +- setup.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7bc787791..7d6d35a67 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5,3.6,3.7,3.8,3.9] + python-version: [3.6,3.7,3.8,3.9] steps: - uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index a09cca0a4..c040d9ca7 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,6 @@ 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', From fda7e0a4371f5f245f79512e84c76e8d8f47fdcd Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:05:23 -0400 Subject: [PATCH 0417/1385] fix the tox tool --- tests/CLI/modules/call_api_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index d00eb4aaf..a22597488 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -305,8 +305,7 @@ def test_call_api_orderBy(self): '--mask=virtualGuests.typeId,maxCpu', '-f', 'virtualGuests.typeId=1']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Account', - 'getVirtualGuests', + self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', filter={ 'virtualGuests': {'id': { From 5ca3bdee066028def3d926d0a78bafdf527c910f Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:27:00 -0400 Subject: [PATCH 0418/1385] update and fix tox tool --- SoftLayer/CLI/firewall/detail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index e3b61e088..23b43e936 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -35,6 +35,7 @@ def cli(env, identifier): rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) + table.add_row(['rules', get_rules_table(rules)]) env.fout(table) From c8893fd6aa20ec8efa44cf712fc210de8971c7ee Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:29:35 -0400 Subject: [PATCH 0419/1385] update and fix tox tool --- SoftLayer/CLI/firewall/detail.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index 23b43e936..e3b61e088 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -35,7 +35,6 @@ def cli(env, identifier): rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) - table.add_row(['rules', get_rules_table(rules)]) env.fout(table) From 8de6d4a51d8c83743054290ce4954d8543c1f0ef Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:36:52 -0500 Subject: [PATCH 0420/1385] moved the test_pypi workflow to only trigger on test-pypi branch as we don't have access to the test environment yet --- .github/workflows/test_pypi_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index 5e7c6b683..f3b39abf9 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master ] + branches: [ test-pypi ] jobs: build-n-publish: From bdade312228af1c17a3ca0a4440648b35cc8e2aa Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:37:09 -0500 Subject: [PATCH 0421/1385] Changelog for v5.9.4 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ab66753..f55340957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Change Log +## [5.9.4] - 2021-04-27 +https://github.com/softlayer/softlayer-python/compare/v5.9.3...v5.9.4 + +#### New Commands +- `slcli hw authorize-storage` #1439 +- `slcli order quote-save` #1451 + + +#### Improvements + +- Refactored managers.ordering_manager.verify_quote() to work better with the REST endpoing #1430 +- Add routers for each DC in slcli hw create-options #1432 +- Add preset datatype in slcli virtual detail #1435 +- Add upgrade option to slcli hw. #1437 +- Ibmcloud authentication support #1315 / #1447 + + `slcli config setup --ibmid` + + `slcli config setup --sso` + + `slcli config setup --cloud_key` + + `slcli config setup --classic_key` +- Refactor slcli hw detail prices. #1443 +- Updated contributing guide #1458 +- Add the Hardware components on "slcli hardware detail" #1452 +- Add billing and lastTransaction on hardware detail #1446 +- Forced reserved capacity guests to be monthly #1454 +- Removing the rwhois commands #1456 +- Added automation to publish to test-pypi #1467 +- Updating author_email to SLDN distro list #1469 +- Add the option to add and upgrade the hw disk. #1455 +- Added a utility to merge objectFilters, #1468 +- Fixes shift+ins when pasteing into a password field for windows users. #1460 +- Add Billing and lastTransaction on slcli virtual detail #1466 +- Fixing 'import mock' pylint issues #1476 + ## [5.9.3] - 2021-03-03 https://github.com/softlayer/softlayer-python/compare/v5.9.2...v5.9.3 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 71c52be75..cec2831e0 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.3' +VERSION = 'v5.9.4' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index c040d9ca7..48c1e4e6c 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.3', + version='5.9.4', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 486b0d068911c5d3214874796261fc298e2ce680 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:52:37 -0500 Subject: [PATCH 0422/1385] fixed a pylint issue --- SoftLayer/CLI/sshkey/add.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/sshkey/add.py b/SoftLayer/CLI/sshkey/add.py index a3a3c6ccb..d80330d7c 100644 --- a/SoftLayer/CLI/sshkey/add.py +++ b/SoftLayer/CLI/sshkey/add.py @@ -33,9 +33,9 @@ def cli(env, label, in_file, key, note): if key: key_text = key else: - key_file = open(path.expanduser(in_file), 'rU') - key_text = key_file.read().strip() - key_file.close() + with open(path.expanduser(in_file), 'rU') as key_file: + key_text = key_file.read().strip() + key_file.close() mgr = SoftLayer.SshKeyManager(env.client) result = mgr.add_key(key_text, label, note) From 9a8fcf8079b73cd8883e6f57eaf40543eac4c499 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 27 Apr 2021 12:37:02 -0400 Subject: [PATCH 0423/1385] #1449 add image detail transaction data --- SoftLayer/CLI/image/__init__.py | 3 ++- SoftLayer/CLI/image/detail.py | 46 +++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/image/__init__.py b/SoftLayer/CLI/image/__init__.py index b0734db99..17d836625 100644 --- a/SoftLayer/CLI/image/__init__.py +++ b/SoftLayer/CLI/image/__init__.py @@ -5,7 +5,8 @@ MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' 'imageType') -DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' +DETAIL_MASK = MASK + (',firstChild,children[id,blockDevicesDiskSpaceTotal,datacenter,' + 'transaction[transactionGroup,transactionStatus]],' 'note,createDate,status,transaction') PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') diff --git a/SoftLayer/CLI/image/detail.py b/SoftLayer/CLI/image/detail.py index 5bba1beac..865f95a7d 100644 --- a/SoftLayer/CLI/image/detail.py +++ b/SoftLayer/CLI/image/detail.py @@ -19,14 +19,10 @@ def cli(env, identifier): image_mgr = SoftLayer.ImageManager(env.client) image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') - image = image_mgr.get_image(image_id, mask=image_mod.DETAIL_MASK) - disk_space = 0 - datacenters = [] - for child in image.get('children'): - disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) - if child.get('datacenter'): - datacenters.append(utils.lookup(child, 'datacenter', 'name')) + + children_images = image.get('children') + total_size = utils.lookup(image, 'firstChild', 'blockDevicesDiskSpaceTotal') or 0 table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' @@ -40,9 +36,10 @@ def cli(env, identifier): utils.lookup(image, 'status', 'keyname'), utils.lookup(image, 'status', 'name'), )]) + table.add_row([ 'active_transaction', - formatting.transaction_status(image.get('transaction')), + formatting.listing(_get_transaction_groups(children_images), separator=','), ]) table.add_row(['account', image.get('accountId', formatting.blank())]) table.add_row(['visibility', @@ -56,8 +53,35 @@ def cli(env, identifier): table.add_row(['flex', image.get('flexImageFlag')]) table.add_row(['note', image.get('note')]) table.add_row(['created', image.get('createDate')]) - table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) - table.add_row(['datacenters', formatting.listing(sorted(datacenters), - separator=',')]) + table.add_row(['total_size', formatting.b_to_gb(total_size)]) + table.add_row(['datacenters', _get_datacenter_table(children_images)]) env.fout(table) + + +def _get_datacenter_table(children_images): + """Returns image details as datacenter, size, and transaction within a formatting table. + + :param children_images: A list of images. + """ + table_datacenter = formatting.Table(['DC', 'size', 'transaction']) + for child in children_images: + table_datacenter.add_row([ + utils.lookup(child, 'datacenter', 'name'), + formatting.b_to_gb(child.get('blockDevicesDiskSpaceTotal', 0)), + formatting.transaction_status(child.get('transaction')) + ]) + + return table_datacenter + + +def _get_transaction_groups(children_images): + """Returns a Set of transaction groups. + + :param children_images: A list of images. + """ + transactions = set() + for child in children_images: + transactions.add(utils.lookup(child, 'transaction', 'transactionGroup', 'name')) + + return transactions From 661a2258de89c0608abef252c713707934c36192 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 16:37:45 -0400 Subject: [PATCH 0424/1385] Fix to Christopher code review comments --- SoftLayer/managers/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 633eddfd8..44eef25ab 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -297,7 +297,7 @@ def get_instance(self, firewall_id, mask=None): :param integer firewall_id: the instance ID of the standard firewall """ if not mask: - mask = ('mask[datacenter,networkVlan]') + mask = 'mask[datacenter,networkVlan]' svc = self.client['Network_Vlan_Firewall'] From aece6185968a6754636476a35b067ca269e3a578 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 17:27:16 -0400 Subject: [PATCH 0425/1385] Fix to Christopher code review comments --- tests/CLI/modules/firewall_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 59c1a5a1a..f248b16f1 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -58,6 +58,8 @@ def test_add_server(self, confirm_mock): def test_detail(self): result = self.run_command(['firewall', 'detail', 'vlan:1234']) self.assert_no_fail(result) + json_result = json.loads(result.output) + self.assertEqual(json_result['rules'][0]['action'], 'permit') self.assertEqual(json.loads(result.output), {'datacenter': 'Amsterdam 1', 'id': 3130, From 66bc9adb655f912082dd9a45ac8ef9327abd2d63 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 17:42:35 -0400 Subject: [PATCH 0426/1385] Fix to Christopher code review comments --- SoftLayer/CLI/firewall/detail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index e3b61e088..afdf13b53 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -25,11 +25,11 @@ def cli(env, identifier): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', result['id']]) - table.add_row(['primaryIpAddress', result['primaryIpAddress']]) - table.add_row(['datacenter', result['datacenter']['longName']]) - table.add_row(['networkVlan', result['networkVlan']['name']]) - table.add_row(['networkVlaniD', result['networkVlan']['id']]) + table.add_row(['id', utils.lookup(result, 'id')]) + table.add_row(['primaryIpAddress', utils.lookup(result, 'primaryIpAddress')]) + table.add_row(['datacenter', utils.lookup(result, 'datacenter', 'longName')]) + table.add_row(['networkVlan', utils.lookup(result, 'networkVlan', 'name')]) + table.add_row(['networkVlaniD', utils.lookup(result, 'networkVlan', 'id')]) if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) From d53dc43b35207cf7e1081e3d9fa7c7fb6221fdef Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 18:02:07 -0400 Subject: [PATCH 0427/1385] Fix to team code review comments --- tests/CLI/modules/call_api_tests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index a22597488..03c861d2a 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -308,10 +308,11 @@ def test_call_api_orderBy(self): self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', filter={ 'virtualGuests': - {'id': { - 'operation': 'orderBy', - 'options': [{ - 'name': 'sort', - 'value': ['DESC']}]}, + {'id': + { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC']}]}, 'typeId': {'operation': 1}} }) From 39b3b2a758d9dbd1eb81612b6d443dc13d0d5d32 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 30 Apr 2021 08:59:21 -0400 Subject: [PATCH 0428/1385] Fix to team code review comments --- SoftLayer/CLI/firewall/detail.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index afdf13b53..647715a3d 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -19,17 +19,17 @@ def cli(env, identifier): mgr = SoftLayer.FirewallManager(env.client) firewall_type, firewall_id = firewall.parse_id(identifier) - result = mgr.get_instance(firewall_id) + _firewall = mgr.get_instance(firewall_id) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', utils.lookup(result, 'id')]) - table.add_row(['primaryIpAddress', utils.lookup(result, 'primaryIpAddress')]) - table.add_row(['datacenter', utils.lookup(result, 'datacenter', 'longName')]) - table.add_row(['networkVlan', utils.lookup(result, 'networkVlan', 'name')]) - table.add_row(['networkVlaniD', utils.lookup(result, 'networkVlan', 'id')]) + table.add_row(['id', _firewall.get('id')]) + table.add_row(['primaryIpAddress', _firewall.get('primaryIpAddress')]) + table.add_row(['datacenter', utils.lookup(_firewall, 'datacenter', 'longName')]) + table.add_row(['networkVlan', utils.lookup(_firewall, 'networkVlan', 'name')]) + table.add_row(['networkVlaniD', utils.lookup(_firewall, 'networkVlan', 'id')]) if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) From bdccfa963a07968b53dd72c26ab2405fbafb848a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 09:53:58 -0400 Subject: [PATCH 0429/1385] add new email feature --- SoftLayer/CLI/email/__init__.py | 0 SoftLayer/CLI/email/detail.py | 72 +++++++++++++++++++ SoftLayer/CLI/routes.py | 3 + SoftLayer/fixtures/SoftLayer_Account.py | 24 +++++++ ...Network_Message_Delivery_Email_Sendgrid.py | 27 +++++++ SoftLayer/managers/email.py | 47 ++++++++++++ docs/api/managers/email.rst | 5 ++ docs/cli/email.rst | 9 +++ tests/CLI/modules/email_tests.py | 15 ++++ tests/managers/email_tests.py | 26 +++++++ 10 files changed, 228 insertions(+) create mode 100644 SoftLayer/CLI/email/__init__.py create mode 100644 SoftLayer/CLI/email/detail.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py create mode 100644 SoftLayer/managers/email.py create mode 100644 docs/api/managers/email.rst create mode 100644 docs/cli/email.rst create mode 100644 tests/CLI/modules/email_tests.py create mode 100644 tests/managers/email_tests.py diff --git a/SoftLayer/CLI/email/__init__.py b/SoftLayer/CLI/email/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py new file mode 100644 index 000000000..44a4bbbdb --- /dev/null +++ b/SoftLayer/CLI/email/detail.py @@ -0,0 +1,72 @@ +"""Get details for an image.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager +from SoftLayer.managers.email import EmailManager +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """""" + manager = AccountManager(env.client) + email_manager = EmailManager(env.client) + result = manager.get_Network_Message_Delivery_Accounts() + + table = formatting.KeyValueTable(['name', 'value']) + + table_information = formatting.KeyValueTable(['id', 'username', 'hostname', 'description', 'vendor']) + table_information.align['id'] = 'r' + table_information.align['username'] = 'l' + + for email in result: + # print(email['id']) + table_information.add_row([email.get('id'), email.get('username'), email.get('emailAddress'), + utils.lookup(email, 'type', 'description'), + utils.lookup(email, 'vendor', 'keyName')]) + + overview_table = _build_overview_table(email_manager.get_AccountOverview(email.get('id'))) + statistics = email_manager.get_statistics(email.get('id'), + ["requests", "delivered", "opens", "clicks", "bounds"], + True, True, True, 6) + + table.add_row(['email information', table_information]) + table.add_row(['email overview', overview_table]) + for statistic in statistics: + table.add_row(['statistics', _build_statistics_table(statistic)]) + + env.fout(table) + + +def _build_overview_table(email_overview): + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['creditsAllowed', email_overview.get('creditsAllowed')]) + table.add_row(['creditsRemain', email_overview.get('creditsRemain')]) + table.add_row(['package', email_overview.get('package')]) + table.add_row(['reputation', email_overview.get('reputation')]) + table.add_row(['requests', email_overview.get('requests')]) + + return table + + +def _build_statistics_table(statistics): + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['delivered', statistics.get('delivered')]) + table.add_row(['requests', statistics.get('requests')]) + table.add_row(['bounces', statistics.get('bounces')]) + table.add_row(['opens', statistics.get('opens')]) + table.add_row(['clicks', statistics.get('clicks')]) + table.add_row(['spam Reports', statistics.get('spamReports')]) + + return table diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..0c6f322b7 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -119,6 +119,9 @@ ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), + ('email', 'SoftLayer.CLI.email'), + ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), ('event-log:types', 'SoftLayer.CLI.event_log.types:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 4261c32a7..4cfafa30f 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1076,3 +1076,27 @@ "username": "SL01SEV1234567_111" } ] + +getNetworkMessageDeliveryAccounts = [ + { + "accountId": 147258, + "createDate": "2020-07-06T10:29:11-06:00", + "id": 1232123, + "typeId": 21, + "username": "test_CLI@ie.ibm.com", + "vendorId": 1, + "type": { + "description": "Delivery of messages through e-mail", + "id": 21, + "keyName": "EMAIL", + "name": "Email" + }, + "vendor": { + "id": 1, + "keyName": "SENDGRID", + "name": "SendGrid" + }, + "emailAddress": "test_CLI@ie.ibm.com", + "smtpAccess": "1" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py new file mode 100644 index 000000000..94fd169fd --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -0,0 +1,27 @@ +getAccountOverview = { + "creditsAllowed": 25000, + "creditsOverage": 0, + "creditsRemain": 25000, + "creditsUsed": 0, + "package": "Free Package", + "reputation": 100, + "requests": 56 +} + +getStatistics = [{ + "blocks": 0, + "bounces": 0, + "clicks": 0, + "date": "2021-04-28", + "delivered": 0, + "invalidEmail": 0, + "opens": 0, + "repeatBounces": 0, + "repeatSpamReports": 0, + "repeatUnsubscribes": 0, + "requests": 0, + "spamReports": 0, + "uniqueClicks": 0, + "uniqueOpens": 0, + "unsubscribes": 0 +}] diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py new file mode 100644 index 000000000..5b5f49e00 --- /dev/null +++ b/SoftLayer/managers/email.py @@ -0,0 +1,47 @@ +""" + SoftLayer.account + ~~~~~~~~~~~~~~~~~~~~~~~ + Account manager + + :license: MIT, see License for more details. +""" + +from SoftLayer import utils + + +# Invalid names are ignored due to long method names and short argument names +# pylint: disable=invalid-name, no-self-use + + +class EmailManager(utils.IdentifierMixin, object): + """Common functions for getting information from the Account service + + :param SoftLayer.API.BaseClient client: the client instance + """ + + def __init__(self, client): + self.client = client + + def get_AccountOverview(self, identifier): + """Gets all the Network Message Delivery Account Overview + + :returns: Network Message Delivery Account overview + """ + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getAccountOverview', id=identifier) + + def get_statistics(self, identifier, selectedStatistics, + startDate, endDate, aggregatesOnly, days): + """Gets statistics Network Message Delivery Account + + :returns: statistics Network Message Delivery Account + """ + body = [selectedStatistics, + startDate, + endDate, + aggregatesOnly, + days + ] + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getStatistics', id=identifier, *body) diff --git a/docs/api/managers/email.rst b/docs/api/managers/email.rst new file mode 100644 index 000000000..45d839c16 --- /dev/null +++ b/docs/api/managers/email.rst @@ -0,0 +1,5 @@ +.. _email: + +.. automodule:: SoftLayer.managers.email + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli/email.rst b/docs/cli/email.rst new file mode 100644 index 000000000..67d5319db --- /dev/null +++ b/docs/cli/email.rst @@ -0,0 +1,9 @@ +.. _cli_email: + +Email Commands +================= + + +.. click:: SoftLayer.CLI.email.detail:cli + :prog: email detail + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py new file mode 100644 index 000000000..db085efbf --- /dev/null +++ b/tests/CLI/modules/email_tests.py @@ -0,0 +1,15 @@ +""" + SoftLayer.tests.CLI.modules.email_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from SoftLayer import testing + + +class EmailCLITests(testing.TestCase): + + def test_detail(self): + result = self.run_command(['email', 'detail']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py new file mode 100644 index 000000000..b573326fa --- /dev/null +++ b/tests/managers/email_tests.py @@ -0,0 +1,26 @@ +""" + SoftLayer.tests.managers.email_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +""" + +from SoftLayer.managers.email import EmailManager +from SoftLayer import testing + + +class AccountManagerTests(testing.TestCase): + + def test_get_AccountOverview(self): + self.manager = EmailManager(self.client) + self.manager.get_AccountOverview(1232123) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getAccountOverview') + + def test_get_statistics(self): + self.manager = EmailManager(self.client) + self.manager.get_statistics(1232123, + ["requests", "delivered", "opens", "clicks", "bounds"], + True, + True, True, 6) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getStatistics') From dc660d8a548562f6dd9026cec550a465696efbfa Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 11:09:45 -0400 Subject: [PATCH 0430/1385] fix the tp --- SoftLayer/managers/account.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d008b92a3..a613be0cc 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -293,3 +293,13 @@ def get_routers(self, mask=None, location=None): } return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) + + def get_Network_Message_Delivery_Accounts(self): + """Gets all Network Message delivery accounts. + + :returns: Network Message delivery accounts + """ + + _mask = """vendor,type""" + + return self.client['SoftLayer_Account'].getNetworkMessageDeliveryAccounts(mask=_mask) From 6cce16c6c9ad594449a3db5af702824806ec5f17 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 11:22:45 -0400 Subject: [PATCH 0431/1385] fix tox tool --- SoftLayer/CLI/email/detail.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py index 44a4bbbdb..bdc4161a6 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/detail.py @@ -13,7 +13,7 @@ @click.command() @environment.pass_env def cli(env): - """""" + """Display the Email Delivery account informatino """ manager = AccountManager(env.client) email_manager = EmailManager(env.client) result = manager.get_Network_Message_Delivery_Accounts() @@ -25,7 +25,6 @@ def cli(env): table_information.align['username'] = 'l' for email in result: - # print(email['id']) table_information.add_row([email.get('id'), email.get('username'), email.get('emailAddress'), utils.lookup(email, 'type', 'description'), utils.lookup(email, 'vendor', 'keyName')]) From 00131b22f81f8daca3c3b0ebb23c8716a03ac963 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 19:00:21 -0400 Subject: [PATCH 0432/1385] update the conflicts merge --- SoftLayer/CLI/hardware/detail.py | 25 +++++++++++-- .../fixtures/SoftLayer_Hardware_Server.py | 18 ++++++++++ SoftLayer/managers/hardware.py | 35 +++++++++++++++++++ tests/CLI/modules/server_tests.py | 4 +++ tests/managers/hardware_tests.py | 20 +++++++++++ 5 files changed, 100 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index da62e5de6..feee666cf 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -9,13 +9,16 @@ from SoftLayer.CLI import helpers from SoftLayer import utils +# pylint: disable=R0915 + @click.command() @click.argument('identifier') @click.option('--passwords', is_flag=True, help='Show passwords (check over your shoulder!)') @click.option('--price', is_flag=True, help='Show associated prices') +@click.option('--components', is_flag=True, default=False, help='Show associated hardware components') @environment.pass_env -def cli(env, identifier, passwords, price): +def cli(env, identifier, passwords, price, components): """Get details for a hardware device.""" hardware = SoftLayer.HardwareManager(env.client) @@ -66,7 +69,7 @@ def cli(env, identifier, passwords, price): utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) table.add_row(['last_transaction', last_transaction]) - table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else 'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: @@ -107,6 +110,24 @@ def cli(env, identifier, passwords, price): pass_table.add_row([item['username'], item['password']]) table.add_row(['remote users', pass_table]) + if components: + components = hardware.get_components(identifier) + components_table = formatting.Table(['name', 'Firmware version', 'Firmware build date', 'Type']) + components_table.align['date'] = 'l' + component_ids = [] + for hw_component in components: + if hw_component['id'] not in component_ids: + firmware = hw_component['hardwareComponentModel']['firmwares'][0] + components_table.add_row([utils.lookup(hw_component, 'hardwareComponentModel', 'longDescription'), + utils.lookup(firmware, 'version'), + utils.clean_time(utils.lookup(firmware, 'createDate')), + utils.lookup(hw_component, 'hardwareComponentModel', + 'hardwareGenericComponentModel', 'hardwareComponentType', + 'keyName')]) + component_ids.append(hw_component['id']) + + table.add_row(['components', components_table]) + table.add_row(['tags', formatting.tags(result['tagReferences'])]) env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 0eacab158..938c5cebc 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -377,3 +377,21 @@ } } ] + +getComponents = [{ + "hardwareComponentModelId": 147, + "hardwareId": 1234, + "id": 369, + "modifyDate": "2017-11-10T16:59:38-06:00", + "serviceProviderId": 1, + "hardwareComponentModel": { + "name": "IMM2 - Onboard", + "firmwares": [ + { + "createDate": "2020-09-24T13:46:29-06:00", + "version": "5.60" + }, + { + "createDate": "2019-10-14T16:51:12-06:00", + "version": "5.10" + }]}}] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8e63bc9e9..2fec42a82 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1030,6 +1030,41 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t return disk_price + def get_components(self, hardware_id, mask=None, filter_component=None): + """Get details about a hardware components. + + :param int hardware_id: the instance ID + :returns: A dictionary containing a large amount of information about + the specified components. + """ + if not mask: + mask = 'id,hardwareComponentModel[longDescription,' \ + 'hardwareGenericComponentModel[description,hardwareComponentType[keyName]],' \ + 'firmwares[createDate,version]]' + + if not filter_component: + filter_component = {"components": { + "hardwareComponentModel": { + "firmwares": { + "createDate": { + "operation": "orderBy", + "options": [ + { + "name": "sort", + "value": [ + "DESC" + ] + }, + { + "name": "sortOrder", + "value": [ + 1 + ]}]} + }}}} + + return self.client.call('Hardware_Server', 'getComponents', + mask=mask, filter=filter_component, id=hardware_id) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 2283e3035..4b286b351 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -985,3 +985,7 @@ def test_upgrade(self, confirm_mock): '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) + + def test_components(self): + result = self.run_command(['hardware', 'detail', '100', '--components']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index bf76de3c2..7fcac5202 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -928,6 +928,26 @@ def test_upgrade_full(self): self.assertIn({'id': 22482}, order_container['prices']) self.assertIn({'id': 50357}, order_container['prices']) + def test_get_components(self): + result = self.hardware.get_components(1234) + components = [{'hardwareComponentModelId': 147, + 'hardwareId': 1234, + 'id': 369, + 'modifyDate': '2017-11-10T16:59:38-06:00', + 'serviceProviderId': 1, + 'hardwareComponentModel': + {'name': 'IMM2 - Onboard', + 'firmwares': + [{'createDate': '2020-09-24T13:46:29-06:00', + 'version': '5.60'}, + {'createDate': '2019-10-14T16:51:12-06:00', + 'version': '5.10'}]}}] + self.assert_called_with('SoftLayer_Hardware_Server', 'getComponents') + self.assertEqual(result, components) + self.assertEqual(result[0]['hardwareId'], 1234) + self.assertEqual(result[0]['hardwareComponentModel']['name'], 'IMM2 - Onboard') + self.assertEqual(result[0]['hardwareComponentModel']['firmwares'][0]['version'], '5.60') + class HardwareHelperTests(testing.TestCase): From 180f7a1f795644815871b2bb2b4e4499b578b742 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 6 May 2021 18:38:14 -0400 Subject: [PATCH 0433/1385] fix team code review comments --- SoftLayer/CLI/email/{detail.py => list.py} | 16 +++++++--------- SoftLayer/CLI/routes.py | 2 +- SoftLayer/managers/account.py | 2 +- SoftLayer/managers/email.py | 22 +++++++++++----------- tests/CLI/modules/email_tests.py | 2 +- tests/managers/email_tests.py | 9 +++------ 6 files changed, 24 insertions(+), 29 deletions(-) rename SoftLayer/CLI/email/{detail.py => list.py} (82%) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/list.py similarity index 82% rename from SoftLayer/CLI/email/detail.py rename to SoftLayer/CLI/email/list.py index bdc4161a6..05c096250 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/list.py @@ -1,4 +1,4 @@ -"""Get details for an image.""" +"""Get lists Email Delivery account Service """ # :license: MIT, see LICENSE for more details. import click @@ -13,10 +13,10 @@ @click.command() @environment.pass_env def cli(env): - """Display the Email Delivery account informatino """ + """Lists Email Delivery Service """ manager = AccountManager(env.client) email_manager = EmailManager(env.client) - result = manager.get_Network_Message_Delivery_Accounts() + result = manager.get_network_message_delivery_accounts() table = formatting.KeyValueTable(['name', 'value']) @@ -29,10 +29,8 @@ def cli(env): utils.lookup(email, 'type', 'description'), utils.lookup(email, 'vendor', 'keyName')]) - overview_table = _build_overview_table(email_manager.get_AccountOverview(email.get('id'))) - statistics = email_manager.get_statistics(email.get('id'), - ["requests", "delivered", "opens", "clicks", "bounds"], - True, True, True, 6) + overview_table = _build_overview_table(email_manager.get_account_overview(email.get('id'))) + statistics = email_manager.get_statistics(email.get('id')) table.add_row(['email information', table_information]) table.add_row(['email overview', overview_table]) @@ -43,7 +41,7 @@ def cli(env): def _build_overview_table(email_overview): - table = formatting.KeyValueTable(['name', 'value']) + table = formatting.Table(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' @@ -57,7 +55,7 @@ def _build_overview_table(email_overview): def _build_statistics_table(statistics): - table = formatting.KeyValueTable(['name', 'value']) + table = formatting.Table(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 0c6f322b7..6037fa265 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -120,7 +120,7 @@ ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), ('email', 'SoftLayer.CLI.email'), - ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('email:list', 'SoftLayer.CLI.email.list:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index a613be0cc..b7398076d 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -294,7 +294,7 @@ def get_routers(self, mask=None, location=None): return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) - def get_Network_Message_Delivery_Accounts(self): + def get_network_message_delivery_accounts(self): """Gets all Network Message delivery accounts. :returns: Network Message delivery accounts diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index 5b5f49e00..ce80a9298 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -1,7 +1,7 @@ """ - SoftLayer.account + SoftLayer.email ~~~~~~~~~~~~~~~~~~~~~~~ - Account manager + Email manager :license: MIT, see License for more details. """ @@ -14,7 +14,7 @@ class EmailManager(utils.IdentifierMixin, object): - """Common functions for getting information from the Account service + """Common functions for getting information from the email service :param SoftLayer.API.BaseClient client: the client instance """ @@ -22,7 +22,7 @@ class EmailManager(utils.IdentifierMixin, object): def __init__(self, client): self.client = client - def get_AccountOverview(self, identifier): + def get_account_overview(self, identifier): """Gets all the Network Message Delivery Account Overview :returns: Network Message Delivery Account overview @@ -30,16 +30,16 @@ def get_AccountOverview(self, identifier): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getAccountOverview', id=identifier) - def get_statistics(self, identifier, selectedStatistics, - startDate, endDate, aggregatesOnly, days): - """Gets statistics Network Message Delivery Account + def get_statistics(self, identifier, days=30): + """gets statistics from email accounts + :days: range number :returns: statistics Network Message Delivery Account """ - body = [selectedStatistics, - startDate, - endDate, - aggregatesOnly, + body = [["requests", "delivered", "opens", "clicks", "bounds"], + True, + True, + True, days ] diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index db085efbf..798745a7f 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -10,6 +10,6 @@ class EmailCLITests(testing.TestCase): def test_detail(self): - result = self.run_command(['email', 'detail']) + result = self.run_command(['email', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index b573326fa..b8a2b58b6 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -8,19 +8,16 @@ from SoftLayer import testing -class AccountManagerTests(testing.TestCase): +class EmailManagerTests(testing.TestCase): def test_get_AccountOverview(self): self.manager = EmailManager(self.client) - self.manager.get_AccountOverview(1232123) + self.manager.get_account_overview(1232123) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getAccountOverview') def test_get_statistics(self): self.manager = EmailManager(self.client) - self.manager.get_statistics(1232123, - ["requests", "delivered", "opens", "clicks", "bounds"], - True, - True, True, 6) + self.manager.get_statistics(1232123, 6) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics') From f8d5882143e425c3c061442c963d524780ec9ecd Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 7 May 2021 08:54:58 -0400 Subject: [PATCH 0434/1385] add documentation --- docs/cli/email.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli/email.rst b/docs/cli/email.rst index 67d5319db..574fba1bc 100644 --- a/docs/cli/email.rst +++ b/docs/cli/email.rst @@ -4,6 +4,6 @@ Email Commands ================= -.. click:: SoftLayer.CLI.email.detail:cli - :prog: email detail +.. click:: SoftLayer.CLI.email.list:cli + :prog: email list :show-nested: \ No newline at end of file From 66a3cde4255d742cbd858d758c11352b26075f09 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 May 2021 12:15:05 -0400 Subject: [PATCH 0435/1385] #1481 refactor block and file cli to shows the whole notes in a format json output --- SoftLayer/CLI/block/list.py | 28 +++---------------------- SoftLayer/CLI/environment.py | 4 ++++ SoftLayer/CLI/file/list.py | 23 ++------------------ SoftLayer/CLI/storage_utils.py | 38 +++++++++++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 47 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 769aad233..bd14c7282 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -5,8 +5,7 @@ import SoftLayer from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - +from SoftLayer.CLI import storage_utils COLUMNS = [ column_helper.Column('id', ('id',), mask="id"), @@ -18,7 +17,7 @@ 'storage_type', lambda b: b['storageType']['keyName'].split('_').pop(0) if 'storageType' in b and 'keyName' in b['storageType'] - and isinstance(b['storageType']['keyName'], str) + and isinstance(b['storageType']['keyName'], str) else '-', mask="storageType.keyName"), column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"), @@ -52,8 +51,6 @@ 'notes' ] -DEFAULT_NOTES_SIZE = 20 - @click.command() @click.option('--username', '-u', help='Volume username') @@ -78,24 +75,5 @@ def cli(env, sortby, columns, datacenter, username, storage_type, order): order=order, mask=columns.mask()) - table = formatting.Table(columns.columns) - table.sortby = sortby - - _reduce_notes(block_volumes) - - for block_volume in block_volumes: - table.add_row([value or formatting.blank() - for value in columns.row(block_volume)]) - + table = storage_utils.build_output_table(env, block_volumes, columns, sortby) env.fout(table) - - -def _reduce_notes(block_volumes): - """Reduces the size of the notes in a volume list. - - :param block_volumes: An list of block volumes - """ - for block_volume in block_volumes: - if len(block_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: - shortened_notes = block_volume['notes'][:DEFAULT_NOTES_SIZE] - block_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index e16c5cde9..c96f4a7c3 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -52,6 +52,10 @@ def fmt(self, output, fmt=None): fmt = self.format return formatting.format_output(output, fmt) + def format_output_is_json(self): + """Return True if format output is json or jsonraw""" + return 'json' in self.format + def fout(self, output, newline=True): """Format the input and output to the console (stdout).""" if output is not None: diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index f7c08fe18..ba72c4fe0 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -5,7 +5,7 @@ import SoftLayer from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils COLUMNS = [ column_helper.Column('id', ('id',), mask="id"), @@ -76,24 +76,5 @@ def cli(env, sortby, columns, datacenter, username, storage_type, order): order=order, mask=columns.mask()) - table = formatting.Table(columns.columns) - table.sortby = sortby - - _reduce_notes(file_volumes) - - for file_volume in file_volumes: - table.add_row([value or formatting.blank() - for value in columns.row(file_volume)]) - + table = storage_utils.build_output_table(env, file_volumes, columns, sortby) env.fout(table) - - -def _reduce_notes(file_volumes): - """Reduces the size of the notes in a volume list. - - :param file_volumes: An list of file volumes - """ - for file_volume in file_volumes: - if len(file_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: - shortened_notes = file_volume['notes'][:DEFAULT_NOTES_SIZE] - file_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/storage_utils.py b/SoftLayer/CLI/storage_utils.py index 47c888960..aa24585eb 100644 --- a/SoftLayer/CLI/storage_utils.py +++ b/SoftLayer/CLI/storage_utils.py @@ -2,6 +2,43 @@ # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import formatting + +DEFAULT_NOTES_SIZE = 20 + + +def reduce_notes(volumes, env): + """Reduces all long notes found in the volumes list just if the format output is different from a JSON format. + + :param list volumes: An list of storage volumes + :param env :A environment console. + """ + if env.format_output_is_json(): + return + + for volume in volumes: + if len(volume.get('notes', '')) > DEFAULT_NOTES_SIZE: + shortened_notes = volume['notes'][:DEFAULT_NOTES_SIZE] + volume['notes'] = shortened_notes + + +def build_output_table(env, volumes, columns, sortby): + """Builds a formatting table for a list of volumes. + + :param env :A Environment console. + :param list volumes: An list of storage volumes + :param columns :A ColumnFormatter for column names + :param str sortby :A string to sort by. + """ + table = formatting.Table(columns.columns) + if sortby in table.columns: + table.sortby = sortby + + reduce_notes(volumes, env) + for volume in volumes: + table.add_row([value or formatting.blank() + for value in columns.row(volume)]) + return table def _format_name(obj): @@ -96,7 +133,6 @@ def _format_name(obj): """), ] - DEFAULT_COLUMNS = [ 'id', 'name', From c1de463a2b98720107c07484a31687a5b3acdb9b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 May 2021 12:16:12 -0400 Subject: [PATCH 0436/1385] #1481 add tests to block and file cli to shows the whole notes in a format json output --- tests/CLI/environment_tests.py | 4 ++++ tests/CLI/modules/block_tests.py | 35 ++++++++++++++++++++++++++++++-- tests/CLI/modules/file_tests.py | 32 +++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index f194bdc87..8829bf93d 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -81,3 +81,7 @@ def test_print_unicode(self, echo): ] self.env.fout(output) self.assertEqual(2, echo.call_count) + + def test_format_output_is_json(self): + self.env.format = 'jsonraw' + self.assertTrue(self.env.format_output_is_json()) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index c38a7544d..1c6b22e14 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -5,10 +5,10 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer import SoftLayerAPIError from SoftLayer import testing - import json from unittest import mock as mock @@ -135,7 +135,7 @@ def test_volume_list(self): 'IOPs': None, 'ip_addr': '10.1.2.3', 'lunId': None, - 'notes': "{'status': 'availabl", + 'notes': "{'status': 'available'}", 'rep_partner_count': None, 'storage_type': 'ENDURANCE', 'username': 'username', @@ -143,6 +143,37 @@ def test_volume_list(self): }], json.loads(result.output)) + @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') + def test_volume_list_notes_format_output_json(self, list_mock): + note_mock = 'test ' * 5 + list_mock.return_value = [ + {'notes': note_mock} + ] + + result = self.run_command(['--format', 'json', 'block', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual( + [{ + 'notes': note_mock, + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') + def test_volume_list_reduced_notes_format_output_table(self, list_mock): + note_mock = 'test ' * 10 + expected_reduced_note = 'test ' * 4 + list_mock.return_value = [ + {'notes': note_mock} + ] + expected_table = formatting.Table(['notes']) + expected_table.add_row([expected_reduced_note]) + expected_output = formatting.format_output(expected_table)+'\n' + result = self.run_command(['--format', 'table', 'block', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual(expected_output, result.output) + def test_volume_list_order(self): result = self.run_command(['block', 'volume-list', '--order=1234567']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 442ca067d..cbe73818c 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer import SoftLayerError from SoftLayer import testing @@ -63,6 +64,37 @@ def test_volume_list_order(self): json_result = json.loads(result.output) self.assertEqual(json_result[0]['id'], 1) + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') + def test_volume_list_notes_format_output_json(self, list_mock): + note_mock = 'test ' * 5 + list_mock.return_value = [ + {'notes': note_mock} + ] + + result = self.run_command(['--format', 'json', 'file', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual( + [{ + 'notes': note_mock, + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') + def test_volume_list_reduced_notes_format_output_table(self, list_mock): + note_mock = 'test ' * 10 + expected_reduced_note = 'test ' * 4 + list_mock.return_value = [ + {'notes': note_mock} + ] + expected_table = formatting.Table(['notes']) + expected_table.add_row([expected_reduced_note]) + expected_output = formatting.format_output(expected_table) + '\n' + result = self.run_command(['--format', 'table', 'file', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual(expected_output, result.output) + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') def test_volume_count(self, list_mock): list_mock.return_value = [ From 1572d8ceb9865dfec714a9a83cad7043ab544f0d Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 10 May 2021 14:57:08 -0400 Subject: [PATCH 0437/1385] fix team code review comments --- SoftLayer/CLI/email/list.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index 05c096250..a5353a31a 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -19,7 +19,8 @@ def cli(env): result = manager.get_network_message_delivery_accounts() table = formatting.KeyValueTable(['name', 'value']) - + table.align['name'] = 'r' + table.align['value'] = 'l' table_information = formatting.KeyValueTable(['id', 'username', 'hostname', 'description', 'vendor']) table_information.align['id'] = 'r' table_information.align['username'] = 'l' @@ -32,8 +33,8 @@ def cli(env): overview_table = _build_overview_table(email_manager.get_account_overview(email.get('id'))) statistics = email_manager.get_statistics(email.get('id')) - table.add_row(['email information', table_information]) - table.add_row(['email overview', overview_table]) + table.add_row(['email_information', table_information]) + table.add_row(['email_overview', overview_table]) for statistic in statistics: table.add_row(['statistics', _build_statistics_table(statistic)]) @@ -41,29 +42,24 @@ def cli(env): def _build_overview_table(email_overview): - table = formatting.Table(['name', 'value']) + table = formatting.Table(['credit_Allowed', 'credits_Remain', 'package', 'reputation', 'requests']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['creditsAllowed', email_overview.get('creditsAllowed')]) - table.add_row(['creditsRemain', email_overview.get('creditsRemain')]) - table.add_row(['package', email_overview.get('package')]) - table.add_row(['reputation', email_overview.get('reputation')]) - table.add_row(['requests', email_overview.get('requests')]) + table.add_row([email_overview.get('creditsAllowed'), email_overview.get('creditsRemain'), + email_overview.get('package'), email_overview.get('reputation'), + email_overview.get('requests')]) return table def _build_statistics_table(statistics): - table = formatting.Table(['name', 'value']) + table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spamReports']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['delivered', statistics.get('delivered')]) - table.add_row(['requests', statistics.get('requests')]) - table.add_row(['bounces', statistics.get('bounces')]) - table.add_row(['opens', statistics.get('opens')]) - table.add_row(['clicks', statistics.get('clicks')]) - table.add_row(['spam Reports', statistics.get('spamReports')]) + table.add_row([statistics.get('delivered'), statistics.get('requests'), + statistics.get('bounces'), statistics.get('opens'), + statistics.get('clicks'), statistics.get('spamReports')]) return table From 7b826af01d80a0150a0732e20ce939a4e5eef233 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 13 May 2021 09:17:49 -0400 Subject: [PATCH 0438/1385] add new email features, email detail and email edit --- SoftLayer/CLI/email/__init__.py | 1 + SoftLayer/CLI/email/detail.py | 33 +++++++++++++++++++ SoftLayer/CLI/email/edit.py | 32 ++++++++++++++++++ SoftLayer/CLI/routes.py | 2 ++ ...Network_Message_Delivery_Email_Sendgrid.py | 31 +++++++++++++++++ SoftLayer/managers/email.py | 30 +++++++++++++++++ docs/cli.rst | 1 + docs/cli/email.rst | 8 +++++ tests/CLI/modules/email_tests.py | 16 ++++++++- tests/managers/email_tests.py | 6 ++++ 10 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/email/detail.py create mode 100644 SoftLayer/CLI/email/edit.py diff --git a/SoftLayer/CLI/email/__init__.py b/SoftLayer/CLI/email/__init__.py index e69de29bb..b765cbe50 100644 --- a/SoftLayer/CLI/email/__init__.py +++ b/SoftLayer/CLI/email/__init__.py @@ -0,0 +1 @@ +"""Network Delivery account""" diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py new file mode 100644 index 000000000..28fccba57 --- /dev/null +++ b/SoftLayer/CLI/email/detail.py @@ -0,0 +1,33 @@ +"""Display details for a specified email.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.email import EmailManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Display details for a specified email.""" + + email_manager = EmailManager(env.client) + result = email_manager.get_instance(identifier) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['id', result.get('id')]) + table.add_row(['username', result.get('username')]) + table.add_row(['create_date', result.get('createDate')]) + table.add_row(['categoryCode', utils.lookup(result, 'billingItem', 'categoryCode')]) + table.add_row(['description', utils.lookup(result, 'billingItem', 'description')]) + table.add_row(['type_description', utils.lookup(result, 'type', 'description')]) + table.add_row(['type', utils.lookup(result, 'type', 'keyName')]) + table.add_row(['vendor', utils.lookup(result, 'vendor', 'keyName')]) + + env.fout(table) diff --git a/SoftLayer/CLI/email/edit.py b/SoftLayer/CLI/email/edit.py new file mode 100644 index 000000000..321e6306f --- /dev/null +++ b/SoftLayer/CLI/email/edit.py @@ -0,0 +1,32 @@ +"""Edit details of an Delivery email account.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.managers.email import EmailManager + + +@click.command() +@click.argument('identifier') +@click.option('--username', help="username account") +@click.option('--email', help="Additional note for the image") +@click.option('--password', + help="Password must be between 8 and 20 characters " + "and must contain one letter and one number.") +@environment.pass_env +def cli(env, identifier, username, email, password): + """Edit details of an email delivery account.""" + data = {} + if username: + data['username'] = username + if email: + data['emailAddress'] = email + if password: + data['password'] = password + + email_manager = EmailManager(env.client) + + if not email_manager.editObject(identifier, data): + raise exceptions.CLIAbort("Failed to Edit email account") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 6037fa265..38a716434 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -121,6 +121,8 @@ ('email', 'SoftLayer.CLI.email'), ('email:list', 'SoftLayer.CLI.email.list:cli'), + ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('email:edit', 'SoftLayer.CLI.email.edit:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py index 94fd169fd..9bff8ad08 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -25,3 +25,34 @@ "uniqueOpens": 0, "unsubscribes": 0 }] + +getObject = { + "accountId": 123456, + "createDate": "2020-07-06T10:29:11-06:00", + "id": 1232123, + "password": "Test123456789", + "typeId": 21, + "username": "techsupport3@ie.ibm.com", + "vendorId": 1, + "billingItem": { + "categoryCode": "network_message_delivery", + "description": "Free Package", + "id": 695735054, + "notes": "techsupport3@ie.ibm.com", + }, + "type": { + "description": "Delivery of messages through e-mail", + "id": 21, + "keyName": "EMAIL", + "name": "Email" + }, + "vendor": { + "id": 1, + "keyName": "SENDGRID", + "name": "SendGrid" + }, + "emailAddress": "techsupport3@ie.ibm.com", + "smtpAccess": "1" +} + +editObject = True diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index ce80a9298..df3c144e5 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -45,3 +45,33 @@ def get_statistics(self, identifier, days=30): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics', id=identifier, *body) + + def get_instance(self, identifier): + """Gets the Network_Message_Delivery_Email_Sendgrid instance + + :return: Network_Message_Delivery_Email_Sendgrid + """ + + _mask = """emailAddress,type,billingItem,vendor""" + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getObject', id=identifier, mask=_mask) + + def editObject(self, identifier, username=None, emailAddress=None, password=None): + """Edit email delivery account related details. + + :param int identifier: The ID of the email account + :param string username: username of the email account. + :param string email: email of the email account. + :param string password: password of the email account to be updated to. + """ + data = {} + if username: + data['username'] = username + if emailAddress: + data['emailAddress'] = emailAddress + if password: + data['password'] = password + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'editObject', data, id=identifier) diff --git a/docs/cli.rst b/docs/cli.rst index a659b145c..264f5bcaf 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -79,6 +79,7 @@ To discover the available commands, simply type `slcli`. config CLI configuration. dedicatedhost Dedicated Host. dns Domain Name System. + email Email Deliviry Network event-log Event Logs. file File Storage. firewall Firewalls. diff --git a/docs/cli/email.rst b/docs/cli/email.rst index 574fba1bc..732d524d7 100644 --- a/docs/cli/email.rst +++ b/docs/cli/email.rst @@ -6,4 +6,12 @@ Email Commands .. click:: SoftLayer.CLI.email.list:cli :prog: email list + :show-nested: + +.. click:: SoftLayer.CLI.email.detail:cli + :prog: email detail + :show-nested: + +.. click:: SoftLayer.CLI.email.edit:cli + :prog: email edit :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index 798745a7f..f2c8aa693 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -9,7 +9,21 @@ class EmailCLITests(testing.TestCase): - def test_detail(self): + def test_list(self): result = self.run_command(['email', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') + + def test_detail(self): + result = self.run_command(['email', 'detail', '1232123']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject') + + def test_edit(self): + result = self.run_command(['email', 'edit', '1232123', + '--username=test@ibm.com', + '--email=test@ibm.com', + '--password=test123456789']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'editObject') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index b8a2b58b6..fad965c7a 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -21,3 +21,9 @@ def test_get_statistics(self): self.manager.get_statistics(1232123, 6) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics') + + def test_get_object(self): + self.manager = EmailManager(self.client) + self.manager.get_instance(1232123) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getObject') From 1671c767ba4442d810d1278e03ac1df323bbe6ca Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 14 May 2021 15:24:36 -0500 Subject: [PATCH 0439/1385] #1486 fixed newly failing unit tests --- SoftLayer/CLI/virt/create.py | 2 +- tests/CLI/modules/vs/vs_create_tests.py | 24 +++++++----------------- tests/api_tests.py | 13 +++++++++++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 23a18457d..9d07f01d2 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -177,7 +177,7 @@ def _parse_create_args(client, args): help="Forces the VS to only have access the private network") @click.option('--like', is_eager=True, callback=_update_with_like_args, help="Use the configuration from an existing VS") -@click.option('--network', '-n', help="Network port speed in Mbps") +@click.option('--network', '-n', help="Network port speed in Mbps", type=click.INT) @helpers.multi_option('--tag', '-g', help="Tags to add to the instance") @click.option('--template', '-t', is_eager=True, callback=template.TemplateCallback(list_args=['disk', 'key', 'tag']), diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index e9bb2fd74..efcb8cbc3 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -42,7 +42,7 @@ def test_create(self, confirm_mock): 'hostname': 'host', 'startCpus': 2, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'networkComponents': [{'maxSpeed': '100'}], + 'networkComponents': [{'maxSpeed': 100}], 'supplementalCreateObjectOptions': {'bootMode': None}},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -225,7 +225,7 @@ def test_create_with_integer_image_guid(self, confirm_mock): 'supplementalCreateObjectOptions': {'bootMode': None}, 'blockDeviceTemplateGroup': {'globalIdentifier': 'aaaa1111bbbb2222'}, 'datacenter': {'name': 'dal05'}, - 'networkComponents': [{'maxSpeed': '100'}] + 'networkComponents': [{'maxSpeed': 100}] },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -257,7 +257,7 @@ def test_create_with_flavor(self, confirm_mock): 'bootMode': None, 'flavorKeyName': 'B1_1X2X25'}, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'networkComponents': [{'maxSpeed': '100'}]},) + 'networkComponents': [{'maxSpeed': 100}]},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -351,21 +351,11 @@ def test_create_with_host_id(self, confirm_mock): 'domain': 'example.com', 'localDiskFlag': True, 'hourlyBillingFlag': True, - 'supplementalCreateObjectOptions': { - 'bootMode': None - }, - 'dedicatedHost': { - 'id': 123 - }, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'dedicatedHost': {'id': 123}, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'datacenter': { - 'name': 'dal05' - }, - 'networkComponents': [ - { - 'maxSpeed': '100' - } - ] + 'datacenter': {'name': 'dal05'}, + 'networkComponents': [{'maxSpeed': 100}] },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=template_args) diff --git a/tests/api_tests.py b/tests/api_tests.py index a83246858..c3d84529a 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -96,6 +96,19 @@ def test_simple_call(self): offset=None, ) + + def test_simple_call_2(self): + mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') + mock.return_value = {"test": "result"} + + resp = self.client.call('SERVICE', 'METHOD', {'networkComponents': [{'maxSpeed': 100}]}) + + self.assertEqual(resp, {"test": "result"}) + self.assert_called_with('SoftLayer_SERVICE', 'METHOD', + mask=None, filter=None, identifier=None, + args=({'networkComponents': [{'maxSpeed': 100}]},), limit=None, offset=None, + ) + def test_verify_request_false(self): client = SoftLayer.BaseClient(transport=self.mocks) mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') From eff3bf2153368f01724d8e265496ac68ee419999 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 14 May 2021 15:30:47 -0500 Subject: [PATCH 0440/1385] fixed tox --- tests/api_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api_tests.py b/tests/api_tests.py index c3d84529a..ea4726a6e 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -96,7 +96,6 @@ def test_simple_call(self): offset=None, ) - def test_simple_call_2(self): mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') mock.return_value = {"test": "result"} From 96889809e04f3aa4114c7e102fa27fef838e5364 Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 17 May 2021 18:21:33 -0400 Subject: [PATCH 0441/1385] Add a table result for the hw upgrade. --- SoftLayer/CLI/hardware/upgrade.py | 97 +++++++++++- SoftLayer/fixtures/SoftLayer_Product_Order.py | 148 ++++++++++++++++++ ...ftLayer_Provisioning_Maintenance_Window.py | 26 +++ SoftLayer/managers/hardware.py | 44 ++++-- tests/CLI/modules/server_tests.py | 49 +++--- tests/managers/hardware_tests.py | 10 +- 6 files changed, 335 insertions(+), 39 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index 037506df0..89b89859e 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -2,6 +2,7 @@ # :license: MIT, see LICENSE for more details. import click +from SoftLayer import utils import SoftLayer from SoftLayer.CLI import environment @@ -35,6 +36,9 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad """Upgrade a Hardware Server.""" mgr = SoftLayer.HardwareManager(env.client) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' if not any([memory, network, drive_controller, public_bandwidth, add_disk, resize_disk]): raise exceptions.ArgumentError("Must provide " @@ -57,7 +61,92 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad disks = {'description': 'resize_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} disk_list.append(disks) - if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, - public_bandwidth=public_bandwidth, disk=disk_list, test=test): - raise exceptions.CLIAbort('Hardware Server Upgrade Failed') - env.fout('Successfully Upgraded.') + response = mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, + public_bandwidth=public_bandwidth, disk=disk_list, test=test) + + if response: + if test: + add_data_to_table(response, table) + else: + table.add_row(['order_date', response.get('orderDate')]) + table.add_row(['order_id', response.get('orderId')]) + add_data_to_table(response['orderDetails'], table) + place_order_table = get_place_order_information(response) + table.add_row(['Place Order Information', place_order_table]) + order_detail_table = get_order_detail(response) + table.add_row(['Order Detail (Billing Information)', order_detail_table]) + + env.fout(table) + + +def add_data_to_table(response, table): + """Add the hardware server upgrade result to the table""" + table.add_row(['location', utils.lookup(response, 'locationObject', 'longName')]) + table.add_row(['quantity', response.get('quantity')]) + table.add_row(['package_id', response.get('packageId')]) + table.add_row(['currency_short_name', response.get('currencyShortName')]) + table.add_row(['prorated_initial_charge', response.get('proratedInitialCharge')]) + table.add_row(['prorated_order_total', response.get('proratedOrderTotal')]) + table.add_row(['use_hourly_pricing', response.get('useHourlyPricing')]) + table_hardware = get_hardware_detail(response) + table.add_row(['Hardware', table_hardware]) + table_prices = get_hardware_prices(response) + table.add_row(['prices', table_prices]) + + +def get_place_order_information(response): + """Get the hardware server place order information.""" + table_place_order = formatting.Table(['id', 'account_id', 'status', 'Account CompanyName', + 'UserRecord FirstName', 'UserRecord lastName', 'UserRecord Username']) + table_place_order.add_row([response.get('id'), + response.get('accountId'), + response.get('status'), + utils.lookup(response, 'account', 'companyName'), + utils.lookup(response, 'userRecord', 'firstName'), + utils.lookup(response, 'account', 'lastName'), + utils.lookup(response, 'account', 'username')]) + + return table_place_order + + +def get_hardware_detail(response): + """Get the hardware server detail.""" + table_hardware = formatting.Table(['account_id', 'hostname', 'domain']) + for hardware in response['hardware']: + table_hardware.add_row([hardware.get('accountId'), + hardware.get('hostname'), + hardware.get('domain')]) + + return table_hardware + + +def get_hardware_prices(response): + """Get the hardware server prices.""" + table_prices = formatting.Table(['id', 'hourlyRecurringFee', 'recurringFee', 'categories', 'Item Description', + 'Item Units']) + for price in response['prices']: + categories = price.get('categories')[0] + table_prices.add_row([price.get('id'), + price.get('hourlyRecurringFee'), + price.get('recurringFee'), + categories.get('name'), + utils.lookup(price, 'item', 'description'), + utils.lookup(price, 'item', 'units')]) + + return table_prices + + +def get_order_detail(response): + """Get the hardware server order detail.""" + table_order_detail = formatting.Table(['billing_city', 'billing_country_code', 'billing_email', + 'billing_name_first', 'billing_name_last', 'billing_postal_code', + 'billing_state']) + table_order_detail.add_row([utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCity'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCountryCode'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingEmail'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingNameFirst'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingNameLast'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingPostalCode'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingState')]) + + return table_order_detail diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 3774f63a8..83604f8d1 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -63,6 +63,154 @@ 'postTaxRecurring': '0.32', } +hardware_verifyOrder = { + "currencyShortName": "USD", + "hardware": [ + { + "accountId": 1111, + "domain": "testedit.com", + "hostname": "bardcabero", + "globalIdentifier": "81434794-af69-44d5-bb97-12312asdasdasd" + } + ], + "location": "1441195", + "locationObject": { + "id": 1441195, + "longName": "Dallas 10", + "name": "dal10" + }, + "packageId": 911, + "postTaxRecurring": "0", + "postTaxRecurringHourly": "0", + "postTaxRecurringMonthly": "0", + "preTaxRecurring": "0", + "preTaxRecurringHourly": "0", + "preTaxRecurringMonthly": "0", + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 209391, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "units": "GB" + } + } + ], + "proratedInitialCharge": "0", + "proratedOrderTotal": "0", + "quantity": 1, + "sendQuoteEmailFlag": None, + "totalRecurringTax": "0", + "useHourlyPricing": False +} + +hardware_placeOrder = { + "orderDate": "2021-05-07T07:41:41-06:00", + "orderDetails": { + "billingInformation": { + "billingAddressLine1": "4849 Alpha Rd", + "billingCity": "Dallas", + "billingCountryCode": "US", + "billingEmail": "test.ibm.com", + "billingNameCompany": "SoftLayer Internal - Development Community", + "billingNameFirst": "Test", + "billingNameLast": "Test", + "billingPhoneVoice": "1111111", + "billingPostalCode": "75244-1111", + "billingState": "TX", + }, + "currencyShortName": "USD", + "hardware": [ + { + "accountId": 1111111, + "bareMetalInstanceFlag": 0, + "domain": "testedit.com", + "fullyQualifiedDomainName": "bardcabero.testedit.com", + "hostname": "bardcabero", + "globalIdentifier": "81434794-af69-44d5-bb97-1111111" + } + ], + "location": "1441195", + "locationObject": { + "id": 1441195, + "longName": "Dallas 10", + "name": "dal10" + }, + "packageId": 911, + "paymentType": "ADD_TO_BALANCE", + "postTaxRecurring": "0", + "postTaxRecurringHourly": "0", + "postTaxRecurringMonthly": "0", + "postTaxSetup": "0", + "preTaxRecurring": "0", + "preTaxRecurringHourly": "0", + "preTaxRecurringMonthly": "0", + "preTaxSetup": "0", + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 209391, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "keyName": "RAM_32_GB_DDR4_2133_ECC_NON_REG", + "units": "GB", + } + } + ], + "proratedInitialCharge": "0", + "proratedOrderTotal": "0", + "quantity": 1, + "totalRecurringTax": "0", + "useHourlyPricing": False + }, + "orderId": 78332111, + "placedOrder": { + "accountId": 1111111, + "id": 1234, + "status": "PENDING_UPGRADE", + "account": { + "brandId": 2, + "companyName": "SoftLayer Internal - Development Community", + "id": 1234 + }, + "items": [ + { + "categoryCode": "ram", + "description": "32 GB RAM", + "id": 824199364, + "recurringFee": "0" + } + ], + "userRecord": { + "accountId": 1234, + "firstName": "test", + "id": 3333, + "lastName": "cabero", + "username": "sl1234-dcabero" + } + } +} + rsc_placeOrder = { 'orderDate': '2013-08-01 15:23:45', 'orderId': 1234, diff --git a/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py b/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py new file mode 100644 index 000000000..f3e597481 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py @@ -0,0 +1,26 @@ +getMaintenanceWindows = [ + { + "beginDate": "2021-05-13T16:00:00-06:00", + "dayOfWeek": 4, + "endDate": "2021-05-13T19:00:00-06:00", + "id": 198351, + "locationId": 1441195, + "portalTzId": 114 + }, + { + "beginDat": "2021-05-14T00:00:00-06:00", + "dayOfWeek": 5, + "endDate": "2021-05-14T03:00:00-06:00", + "id": 189747, + "locationId": 1441195, + "portalTzId": 114 + }, + { + "beginDate": "2021-05-14T08:00:00-06:00", + "dayOfWeek": 5, + "endDate": "2021-05-14T11:00:00-06:00", + "id": 198355, + "locationId": 1441195, + "portalTzId": 114 + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8e63bc9e9..4643adcb8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -834,6 +834,7 @@ def upgrade(self, instance_id, memory=None, :returns: bool """ + result = None upgrade_prices = self._get_upgrade_prices(instance_id) prices = [] data = {} @@ -849,14 +850,24 @@ def upgrade(self, instance_id, memory=None, server_response = self.get_instance(instance_id) package_id = server_response['billingItem']['package']['id'] + location_id = server_response['datacenter']['id'] maintenance_window = datetime.datetime.now(utils.UTC()) + maintenance_window_id = self.get_maintenance_windows_id(location_id) + order = { 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', - 'properties': [{ - 'name': 'MAINTENANCE_WINDOW', - 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") - }], + 'properties': [ + { + 'name': 'MAINTENANCE_WINDOW', + 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") + }, + { + 'name': 'MAINTENANCE_WINDOW_ID', + 'value': str(maintenance_window_id) + } + + ], 'hardware': [{'id': int(instance_id)}], 'packageId': package_id } @@ -878,11 +889,26 @@ def upgrade(self, instance_id, memory=None, if prices: if test: - self.client['Product_Order'].verifyOrder(order) + result = self.client['Product_Order'].verifyOrder(order) else: - self.client['Product_Order'].placeOrder(order) - return True - return False + result = self.client['Product_Order'].placeOrder(order) + return result + + def get_maintenance_windows_id(self, location_id): + """Get the disks prices to be added or upgraded. + + :param int location_id: Hardware Server location id. + :return int. + """ + begin_date_object = datetime.datetime.now() + begin_date = begin_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") + end_date_object = datetime.date.today() + datetime.timedelta(days=30) + end_date = end_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") + + result_windows = self.client['SoftLayer_Provisioning_Maintenance_Window'].getMaintenanceWindows(begin_date, + end_date, + location_id) + return result_windows[0]['id'] @retry(logger=LOGGER) def get_instance(self, instance_id): @@ -893,7 +919,7 @@ def get_instance(self, instance_id): the specified instance. """ mask = [ - 'billingItem[id,package[id,keyName],nextInvoiceChildren]' + 'datacenter,billingItem[id,package[id,keyName],nextInvoiceChildren]' ] mask = "mask[%s]" % ','.join(mask) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 2283e3035..3a400842f 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -14,6 +14,7 @@ from unittest import mock as mock from SoftLayer.CLI import exceptions +from SoftLayer.fixtures import SoftLayer_Product_Order from SoftLayer import SoftLayerError from SoftLayer import testing @@ -691,19 +692,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -746,12 +747,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) @@ -936,9 +937,9 @@ def test_upgrade_aborted(self, confirm_mock): self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.CLI.formatting.confirm') - def test_upgrade_test(self, confirm_mock): - confirm_mock.return_value = True + def test_upgrade_test(self): + order_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_verifyOrder result = self.run_command(['hw', 'upgrade', '100', '--test', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) @@ -946,6 +947,8 @@ def test_upgrade_test(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_add_disk(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '2']) self.assert_no_fail(result) @@ -953,6 +956,8 @@ def test_upgrade_add_disk(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_resize_disk(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '1']) self.assert_no_fail(result) @@ -981,6 +986,8 @@ def test_upgrade_disk_does_not_exist(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index bf76de3c2..c3dd67a4d 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -879,7 +879,7 @@ def test_get_price_id_disk_capacity(self): def test_upgrade(self): result = self.hardware.upgrade(1, memory=32) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -891,7 +891,7 @@ def test_upgrade_add_disk(self): disk_list.append(disks) result = self.hardware.upgrade(1, disk=disk_list) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -903,7 +903,7 @@ def test_upgrade_resize_disk(self): disk_list.append(disks) result = self.hardware.upgrade(1, disk=disk_list) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -912,14 +912,14 @@ def test_upgrade_resize_disk(self): def test_upgrade_blank(self): result = self.hardware.upgrade(1) - self.assertEqual(result, False) + self.assertEqual(result, None) self.assertEqual(self.calls('SoftLayer_Product_Order', 'placeOrder'), []) def test_upgrade_full(self): result = self.hardware.upgrade(1, memory=32, nic_speed="10000 Redundant", drive_controller="RAID", public_bandwidth=500, test=False) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] From 6bd61d8d982a09b44359a71633db88b4f6431844 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 May 2021 09:19:38 -0400 Subject: [PATCH 0442/1385] fix team code review comments --- SoftLayer/CLI/email/detail.py | 11 ++++++-- SoftLayer/CLI/email/edit.py | 25 +++++++++++++------ SoftLayer/CLI/email/list.py | 12 ++++++--- ...Network_Message_Delivery_Email_Sendgrid.py | 1 + SoftLayer/managers/email.py | 14 ++++++++--- tests/CLI/modules/email_tests.py | 2 ++ tests/managers/email_tests.py | 6 +++++ 7 files changed, 54 insertions(+), 17 deletions(-) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py index 28fccba57..8d7ca18a5 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/detail.py @@ -1,7 +1,8 @@ -"""Display details for a specified email.""" +"""Display details for a specified email account.""" # :license: MIT, see LICENSE for more details. import click +from SoftLayer.CLI.email.list import build_statistics_table from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.email import EmailManager @@ -23,11 +24,17 @@ def cli(env, identifier): table.add_row(['id', result.get('id')]) table.add_row(['username', result.get('username')]) + table.add_row(['email_address', result.get('emailAddress')]) table.add_row(['create_date', result.get('createDate')]) - table.add_row(['categoryCode', utils.lookup(result, 'billingItem', 'categoryCode')]) + table.add_row(['category_code', utils.lookup(result, 'billingItem', 'categoryCode')]) table.add_row(['description', utils.lookup(result, 'billingItem', 'description')]) table.add_row(['type_description', utils.lookup(result, 'type', 'description')]) table.add_row(['type', utils.lookup(result, 'type', 'keyName')]) table.add_row(['vendor', utils.lookup(result, 'vendor', 'keyName')]) + statistics = email_manager.get_statistics(identifier) + + for statistic in statistics: + table.add_row(['statistics', build_statistics_table(statistic)]) + env.fout(table) diff --git a/SoftLayer/CLI/email/edit.py b/SoftLayer/CLI/email/edit.py index 321e6306f..16703b2aa 100644 --- a/SoftLayer/CLI/email/edit.py +++ b/SoftLayer/CLI/email/edit.py @@ -10,23 +10,32 @@ @click.command() @click.argument('identifier') -@click.option('--username', help="username account") -@click.option('--email', help="Additional note for the image") +@click.option('--username', help="Sets username for this account") +@click.option('--email', help="Sets the contact email for this account") @click.option('--password', help="Password must be between 8 and 20 characters " "and must contain one letter and one number.") @environment.pass_env def cli(env, identifier, username, email, password): """Edit details of an email delivery account.""" + email_manager = EmailManager(env.client) + data = {} + update = False + if email: + if email_manager.update_email(identifier, email): + update = True + else: + raise exceptions.CLIAbort("Failed to Edit emailAddress account") if username: data['username'] = username - if email: - data['emailAddress'] = email if password: data['password'] = password + if len(data) != 0: + if email_manager.editObject(identifier, **data): + update = True + else: + raise exceptions.CLIAbort("Failed to Edit email account") - email_manager = EmailManager(env.client) - - if not email_manager.editObject(identifier, data): - raise exceptions.CLIAbort("Failed to Edit email account") + if update: + env.fout('Updated Successfully') diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index a5353a31a..87912b37f 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -36,25 +36,29 @@ def cli(env): table.add_row(['email_information', table_information]) table.add_row(['email_overview', overview_table]) for statistic in statistics: - table.add_row(['statistics', _build_statistics_table(statistic)]) + table.add_row(['statistics', build_statistics_table(statistic)]) env.fout(table) def _build_overview_table(email_overview): - table = formatting.Table(['credit_Allowed', 'credits_Remain', 'package', 'reputation', 'requests']) + table = formatting.Table( + ['credit_allowed', 'credits_remain', 'credits_overage', 'credits_used', + 'package', 'reputation', 'requests']) table.align['name'] = 'r' table.align['value'] = 'l' table.add_row([email_overview.get('creditsAllowed'), email_overview.get('creditsRemain'), + email_overview.get('creditsOverage'), email_overview.get('creditsUsed'), email_overview.get('package'), email_overview.get('reputation'), email_overview.get('requests')]) return table -def _build_statistics_table(statistics): - table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spamReports']) +def build_statistics_table(statistics): + """statistics records of Email Delivery account""" + table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spam_reports']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py index 9bff8ad08..6a64d1be6 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -56,3 +56,4 @@ } editObject = True +updateEmailAddress = True diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index df3c144e5..57af17ce7 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -57,7 +57,7 @@ def get_instance(self, identifier): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject', id=identifier, mask=_mask) - def editObject(self, identifier, username=None, emailAddress=None, password=None): + def editObject(self, identifier, username=None, password=None): """Edit email delivery account related details. :param int identifier: The ID of the email account @@ -68,10 +68,18 @@ def editObject(self, identifier, username=None, emailAddress=None, password=None data = {} if username: data['username'] = username - if emailAddress: - data['emailAddress'] = emailAddress if password: data['password'] = password return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'editObject', data, id=identifier) + + def update_email(self, identifier, email): + """Edit email address delivery account . + + :param int identifier: The ID of the email account + :param string email: email of the email account. + + """ + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress', email, id=identifier) diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index f2c8aa693..0e2b398d6 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -27,3 +27,5 @@ def test_edit(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'editObject') + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index fad965c7a..94ea84b52 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -27,3 +27,9 @@ def test_get_object(self): self.manager.get_instance(1232123) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject') + + def test_update_email_address(self): + self.manager = EmailManager(self.client) + self.manager.update_email(1232123, 'test@ibm.com') + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress') From d92a1723f77eacc1f91707ffc4019b4ce1e1af77 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 24 May 2021 16:22:42 -0500 Subject: [PATCH 0443/1385] changed a testing domain to one that really doesnt exist --- SoftLayer/transports.py | 7 +++--- tests/transport_tests.py | 49 ++++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index d0afe3d3b..bd643b568 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -395,7 +395,8 @@ def __call__(self, request): try: result = json.loads(resp.text) except ValueError as json_ex: - raise exceptions.SoftLayerAPIError(resp.status_code, str(json_ex)) + LOGGER.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, str(resp.text)) else: raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") @@ -413,8 +414,8 @@ def __call__(self, request): except ValueError as json_ex: if ex.response.text == "": raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") - - raise exceptions.SoftLayerAPIError(resp.status_code, str(json_ex)) + LOGGER.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, ex.response.text) raise exceptions.SoftLayerAPIError(ex.response.status_code, message) except requests.RequestException as ex: diff --git a/tests/transport_tests.py b/tests/transport_tests.py index ca3dcc73b..c23f1054f 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -40,7 +40,7 @@ class TestXmlRpcAPICall(testing.TestCase): def set_up(self): self.transport = transports.XmlRpcTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) self.response = get_xmlrpc_response() @@ -71,7 +71,7 @@ def test_call(self, request): resp = self.transport(req) request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', headers={'Content-Type': 'application/xml', 'User-Agent': consts.USER_AGENT}, proxies=None, @@ -123,7 +123,7 @@ def test_identifier(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.identifier = 1234 @@ -141,7 +141,7 @@ def test_filter(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} @@ -159,7 +159,7 @@ def test_limit_offset(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.limit = 10 @@ -179,7 +179,7 @@ def test_old_mask(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = {"something": "nested"} @@ -201,7 +201,7 @@ def test_mask_call_no_mask_prefix(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "something.nested" @@ -217,7 +217,7 @@ def test_mask_call_v2(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "mask[something[nested]]" @@ -233,7 +233,7 @@ def test_mask_call_filteredMask(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "filteredMask[something[nested]]" @@ -249,7 +249,7 @@ def test_mask_call_v2_dot(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "mask.something.nested" @@ -313,7 +313,7 @@ def test_ibm_id_call(self, auth, request): auth.assert_called_with('apikey', '1234567890qweasdzxc') request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', headers={'Content-Type': 'application/xml', 'User-Agent': consts.USER_AGENT}, proxies=None, @@ -385,7 +385,7 @@ def test_verify(request, request.return_value = get_xmlrpc_response() transport = transports.XmlRpcTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) req = transports.Request() @@ -401,7 +401,7 @@ def test_verify(request, transport(req) request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', data=mock.ANY, headers=mock.ANY, cert=mock.ANY, @@ -415,7 +415,7 @@ class TestRestAPICall(testing.TestCase): def set_up(self): self.transport = transports.RestTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -435,7 +435,7 @@ def test_basic(self, request): self.assertIsInstance(resp, transports.SoftLayerListResult) self.assertEqual(resp.total_count, 10) request.assert_called_with( - 'GET', 'http://something.com/SoftLayer_Service/Resource.json', + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', headers=mock.ANY, auth=None, data=None, @@ -501,7 +501,6 @@ def test_proxy_without_protocol(self): req.service = 'SoftLayer_Service' req.method = 'Resource' req.proxy = 'localhost:3128' - try: self.assertRaises(SoftLayer.TransportError, self.transport, req) except AssertionError: @@ -519,7 +518,7 @@ def test_valid_proxy(self, request): self.transport(req) request.assert_called_with( - 'GET', 'http://something.com/SoftLayer_Service/Resource.json', + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, auth=None, @@ -544,7 +543,7 @@ def test_with_id(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=None, data=None, @@ -568,7 +567,7 @@ def test_with_args(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'POST', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', headers=mock.ANY, auth=None, data='{"parameters": ["test", 1]}', @@ -592,7 +591,7 @@ def test_with_args_bytes(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'POST', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', headers=mock.ANY, auth=None, data='{"parameters": ["test", "YXNkZg=="]}', @@ -616,7 +615,7 @@ def test_with_filter(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectFilter': '{"TYPE": {"attribute": {"operation": "^= prefix"}}}'}, headers=mock.ANY, @@ -641,7 +640,7 @@ def test_with_mask(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectMask': 'mask[id,property]'}, headers=mock.ANY, auth=None, @@ -662,7 +661,7 @@ def test_with_mask(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectMask': 'mask[id,property]'}, headers=mock.ANY, auth=None, @@ -688,7 +687,7 @@ def test_with_limit_offset(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=None, data=None, @@ -731,7 +730,7 @@ def test_with_special_auth(self, auth, request): auth.assert_called_with(user, password) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=mock.ANY, data=None, From 2a8637ac677498ecd728aaf1fcc3717387425951 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 24 May 2021 20:44:48 -0400 Subject: [PATCH 0444/1385] fix team code review --- SoftLayer/CLI/email/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index 87912b37f..d2042b350 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -1,4 +1,4 @@ -"""Get lists Email Delivery account Service """ +"""Lists Email Delivery Service """ # :license: MIT, see LICENSE for more details. import click From 668035f02f2f477bfecd5eac59526d7dee710e59 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 May 2021 16:48:20 -0500 Subject: [PATCH 0445/1385] v5.9.5 updates --- CHANGELOG.md | 14 ++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f55340957..4e70595fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log + +## [5.9.4] - 2021-04-24 +https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 + +#### Improvements +- Changed a testing domain to one that really doesnt exist #1492 +- Fix Incomplete notes field for file and block #1484 +- Show component versions on hw detail #1470 +- Add the firewall information on slcli firewall detail #1475 +- Add an --orderBy parameters to call-api #1459 +- Add image detail transaction data #1479 + + + ## [5.9.4] - 2021-04-27 https://github.com/softlayer/softlayer-python/compare/v5.9.3...v5.9.4 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index cec2831e0..26a00c4cd 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.4' +VERSION = 'v5.9.5' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 48c1e4e6c..c2e70f61a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.4', + version='5.9.5', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 66a0908452f03c2980b93db04b34fba1eeebb356 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 May 2021 21:05:39 -0500 Subject: [PATCH 0446/1385] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e70595fa..0a79b6d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [5.9.4] - 2021-04-24 +## [5.9.5] - 2021-05-25 https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 #### Improvements From a88f14761caf134d374f01d2e2b08bd4aec0143c Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:24:22 -0500 Subject: [PATCH 0447/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a318890e6..c4a0daeeb 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -6,7 +6,7 @@ description: | license: MIT -base: core18 +base: core20 grade: stable confinement: strict @@ -25,7 +25,6 @@ parts: source: https://github.com/softlayer/softlayer-python source-type: git plugin: python - python-version: python3 override-pull: | snapcraftctl pull snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" @@ -34,4 +33,4 @@ parts: - python3 stage-packages: - - python3 \ No newline at end of file + - python3 From 8fd0e86c91f9a6aacf58db9712eacbef7abf1ceb Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:28:04 -0500 Subject: [PATCH 0448/1385] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c4a0daeeb..f8e2e2267 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -12,7 +12,7 @@ confinement: strict apps: slcli: - command: slcli + command: bin/slcli environment: LC_ALL: C.UTF-8 plugs: From 79a06c38bb48bb4d9712fec2d50ec26a7b2e2d72 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:34:47 -0500 Subject: [PATCH 0449/1385] Update README.rst --- README.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2ae928347..fc05a3503 100644 --- a/README.rst +++ b/README.rst @@ -60,7 +60,12 @@ To install the slcli snap: .. code-block:: bash - $ sudo snap install slcli + $ sudo snap install slcli + + (or to get the latest release) + + $ sudo snap install slcli --edge + From b97540bf0eb7bbd9c16d3a745cbf3a5b7d9aa238 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 27 May 2021 17:32:58 -0400 Subject: [PATCH 0450/1385] slcli vlan cancel should report if a vlan is automatic --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/cancel.py | 30 ++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 12 +++++++- SoftLayer/managers/billing.py | 27 ++++++++++++++++++ SoftLayer/managers/network.py | 8 ++++++ docs/api/managers/billing.rst | 5 ++++ docs/cli/vlan.rst | 4 +++ tests/CLI/modules/vlan_tests.py | 12 ++++++++ 8 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/vlan/cancel.py create mode 100644 SoftLayer/managers/billing.py create mode 100644 docs/api/managers/billing.rst diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..6672477ed 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -336,6 +336,7 @@ ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), + ('vlan:cancel', 'SoftLayer.CLI.vlan.cancel:cli'), ('summary', 'SoftLayer.CLI.summary:cli'), diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py new file mode 100644 index 000000000..7bef5e458 --- /dev/null +++ b/SoftLayer/CLI/vlan/cancel.py @@ -0,0 +1,30 @@ +"""Cancel Network Vlan.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.managers.billing import BillingManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancel network vlan.""" + + mgr = SoftLayer.NetworkManager(env.client) + billing = BillingManager(env.client) + if not (env.skip_confirmations or formatting.no_going_back(identifier)): + raise exceptions.CLIAbort('Aborted') + + item = mgr.get_vlan(identifier).get('billingItem') + if item: + billing.cancel_item(item.get('id'), 'cancel by cli command') + env.fout('Cancel Successfully') + else: + res = mgr.get_cancel_failure_reasons(identifier) + raise exceptions.ArgumentError(res) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 758fe3b39..4e1c100ad 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -5,9 +5,19 @@ }, 'id': 1234, 'vlanNumber': 4444, - 'firewallInterfaces': None + 'firewallInterfaces': None, + 'billingItem': { + 'allowCancellationFlag': 1, + 'categoryCode': 'network_vlan', + 'description': 'Private Network Vlan', + 'id': 235689, + 'notes': 'test cli', + 'orderItemId': 147258, + } } editObject = True setTags = True getList = [getObject] + +cancel = True diff --git a/SoftLayer/managers/billing.py b/SoftLayer/managers/billing.py new file mode 100644 index 000000000..bb2c11e23 --- /dev/null +++ b/SoftLayer/managers/billing.py @@ -0,0 +1,27 @@ +""" + SoftLayer.BillingItem + ~~~~~~~~~~~~~~~~~~~ + BillingItem manager + + :license: MIT, see LICENSE for more details. +""" + + +class BillingManager(object): + """Manager for interacting with Billing item instances.""" + + def __init__(self, client): + self.client = client + + def cancel_item(self, identifier, reason_cancel): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + True, + True, + reason_cancel, + reason_cancel, + id=identifier) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index d609de5d5..50429cbb1 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -49,6 +49,7 @@ 'primaryRouter[id, fullyQualifiedDomainName, datacenter]', 'totalPrimaryIpAddressCount', 'networkSpace', + 'billingItem', 'hardware', 'subnets', 'virtualGuests', @@ -752,3 +753,10 @@ def set_subnet_ipddress_note(self, identifier, note): """ result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) return result + + def get_cancel_failure_reasons(self, identifier): + """get the reasons by cannot cancel the VLAN + + :param integer identifier: the instance ID + """ + return self.vlan.getCancelFailureReasons(id=identifier) diff --git a/docs/api/managers/billing.rst b/docs/api/managers/billing.rst new file mode 100644 index 000000000..f44a9333c --- /dev/null +++ b/docs/api/managers/billing.rst @@ -0,0 +1,5 @@ +.. _billing: + +.. automodule:: SoftLayer.managers.billing + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 6fc084da7..57a58932a 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -14,3 +14,7 @@ VLANs .. click:: SoftLayer.CLI.vlan.list:cli :prog: vlan list :show-nested: + +.. click:: SoftLayer.CLI.vlan.cancel:cli + :prog: vlan cancel + :show-nested: diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 73c1fab97..59de39ad3 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -99,3 +99,15 @@ def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkVlans') + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vlan', 'cancel', '1234']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel_fail(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['vlan', 'cancel', '1234']) + self.assertTrue(result.exit_code, 2) From 03583ee7adb275d211a7c8a137d64b153c21186f Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 27 May 2021 20:42:31 -0400 Subject: [PATCH 0451/1385] Refactor hw upgrade. --- SoftLayer/CLI/hardware/upgrade.py | 32 +++++++++---------- SoftLayer/fixtures/SoftLayer_Product_Order.py | 10 +++--- SoftLayer/managers/hardware.py | 3 +- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index 89b89859e..d9e0c9487 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -68,8 +68,8 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad if test: add_data_to_table(response, table) else: - table.add_row(['order_date', response.get('orderDate')]) - table.add_row(['order_id', response.get('orderId')]) + table.add_row(['Order Date', response.get('orderDate')]) + table.add_row(['Order Id', response.get('orderId')]) add_data_to_table(response['orderDetails'], table) place_order_table = get_place_order_information(response) table.add_row(['Place Order Information', place_order_table]) @@ -83,21 +83,21 @@ def add_data_to_table(response, table): """Add the hardware server upgrade result to the table""" table.add_row(['location', utils.lookup(response, 'locationObject', 'longName')]) table.add_row(['quantity', response.get('quantity')]) - table.add_row(['package_id', response.get('packageId')]) - table.add_row(['currency_short_name', response.get('currencyShortName')]) - table.add_row(['prorated_initial_charge', response.get('proratedInitialCharge')]) - table.add_row(['prorated_order_total', response.get('proratedOrderTotal')]) - table.add_row(['use_hourly_pricing', response.get('useHourlyPricing')]) + table.add_row(['Package Id', response.get('packageId')]) + table.add_row(['Currency Short Name', response.get('currencyShortName')]) + table.add_row(['Prorated Initial Charge', response.get('proratedInitialCharge')]) + table.add_row(['Prorated Order Total', response.get('proratedOrderTotal')]) + table.add_row(['Hourly Pricing', response.get('useHourlyPricing')]) table_hardware = get_hardware_detail(response) table.add_row(['Hardware', table_hardware]) table_prices = get_hardware_prices(response) - table.add_row(['prices', table_prices]) + table.add_row(['Prices', table_prices]) def get_place_order_information(response): """Get the hardware server place order information.""" - table_place_order = formatting.Table(['id', 'account_id', 'status', 'Account CompanyName', - 'UserRecord FirstName', 'UserRecord lastName', 'UserRecord Username']) + table_place_order = formatting.Table(['Id', 'Account Id', 'Status', 'Account CompanyName', + 'UserRecord FirstName', 'UserRecord LastName', 'UserRecord Username']) table_place_order.add_row([response.get('id'), response.get('accountId'), response.get('status'), @@ -111,8 +111,8 @@ def get_place_order_information(response): def get_hardware_detail(response): """Get the hardware server detail.""" - table_hardware = formatting.Table(['account_id', 'hostname', 'domain']) - for hardware in response['hardware']: + table_hardware = formatting.Table(['Account Id', 'Hostname', 'Domain']) + for hardware in response['Hardware']: table_hardware.add_row([hardware.get('accountId'), hardware.get('hostname'), hardware.get('domain')]) @@ -122,7 +122,7 @@ def get_hardware_detail(response): def get_hardware_prices(response): """Get the hardware server prices.""" - table_prices = formatting.Table(['id', 'hourlyRecurringFee', 'recurringFee', 'categories', 'Item Description', + table_prices = formatting.Table(['Id', 'HourlyRecurringFee', 'RecurringFee', 'Categories', 'Item Description', 'Item Units']) for price in response['prices']: categories = price.get('categories')[0] @@ -138,9 +138,9 @@ def get_hardware_prices(response): def get_order_detail(response): """Get the hardware server order detail.""" - table_order_detail = formatting.Table(['billing_city', 'billing_country_code', 'billing_email', - 'billing_name_first', 'billing_name_last', 'billing_postal_code', - 'billing_state']) + table_order_detail = formatting.Table(['Billing City', 'Billing Country Code', 'Billing Email', + 'Billing Name First', 'Billing Name Last', 'Billing Postal Code', + 'Billing State']) table_order_detail.add_row([utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCity'), utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCountryCode'), utils.lookup(response, 'orderDetails', 'billingInformation', 'billingEmail'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 83604f8d1..be702ccba 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -69,7 +69,7 @@ { "accountId": 1111, "domain": "testedit.com", - "hostname": "bardcabero", + "hostname": "test", "globalIdentifier": "81434794-af69-44d5-bb97-12312asdasdasd" } ], @@ -135,8 +135,8 @@ "accountId": 1111111, "bareMetalInstanceFlag": 0, "domain": "testedit.com", - "fullyQualifiedDomainName": "bardcabero.testedit.com", - "hostname": "bardcabero", + "fullyQualifiedDomainName": "test.testedit.com", + "hostname": "test", "globalIdentifier": "81434794-af69-44d5-bb97-1111111" } ], @@ -205,8 +205,8 @@ "accountId": 1234, "firstName": "test", "id": 3333, - "lastName": "cabero", - "username": "sl1234-dcabero" + "lastName": "test", + "username": "sl1234-test" } } } diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fd14549ce..5bfd50869 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -908,7 +908,8 @@ def get_maintenance_windows_id(self, location_id): result_windows = self.client['SoftLayer_Provisioning_Maintenance_Window'].getMaintenanceWindows(begin_date, end_date, location_id) - return result_windows[0]['id'] + if len(result_windows) > 0: + return result_windows[0].get('id') @retry(logger=LOGGER) def get_instance(self, instance_id): From 0ad4846fa9a781d60efe923c22f6553c870f55b3 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 27 May 2021 21:05:38 -0400 Subject: [PATCH 0452/1385] Fix unit test and tox analysis. --- SoftLayer/CLI/hardware/upgrade.py | 2 +- SoftLayer/managers/hardware.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index d9e0c9487..a5b432e82 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -112,7 +112,7 @@ def get_place_order_information(response): def get_hardware_detail(response): """Get the hardware server detail.""" table_hardware = formatting.Table(['Account Id', 'Hostname', 'Domain']) - for hardware in response['Hardware']: + for hardware in response['hardware']: table_hardware.add_row([hardware.get('accountId'), hardware.get('hostname'), hardware.get('domain')]) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5bfd50869..d7e65694e 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -835,6 +835,7 @@ def upgrade(self, instance_id, memory=None, :returns: bool """ result = None + maintenance_window_id = None upgrade_prices = self._get_upgrade_prices(instance_id) prices = [] data = {} @@ -853,7 +854,9 @@ def upgrade(self, instance_id, memory=None, location_id = server_response['datacenter']['id'] maintenance_window = datetime.datetime.now(utils.UTC()) - maintenance_window_id = self.get_maintenance_windows_id(location_id) + maintenance_window_detail = self.get_maintenance_windows_detail(location_id) + if maintenance_window_detail: + maintenance_window_id = maintenance_window_detail.get('id') order = { 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', @@ -894,12 +897,13 @@ def upgrade(self, instance_id, memory=None, result = self.client['Product_Order'].placeOrder(order) return result - def get_maintenance_windows_id(self, location_id): + def get_maintenance_windows_detail(self, location_id): """Get the disks prices to be added or upgraded. :param int location_id: Hardware Server location id. :return int. """ + result = None begin_date_object = datetime.datetime.now() begin_date = begin_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") end_date_object = datetime.date.today() + datetime.timedelta(days=30) @@ -909,7 +913,9 @@ def get_maintenance_windows_id(self, location_id): end_date, location_id) if len(result_windows) > 0: - return result_windows[0].get('id') + result = result_windows[0] + + return result @retry(logger=LOGGER) def get_instance(self, instance_id): From 773f4d59d37407566c8d446dbcc1bbb2b64f6a35 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 1 Jun 2021 11:25:16 -0400 Subject: [PATCH 0453/1385] Remove block/file interval option for replica volume. --- SoftLayer/CLI/block/replication/order.py | 15 +++++++++------ SoftLayer/CLI/file/replication/order.py | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/block/replication/order.py b/SoftLayer/CLI/block/replication/order.py index 743c91c0e..5324dfc19 100644 --- a/SoftLayer/CLI/block/replication/order.py +++ b/SoftLayer/CLI/block/replication/order.py @@ -5,6 +5,8 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers +from SoftLayer import utils CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @@ -14,9 +16,9 @@ @click.argument('volume_id') @click.option('--snapshot-schedule', '-s', help='Snapshot schedule to use for replication, ' - '(INTERVAL | HOURLY | DAILY | WEEKLY)', + '(HOURLY | DAILY | WEEKLY)', required=True, - type=click.Choice(['INTERVAL', 'HOURLY', 'DAILY', 'WEEKLY'])) + type=click.Choice(['HOURLY', 'DAILY', 'WEEKLY'])) @click.option('--location', '-l', help='Short name of the data center for the replicant ' '(e.g.: dal09)', @@ -40,13 +42,14 @@ def cli(env, volume_id, snapshot_schedule, location, tier, os_type): """Order a block storage replica volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) + block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') if tier is not None: tier = float(tier) try: order = block_manager.order_replicant_volume( - volume_id, + block_volume_id, snapshot_schedule=snapshot_schedule, location=location, tier=tier, @@ -57,9 +60,9 @@ def cli(env, volume_id, snapshot_schedule, location, tier, os_type): if 'placedOrder' in order.keys(): click.echo("Order #{0} placed successfully!".format( - order['placedOrder']['id'])) - for item in order['placedOrder']['items']: - click.echo(" > %s" % item['description']) + utils.lookup(order, 'placedOrder', 'id'))) + for item in utils.lookup(order, 'placedOrder', 'items'): + click.echo(" > %s" % item.get('description')) else: click.echo("Order could not be placed! Please verify your options " + "and try again.") diff --git a/SoftLayer/CLI/file/replication/order.py b/SoftLayer/CLI/file/replication/order.py index 9ba2c84f8..2961cbd64 100644 --- a/SoftLayer/CLI/file/replication/order.py +++ b/SoftLayer/CLI/file/replication/order.py @@ -5,6 +5,8 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers +from SoftLayer import utils CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @@ -14,9 +16,9 @@ @click.argument('volume_id') @click.option('--snapshot-schedule', '-s', help='Snapshot schedule to use for replication, ' - '(INTERVAL | HOURLY | DAILY | WEEKLY)', + '(HOURLY | DAILY | WEEKLY)', required=True, - type=click.Choice(['INTERVAL', 'HOURLY', 'DAILY', 'WEEKLY'])) + type=click.Choice(['HOURLY', 'DAILY', 'WEEKLY'])) @click.option('--location', '-l', help='Short name of the data center for the replicant ' '(e.g.: dal09)', @@ -29,13 +31,14 @@ def cli(env, volume_id, snapshot_schedule, location, tier): """Order a file storage replica volume.""" file_manager = SoftLayer.FileStorageManager(env.client) + file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') if tier is not None: tier = float(tier) try: order = file_manager.order_replicant_volume( - volume_id, + file_volume_id, snapshot_schedule=snapshot_schedule, location=location, tier=tier, @@ -45,9 +48,9 @@ def cli(env, volume_id, snapshot_schedule, location, tier): if 'placedOrder' in order.keys(): click.echo("Order #{0} placed successfully!".format( - order['placedOrder']['id'])) - for item in order['placedOrder']['items']: - click.echo(" > %s" % item['description']) + utils.lookup(order, 'placedOrder', 'id'))) + for item in utils.lookup(order, 'placedOrder', 'items'): + click.echo(" > %s" % item.get('description')) else: click.echo("Order could not be placed! Please verify your options " + "and try again.") From 7d2de3c5d37f29a89b2d40ec4f66c3cc3cbf64ac Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 3 Jun 2021 12:35:56 -0400 Subject: [PATCH 0454/1385] add new feature on vlan cli --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/create.py | 40 ++++++++++++ SoftLayer/fixtures/SoftLayer_Product_Order.py | 16 +++++ .../fixtures/SoftLayer_Product_Package.py | 63 +++++++++++++++++++ docs/cli/vlan.rst | 4 ++ tests/CLI/modules/vlan_tests.py | 21 +++++++ 6 files changed, 145 insertions(+) create mode 100644 SoftLayer/CLI/vlan/create.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..5eb3a006b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -333,6 +333,7 @@ ('user:vpn-subnet', 'SoftLayer.CLI.user.vpn_subnet:cli'), ('vlan', 'SoftLayer.CLI.vlan'), + ('vlan:create', 'SoftLayer.CLI.vlan.create:cli'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py new file mode 100644 index 000000000..6d4a85273 --- /dev/null +++ b/SoftLayer/CLI/vlan/create.py @@ -0,0 +1,40 @@ +"""Order/create a dedicated server.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer.managers import ordering + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(epilog="See 'slcli server create-options' for valid options.") +@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--network', default='public', show_default=True, type=click.Choice(['public', 'private']), + help='Network vlan type') +@click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), + help="Billing rate") +@environment.pass_env +def cli(env, hostname, datacenter, network, billing): + """Order/create a vlan instance.""" + + item_package = ['PUBLIC_NETWORK_VLAN'] + complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' + if not network: + item_package = ['PRIVATE_NETWORK_VLAN'] + + ordering_manager = ordering.OrderingManager(env.client) + result = ordering_manager.place_order(package_keyname='NETWORK_VLAN', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=billing, + extras={'name': hostname}) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', result['orderId']]) + table.add_row(['created', result['orderDate']]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 3774f63a8..df9747173 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -104,3 +104,19 @@ ] } } + +vlan_placeOrder = {"orderDate": "2021-06-02 15:23:47", + "orderId": 123456, + "prices": [{ + "id": 2018, + "itemId": 1071, + "categories": [{ + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan"}], + "item": { + "capacity": "0", + "description": "Public Network Vlan", + "id": 1071, + "keyName": "PUBLIC_NETWORK_VLAN"}} + ]} diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..54c7880c9 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,66 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItemsVLAN = [{ + "description": "Private Network Vlan", + "id": 1072, + "itemTaxCategoryId": 166, + "keyName": "PRIVATE_NETWORK_VLAN", + "itemCategory": { + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan"}, + "prices": [{ + "id": 203707, + "itemId": 1072, + "laborFee": "0", + "locationGroupId": 505, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }, + { + "id": 203727, + "itemId": 1072, + "laborFee": "0", + "locationGroupId": 545, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }] +}, { + "description": "Public Network Vlan", + "id": 1071, + "itemTaxCategoryId": 166, + "keyName": "PUBLIC_NETWORK_VLAN", + "units": "N/A", + "itemCategory": { + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan", + }, + "prices": [{ + "id": 203637, + "itemId": 1071, + "laborFee": "0", + "locationGroupId": 509, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }, + { + "id": 203667, + "itemId": 1071, + "laborFee": "0", + "locationGroupId": 545, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }] +} +] diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 6fc084da7..b68f3d26e 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -3,6 +3,10 @@ VLANs ===== +.. click:: SoftLayer.CLI.vlan.create:cli + :prog: vlan create + :show-nested: + .. click:: SoftLayer.CLI.vlan.detail:cli :prog: vlan detail :show-nested: diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 73c1fab97..5a1e7d4ae 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -4,8 +4,11 @@ :license: MIT, see LICENSE for more details. """ +import json from unittest import mock as mock +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing @@ -99,3 +102,21 @@ def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkVlans') + + def test_create_vlan(self): + amock = self.set_mock('SoftLayer_Product_Package', 'getItems') + amock.return_value = SoftLayer_Product_Package.getItemsVLAN + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder + + result = self.run_command(['vlan', 'create', + '-H test', + '-d TEST00', + '--network', 'public', + '--billing', 'hourly' + ]) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + {'id': 123456, 'created': '2021-06-02 15:23:47'}) From 47d14d0918cfe16cc6c99d088b664c9b65fd46c4 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 4 Jun 2021 10:49:25 -0400 Subject: [PATCH 0455/1385] fix team code review comments --- SoftLayer/CLI/vlan/cancel.py | 17 +++++++----- SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 5 ++++ SoftLayer/managers/billing.py | 27 -------------------- SoftLayer/managers/network.py | 16 +++++++++++- tests/CLI/modules/vlan_tests.py | 8 ++++++ 5 files changed, 39 insertions(+), 34 deletions(-) delete mode 100644 SoftLayer/managers/billing.py diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py index 7bef5e458..79647a7eb 100644 --- a/SoftLayer/CLI/vlan/cancel.py +++ b/SoftLayer/CLI/vlan/cancel.py @@ -7,7 +7,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer.managers.billing import BillingManager @click.command() @@ -17,14 +16,20 @@ def cli(env, identifier): """Cancel network vlan.""" mgr = SoftLayer.NetworkManager(env.client) - billing = BillingManager(env.client) + if not (env.skip_confirmations or formatting.no_going_back(identifier)): raise exceptions.CLIAbort('Aborted') + reasons = mgr.get_cancel_failure_reasons(identifier) + if len(reasons) > 0: + raise exceptions.CLIAbort(reasons) item = mgr.get_vlan(identifier).get('billingItem') if item: - billing.cancel_item(item.get('id'), 'cancel by cli command') - env.fout('Cancel Successfully') + mgr.cancel_item(item.get('id'), + True, + 'Cancel by cli command', + 'Cancel by cli command') else: - res = mgr.get_cancel_failure_reasons(identifier) - raise exceptions.ArgumentError(res) + raise exceptions.CLIAbort( + "VLAN is an automatically assigned and free of charge VLAN," + " it will automatically be removed from your account when it is empty") diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 4e1c100ad..960c98995 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -21,3 +21,8 @@ getList = [getObject] cancel = True + +getCancelFailureReasons = [ + "1 bare metal server(s) still on the VLAN ", + "1 virtual guest(s) still on the VLAN " +] diff --git a/SoftLayer/managers/billing.py b/SoftLayer/managers/billing.py deleted file mode 100644 index bb2c11e23..000000000 --- a/SoftLayer/managers/billing.py +++ /dev/null @@ -1,27 +0,0 @@ -""" - SoftLayer.BillingItem - ~~~~~~~~~~~~~~~~~~~ - BillingItem manager - - :license: MIT, see LICENSE for more details. -""" - - -class BillingManager(object): - """Manager for interacting with Billing item instances.""" - - def __init__(self, client): - self.client = client - - def cancel_item(self, identifier, reason_cancel): - """Cancel a billing item immediately, deleting all its data. - - :param integer identifier: the instance ID to cancel - :param string reason_cancel: reason cancel - """ - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', - True, - True, - reason_cancel, - reason_cancel, - id=identifier) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 50429cbb1..7c86dda58 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -755,8 +755,22 @@ def set_subnet_ipddress_note(self, identifier, note): return result def get_cancel_failure_reasons(self, identifier): - """get the reasons by cannot cancel the VLAN + """get the reasons why we cannot cancel the VLAN. :param integer identifier: the instance ID """ return self.vlan.getCancelFailureReasons(id=identifier) + + def cancel_item(self, identifier, cancel_immediately, + reason_cancel, customer_note): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + True, + cancel_immediately, + reason_cancel, + customer_note, + id=identifier) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 59de39ad3..af2b22290 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -103,9 +103,17 @@ def test_vlan_list(self): @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): confirm_mock.return_value = True + mock = self.set_mock('SoftLayer_Network_Vlan', 'getCancelFailureReasons') + mock.return_value = [] result = self.run_command(['vlan', 'cancel', '1234']) self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel_error(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vlan', 'cancel', '1234']) + self.assertTrue(result.exit_code, 2) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel_fail(self, confirm_mock): confirm_mock.return_value = False From 643bae8b3239d60fea786043d0722e003276f107 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 8 Jun 2021 10:57:38 -0400 Subject: [PATCH 0456/1385] Add slcli account licenses --- SoftLayer/CLI/account/licenses.py | 41 +++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Account.py | 107 ++++++++++++++++++++++++ SoftLayer/managers/account.py | 22 +++++ tests/CLI/modules/account_tests.py | 6 ++ tests/managers/account_tests.py | 8 ++ 6 files changed, 185 insertions(+) create mode 100644 SoftLayer/CLI/account/licenses.py diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py new file mode 100644 index 000000000..cdf6e177f --- /dev/null +++ b/SoftLayer/CLI/account/licenses.py @@ -0,0 +1,41 @@ +"""Show the all account licenses.""" +# :license: MIT, see LICENSE for more details. +import click +from SoftLayer import utils + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager + + +@click.command() +@environment.pass_env +def cli(env): + """return the control panel and VMWare licenses""" + + manager = AccountManager(env.client) + + panel_control = manager.get_active_virtual_licenses() + vmwares = manager.get_active_account_licenses() + + table_panel = formatting.KeyValueTable(['id', 'ip_address', 'manufacturer', 'software', + 'key', 'subnet', 'subnet notes']) + + table_vmware = formatting.KeyValueTable(['name', 'license_key', 'cpus', 'description', + 'manufacturer', 'requiredUser']) + for panel in panel_control: + table_panel.add_row([panel.get('id'), panel.get('ipAddress'), + utils.lookup(panel, 'softwareDescription', 'manufacturer'), + utils.trim_to(utils.lookup(panel, 'softwareDescription', 'longDescription'), 40), + panel.get('key'), utils.lookup(panel, 'subnet', 'broadcastAddress'), + utils.lookup(panel, 'subnet', 'note')]) + + env.fout(table_panel) + for vmware in vmwares: + table_vmware.add_row([utils.lookup(vmware, 'softwareDescription', 'name'), + vmware.get('key'), vmware.get('capacity'), + utils.lookup(vmware, 'billingItem', 'description'), + utils.lookup(vmware, 'softwareDescription', 'manufacturer'), + utils.lookup(vmware, 'softwareDescription', 'requiredUser')]) + + env.fout(table_vmware) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..4a267b7a4 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -16,6 +16,7 @@ ('account:invoices', 'SoftLayer.CLI.account.invoices:cli'), ('account:events', 'SoftLayer.CLI.account.events:cli'), ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), + ('account:licenses', 'SoftLayer.CLI.account.licenses:cli'), ('account:summary', 'SoftLayer.CLI.account.summary:cli'), ('account:billing-items', 'SoftLayer.CLI.account.billing_items:cli'), ('account:item-detail', 'SoftLayer.CLI.account.item_detail:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 4cfafa30f..11d1b26d0 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1100,3 +1100,110 @@ "smtpAccess": "1" } ] + +getActiveAccountLicenses = [{ + "accountId": 123456, + "capacity": "4", + "key": "M02A5-6CJ8L-J8R9H-000R0-CDR70", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "cycleStartDate": "2021-06-03T23:11:22-06:00", + "description": "vCenter Server Appliance 6.0", + "id": 741258963, + "laborFee": "0", + "laborFeeTaxRate": "0", + "oneTimeFee": "0", + "oneTimeFeeTaxRate": "0", + "orderItemId": 963258741, + "recurringFee": "0", + "recurringFeeTaxRate": "0", + "recurringMonths": 1, + "serviceProviderId": 1, + "setupFee": "0", + "setupFeeTaxRate": "0" + }, + "softwareDescription": { + "controlPanel": 0, + "id": 15963, + "licenseTermValue": 0, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "operatingSystem": 0, + "version": "6.0", + "virtualLicense": 0, + "virtualizationPlatform": 0, + "requiredUser": "administrator@vsphere.local" + } +}, + { + "accountId": 123456, + "capacity": "4", + "key": "4122M-ABXC05-K829T-098HP-00QJM", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "description": "vCenter Server Appliance 6.x", + "id": 36987456, + "laborFee": "0", + "laborFeeTaxRate": "0", + "oneTimeFee": "0", + "oneTimeFeeTaxRate": "0", + "orderItemId": 25839, + "recurringFee": "0", + "recurringFeeTaxRate": "0", + "recurringMonths": 1, + "serviceProviderId": 1, + "setupFee": "0", + "setupFeeTaxRate": "0" + }, + "softwareDescription": { + "controlPanel": 0, + "id": 1472, + "licenseTermValue": 0, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "operatingSystem": 0, + "version": "6.0", + "virtualLicense": 0, + "virtualizationPlatform": 0, + "requiredUser": "administrator@vsphere.local" + } + } +] + +getActiveVirtualLicenses = [{ + "id": 12345, + "ipAddress": "192.168.23.78", + "key": "PLSK.06866259.0000", + "billingItem": { + "categoryCode": "control_panel", + "description": "Plesk Onyx (Linux) - (Unlimited) - VPS " + }, + "softwareDescription": { + "longDescription": "Plesk - Unlimited Domain w/ Power Pack for VPS 17.8.11 Linux", + "manufacturer": "Plesk", + "name": "Plesk - Unlimited Domain w/ Power Pack for VPS" + }, + "subnet": { + "broadcastAddress": "192.168.23.79", + "cidr": 28, + "gateway": "192.168.23.65", + "id": 1973163, + "isCustomerOwned": False, + "isCustomerRoutable": False, + "netmask": "255.255.255.240", + "networkIdentifier": "128.116.23.64", + "networkVlanId": 123456, + "note": "test note", + "sortOrder": "1", + "subnetType": "ADDITIONAL_PRIMARY", + "totalIpAddresses": "16", + "usableIpAddressCount": "13", + "version": 4 + } +}] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index b7398076d..841f2e92d 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -303,3 +303,25 @@ def get_network_message_delivery_accounts(self): _mask = """vendor,type""" return self.client['SoftLayer_Account'].getNetworkMessageDeliveryAccounts(mask=_mask) + + def get_active_virtual_licenses(self): + """Gets all active virtual licenses account. + + :returns: active virtual licenses account + """ + + _mask = """billingItem[categoryCode,createDate,description], + key,id,ipAddress, + softwareDescription[longDescription,name,manufacturer], + subnet""" + + return self.client['SoftLayer_Account'].getActiveVirtualLicenses(mask=_mask) + + def get_active_account_licenses(self): + """Gets all active account licenses. + + :returns: Active account Licenses + """ + _mask = """billingItem,softwareDescription""" + + return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 9b88e3fae..fe9c11b0b 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -116,3 +116,9 @@ def test_acccount_order(self): result = self.run_command(['account', 'orders']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Billing_Order', 'getAllObjects') + + def test_acccount_licenses(self): + result = self.run_command(['account', 'licenses']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getActiveVirtualLicenses') + self.assert_called_with('SoftLayer_Account', 'getActiveAccountLicenses') diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index c5d2edf95..26b5dadff 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -153,3 +153,11 @@ def test_get_item_details_with_invoice_item_id(self): def test_get_routers(self): self.manager.get_routers() self.assert_called_with("SoftLayer_Account", "getRouters") + + def test_get_active_account_licenses(self): + self.manager.get_active_account_licenses() + self.assert_called_with("SoftLayer_Account", "getActiveAccountLicenses") + + def test_get_active_virtual_licenses(self): + self.manager.get_active_virtual_licenses() + self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") From d3e37906092e1351a0a629a3c95edcc29c9fd0eb Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 8 Jun 2021 11:01:08 -0400 Subject: [PATCH 0457/1385] add documentation --- docs/cli/account.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 27b4198d5..719c44fde 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -38,4 +38,8 @@ Account Commands .. click:: SoftLayer.CLI.account.orders:cli :prog: account orders - :show-nested: \ No newline at end of file + :show-nested: + +.. click:: SoftLayer.CLI.account.licenses:cli + :prog: account licenses + :show-nested: From 0b0e2ef76dc5329314bf888a5b7e8a8f978e487a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 9 Jun 2021 11:14:51 -0400 Subject: [PATCH 0458/1385] fix the last code review comments --- docs/api/managers/billing.rst | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/api/managers/billing.rst diff --git a/docs/api/managers/billing.rst b/docs/api/managers/billing.rst deleted file mode 100644 index f44a9333c..000000000 --- a/docs/api/managers/billing.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. _billing: - -.. automodule:: SoftLayer.managers.billing - :members: - :inherited-members: \ No newline at end of file From bee41a23aead3ab4a46e5ce7dc46a85ef4368dab Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 9 Jun 2021 14:24:21 -0400 Subject: [PATCH 0459/1385] fix the last code review comments --- SoftLayer/CLI/vlan/cancel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py index 79647a7eb..35f5aa0f9 100644 --- a/SoftLayer/CLI/vlan/cancel.py +++ b/SoftLayer/CLI/vlan/cancel.py @@ -13,7 +13,7 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Cancel network vlan.""" + """Cancel network VLAN.""" mgr = SoftLayer.NetworkManager(env.client) From b62006f80e5d13e3598f0aa9a8fcdb402bbd961b Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 9 Jun 2021 15:19:53 -0400 Subject: [PATCH 0460/1385] Add the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 76 ++++++++++++++++ SoftLayer/CLI/routes.py | 1 + ...rk_CdnMarketplace_Configuration_Mapping.py | 21 +++++ SoftLayer/managers/cdn.py | 91 ++++++++++++++++++- docs/cli/cdn.rst | 4 + tests/CLI/modules/cdn_tests.py | 28 ++++++ tests/managers/cdn_tests.py | 36 ++++++++ 7 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/cdn/edit.py diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py new file mode 100644 index 000000000..c6cb052cd --- /dev/null +++ b/SoftLayer/CLI/cdn/edit.py @@ -0,0 +1,76 @@ +"""Edit a CDN Account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('hostname') +@click.option('--header', '-H', + type=click.STRING, + help="Host header." + ) +@click.option('--http-port', '-t', + type=click.INT, + help="HTTP port." + ) +@click.option('--origin', '-o', + required=True, + type=click.STRING, + help="Origin server address." + ) +@click.option('--respect-headers', '-r', + type=click.Choice(['1', '0']), + help="Respect headers. The value 1 is On and 0 is Off." + ) +@click.option('--cache', '-c', multiple=True, type=str, + help="Cache key optimization. These are the valid options to choose: 'include-all', 'ignore-all', " + "'include-specified', 'ignore-specified'. If you select 'include-specified' or 'ignore-specified' " + "please add a description too using again --cache, " + "e.g --cache=include-specified --cache=description." + ) +@click.option('--performance-configuration', '-p', + type=click.Choice(['General web delivery', 'Large file optimization', 'Video on demand optimization']), + help="Optimize for, General web delivery', 'Large file optimization', 'Video on demand optimization', " + "the Dynamic content acceleration option is not added because this has a special configuration." + ) +@environment.pass_env +def cli(env, hostname, header, http_port, origin, respect_headers, cache, performance_configuration): + """Edit a CDN Account.""" + + manager = SoftLayer.CDNManager(env.client) + + cache_result = {} + if cache: + if len(cache) > 1: + cache_result['cacheKeyQueryRule'] = cache[0] + cache_result['description'] = cache[1] + else: + cache_result['cacheKeyQueryRule'] = cache[0] + + cdn_result = manager.edit(hostname, header=header, http_port=http_port, origin=origin, + respect_headers=respect_headers, cache=cache_result, + performance_configuration=performance_configuration) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + for cdn in cdn_result: + table.add_row(['Create Date', cdn.get('createDate')]) + table.add_row(['Header', cdn.get('header')]) + table.add_row(['Http Port', cdn.get('httpPort')]) + table.add_row(['Origin Type', cdn.get('originType')]) + table.add_row(['Performance Configuration', cdn.get('performanceConfiguration')]) + table.add_row(['Protocol', cdn.get('protocol')]) + table.add_row(['Respect Headers', cdn.get('respectHeaders')]) + table.add_row(['Unique Id', cdn.get('uniqueId')]) + table.add_row(['Vendor Name', cdn.get('vendorName')]) + table.add_row(['CacheKeyQueryRule', cdn.get('cacheKeyQueryRule')]) + table.add_row(['cname', cdn.get('cname')]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..05bbc997c 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -61,6 +61,7 @@ ('cdn', 'SoftLayer.CLI.cdn'), ('cdn:detail', 'SoftLayer.CLI.cdn.detail:cli'), + ('cdn:edit', 'SoftLayer.CLI.cdn.edit:cli'), ('cdn:list', 'SoftLayer.CLI.cdn.list:cli'), ('cdn:origin-add', 'SoftLayer.CLI.cdn.origin_add:cli'), ('cdn:origin-list', 'SoftLayer.CLI.cdn.origin_list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py index 51950b919..dc3ca1789 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py +++ b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py @@ -1,6 +1,8 @@ listDomainMappings = [ { + "cacheKeyQueryRule": "include-all", "cname": "cdnakauuiet7s6u6.cdnedge.bluemix.net", + "createDate": "2020-09-29T15:19:01-06:00", "domain": "test.example.com", "header": "test.example.com", "httpPort": 80, @@ -17,6 +19,7 @@ listDomainMappingByUniqueId = [ { "cname": "cdnakauuiet7s6u6.cdnedge.bluemix.net", + "performanceConfiguration": "Large file optimization", "domain": "test.example.com", "header": "test.example.com", "httpPort": 80, @@ -29,3 +32,21 @@ "vendorName": "akamai" } ] + +updateDomainMapping = [ + { + "createDate": "2021-02-09T19:32:29-06:00", + "originType": "HOST_SERVER", + "path": "/*", + "performanceConfiguration": "Large file optimization", + "protocol": "HTTP", + "respectHeaders": True, + "uniqueId": "424406419091111", + "vendorName": "akamai", + "header": "www.test.com", + "httpPort": 83, + "cname": "cdn.test.cloud", + "originHost": "1.1.1.1", + "cacheKeyQueryRule": "include: test" + } +] diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 2ead8c1fc..42acee390 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ - +import SoftLayer from SoftLayer import utils @@ -170,3 +170,92 @@ def start_data(self): def end_date(self): """Retrieve the cdn usage metric end date.""" return self._end_date + + def edit(self, hostname, header=None, http_port=None, origin=None, + respect_headers=None, cache=None, performance_configuration=None): + """Edit the cdn object. + + :param string hostname: The CDN hostname. + :param header: The cdn Host header. + :param http_port: The cdn HTTP port. + :param origin: The cdn Origin server address. + :param respect_headers: The cdn Respect headers. + :param cache: The cdn Cache key optimization. + :param performance_configuration: The cdn performance configuration. + + :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. + """ + cdn_instance_detail = self.get_cdn_instance_by_hostname(hostname) + if cdn_instance_detail is None: + raise SoftLayer.SoftLayerError('The CDN was not found with the hostname: %s' % hostname) + + unique_id = cdn_instance_detail.get('uniqueId') + + config = { + 'uniqueId': unique_id, + 'originType': cdn_instance_detail.get('originType'), + 'protocol': cdn_instance_detail.get('protocol'), + 'path': cdn_instance_detail.get('path'), + 'vendorName': cdn_instance_detail.get('vendorName'), + 'cname': cdn_instance_detail.get('cname'), + 'domain': cdn_instance_detail.get('domain'), + 'httpPort': cdn_instance_detail.get('httpPort') + } + + if header: + config['header'] = header + + if http_port: + config['httpPort'] = http_port + + if origin: + config['origin'] = origin + + if respect_headers: + config['respectHeaders'] = respect_headers + + if cache: + if 'include-specified' in cache['cacheKeyQueryRule']: + cache_key_rule = self.get_cache_key_query_rule('include', cache) + config['cacheKeyQueryRule'] = cache_key_rule + elif 'ignore-specified' in cache['cacheKeyQueryRule']: + cache_key_rule = self.get_cache_key_query_rule('ignore', cache) + config['cacheKeyQueryRule'] = cache_key_rule + else: + config['cacheKeyQueryRule'] = cache['cacheKeyQueryRule'] + + if performance_configuration: + config['performanceConfiguration'] = performance_configuration + + return self.cdn_configuration.updateDomainMapping(config) + + def get_cdn_instance_by_hostname(self, hostname): + """Get the cdn object detail. + + :param string hostname: The CDN identifier. + :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. + """ + result = None + cdn_list = self.cdn_configuration.listDomainMappings() + for cdn in cdn_list: + if cdn.get('domain') == hostname: + result = cdn + break + + return result + + @staticmethod + def get_cache_key_query_rule(cache_type, cache): + """Get the cdn object detail. + + :param string cache_type: Cache type. + :param cache: Cache description. + + :return: string value. + """ + if 'description' not in cache: + raise SoftLayer.SoftLayerError('Please add a description to be able to update the' + ' cache.') + cache_result = '%s: %s' % (cache_type, cache['description']) + + return cache_result diff --git a/docs/cli/cdn.rst b/docs/cli/cdn.rst index e334cd6f3..3b749995c 100644 --- a/docs/cli/cdn.rst +++ b/docs/cli/cdn.rst @@ -27,3 +27,7 @@ Interacting with CDN .. click:: SoftLayer.CLI.cdn.purge:cli :prog: cdn purge :show-nested: + +.. click:: SoftLayer.CLI.cdn.edit:cli + :prog: cdn edit + :show-nested: diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index cb3c59e43..2a1cf627a 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -95,3 +95,31 @@ def test_remove_origin(self): self.assert_no_fail(result) self.assertEqual(result.output, "Origin with path /example1 has been deleted\n") + + def test_edit_header(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--header=www.test.com']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('www.test.com', header_result['Header']) + + def test_edit_http_port(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--http-port=83']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual(83, header_result['Http Port']) + + def test_edit_respect_headers(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--respect-headers=1']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual(True, header_result['Respect Headers']) + + def test_edit_cache(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('include: test', header_result['CacheKeyQueryRule']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 32f9ea9e8..d7f76bbd9 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import fixtures from SoftLayer.managers import cdn from SoftLayer import testing @@ -102,3 +103,38 @@ def test_purge_content(self): self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge', 'createPurge', args=args) + + def test_cdn_edit(self): + hostname = 'test.example.com' + header = 'www.test.com' + origin = '1.1.1.1' + result = self.cdn_client.edit(hostname, header=header, origin=origin) + + self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. + updateDomainMapping, result) + + self.assert_called_with( + 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', + 'updateDomainMapping', + args=({ + 'uniqueId': '9934111111111', + 'originType': 'HOST_SERVER', + 'protocol': 'HTTP', + 'path': '/', + 'vendorName': 'akamai', + 'cname': 'cdnakauuiet7s6u6.cdnedge.bluemix.net', + 'domain': 'test.example.com', + 'httpPort': 80, + 'header': 'www.test.com', + 'origin': '1.1.1.1' + },) + ) + + def test_cdn_instance_by_hostname(self): + hostname = 'test.example.com' + result = self.cdn_client.get_cdn_instance_by_hostname(hostname) + expected_result = fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping.listDomainMappings + self.assertEqual(expected_result[0], result) + self.assert_called_with( + 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', + 'listDomainMappings',) From a48c7c5aed9a33d8663b959456a51d64e025aa4c Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:46:00 -0400 Subject: [PATCH 0461/1385] create a new commands on slcli that create/cancel a VMware licenses simulate to IBMCloud portal --- SoftLayer/CLI/licenses/__init__.py | 0 SoftLayer/CLI/licenses/cancel.py | 37 ++++++++++++++ SoftLayer/CLI/licenses/create.py | 33 ++++++++++++ SoftLayer/CLI/routes.py | 4 ++ SoftLayer/fixtures/SoftLayer_Product_Order.py | 32 ++++++++++++ .../fixtures/SoftLayer_Product_Package.py | 26 ++++++++++ .../SoftLayer_Software_AccountLicense.py | 51 +++++++++++++++++++ SoftLayer/managers/license.py | 40 +++++++++++++++ docs/cli/licenses.rst | 12 +++++ tests/CLI/modules/licenses_test.py | 34 +++++++++++++ 10 files changed, 269 insertions(+) create mode 100644 SoftLayer/CLI/licenses/__init__.py create mode 100644 SoftLayer/CLI/licenses/cancel.py create mode 100644 SoftLayer/CLI/licenses/create.py create mode 100644 SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py create mode 100644 SoftLayer/managers/license.py create mode 100644 docs/cli/licenses.rst create mode 100644 tests/CLI/modules/licenses_test.py diff --git a/SoftLayer/CLI/licenses/__init__.py b/SoftLayer/CLI/licenses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py new file mode 100644 index 000000000..2dc8e9d81 --- /dev/null +++ b/SoftLayer/CLI/licenses/cancel.py @@ -0,0 +1,37 @@ +"""Cancel VMware licenses.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer import utils + +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.managers.license import LicensesManager + + +@click.command() +@click.argument('key') +@click.option('--immediate', is_flag=True, help='Immediate cancellation') +@environment.pass_env +def cli(env, key, immediate): + """Cancel VMware license.""" + + if not immediate: + immediate = False + vmware_find = False + license = LicensesManager(env.client) + + vmware_licenses = license.get_all_objects() + + for vmware in vmware_licenses: + if vmware.get('key') == key: + vmware_find = True + license.cancel_item(utils.lookup(vmware, 'billingItem', 'id'), + immediate, + 'Cancel by cli command', + 'Cancel by cli command') + break + + if not vmware_find: + raise exceptions.CLIAbort( + "The VMware not found, try whit another key") diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py new file mode 100644 index 000000000..7c66cacc8 --- /dev/null +++ b/SoftLayer/CLI/licenses/create.py @@ -0,0 +1,33 @@ +"""Order/create a vwmare licenses.""" +# :licenses: MIT, see LICENSE for more details. + +import click +from SoftLayer.managers import ordering + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.option('--key', '-k', required=True, prompt=True, help="The License Key for this specific Account License.") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@environment.pass_env +def cli(env, key, datacenter): + """Order/create a vlan instance.""" + + complex_type = 'SoftLayer_Container_Product_Order_Software_License' + item_package = [key] + + ordering_manager = ordering.OrderingManager(env.client) + result = ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=False) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', result['orderId']]) + table.add_row(['created', result['orderDate']]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..b931bd3a6 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -217,6 +217,10 @@ ('nas:list', 'SoftLayer.CLI.nas.list:cli'), ('nas:credentials', 'SoftLayer.CLI.nas.credentials:cli'), + ('licenses', 'SoftLayer.CLI.licenses'), + ('licenses:create', 'SoftLayer.CLI.licenses.create:cli'), + ('licenses:cancel', 'SoftLayer.CLI.licenses.cancel:cli'), + ('object-storage', 'SoftLayer.CLI.object_storage'), ('object-storage:accounts', 'SoftLayer.CLI.object_storage.list_accounts:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index be702ccba..2202f2501 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -252,3 +252,35 @@ ] } } + +wmware_placeOrder = { + "orderDate": "2021-06-02 15:23:47", + "orderId": 123456, + "prices": [ + { + "id": 176535, + "itemId": 8109, + "categories": [ + { + "categoryCode": "software_license", + "id": 438, + "name": "Software License" + } + ], + "item": { + "capacity": "1", + "description": "VMware vSAN Advanced Tier III 64 - 124 TB 6.x", + "id": 8109, + "keyName": "VMWARE_VSAN_ADVANCE_TIER_III_64_124_6_X", + "softwareDescription": { + "id": 1795, + }, + "thirdPartyPolicyAssignments": [ + { + "id": 29263, + "policyName": "3rd Party Software Terms VMWare v4" + } + ] + } + } + ]} diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..2ec118f3a 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,29 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItems_vmware = [{ + "capacity": "2", + "description": "VMware vSAN Enterprise Tier III 65 - 124 TB 6.x", + "id": 9567, + "itemTaxCategoryId": 166, + "keyName": "VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2", + "softwareDescriptionId": 1979, + "units": "CPU", + "itemCategory": { + "categoryCode": "software_license", + "id": 438, + "name": "Software License", + "quantityLimit": 1, + }, + "prices": [ + { + "id": 245164, + "itemId": 9567, + "laborFee": "0", + "locationGroupId": None, + "oneTimeFee": "0", + "setupFee": "0", + "sort": 0, + } + ]}] diff --git a/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py new file mode 100644 index 000000000..b4433d104 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py @@ -0,0 +1,51 @@ +getAllObjects = [{ + "capacity": "4", + "key": "ABCDE-6CJ8L-J8R9H-000R0-CDR70", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "createDate": "2018-10-22T11:16:48-06:00", + "cycleStartDate": "2021-06-03T23:11:22-06:00", + "description": "vCenter Server Appliance 6.0", + "id": 123654789, + "lastBillDate": "2021-06-03T23:11:22-06:00", + "modifyDate": "2021-06-03T23:11:22-06:00", + "nextBillDate": "2021-07-03T23:00:00-06:00", + "orderItemId": 385054741, + "recurringMonths": 1, + "serviceProviderId": 1, + }, + "softwareDescription": { + "id": 1529, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "version": "6.0", + "requiredUser": "administrator@vsphere.local" + } + }, + { + "capacity": "1", + "key": "CBERT-4RL92-K8999-031K4-AJF5J", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "createDate": "2021-06-09T14:51:38-06:00", + "cycleStartDate": "2021-06-09T14:51:38-06:00", + "description": "VMware vSAN Advanced Tier III 64 - 124 TB 6.x", + "id": 369852174, + "nextBillDate": "2021-07-03T23:00:00-06:00", + "orderItemId": 836502628, + "recurringMonths": 1, + "serviceProviderId": 1, + }, + "softwareDescription": { + "id": 1795, + "longDescription": "VMware Virtual SAN Advanced Tier III 6.2", + "manufacturer": "VMware", + "name": "Virtual SAN Advanced Tier III", + "version": "6.2", + } + }] diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py new file mode 100644 index 000000000..84e5e538f --- /dev/null +++ b/SoftLayer/managers/license.py @@ -0,0 +1,40 @@ +""" + SoftLayer.license + ~~~~~~~~~~~~~~~ + License Manager + + :license: MIT, see LICENSE for more details. +""" + + +# pylint: disable=too-many-public-methods + + +class LicensesManager(object): + """Manages account lincese.""" + + def __init__(self, client): + self.client = client + + def get_all_objects(self): + """Show the all VM ware licenses of account. + + """ + _mask = '''softwareDescription,billingItem''' + + return self.client.call('SoftLayer_Software_AccountLicense', + 'getAllObjects', mask=_mask) + + def cancel_item(self, identifier, cancel_immediately, + reason_cancel, customer_note): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + cancel_immediately, + True, + reason_cancel, + customer_note, + id=identifier) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst new file mode 100644 index 000000000..55ca0fe10 --- /dev/null +++ b/docs/cli/licenses.rst @@ -0,0 +1,12 @@ +.. _cli_licenses: + +licenses Commands +================= + +.. click:: SoftLayer.CLI.licenses.create:cli + :prog: licenses create + :show-nested: + +.. click:: SoftLayer.CLI.licenses.cancel:cli + :prog: licenses cancel + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/licenses_test.py b/tests/CLI/modules/licenses_test.py new file mode 100644 index 000000000..b769e80bd --- /dev/null +++ b/tests/CLI/modules/licenses_test.py @@ -0,0 +1,34 @@ +""" + SoftLayer.tests.CLI.modules.licenses_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package + +from SoftLayer import testing + + +class LicensesTests(testing.TestCase): + + def test_create(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + _mock.return_value = SoftLayer_Product_Package.getItems_vmware + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.wmware_placeOrder + result = self.run_command(['licenses', + 'create', + '-k', 'VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2', + '-d dal03']) + self.assert_no_fail(result) + + def test_cancel(self): + result = self.run_command(['licenses', + 'cancel', + 'ABCDE-6CJ8L-J8R9H-000R0-CDR70', + '--immediate']) + self.assert_no_fail(result) From 3e41e5b6ca4e9ac3ed55e7412379ba693b98acc9 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:51:49 -0400 Subject: [PATCH 0462/1385] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 2dc8e9d81..c5b5f4bbd 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -1,5 +1,5 @@ -"""Cancel VMware licenses.""" -# :license: MIT, see LICENSE for more details. +"""Cancel a vwmare licenses.""" +# :licenses: MIT, see LICENSE for more details. import click from SoftLayer import utils From 15749114ea1b13dccb7ed8233aa1293fff001e7f Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:58:11 -0400 Subject: [PATCH 0463/1385] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 2 +- SoftLayer/CLI/licenses/create.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index c5b5f4bbd..931685bda 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -14,7 +14,7 @@ @click.option('--immediate', is_flag=True, help='Immediate cancellation') @environment.pass_env def cli(env, key, immediate): - """Cancel VMware license.""" + """Cancel VMware licenses.""" if not immediate: immediate = False diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py index 7c66cacc8..d3d779c96 100644 --- a/SoftLayer/CLI/licenses/create.py +++ b/SoftLayer/CLI/licenses/create.py @@ -13,7 +13,7 @@ @click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") @environment.pass_env def cli(env, key, datacenter): - """Order/create a vlan instance.""" + """Order/create a Vm licenses instance.""" complex_type = 'SoftLayer_Container_Product_Order_Software_License' item_package = [key] From 4a15aad12ca62f7392cd4f95617dbf67e31085e2 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 15:04:04 -0400 Subject: [PATCH 0464/1385] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 931685bda..03d6bea1b 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -18,20 +18,20 @@ def cli(env, key, immediate): if not immediate: immediate = False - vmware_find = False - license = LicensesManager(env.client) + vm_ware_find = False + licenses = LicensesManager(env.client) - vmware_licenses = license.get_all_objects() + vm_ware_licenses = licenses.get_all_objects() - for vmware in vmware_licenses: - if vmware.get('key') == key: - vmware_find = True - license.cancel_item(utils.lookup(vmware, 'billingItem', 'id'), + for vm_ware in vm_ware_licenses: + if vm_ware.get('key') == key: + vm_ware_find = True + licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), immediate, 'Cancel by cli command', 'Cancel by cli command') break - if not vmware_find: + if not vm_ware_find: raise exceptions.CLIAbort( "The VMware not found, try whit another key") From 1417c26c06ba72ab4d88f86f791171aaa7784a2a Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 15:06:17 -0400 Subject: [PATCH 0465/1385] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 03d6bea1b..8b7007319 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -27,9 +27,9 @@ def cli(env, key, immediate): if vm_ware.get('key') == key: vm_ware_find = True licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), - immediate, - 'Cancel by cli command', - 'Cancel by cli command') + immediate, + 'Cancel by cli command', + 'Cancel by cli command') break if not vm_ware_find: From 5fbd5e4a90449a83d1d5fd564eddca1251170131 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 15 Jun 2021 17:59:49 -0400 Subject: [PATCH 0466/1385] new method to fix the table disconfigurate when the text data is very long --- SoftLayer/utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 6b18b6570..b65d3634a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -406,3 +406,24 @@ def trim_to(string, length=80, tail="..."): return string[:length] + tail else: return string + + +def format_comment(comment, max_line_length): + """Return a string that is length long, added a next line and keep the table format. + + :param string string: String you want to add next line + :param int length: max length for the string + """ + comment_length = 0 + words = comment.split(" ") + formatted_comment = "" + for word in words: + if comment_length + (len(word) + 1) <= max_line_length: + formatted_comment = formatted_comment + word + " " + + comment_length = comment_length + len(word) + 1 + else: + formatted_comment = formatted_comment + "\n" + word + " " + + comment_length = len(word) + 1 + return formatted_comment From 402308b6d2bc7d78dcadee5943a2a0d0935c8d1d Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 09:03:33 -0400 Subject: [PATCH 0467/1385] fix tox tool --- tests/CLI/modules/vlan_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 22c579ac7..ce46692d4 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -111,7 +111,7 @@ def test_create_vlan(self): order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder result = self.run_command(['vlan', 'create', - '-H test', + '--name','test', '-d TEST00', '--network', 'public', '--billing', 'hourly' From ec5efc11a403ad49459b1edd14b0f604d4d035de Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 09:09:48 -0400 Subject: [PATCH 0468/1385] fix tox tool --- tests/CLI/modules/vlan_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index ce46692d4..c1417a018 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -111,7 +111,7 @@ def test_create_vlan(self): order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder result = self.run_command(['vlan', 'create', - '--name','test', + '--name', 'test', '-d TEST00', '--network', 'public', '--billing', 'hourly' From e6feb690fda1a85933e74695c78ea515e9c654f8 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 12:13:21 -0400 Subject: [PATCH 0469/1385] fix the team code review comments --- SoftLayer/CLI/account/licenses.py | 4 ++-- SoftLayer/fixtures/SoftLayer_Account.py | 6 +++--- SoftLayer/managers/account.py | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py index cdf6e177f..ab607f4de 100644 --- a/SoftLayer/CLI/account/licenses.py +++ b/SoftLayer/CLI/account/licenses.py @@ -1,4 +1,4 @@ -"""Show the all account licenses.""" +"""Show all licenses.""" # :license: MIT, see LICENSE for more details. import click from SoftLayer import utils @@ -11,7 +11,7 @@ @click.command() @environment.pass_env def cli(env): - """return the control panel and VMWare licenses""" + """Show all licenses.""" manager = AccountManager(env.client) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 11d1b26d0..7ca2d7e67 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1104,7 +1104,7 @@ getActiveAccountLicenses = [{ "accountId": 123456, "capacity": "4", - "key": "M02A5-6CJ8L-J8R9H-000R0-CDR70", + "key": "Y8GNS-7QRNG-OUIJO-MATEI-5GJRM", "units": "CPU", "billingItem": { "allowCancellationFlag": 1, @@ -1141,7 +1141,7 @@ { "accountId": 123456, "capacity": "4", - "key": "4122M-ABXC05-K829T-098HP-00QJM", + "key": "TSZES-SJF85-04GLD-AXA64-8O1EO", "units": "CPU", "billingItem": { "allowCancellationFlag": 1, @@ -1179,7 +1179,7 @@ getActiveVirtualLicenses = [{ "id": 12345, "ipAddress": "192.168.23.78", - "key": "PLSK.06866259.0000", + "key": "TEST.60220734.0000", "billingItem": { "categoryCode": "control_panel", "description": "Plesk Onyx (Linux) - (Unlimited) - VPS " diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 841f2e92d..d9814b1ce 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -307,8 +307,8 @@ def get_network_message_delivery_accounts(self): def get_active_virtual_licenses(self): """Gets all active virtual licenses account. - :returns: active virtual licenses account - """ + :returns: active virtual licenses account + """ _mask = """billingItem[categoryCode,createDate,description], key,id,ipAddress, @@ -320,8 +320,9 @@ def get_active_virtual_licenses(self): def get_active_account_licenses(self): """Gets all active account licenses. - :returns: Active account Licenses - """ + :returns: Active account Licenses + """ + _mask = """billingItem,softwareDescription""" return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) From cba97f7e73ee84039de261c7f9fa1e3d4e4ad53f Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 2 Jun 2021 16:33:44 -0400 Subject: [PATCH 0470/1385] #1480 add gateway/firewall name to vlan detail and list command --- SoftLayer/CLI/vlan/detail.py | 37 +++++++++++++++++++++------------ SoftLayer/CLI/vlan/list.py | 15 ++++++------- SoftLayer/managers/network.py | 14 +++++++++++++ tests/CLI/modules/vlan_tests.py | 36 ++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/vlan/detail.py b/SoftLayer/CLI/vlan/detail.py index 6acfb50cb..323699139 100644 --- a/SoftLayer/CLI/vlan/detail.py +++ b/SoftLayer/CLI/vlan/detail.py @@ -7,6 +7,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer import utils @click.command() @@ -30,26 +31,24 @@ def cli(env, identifier, no_vs, no_hardware): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', vlan['id']]) - table.add_row(['number', vlan['vlanNumber']]) + table.add_row(['id', vlan.get('id')]) + table.add_row(['number', vlan.get('vlanNumber')]) table.add_row(['datacenter', - vlan['primaryRouter']['datacenter']['longName']]) + utils.lookup(vlan, 'primaryRouter', 'datacenter', 'longName')]) table.add_row(['primary_router', - vlan['primaryRouter']['fullyQualifiedDomainName']]) - table.add_row(['firewall', - 'Yes' if vlan['firewallInterfaces'] else 'No']) + utils.lookup(vlan, 'primaryRouter', 'fullyQualifiedDomainName')]) + table.add_row(['Gateway/Firewall', get_gateway_firewall(vlan)]) subnets = [] for subnet in vlan.get('subnets', []): subnet_table = formatting.KeyValueTable(['name', 'value']) subnet_table.align['name'] = 'r' subnet_table.align['value'] = 'l' - subnet_table.add_row(['id', subnet['id']]) - subnet_table.add_row(['identifier', subnet['networkIdentifier']]) - subnet_table.add_row(['netmask', subnet['netmask']]) - subnet_table.add_row(['gateway', subnet.get('gateway', '-')]) - subnet_table.add_row(['type', subnet['subnetType']]) - subnet_table.add_row(['usable ips', - subnet['usableIpAddressCount']]) + subnet_table.add_row(['id', subnet.get('id')]) + subnet_table.add_row(['identifier', subnet.get('networkIdentifier')]) + subnet_table.add_row(['netmask', subnet.get('netmask')]) + subnet_table.add_row(['gateway', subnet.get('gateway', formatting.blank())]) + subnet_table.add_row(['type', subnet.get('subnetType')]) + subnet_table.add_row(['usable ips', subnet.get('usableIpAddressCount')]) subnets.append(subnet_table) table.add_row(['subnets', subnets]) @@ -81,3 +80,15 @@ def cli(env, identifier, no_vs, no_hardware): table.add_row(['hardware', 'none']) env.fout(table) + + +def get_gateway_firewall(vlan): + """Gets the name of a gateway/firewall from a VLAN. """ + + firewall = utils.lookup(vlan, 'networkVlanFirewall', 'fullyQualifiedDomainName') + if firewall: + return firewall + gateway = utils.lookup(vlan, 'attachedNetworkGateway', 'name') + if gateway: + return gateway + return formatting.blank() diff --git a/SoftLayer/CLI/vlan/list.py b/SoftLayer/CLI/vlan/list.py index 44500532a..acb4460e5 100644 --- a/SoftLayer/CLI/vlan/list.py +++ b/SoftLayer/CLI/vlan/list.py @@ -6,12 +6,13 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI.vlan.detail import get_gateway_firewall from SoftLayer import utils COLUMNS = ['id', 'number', 'name', - 'firewall', + 'Gateway/Firewall', 'datacenter', 'hardware', 'virtual_servers', @@ -45,14 +46,14 @@ def cli(env, sortby, datacenter, number, name, limit): limit=limit) for vlan in vlans: table.add_row([ - vlan['id'], - vlan['vlanNumber'], + vlan.get('id'), + vlan.get('vlanNumber'), vlan.get('name') or formatting.blank(), - 'Yes' if vlan['firewallInterfaces'] else 'No', + get_gateway_firewall(vlan), utils.lookup(vlan, 'primaryRouter', 'datacenter', 'name'), - vlan['hardwareCount'], - vlan['virtualGuestCount'], - vlan['totalPrimaryIpAddressCount'], + vlan.get('hardwareCount'), + vlan.get('virtualGuestCount'), + vlan.get('totalPrimaryIpAddressCount'), ]) env.fout(table) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 7c86dda58..840d4f3b9 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -43,6 +43,8 @@ 'totalPrimaryIpAddressCount', 'virtualGuestCount', 'networkSpace', + 'networkVlanFirewall[id,fullyQualifiedDomainName,primaryIpAddress]', + 'attachedNetworkGateway[id,name,networkFirewall]', ]) DEFAULT_GET_VLAN_MASK = ','.join([ 'firewallInterfaces', @@ -53,6 +55,8 @@ 'hardware', 'subnets', 'virtualGuests', + 'networkVlanFirewall[id,fullyQualifiedDomainName,primaryIpAddress]', + 'attachedNetworkGateway[id,name,networkFirewall]', ]) @@ -433,6 +437,16 @@ def get_vlan(self, vlan_id): """ return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) + def get_network_gateway_firewall(self, vlan_id): + """Returns information about a single VLAN. + + :param int id: The unique identifier for the VLAN + :returns: A dictionary containing a large amount of information about + the specified VLAN. + + """ + return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) + def list_global_ips(self, version=None, identifier=None, **kwargs): """Returns a list of all global IP address records on the account. diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index af2b22290..0ffb80165 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -95,6 +95,42 @@ def test_vlan_edit_failure(self, click): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) + def test_vlan_detail_firewall(self): + vlan_mock = self.set_mock('SoftLayer_Network_Vlan', 'getObject') + get_object = { + 'primaryRouter': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + 'id': 1234, + 'vlanNumber': 4444, + 'networkVlanFirewall': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + } + vlan_mock.return_value = get_object + result = self.run_command(['vlan', 'detail', '1234']) + self.assert_no_fail(result) + + def test_vlan_detail_gateway(self): + vlan_mock = self.set_mock('SoftLayer_Network_Vlan', 'getObject') + get_object = { + 'primaryRouter': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + 'id': 1234, + 'vlanNumber': 4444, + 'attachedNetworkGateway': { + 'id': 54321, + "name": 'support' + }, + } + vlan_mock.return_value = get_object + result = self.run_command(['vlan', 'detail', '1234']) + self.assert_no_fail(result) + def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) From 6372aa2b3dd4792c4f201610cbca70c7c8ae947d Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 17 Jun 2021 20:22:51 -0400 Subject: [PATCH 0471/1385] #1480 remove duplicated method added on network manager --- SoftLayer/managers/network.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 840d4f3b9..21a4516ac 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -437,16 +437,6 @@ def get_vlan(self, vlan_id): """ return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) - def get_network_gateway_firewall(self, vlan_id): - """Returns information about a single VLAN. - - :param int id: The unique identifier for the VLAN - :returns: A dictionary containing a large amount of information about - the specified VLAN. - - """ - return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) - def list_global_ips(self, version=None, identifier=None, **kwargs): """Returns a list of all global IP address records on the account. From 13ae244d527187527f34a5c2df81eef1de71520d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 18 Jun 2021 15:14:51 -0400 Subject: [PATCH 0472/1385] new feature licenses create-options --- SoftLayer/CLI/licenses/__init__.py | 0 SoftLayer/CLI/licenses/create_options.py | 28 +++++++++++++++++++ SoftLayer/CLI/routes.py | 3 ++ .../fixtures/SoftLayer_Product_Package.py | 26 +++++++++++++++++ SoftLayer/managers/licenses.py | 25 +++++++++++++++++ docs/cli/licenses.rst | 8 ++++++ tests/CLI/modules/licenses_test.py | 13 +++++++++ 7 files changed, 103 insertions(+) create mode 100644 SoftLayer/CLI/licenses/__init__.py create mode 100644 SoftLayer/CLI/licenses/create_options.py create mode 100644 SoftLayer/managers/licenses.py create mode 100644 docs/cli/licenses.rst create mode 100644 tests/CLI/modules/licenses_test.py diff --git a/SoftLayer/CLI/licenses/__init__.py b/SoftLayer/CLI/licenses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/licenses/create_options.py b/SoftLayer/CLI/licenses/create_options.py new file mode 100644 index 000000000..d4f027f3d --- /dev/null +++ b/SoftLayer/CLI/licenses/create_options.py @@ -0,0 +1,28 @@ +"""Licenses order options for a given VMware licenses.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import licenses +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """Server order options for a given chassis.""" + + licenses_manager = licenses.LicensesManager(env.client) + + options = licenses_manager.get_create_options() + + table = formatting.Table(['Id', 'description', 'keyName', 'recurringFee']) + for item in options: + table.add_row([item.get('id'), + utils.trim_to(item.get('description'), 40), + item.get('keyName'), + item.get('prices')[0]['recurringFee']]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d6266f95b..9f9e7df6f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -124,6 +124,9 @@ ('email:detail', 'SoftLayer.CLI.email.detail:cli'), ('email:edit', 'SoftLayer.CLI.email.edit:cli'), + ('licenses', 'SoftLayer.CLI.licenses'), + ('licenses:create-options', 'SoftLayer.CLI.licenses.create_options:cli'), + ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), ('event-log:types', 'SoftLayer.CLI.event_log.types:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..273ec9970 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,29 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItems_vmware = [{ + "capacity": "2", + "description": "VMware vSAN Enterprise Tier III 65 - 124 TB 6.x", + "id": 9567, + "itemTaxCategoryId": 166, + "keyName": "VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2", + "softwareDescriptionId": 1979, + "units": "CPU", + "itemCategory": { + "categoryCode": "software_license", + "id": 438, + "name": "Software License", + "quantityLimit": 1, + }, + "prices": [ + { + "id": 245164, + "itemId": 9567, + "laborFee": "0", + "locationGroupId": None, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + } + ]}] diff --git a/SoftLayer/managers/licenses.py b/SoftLayer/managers/licenses.py new file mode 100644 index 000000000..3d6525382 --- /dev/null +++ b/SoftLayer/managers/licenses.py @@ -0,0 +1,25 @@ +""" + SoftLayer.license + ~~~~~~~~~~~~~~~ + License Manager + :license: MIT, see LICENSE for more details. +""" + + +# pylint: disable=too-many-public-methods + + +class LicensesManager(object): + """Manages account lincese.""" + + def __init__(self, client): + self.client = client + + def get_create_options(self): + """Returns valid options for ordering Licenses. + + :param string datacenter: short name, like dal09 + """ + + return self.client.call('SoftLayer_Product_Package', 'getItems', + id=301) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst new file mode 100644 index 000000000..8a0326d0e --- /dev/null +++ b/docs/cli/licenses.rst @@ -0,0 +1,8 @@ +.. _cli_licenses: + +licenses Commands +================= + +.. click:: SoftLayer.CLI.licenses.create-options:cli + :prog: licenses create-options + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/licenses_test.py b/tests/CLI/modules/licenses_test.py new file mode 100644 index 000000000..b50b4edff --- /dev/null +++ b/tests/CLI/modules/licenses_test.py @@ -0,0 +1,13 @@ +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer import testing + + +class LicensesTests(testing.TestCase): + + def test_create(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + _mock.return_value = SoftLayer_Product_Package.getItems_vmware + + result = self.run_command(['licenses', 'create-options']) + self.assert_no_fail(result) From 9341bac077d365a0d5ff6a4f9fb1f61724fa165d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 18 Jun 2021 15:28:52 -0400 Subject: [PATCH 0473/1385] fix the tox tool --- docs/cli/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst index 8a0326d0e..b6d6fe66e 100644 --- a/docs/cli/licenses.rst +++ b/docs/cli/licenses.rst @@ -3,6 +3,6 @@ licenses Commands ================= -.. click:: SoftLayer.CLI.licenses.create-options:cli +.. click:: SoftLayer.CLI.licenses.create_options:cli :prog: licenses create-options :show-nested: \ No newline at end of file From d50b30cbbd9226f3b3c4bb093833b6ff961e53b0 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Jun 2021 11:02:44 -0400 Subject: [PATCH 0474/1385] Fix the code review comments --- SoftLayer/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index b65d3634a..5bac80696 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -408,11 +408,11 @@ def trim_to(string, length=80, tail="..."): return string -def format_comment(comment, max_line_length): +def format_comment(comment, max_line_length=60): """Return a string that is length long, added a next line and keep the table format. - :param string string: String you want to add next line - :param int length: max length for the string + :param string comment: String you want to add next line + :param int max_line_length: max length for the string """ comment_length = 0 words = comment.split(" ") From c8033c5ab7fec80bffc2111a6898a7f71d1039d4 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 22 Jun 2021 15:31:31 -0400 Subject: [PATCH 0475/1385] Refactor and fix the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 15 ++++++++++----- SoftLayer/managers/cdn.py | 24 ++++++++++++------------ tests/CLI/modules/cdn_tests.py | 9 ++++++++- tests/managers/cdn_tests.py | 8 ++++---- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py index c6cb052cd..677bcd4d7 100644 --- a/SoftLayer/CLI/cdn/edit.py +++ b/SoftLayer/CLI/cdn/edit.py @@ -6,10 +6,11 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() -@click.argument('hostname') +@click.argument('identifier') @click.option('--header', '-H', type=click.STRING, help="Host header." @@ -39,10 +40,14 @@ "the Dynamic content acceleration option is not added because this has a special configuration." ) @environment.pass_env -def cli(env, hostname, header, http_port, origin, respect_headers, cache, performance_configuration): - """Edit a CDN Account.""" +def cli(env, identifier, header, http_port, origin, respect_headers, cache, performance_configuration): + """Edit a CDN Account. + + You can use the hostname or uniqueId as IDENTIFIER. + """ manager = SoftLayer.CDNManager(env.client) + cdn_id = helpers.resolve_id(manager.resolve_ids, identifier, 'CDN') cache_result = {} if cache: @@ -52,7 +57,7 @@ def cli(env, hostname, header, http_port, origin, respect_headers, cache, perfor else: cache_result['cacheKeyQueryRule'] = cache[0] - cdn_result = manager.edit(hostname, header=header, http_port=http_port, origin=origin, + cdn_result = manager.edit(cdn_id, header=header, http_port=http_port, origin=origin, respect_headers=respect_headers, cache=cache_result, performance_configuration=performance_configuration) @@ -70,7 +75,7 @@ def cli(env, hostname, header, http_port, origin, respect_headers, cache, perfor table.add_row(['Respect Headers', cdn.get('respectHeaders')]) table.add_row(['Unique Id', cdn.get('uniqueId')]) table.add_row(['Vendor Name', cdn.get('vendorName')]) - table.add_row(['CacheKeyQueryRule', cdn.get('cacheKeyQueryRule')]) + table.add_row(['Cache key optimization', cdn.get('cacheKeyQueryRule')]) table.add_row(['cname', cdn.get('cname')]) env.fout(table) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 42acee390..7189cbaa4 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -9,6 +9,9 @@ from SoftLayer import utils +# pylint: disable=no-self-use,too-many-lines,too-many-instance-attributes + + class CDNManager(utils.IdentifierMixin, object): """Manage Content Delivery Networks in the account. @@ -27,6 +30,7 @@ def __init__(self, client): self.cdn_path = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path'] self.cdn_metrics = self.client['Network_CdnMarketplace_Metrics'] self.cdn_purge = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge'] + self.resolvers = [self._get_ids_from_hostname] def list_cdn(self, **kwargs): """Lists Content Delivery Networks for the active user. @@ -171,11 +175,11 @@ def end_date(self): """Retrieve the cdn usage metric end date.""" return self._end_date - def edit(self, hostname, header=None, http_port=None, origin=None, + def edit(self, identifier, header=None, http_port=None, origin=None, respect_headers=None, cache=None, performance_configuration=None): """Edit the cdn object. - :param string hostname: The CDN hostname. + :param string identifier: The CDN identifier. :param header: The cdn Host header. :param http_port: The cdn HTTP port. :param origin: The cdn Origin server address. @@ -185,14 +189,10 @@ def edit(self, hostname, header=None, http_port=None, origin=None, :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. """ - cdn_instance_detail = self.get_cdn_instance_by_hostname(hostname) - if cdn_instance_detail is None: - raise SoftLayer.SoftLayerError('The CDN was not found with the hostname: %s' % hostname) - - unique_id = cdn_instance_detail.get('uniqueId') + cdn_instance_detail = self.get_cdn(str(identifier)) config = { - 'uniqueId': unique_id, + 'uniqueId': cdn_instance_detail.get('uniqueId'), 'originType': cdn_instance_detail.get('originType'), 'protocol': cdn_instance_detail.get('protocol'), 'path': cdn_instance_detail.get('path'), @@ -229,17 +229,17 @@ def edit(self, hostname, header=None, http_port=None, origin=None, return self.cdn_configuration.updateDomainMapping(config) - def get_cdn_instance_by_hostname(self, hostname): + def _get_ids_from_hostname(self, hostname): """Get the cdn object detail. :param string hostname: The CDN identifier. :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. """ - result = None + result = [] cdn_list = self.cdn_configuration.listDomainMappings() for cdn in cdn_list: - if cdn.get('domain') == hostname: - result = cdn + if cdn.get('domain', '').lower() == hostname.lower(): + result.append(cdn.get('uniqueId')) break return result diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index 2a1cf627a..5bff7e647 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -122,4 +122,11 @@ def test_edit_cache(self): '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) - self.assertEqual('include: test', header_result['CacheKeyQueryRule']) + self.assertEqual('include: test', header_result['Cache key optimization']) + + def test_edit_cache_by_uniqueId(self): + result = self.run_command(['cdn', 'edit', '9934111111111', + '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('include: test', header_result['Cache key optimization']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index d7f76bbd9..a57fe0b68 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -105,10 +105,10 @@ def test_purge_content(self): args=args) def test_cdn_edit(self): - hostname = 'test.example.com' + identifier = '9934111111111' header = 'www.test.com' origin = '1.1.1.1' - result = self.cdn_client.edit(hostname, header=header, origin=origin) + result = self.cdn_client.edit(identifier, header=header, origin=origin) self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. updateDomainMapping, result) @@ -132,9 +132,9 @@ def test_cdn_edit(self): def test_cdn_instance_by_hostname(self): hostname = 'test.example.com' - result = self.cdn_client.get_cdn_instance_by_hostname(hostname) + result = self.cdn_client._get_ids_from_hostname(hostname) expected_result = fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping.listDomainMappings - self.assertEqual(expected_result[0], result) + self.assertEqual(expected_result[0]['uniqueId'], result[0]) self.assert_called_with( 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', 'listDomainMappings',) From 58736a0eb74d40ea06e768d3c4fdd747f97e3282 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Jun 2021 10:11:49 -0400 Subject: [PATCH 0476/1385] add the pod option --- SoftLayer/CLI/vlan/create.py | 9 +++++++-- tests/CLI/modules/vlan_tests.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 1af33aec5..ad45735e7 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -10,17 +10,22 @@ @click.command() @click.option('--name', required=False, prompt=True, help="Vlan name") -@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--datacenter', '-d', required=False, help="Datacenter shortname") +@click.option('--pod', '-p', required=False, help="Pod name. E.g dal05.pod01") @click.option('--network', default='public', show_default=True, type=click.Choice(['public', 'private']), help='Network vlan type') @click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), help="Billing rate") @environment.pass_env -def cli(env, name, datacenter, network, billing): +def cli(env, name, datacenter, pod, network, billing): """Order/create a VLAN instance.""" item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' + + if pod and not datacenter: + datacenter = pod.split('.')[0] + if not network: item_package = ['PRIVATE_NETWORK_VLAN'] diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index c1417a018..86884dff6 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -121,6 +121,24 @@ def test_create_vlan(self): self.assertEqual(json.loads(result.output), {'id': 123456, 'created': '2021-06-02 15:23:47'}) + def test_create_vlan_pod(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + _mock.return_value = SoftLayer_Product_Package.getItemsVLAN + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder + + result = self.run_command(['vlan', 'create', + '--name', 'test', + '-p TEST00.pod2', + '--network', 'public', + '--billing', 'hourly' + ]) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + {'id': 123456, 'created': '2021-06-02 15:23:47'}) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): confirm_mock.return_value = True From 14f19da1be6fcdb8a3b1624833e8f29f7e1136e1 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Jun 2021 15:15:33 -0400 Subject: [PATCH 0477/1385] fix the team code review comments --- SoftLayer/CLI/account/licenses.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py index ab607f4de..42ddd9a8d 100644 --- a/SoftLayer/CLI/account/licenses.py +++ b/SoftLayer/CLI/account/licenses.py @@ -1,11 +1,12 @@ """Show all licenses.""" # :license: MIT, see LICENSE for more details. import click + from SoftLayer import utils from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.managers.account import AccountManager +from SoftLayer.managers import account @click.command() @@ -13,17 +14,17 @@ def cli(env): """Show all licenses.""" - manager = AccountManager(env.client) + manager = account.AccountManager(env.client) - panel_control = manager.get_active_virtual_licenses() + control_panel = manager.get_active_virtual_licenses() vmwares = manager.get_active_account_licenses() table_panel = formatting.KeyValueTable(['id', 'ip_address', 'manufacturer', 'software', - 'key', 'subnet', 'subnet notes']) + 'key', 'subnet', 'subnet notes'], title="Control Panel Licenses") table_vmware = formatting.KeyValueTable(['name', 'license_key', 'cpus', 'description', - 'manufacturer', 'requiredUser']) - for panel in panel_control: + 'manufacturer', 'requiredUser'], title="VMware Licenses") + for panel in control_panel: table_panel.add_row([panel.get('id'), panel.get('ipAddress'), utils.lookup(panel, 'softwareDescription', 'manufacturer'), utils.trim_to(utils.lookup(panel, 'softwareDescription', 'longDescription'), 40), From bcd52a696c4a040bf03ace6aefbe03da36b6b920 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Jun 2021 17:44:41 -0400 Subject: [PATCH 0478/1385] fix the team code review comments --- SoftLayer/CLI/licenses/cancel.py | 28 ++++----------------- SoftLayer/CLI/licenses/create.py | 21 ++++++++-------- SoftLayer/managers/__init__.py | 2 ++ SoftLayer/managers/license.py | 43 ++++++++++++++++++++++++-------- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 8b7007319..783ce2857 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -1,12 +1,10 @@ -"""Cancel a vwmare licenses.""" +"""Cancel a license.""" # :licenses: MIT, see LICENSE for more details. import click -from SoftLayer import utils +import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.managers.license import LicensesManager @click.command() @@ -14,24 +12,8 @@ @click.option('--immediate', is_flag=True, help='Immediate cancellation') @environment.pass_env def cli(env, key, immediate): - """Cancel VMware licenses.""" + """Cancel a license.""" - if not immediate: - immediate = False - vm_ware_find = False - licenses = LicensesManager(env.client) + licenses = SoftLayer.LicensesManager(env.client) - vm_ware_licenses = licenses.get_all_objects() - - for vm_ware in vm_ware_licenses: - if vm_ware.get('key') == key: - vm_ware_find = True - licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), - immediate, - 'Cancel by cli command', - 'Cancel by cli command') - break - - if not vm_ware_find: - raise exceptions.CLIAbort( - "The VMware not found, try whit another key") + env.fout(licenses.cancel_item(key, immediate)) diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py index d3d779c96..83c7c5a0d 100644 --- a/SoftLayer/CLI/licenses/create.py +++ b/SoftLayer/CLI/licenses/create.py @@ -2,28 +2,29 @@ # :licenses: MIT, see LICENSE for more details. import click -from SoftLayer.managers import ordering + +import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting @click.command() -@click.option('--key', '-k', required=True, prompt=True, help="The License Key for this specific Account License.") +@click.option('--key', '-k', required=True, prompt=True, + help="The VMware License Key. " + "To get could use the product_package::getItems id=301 with name Software License Package" + "E.g VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2") @click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") @environment.pass_env def cli(env, key, datacenter): - """Order/create a Vm licenses instance.""" + """Order/Create License.""" - complex_type = 'SoftLayer_Container_Product_Order_Software_License' item_package = [key] - ordering_manager = ordering.OrderingManager(env.client) - result = ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', - location=datacenter, - item_keynames=item_package, - complex_type=complex_type, - hourly=False) + licenses = SoftLayer.LicensesManager(env.client) + + result = licenses.create(datacenter, item_package) + table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 8053ec70e..1b2ddf6f9 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -17,6 +17,7 @@ from SoftLayer.managers.hardware import HardwareManager from SoftLayer.managers.image import ImageManager from SoftLayer.managers.ipsec import IPSECManager +from SoftLayer.managers.license import LicensesManager from SoftLayer.managers.load_balancer import LoadBalancerManager from SoftLayer.managers.metadata import MetadataManager from SoftLayer.managers.network import NetworkManager @@ -43,6 +44,7 @@ 'HardwareManager', 'ImageManager', 'IPSECManager', + 'LicensesManager', 'LoadBalancerManager', 'MetadataManager', 'NetworkManager', diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py index 84e5e538f..c529e7e67 100644 --- a/SoftLayer/managers/license.py +++ b/SoftLayer/managers/license.py @@ -6,18 +6,20 @@ :license: MIT, see LICENSE for more details. """ - # pylint: disable=too-many-public-methods +from SoftLayer.CLI import exceptions +from SoftLayer.managers import ordering +from SoftLayer import utils class LicensesManager(object): - """Manages account lincese.""" + """Manages account license.""" def __init__(self, client): self.client = client def get_all_objects(self): - """Show the all VM ware licenses of account. + """Show the all VMware licenses of an account. """ _mask = '''softwareDescription,billingItem''' @@ -25,16 +27,35 @@ def get_all_objects(self): return self.client.call('SoftLayer_Software_AccountLicense', 'getAllObjects', mask=_mask) - def cancel_item(self, identifier, cancel_immediately, - reason_cancel, customer_note): + def cancel_item(self, key, cancel_immediately=False): """Cancel a billing item immediately, deleting all its data. :param integer identifier: the instance ID to cancel :param string reason_cancel: reason cancel """ - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', - cancel_immediately, - True, - reason_cancel, - customer_note, - id=identifier) + vm_ware_licenses = self.get_all_objects() + vm_ware_find = False + for vm_ware in vm_ware_licenses: + if vm_ware.get('key') == key: + vm_ware_find = True + self.client.call('SoftLayer_Billing_Item', 'cancelItem', + cancel_immediately, + True, + 'Cancel by cli command', + 'Cancel by cli command', + id=utils.lookup(vm_ware, 'billingItem', 'id')) + + if not vm_ware_find: + raise exceptions.CLIAbort( + "Unable to find license key: {}".format(key)) + return vm_ware_find + + def create(self, datacenter, item_package): + + complex_type = 'SoftLayer_Container_Product_Order_Software_License' + ordering_manager = ordering.OrderingManager(self.client) + return ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=False) From 22f58ce2bb7271dfe3d21bbecdb2050b08e7de30 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Jun 2021 18:10:05 -0400 Subject: [PATCH 0479/1385] fix the team code review comments --- SoftLayer/managers/license.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py index c529e7e67..00b854c16 100644 --- a/SoftLayer/managers/license.py +++ b/SoftLayer/managers/license.py @@ -51,7 +51,11 @@ def cancel_item(self, key, cancel_immediately=False): return vm_ware_find def create(self, datacenter, item_package): + """Create a license + :param string datacenter: the datacenter shortname + :param string[] item_package: items array + """ complex_type = 'SoftLayer_Container_Product_Order_Software_License' ordering_manager = ordering.OrderingManager(self.client) return ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', From f3d271668ee6288570ac8fb933798af2884e6ae7 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 25 Jun 2021 18:16:19 -0400 Subject: [PATCH 0480/1385] fix the team code review comments --- SoftLayer/CLI/vlan/create.py | 17 +++++++++++++---- SoftLayer/fixtures/SoftLayer_Network_Pod.py | 10 ++++++++++ SoftLayer/managers/network.py | 7 +++++++ tests/CLI/modules/vlan_tests.py | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index ad45735e7..1b95ae8b7 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -1,10 +1,11 @@ """Order/create a VLAN instance.""" # :license: MIT, see LICENSE for more details. - import click +import SoftLayer from SoftLayer.managers import ordering from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting @@ -22,10 +23,18 @@ def cli(env, name, datacenter, pod, network, billing): item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' - + extras = {'name': name} if pod and not datacenter: datacenter = pod.split('.')[0] - + mgr = SoftLayer.NetworkManager(env.client) + pods = mgr.get_router() + for router in pods: + if router.get('name') == pod: + extras['routerId'] = router.get('frontendRouterId') + break + if not extras.get('routerId'): + raise exceptions.CLIAbort( + "Unable to find pod name: {}".format(pod)) if not network: item_package = ['PRIVATE_NETWORK_VLAN'] @@ -35,7 +44,7 @@ def cli(env, name, datacenter, pod, network, billing): item_keynames=item_package, complex_type=complex_type, hourly=billing, - extras={'name': name}) + extras=extras) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/fixtures/SoftLayer_Network_Pod.py b/SoftLayer/fixtures/SoftLayer_Network_Pod.py index 4e6088270..0a96521ba 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Pod.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Pod.py @@ -18,5 +18,15 @@ 'frontendRouterId': 1114993, 'frontendRouterName': 'fcr01a.wdc07', 'name': 'wdc07.pod01' + }, + { + 'backendRouterId': 1234567, + 'backendRouterName': 'bcr01a.wdc07', + 'datacenterId': 2017603, + 'datacenterLongName': 'Washington 7', + 'datacenterName': 'wdc07', + 'frontendRouterId': 258741369, + 'frontendRouterName': 'fcr01a.wdc07', + 'name': 'TEST00.pod2' } ] diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 7c86dda58..d57c8050c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -774,3 +774,10 @@ def cancel_item(self, identifier, cancel_immediately, reason_cancel, customer_note, id=identifier) + + def get_router(self): + """return routers account. + + Returns routers. + """ + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects') diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 86884dff6..e0efe0271 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -130,7 +130,7 @@ def test_create_vlan_pod(self): result = self.run_command(['vlan', 'create', '--name', 'test', - '-p TEST00.pod2', + '-p', 'TEST00.pod2', '--network', 'public', '--billing', 'hourly' ]) From 835d1ee9c5b225f7b1d0e696ccb1c5b7e7a35c1b Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Jun 2021 12:30:17 -0400 Subject: [PATCH 0481/1385] fix the team code review comments --- SoftLayer/CLI/licenses/create_options.py | 7 ++++--- SoftLayer/managers/licenses.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/licenses/create_options.py b/SoftLayer/CLI/licenses/create_options.py index d4f027f3d..80e8268a2 100644 --- a/SoftLayer/CLI/licenses/create_options.py +++ b/SoftLayer/CLI/licenses/create_options.py @@ -5,7 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.managers import licenses +from SoftLayer.managers.licenses import LicensesManager from SoftLayer import utils @@ -14,15 +14,16 @@ def cli(env): """Server order options for a given chassis.""" - licenses_manager = licenses.LicensesManager(env.client) + licenses_manager = LicensesManager(env.client) options = licenses_manager.get_create_options() - table = formatting.Table(['Id', 'description', 'keyName', 'recurringFee']) + table = formatting.Table(['Id', 'description', 'keyName', 'capacity', 'recurringFee']) for item in options: table.add_row([item.get('id'), utils.trim_to(item.get('description'), 40), item.get('keyName'), + item.get('capacity'), item.get('prices')[0]['recurringFee']]) env.fout(table) diff --git a/SoftLayer/managers/licenses.py b/SoftLayer/managers/licenses.py index 3d6525382..3f2dbb7c4 100644 --- a/SoftLayer/managers/licenses.py +++ b/SoftLayer/managers/licenses.py @@ -5,9 +5,10 @@ :license: MIT, see LICENSE for more details. """ - # pylint: disable=too-many-public-methods +LICENSE_PACKAGE_ID = 301 + class LicensesManager(object): """Manages account lincese.""" @@ -18,8 +19,7 @@ def __init__(self, client): def get_create_options(self): """Returns valid options for ordering Licenses. - :param string datacenter: short name, like dal09 """ return self.client.call('SoftLayer_Product_Package', 'getItems', - id=301) + id=LICENSE_PACKAGE_ID) From 366dc47256a88b7cbcade41f04d5f2c3f36332a0 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Jun 2021 18:29:43 -0400 Subject: [PATCH 0482/1385] Fix the Christopher code review comments --- SoftLayer/CLI/vlan/create.py | 6 +++--- SoftLayer/managers/network.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 1b95ae8b7..0927daf99 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -24,10 +24,10 @@ def cli(env, name, datacenter, pod, network, billing): item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' extras = {'name': name} - if pod and not datacenter: + if pod: datacenter = pod.split('.')[0] mgr = SoftLayer.NetworkManager(env.client) - pods = mgr.get_router() + pods = mgr.get_pods() for router in pods: if router.get('name') == pod: extras['routerId'] = router.get('frontendRouterId') @@ -35,7 +35,7 @@ def cli(env, name, datacenter, pod, network, billing): if not extras.get('routerId'): raise exceptions.CLIAbort( "Unable to find pod name: {}".format(pod)) - if not network: + if network == 'private': item_package = ['PRIVATE_NETWORK_VLAN'] ordering_manager = ordering.OrderingManager(env.client) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index d57c8050c..638ce4f91 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -775,9 +775,13 @@ def cancel_item(self, identifier, cancel_immediately, customer_note, id=identifier) - def get_router(self): - """return routers account. + def get_pods(self, datacenter=None): + """Calls SoftLayer_Network_Pod::getAllObjects() - Returns routers. + returns list of all network pods and their routers. """ - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects') + _filter = None + if datacenter: + _filter = {"datacenterName": {"operation": datacenter}} + + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) From 4af46bdde1e0d67752b0d85aaa4beee45aa9707a Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Jul 2021 17:06:52 -0400 Subject: [PATCH 0483/1385] Fix analysis issues. --- SoftLayer/CLI/block/count.py | 4 ++-- SoftLayer/CLI/core.py | 6 +++--- SoftLayer/CLI/file/count.py | 4 ++-- SoftLayer/CLI/formatting.py | 8 ++++---- SoftLayer/CLI/loadbal/detail.py | 4 ++-- SoftLayer/CLI/loadbal/pools.py | 6 +++--- SoftLayer/CLI/ssl/add.py | 20 ++++++++++++-------- SoftLayer/CLI/ssl/edit.py | 20 ++++++++++++-------- SoftLayer/CLI/template.py | 10 +++++----- SoftLayer/CLI/virt/bandwidth.py | 8 ++++---- SoftLayer/utils.py | 2 +- 11 files changed, 50 insertions(+), 42 deletions(-) diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py index dc4fb89c1..9f5fd35cf 100644 --- a/SoftLayer/CLI/block/count.py +++ b/SoftLayer/CLI/block/count.py @@ -37,6 +37,6 @@ def cli(env, sortby, datacenter): table = formatting.KeyValueTable(DEFAULT_COLUMNS) table.sortby = sortby - for datacenter_name in datacenters: - table.add_row([datacenter_name, datacenters[datacenter_name]]) + for key, value in datacenters.items(): + table.add_row([key, value]) env.fout(table) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 7257c59d9..1dcd68acb 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -54,17 +54,17 @@ def list_commands(self, ctx): return sorted(env.list_commands(*self.path)) - def get_command(self, ctx, name): + def get_command(self, ctx, cmd_name): """Get command for click.""" env = ctx.ensure_object(environment.Environment) env.load() # Do alias lookup (only available for root commands) if len(self.path) == 0: - name = env.resolve_alias(name) + cmd_name = env.resolve_alias(cmd_name) new_path = list(self.path) - new_path.append(name) + new_path.append(cmd_name) module = env.get_command(*new_path) if isinstance(module, types.ModuleType): return CommandLoader(*new_path, help=module.__doc__ or '') diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py index addb14300..215d5c4d7 100644 --- a/SoftLayer/CLI/file/count.py +++ b/SoftLayer/CLI/file/count.py @@ -36,6 +36,6 @@ def cli(env, sortby, datacenter): table = formatting.KeyValueTable(DEFAULT_COLUMNS) table.sortby = sortby - for datacenter_name in datacenters: - table.add_row([datacenter_name, datacenters[datacenter_name]]) + for key, value in datacenters.items(): + table.add_row([key, value]) env.fout(table) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 0462197c0..b28c54fe6 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -248,11 +248,11 @@ def __str__(self): class CLIJSONEncoder(json.JSONEncoder): """A JSON encoder which is able to use a .to_python() method on objects.""" - def default(self, obj): + def default(self, o): """Encode object if it implements to_python().""" - if hasattr(obj, 'to_python'): - return obj.to_python() - return super().default(obj) + if hasattr(o, 'to_python'): + return o.to_python() + return super().default(o) class Table(object): diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index eb832d594..35e10f376 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -83,8 +83,8 @@ def lbaas_table(this_lb): # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Member/ member_col = ['UUID', 'Address', 'Weight', 'Modify', 'Active'] - for uuid in pools: - member_col.append(pools[uuid]) + for uuid in pools.values(): + member_col.append(uuid) member_table = formatting.Table(member_col) for member in this_lb.get('members', []): row = [ diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index bfd1d48f7..bf77a4c03 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -114,9 +114,9 @@ def edit(env, identifier, listener, **args): 'sslcert': 'tlsCertificateId' } - for arg in args: - if args[arg]: - new_listener[arg_to_option[arg]] = args[arg] + for key, value in args.items(): + if value: + new_listener[arg_to_option[key]] = value try: mgr.add_lb_listener(uuid, new_listener) diff --git a/SoftLayer/CLI/ssl/add.py b/SoftLayer/CLI/ssl/add.py index c017cb5b3..161b48c5e 100644 --- a/SoftLayer/CLI/ssl/add.py +++ b/SoftLayer/CLI/ssl/add.py @@ -28,15 +28,19 @@ def cli(env, crt, csr, icc, key, notes): 'certificateSigningRequest': '', 'notes': notes, } - template['certificate'] = open(crt).read() - template['privateKey'] = open(key).read() - if csr: - body = open(csr).read() - template['certificateSigningRequest'] = body + with open(crt) as file_crt: + template['certificate'] = file_crt.read() + with open(key) as file_key: + template['privateKey'] = file_key.read() + with open(csr) as file_csr: + if csr: + body = file_csr.read() + template['certificateSigningRequest'] = body - if icc: - body = open(icc).read() - template['intermediateCertificate'] = body + with open(icc) as file_icc: + if icc: + body = file_icc.read() + template['intermediateCertificate'] = body manager = SoftLayer.SSLManager(env.client) manager.add_certificate(template) diff --git a/SoftLayer/CLI/ssl/edit.py b/SoftLayer/CLI/ssl/edit.py index 4893ebf8f..da899f34f 100644 --- a/SoftLayer/CLI/ssl/edit.py +++ b/SoftLayer/CLI/ssl/edit.py @@ -24,14 +24,18 @@ def cli(env, identifier, crt, csr, icc, key, notes): """Edit SSL certificate.""" template = {'id': identifier} - if crt: - template['certificate'] = open(crt).read() - if key: - template['privateKey'] = open(key).read() - if csr: - template['certificateSigningRequest'] = open(csr).read() - if icc: - template['intermediateCertificate'] = open(icc).read() + with open(crt) as file_crt: + if crt: + template['certificate'] = file_crt.read() + with open(key) as file_key: + if key: + template['privateKey'] = file_key.read() + with open(csr) as file_csr: + if csr: + template['certificateSigningRequest'] = file_csr.read() + with open(icc) as file_icc: + if icc: + template['intermediateCertificate'] = file_icc.read() if notes: template['notes'] = notes diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 437978f78..012644aa6 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -24,11 +24,11 @@ def __call__(self, ctx, param, value): if value is None: return - config = configparser.ConfigParser() - ini_str = '[settings]\n' + open( - os.path.expanduser(value), 'r').read() - ini_fp = io.StringIO(ini_str) - config.read_file(ini_fp) + with open(os.path.expanduser(value), 'r') as file_handle: + config = configparser.ConfigParser() + ini_str = '[settings]\n' + file_handle.read() + ini_fp = io.StringIO(ini_str) + config.read_file(ini_fp) # Merge template options with the options passed in args = {} diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index 2f29cc7f8..68d3d986c 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -71,15 +71,15 @@ def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): {'keyName': 'privateOut_net_octet', 'sum': 0.0, 'max': 0, 'name': 'Pri Out'}, ] - for point in formatted_data: - new_row = [point] + for key, value in formatted_data.items(): + new_row = [key] for bw_type in bw_totals: - counter = formatted_data[point].get(bw_type['keyName'], 0) + counter = value.get(bw_type['keyName'], 0) new_row.append(mb_to_gb(counter)) bw_type['sum'] = bw_type['sum'] + counter if counter > bw_type['max']: bw_type['max'] = counter - bw_type['maxDate'] = point + bw_type['maxDate'] = key table.add_row(new_row) for bw_type in bw_totals: diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 5bac80696..e38760861 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -228,7 +228,7 @@ def build_filter_orderby(orderby): for keyword in reverse_filter: _aux_filter = {} if '=' in keyword: - _aux_filter[str(keyword).split('=')[0]] = query_filter_orderby(str(keyword).split('=')[1]) + _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter elif keyword == list(reverse_filter)[0]: _aux_filter[keyword] = query_filter_orderby('DESC') From 03539d86156be95253a245f189e937d94512e721 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 2 Jul 2021 10:24:48 -0400 Subject: [PATCH 0484/1385] fix the toox tool and update --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index e38760861..0641b019c 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -228,7 +228,7 @@ def build_filter_orderby(orderby): for keyword in reverse_filter: _aux_filter = {} if '=' in keyword: - _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) + _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter elif keyword == list(reverse_filter)[0]: _aux_filter[keyword] = query_filter_orderby('DESC') From 13fb0879752bfff2f4dedd342684a8aa20b706f0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Jul 2021 16:41:55 -0500 Subject: [PATCH 0485/1385] 5.9.6 change log and updates --- CHANGELOG.md | 23 +++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a79b6d82..7736e005f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Change Log +## [5.9.6] - 2021-07-05 +https://github.com/softlayer/softlayer-python/compare/v5.9.5...v5.9.6 + +#### Improvements +- Updated snap to core20 and edited README #1494 +- Add a table result for `slcli hw upgrade` output. #1488 +- Remove block/file interval option for replica volume. #1497 +- `slcli vlan cancel` should report if a vlan is automatic. #1495 +- New method to manage how long text is in output tables. #1506 +- Fix Tox-analysis issues. #1510 + +#### New Commands +- add new email feature #1483 + + `slcli email list` + + `slcli email detail` + + `slcli email edit` +- `slcli vlan cancel` +- Add slcli account licenses #1501 + + `slcli account licenses` +- Create a new commands on slcli that create/cancel a VMware licenses #1504 + + `slcli licenses create` + + `slcli licenses cancel` + ## [5.9.5] - 2021-05-25 https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 26a00c4cd..5eddbd2b4 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.5' +VERSION = 'v5.9.6' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index c2e70f61a..43bacbb48 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.5', + version='5.9.6', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 3835c9a848c9582cb300fd732117e505c8e5c14b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 6 Jul 2021 11:25:41 -0400 Subject: [PATCH 0486/1385] fix the team code review comments --- SoftLayer/CLI/vlan/create.py | 6 +++++- tests/managers/network_tests.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 0927daf99..a3b88f85b 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -30,7 +30,10 @@ def cli(env, name, datacenter, pod, network, billing): pods = mgr.get_pods() for router in pods: if router.get('name') == pod: - extras['routerId'] = router.get('frontendRouterId') + if network == 'public': + extras['routerId'] = router.get('frontendRouterId') + elif network == 'private': + extras['routerId'] = router.get('backendRouterId') break if not extras.get('routerId'): raise exceptions.CLIAbort( @@ -50,5 +53,6 @@ def cli(env, name, datacenter, pod, network, billing): table.align['value'] = 'l' table.add_row(['id', result['orderId']]) table.add_row(['created', result['orderDate']]) + table.add_row(['name', result['orderDetails']['orderContainers'][0]['name']]) env.fout(table) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 2463ed999..a578fd604 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -624,3 +624,7 @@ def test_vlan_edit(self): self.network.edit(vlan_id, name, note, tags) self.assert_called_with('SoftLayer_Network_Vlan', 'editObject') + + def test_get_all_pods(self): + self.network.get_pods() + self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') From 6112c5419dc81b356115f5c51346cb7f5d33d6d6 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 6 Jul 2021 12:20:35 -0400 Subject: [PATCH 0487/1385] fix the unit test and tox tool --- SoftLayer/fixtures/SoftLayer_Product_Order.py | 3 +++ tests/CLI/modules/vlan_tests.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index e420315e3..b0d67d868 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -287,6 +287,9 @@ vlan_placeOrder = {"orderDate": "2021-06-02 15:23:47", "orderId": 123456, + "orderDetails": { + "orderContainers": [{ + "name": "test"}]}, "prices": [{ "id": 2018, "itemId": 1071, diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 1397f08b0..204788d4d 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -155,7 +155,7 @@ def test_create_vlan(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - {'id': 123456, 'created': '2021-06-02 15:23:47'}) + {'id': 123456, 'created': '2021-06-02 15:23:47', 'name': 'test'}) def test_create_vlan_pod(self): _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') @@ -173,7 +173,7 @@ def test_create_vlan_pod(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - {'id': 123456, 'created': '2021-06-02 15:23:47'}) + {'id': 123456, 'created': '2021-06-02 15:23:47', 'name': 'test'}) @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): From adee15c8e3e9ad07321bd075bb321947a00d221a Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:16:53 -0500 Subject: [PATCH 0488/1385] Fixed some doc block issues when generating HTML --- SoftLayer/API.py | 1 + SoftLayer/managers/hardware.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index a32491ba7..21f21ffc6 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -31,6 +31,7 @@ 'BaseClient', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT', + 'IAMClient', ] VALID_CALL_ARGS = set(( diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d7e65694e..0f410b322 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -901,7 +901,7 @@ def get_maintenance_windows_detail(self, location_id): """Get the disks prices to be added or upgraded. :param int location_id: Hardware Server location id. - :return int. + :return int: """ result = None begin_date_object = datetime.datetime.now() From 6017a35746ef1286205d2dcd09838b9b342e09e4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:33:23 -0500 Subject: [PATCH 0489/1385] updating test-pypi release action --- .github/workflows/test_pypi_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index f3b39abf9..f33420f68 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ test-pypi ] + branches: [ master ] jobs: build-n-publish: @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@master - - name: Set up Python 3.7 + - name: Set up Python 3.8 uses: actions/setup-python@v1 with: - python-version: 3.7 + python-version: 3.8 - name: Install pypa/build run: >- python -m From 4d755f08ee22e498b32f4d2bece49e079e5af108 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:33:38 -0500 Subject: [PATCH 0490/1385] updating test-pypi release action --- .github/workflows/test_pypi_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index f33420f68..439ed17cb 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master ] + branches: [ master, test-pypi ] jobs: build-n-publish: From 198937ae1e0cf7f85a73158d0c35c635fe1e70f5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 16:12:28 -0500 Subject: [PATCH 0491/1385] updated release workflow to publish to test pypi --- .github/workflows/release.yml | 30 ++++++++++++++++++++++++- .github/workflows/test_pypi_release.yml | 6 ++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b244a86b..f4fd12536 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Release +name: Release Snapcraft and PyPi (Testing) on: release: @@ -20,4 +20,32 @@ jobs: VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` echo Publishing $VERSION on ${{ matrix.arch }} snapcraft release slcli $VERSION stable + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.CGALLO_TEST_PYPI }} + repository_url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index 439ed17cb..aea906c54 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,16 +4,16 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master, test-pypi ] + branches: [test-pypi ] jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Set up Python 3.8 - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: 3.8 - name: Install pypa/build From f6af86dafebc2b8f041248c96a0278f7b24d0ad8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 7 Jul 2021 15:28:42 -0500 Subject: [PATCH 0492/1385] Adding in CodeQL Analysis --- .github/workflows/codeql-analysis.yml | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..19d4bd814 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '41 6 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 1162f3fe9d07071463443331ee38695dbf50f210 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 7 Jul 2021 15:46:40 -0500 Subject: [PATCH 0493/1385] Create SECURITY.md --- SECURITY.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..290f09332 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +Generally only the latest release will be actively worked on and supported. +Version 5.7.2 is the last version that supports python2.7. + +| Version | Supported | +| ------- | ------------------ | +| 5.9.x | :white_check_mark: | +| 5.7.2 | :white_check_mark: | +| < 5.7.2 | :x: | + +## Reporting a Vulnerability + +Create a new [Bug Report](https://github.com/softlayer/softlayer-python/issues/new?assignees=&labels=Bug&template=bug_report.md&title=) to let us know about any vulnerabilities in the code base. From 72f8eb19c8f2affe3aa3a1a35b29448b197530a2 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 8 Jul 2021 11:14:15 -0400 Subject: [PATCH 0494/1385] Refactor the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 4 ++-- SoftLayer/managers/cdn.py | 3 ++- tests/CLI/modules/cdn_tests.py | 16 ++++++---------- tests/managers/cdn_tests.py | 3 +-- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py index 677bcd4d7..f39e20a02 100644 --- a/SoftLayer/CLI/cdn/edit.py +++ b/SoftLayer/CLI/cdn/edit.py @@ -20,7 +20,6 @@ help="HTTP port." ) @click.option('--origin', '-o', - required=True, type=click.STRING, help="Origin server address." ) @@ -43,7 +42,7 @@ def cli(env, identifier, header, http_port, origin, respect_headers, cache, performance_configuration): """Edit a CDN Account. - You can use the hostname or uniqueId as IDENTIFIER. + Note: You can use the hostname or uniqueId as IDENTIFIER. """ manager = SoftLayer.CDNManager(env.client) @@ -77,5 +76,6 @@ def cli(env, identifier, header, http_port, origin, respect_headers, cache, perf table.add_row(['Vendor Name', cdn.get('vendorName')]) table.add_row(['Cache key optimization', cdn.get('cacheKeyQueryRule')]) table.add_row(['cname', cdn.get('cname')]) + table.add_row(['Origin server address', cdn.get('originHost')]) env.fout(table) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 7189cbaa4..7edb97628 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -199,7 +199,8 @@ def edit(self, identifier, header=None, http_port=None, origin=None, 'vendorName': cdn_instance_detail.get('vendorName'), 'cname': cdn_instance_detail.get('cname'), 'domain': cdn_instance_detail.get('domain'), - 'httpPort': cdn_instance_detail.get('httpPort') + 'httpPort': cdn_instance_detail.get('httpPort'), + 'origin': cdn_instance_detail.get('originHost') } if header: diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index 5bff7e647..c0a96fee4 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -97,36 +97,32 @@ def test_remove_origin(self): self.assertEqual(result.output, "Origin with path /example1 has been deleted\n") def test_edit_header(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--header=www.test.com']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--header=www.test.com']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('www.test.com', header_result['Header']) def test_edit_http_port(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--http-port=83']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--http-port=83']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual(83, header_result['Http Port']) def test_edit_respect_headers(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--respect-headers=1']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--respect-headers=1']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual(True, header_result['Respect Headers']) def test_edit_cache(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--cache', 'include-specified', + '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('include: test', header_result['Cache key optimization']) def test_edit_cache_by_uniqueId(self): - result = self.run_command(['cdn', 'edit', '9934111111111', - '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + result = self.run_command(['cdn', 'edit', '9934111111111', '--cache', 'include-specified', '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('include: test', header_result['Cache key optimization']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index a57fe0b68..7e56f81ab 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -107,8 +107,7 @@ def test_purge_content(self): def test_cdn_edit(self): identifier = '9934111111111' header = 'www.test.com' - origin = '1.1.1.1' - result = self.cdn_client.edit(identifier, header=header, origin=origin) + result = self.cdn_client.edit(identifier, header=header) self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. updateDomainMapping, result) From 835c5af88eec9191af5845a6615d3e55e6646ea1 Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 12 Jul 2021 19:12:20 -0400 Subject: [PATCH 0495/1385] Refactor the cdn edit option. --- SoftLayer/managers/cdn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 7edb97628..daa7ff377 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -200,7 +200,8 @@ def edit(self, identifier, header=None, http_port=None, origin=None, 'cname': cdn_instance_detail.get('cname'), 'domain': cdn_instance_detail.get('domain'), 'httpPort': cdn_instance_detail.get('httpPort'), - 'origin': cdn_instance_detail.get('originHost') + 'origin': cdn_instance_detail.get('originHost'), + 'header': cdn_instance_detail.get('header') } if header: From 9a2752b96d6cd707f1cac31d90a6f06782721fe6 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Jul 2021 17:52:33 -0400 Subject: [PATCH 0496/1385] Refactor loadbal order-options --- SoftLayer/CLI/loadbal/order.py | 103 ++++++++++++++++------------- tests/CLI/modules/loadbal_tests.py | 8 +-- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index 2293da8db..e36b28bc9 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -89,53 +89,64 @@ def order_options(env, datacenter): net_mgr = SoftLayer.NetworkManager(env.client) package = mgr.lbaas_order_options() - for region in package['regions']: - dc_name = utils.lookup(region, 'location', 'location', 'name') - - # Skip locations if they are not the one requested. - if datacenter and dc_name != datacenter: - continue - this_table = formatting.Table( - ['Prices', 'Private Subnets'], - title="{}: {}".format(region['keyname'], region['description']) - ) - - l_groups = [] - for group in region['location']['location']['groups']: - l_groups.append(group.get('id')) - - # Price lookups - prices = [] - price_table = formatting.KeyValueTable(['KeyName', 'Cost']) - for item in package['items']: - i_price = {'keyName': item['keyName']} - for price in item.get('prices', []): - if not price.get('locationGroupId'): - i_price['default_price'] = price.get('hourlyRecurringFee') - elif price.get('locationGroupId') in l_groups: - i_price['region_price'] = price.get('hourlyRecurringFee') - prices.append(i_price) - for price in prices: - if price.get('region_price'): - price_table.add_row([price.get('keyName'), price.get('region_price')]) - else: - price_table.add_row([price.get('keyName'), price.get('default_price')]) - - # Vlan/Subnet Lookups - mask = "mask[networkVlan,podName,addressSpace]" - subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) - subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) - - for subnet in subnets: - # Only show these types, easier to filter here than in an API call. - if subnet.get('subnetType') != 'PRIMARY' and subnet.get('subnetType') != 'ADDITIONAL_PRIMARY': + if not datacenter: + data_table = formatting.KeyValueTable(['Datacenters', 'City']) + for region in package['regions']: + data_table.add_row([region['description'].split('-')[0], region['description'].split('-')[1]]) + # print(region) + env.fout(data_table) + click.secho("ERROR: Use `slcli lb order-options --datacenter ` " + "to find pricing information and private subnets for that specific site.") + + else: + for region in package['regions']: + dc_name = utils.lookup(region, 'location', 'location', 'name') + + # Skip locations if they are not the one requested. + if datacenter and dc_name != datacenter: continue - space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) - vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) - subnet_table.add_row([subnet.get('id'), space, vlan]) - this_table.add_row([price_table, subnet_table]) - - env.fout(this_table) + this_table = formatting.Table( + ['Prices', 'Private Subnets'], + title="{}: {}".format(region['keyname'], region['description']) + ) + + l_groups = [] + for group in region['location']['location']['groups']: + l_groups.append(group.get('id')) + + # Price lookups + prices = [] + price_table = formatting.KeyValueTable(['KeyName', 'Cost']) + for item in package['items']: + i_price = {'keyName': item['keyName']} + for price in item.get('prices', []): + if not price.get('locationGroupId'): + i_price['default_price'] = price.get('hourlyRecurringFee') + elif price.get('locationGroupId') in l_groups: + i_price['region_price'] = price.get('hourlyRecurringFee') + prices.append(i_price) + for price in prices: + if price.get('region_price'): + price_table.add_row([price.get('keyName'), price.get('region_price')]) + else: + price_table.add_row([price.get('keyName'), price.get('default_price')]) + + # Vlan/Subnet Lookups + mask = "mask[networkVlan,podName,addressSpace]" + subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) + subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) + + for subnet in subnets: + # Only show these types, easier to filter here than in an API call. + if subnet.get('subnetType') != 'PRIMARY' and \ + subnet.get('subnetType') != 'ADDITIONAL_PRIMARY': + continue + space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) + vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) + subnet_table.add_row([subnet.get('id'), space, vlan]) + this_table.add_row([price_table, subnet_table]) + + env.fout(this_table) @click.command() diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 8576a8292..5a9a91134 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -258,11 +258,11 @@ def test_verify_order(self): self.assert_no_fail(result) def test_order_options(self): - mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = SoftLayer_Product_Package.getAllObjectsLoadbal + fault_string = 'Use `slcli lb order-options --datacenter `' \ + ' to find pricing information and private subnets for that specific site.' result = self.run_command(['loadbal', 'order-options']) - - self.assert_no_fail(result) + self.assertIn("ERROR: {}".format(fault_string), + result.output) def test_order_options_with_datacenter(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') From 98f4a40541dce485280e8389b7434df50e521009 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 27 Jul 2021 16:33:57 -0400 Subject: [PATCH 0497/1385] fix the network space is empty on subnet detail --- SoftLayer/CLI/subnet/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 7eb934431..e4ddc2f9e 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -26,7 +26,7 @@ def cli(env, identifier, no_vs, no_hardware): subnet_id = helpers.resolve_id(mgr.resolve_subnet_ids, identifier, name='subnet') - mask = 'mask[ipAddresses[id, ipAddress,note], datacenter, virtualGuests, hardware]' + mask = 'mask[ipAddresses[id, ipAddress,note], datacenter, virtualGuests, hardware, networkVlan[networkSpace]]' subnet = mgr.get_subnet(subnet_id, mask=mask) From d4c55d2338156bbf929967db28e08cc8068c7e39 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 2 Aug 2021 13:54:05 -0500 Subject: [PATCH 0498/1385] changed name of the snapcraft release build --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4fd12536..b009483c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: types: [published] jobs: - release: + snap-release: runs-on: ubuntu-18.04 strategy: matrix: From 19d98a686cc4c8169919ffeb0d7e79dd81b37fd3 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 15:55:38 -0400 Subject: [PATCH 0499/1385] slcli server create-options dal13 Error --- SoftLayer/managers/account.py | 2 +- tests/managers/account_tests.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d9814b1ce..e6337e716 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, location=None): + def get_routers(self, location=None, mask=None): """Gets all the routers currently active on the account :param string mask: Object Mask diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 26b5dadff..5e91c997c 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -161,3 +161,8 @@ def test_get_active_account_licenses(self): def test_get_active_virtual_licenses(self): self.manager.get_active_virtual_licenses() self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") + + def test_get_routers_with_datacenter(self): + self.manager.get_routers('dal13') + object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} + self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) \ No newline at end of file From d4591f058470b233c7e29fc4bcfb22198cead406 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 16:02:23 -0400 Subject: [PATCH 0500/1385] fix the tox analysis --- tests/managers/account_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 5e91c997c..24efd603e 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -165,4 +165,4 @@ def test_get_active_virtual_licenses(self): def test_get_routers_with_datacenter(self): self.manager.get_routers('dal13') object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} - self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) \ No newline at end of file + self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) From 8c1677a917832ef98d207623aa7ec90ff3ba4e14 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 2 Aug 2021 16:03:22 -0500 Subject: [PATCH 0501/1385] prevents --version from looking at environment variables --- README.rst | 20 ++++++++++++++++++++ SoftLayer/CLI/core.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fc05a3503..7cf2e65b3 100644 --- a/README.rst +++ b/README.rst @@ -76,6 +76,26 @@ InsecurePlatformWarning Notice ------------------------------ This library relies on the `requests `_ library to make HTTP requests. On Python versions below Python 2.7.9, requests has started emitting a security warning (InsecurePlatformWarning) due to insecurities with creating SSL connections. To resolve this, upgrade to Python 2.7.9+ or follow the instructions here: http://stackoverflow.com/a/29099439. +Basic Usage +----------- + +- `The Complete Command Directory `_ + +Advanced Usage +-------------- + +You can automatically set some parameters via environment variables with by using the SLCLI prefix. For example + +.. code-block:: bash + $ export SLCLI_VERBOSE=3 + $ export SLCLI_FORMAT=json + $ slcli vs list + +is equivalent to + +.. code-block:: bash + $ slcli -vvv --format=json vs list + Getting Help ------------ Bugs and feature requests about this library should have a `GitHub issue `_ opened about them. diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 1dcd68acb..0f7c12f8c 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -129,7 +129,7 @@ def get_version_message(ctx, param, value): required=False, help="Use demo data instead of actually making API calls") @click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=get_version_message, - help="Show version information.") + help="Show version information.", allow_from_autoenv=False,) @environment.pass_env def cli(env, format='table', From b8e2a4b7b8691d52e912df4cabcae97192a03668 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 17:09:50 -0400 Subject: [PATCH 0502/1385] fix the team code review comments --- SoftLayer/CLI/hardware/create_options.py | 2 +- SoftLayer/managers/account.py | 2 +- tests/managers/account_tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 7d104d877..646d37c09 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -21,7 +21,7 @@ def cli(env, prices, location=None): hardware_manager = hardware.HardwareManager(env.client) account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - routers = account_manager.get_routers(location) + routers = account_manager.get_routers(location=location) tables = [] # Datacenters diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index e6337e716..d9814b1ce 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, location=None, mask=None): + def get_routers(self, mask=None, location=None): """Gets all the routers currently active on the account :param string mask: Object Mask diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 24efd603e..6d515de38 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -163,6 +163,6 @@ def test_get_active_virtual_licenses(self): self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") def test_get_routers_with_datacenter(self): - self.manager.get_routers('dal13') + self.manager.get_routers(location='dal13') object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) From cbba4eb0f0932aa39e508e2be048f302f5c974b5 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 11:12:42 -0400 Subject: [PATCH 0503/1385] fix the team code review comments --- SoftLayer/CLI/loadbal/order.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index e36b28bc9..704fd9704 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -95,7 +95,7 @@ def order_options(env, datacenter): data_table.add_row([region['description'].split('-')[0], region['description'].split('-')[1]]) # print(region) env.fout(data_table) - click.secho("ERROR: Use `slcli lb order-options --datacenter ` " + click.secho("Use `slcli lb order-options --datacenter ` " "to find pricing information and private subnets for that specific site.") else: @@ -105,10 +105,6 @@ def order_options(env, datacenter): # Skip locations if they are not the one requested. if datacenter and dc_name != datacenter: continue - this_table = formatting.Table( - ['Prices', 'Private Subnets'], - title="{}: {}".format(region['keyname'], region['description']) - ) l_groups = [] for group in region['location']['location']['groups']: @@ -116,7 +112,7 @@ def order_options(env, datacenter): # Price lookups prices = [] - price_table = formatting.KeyValueTable(['KeyName', 'Cost']) + price_table = formatting.KeyValueTable(['KeyName', 'Cost'], title='Prices') for item in package['items']: i_price = {'keyName': item['keyName']} for price in item.get('prices', []): @@ -134,7 +130,7 @@ def order_options(env, datacenter): # Vlan/Subnet Lookups mask = "mask[networkVlan,podName,addressSpace]" subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) - subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) + subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan'], title='Private subnet') for subnet in subnets: # Only show these types, easier to filter here than in an API call. @@ -144,9 +140,9 @@ def order_options(env, datacenter): space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) subnet_table.add_row([subnet.get('id'), space, vlan]) - this_table.add_row([price_table, subnet_table]) - env.fout(this_table) + env.fout(price_table) + env.fout(subnet_table) @click.command() From b43d7342a8d11506a6d2288b106c7fd483c38ee5 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 11:18:30 -0400 Subject: [PATCH 0504/1385] fix the unit test --- tests/CLI/modules/loadbal_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 5a9a91134..0fba53cea 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -261,8 +261,7 @@ def test_order_options(self): fault_string = 'Use `slcli lb order-options --datacenter `' \ ' to find pricing information and private subnets for that specific site.' result = self.run_command(['loadbal', 'order-options']) - self.assertIn("ERROR: {}".format(fault_string), - result.output) + self.assertIn(fault_string, result.output) def test_order_options_with_datacenter(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') From eee1c28877c0cfdc28553aa168c7552aa5e833ec Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 18:43:45 -0400 Subject: [PATCH 0505/1385] fix the team code review comments --- SoftLayer/CLI/loadbal/order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index 704fd9704..9df0af1cc 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -103,7 +103,7 @@ def order_options(env, datacenter): dc_name = utils.lookup(region, 'location', 'location', 'name') # Skip locations if they are not the one requested. - if datacenter and dc_name != datacenter: + if datacenter and dc_name != datacenter.lower(): continue l_groups = [] From 2efe374cc7ea723787adf4d9ce99ff28636ae8f4 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 4 Aug 2021 17:13:22 -0400 Subject: [PATCH 0506/1385] last fix team code review comments --- SoftLayer/managers/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d9814b1ce..e6337e716 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, location=None): + def get_routers(self, location=None, mask=None): """Gets all the routers currently active on the account :param string mask: Object Mask From 05288e9863a9bda034959f0b67ad2803f57daf0b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 4 Aug 2021 17:26:01 -0500 Subject: [PATCH 0507/1385] v5.9.7 changelog --- CHANGELOG.md | 18 ++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7736e005f..cf0eb2848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## [5.9.7] - 2021-08-04 +https://github.com/softlayer/softlayer-python/compare/v5.9.6...v5.9.7 + +#### Improvements +- Fixed some doc block issues when generating HTML #1513 +- Updates to the Release workflow for publishing to test pypi #1514 + +- Adding in CodeQL Analysis #1517 +- Create SECURITY.md #1518 +- Fix the network space is empty on subnet detail #1523 +- Prevents SLCLI_VERSION environment variable from breaking things #1527 +- Refactor loadbal order-options #1521 +- slcli server create-options dal13 Error #1526 + +#### New Commands +- add new feature on vlan cli #1499 + + `slcli vlan create` + ## [5.9.6] - 2021-07-05 https://github.com/softlayer/softlayer-python/compare/v5.9.5...v5.9.6 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 5eddbd2b4..6d55ed2df 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.6' +VERSION = 'v5.9.7' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 43bacbb48..4eec2cad5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.6', + version='5.9.7', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 7c6fb218811663c401b402abd4a3c62006f1ba74 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 4 Aug 2021 17:40:29 -0500 Subject: [PATCH 0508/1385] Fixed formatting in README --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 7cf2e65b3..15d5bcca3 100644 --- a/README.rst +++ b/README.rst @@ -87,6 +87,7 @@ Advanced Usage You can automatically set some parameters via environment variables with by using the SLCLI prefix. For example .. code-block:: bash + $ export SLCLI_VERBOSE=3 $ export SLCLI_FORMAT=json $ slcli vs list @@ -94,6 +95,7 @@ You can automatically set some parameters via environment variables with by usin is equivalent to .. code-block:: bash + $ slcli -vvv --format=json vs list Getting Help From 3dcfff51119f6e0358d1738a51c8e5c4b7180625 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 12 Aug 2021 18:27:41 -0400 Subject: [PATCH 0509/1385] #1530 fix code blocks formatting of The Solution section docs --- docs/api/client.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index f1692eb6a..c90aa3d88 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -228,11 +228,13 @@ The Solution Using the python dictionary's `.get() `_ is great for non-nested items. :: + print("{}, {}".format(item.get('id'), item.get('hostName'))) Otherwise, this SDK provides a util function to do something similar. Each additional argument passed into `utils.lookup` will go one level deeper into the nested dictionary to find the item requested, returning `None` if a KeyError shows up. :: + itemId = SoftLayer.utils.lookup(item, 'id') itemHostname = SoftLayer.utils.lookup(item, 'hostName') print("{}, {}".format(itemId, itemHostname)) From 499746eb972e0119f7407b67793327567fe5f92a Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 18 Aug 2021 10:58:41 -0400 Subject: [PATCH 0510/1385] #1531 Add retry decorator to documentation --- docs/api/client.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index c90aa3d88..3f7700cbb 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -245,3 +245,11 @@ API Reference .. automodule:: SoftLayer :members: + :ignore-module-all: + +.. automodule:: SoftLayer.API + :members: + :ignore-module-all: + +.. automodule:: SoftLayer.decoration + :members: From 1d4a285260c9bc3126840e67534a8deec3a3e082 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 16:30:32 -0400 Subject: [PATCH 0511/1385] #1532 Add Utility reference to Documentation --- docs/api/client.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index 3f7700cbb..563212577 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -253,3 +253,6 @@ API Reference .. automodule:: SoftLayer.decoration :members: + +.. automodule:: SoftLayer.utils + :members: From 8a62480a35284866a5848e31df38b7ee8d41de8b Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 16:33:57 -0400 Subject: [PATCH 0512/1385] #1532 Fix Sphinx WARNING: Inline emphasis start-string without end-string. --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 0641b019c..929b6524b 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -20,7 +20,7 @@ def lookup(dic, key, *keys): """A generic dictionary access helper. This helps simplify code that uses heavily nested dictionaries. It will - return None if any of the keys in *keys do not exist. + return None if any of the keys in `*keys` do not exist. :: From 5e594202114977c6715b301b244f149c55290d91 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 19:05:01 -0400 Subject: [PATCH 0513/1385] #1533 Add Exceptions to Documentation --- docs/api/client.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api/client.rst b/docs/api/client.rst index 3f7700cbb..13b488bee 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -249,7 +249,10 @@ API Reference .. automodule:: SoftLayer.API :members: - :ignore-module-all: + :ignore-module-all: + +.. automodule:: SoftLayer.exceptions + :members: .. automodule:: SoftLayer.decoration :members: From 9ec4f324cfea39b8cc8f94ca9ecf3b79f21b2a10 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 31 Aug 2021 15:48:48 -0500 Subject: [PATCH 0514/1385] enforcing encodings on XMLRPC calls so we can safely use unicode characters --- SoftLayer/transports.py | 8 +++-- tests/transport_tests.py | 77 +++++++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index bd643b568..e243f16f6 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -205,7 +205,8 @@ def __call__(self, request): request.url = '/'.join([self.endpoint_url, request.service]) request.payload = xmlrpc.client.dumps(tuple(largs), methodname=request.method, - allow_none=True) + allow_none=True, + encoding="iso-8859-1") # Prefer the request setting, if it's not None verify = request.verify @@ -214,7 +215,7 @@ def __call__(self, request): try: resp = self.client.request('POST', request.url, - data=request.payload, + data=request.payload.encode(), auth=auth, headers=request.transport_headers, timeout=self.timeout, @@ -273,7 +274,7 @@ def print_reproduceable(self, request): #auth=HTTPBasicAuth('apikey', YOUR_CLOUD_API_KEY) auth=None url = '$url' -payload = """$payload""" +payload = $payload transport_headers = $transport_headers timeout = $timeout verify = $verify @@ -287,6 +288,7 @@ def print_reproduceable(self, request): safe_payload = re.sub(r'[a-z0-9]{64}', r'API_KEY_GOES_HERE', request.payload) safe_payload = re.sub(r'(\s+)', r' ', safe_payload) + safe_payload = safe_payload.encode() substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers, timeout=self.timeout, verify=request.verify, cert=request.cert, proxy=_proxies_dict(self.proxy)) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index c23f1054f..d09a65c51 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -48,7 +48,7 @@ def set_up(self): def test_call(self, request): request.return_value = self.response - data = ''' + data = ''' getObject @@ -63,7 +63,7 @@ def test_call(self, request): -''' +'''.encode() req = transports.Request() req.service = 'SoftLayer_Service' @@ -134,7 +134,7 @@ def test_identifier(self, request): """ id 1234 -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_filter(self, request): @@ -152,7 +152,7 @@ def test_filter(self, request): """ operation ^= prefix -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_limit_offset(self, request): @@ -169,10 +169,10 @@ def test_limit_offset(self, request): self.assertIn(""" resultLimit -""", kwargs['data']) +""".encode(), kwargs['data']) self.assertIn("""limit 10 -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_old_mask(self, request): @@ -194,7 +194,7 @@ def test_old_mask(self, request): nested -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_mask_call_no_mask_prefix(self, request): @@ -209,7 +209,7 @@ def test_mask_call_no_mask_prefix(self, request): args, kwargs = request.call_args self.assertIn( - "mask[something.nested]", + "mask[something.nested]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -225,7 +225,7 @@ def test_mask_call_v2(self, request): args, kwargs = request.call_args self.assertIn( - "mask[something[nested]]", + "mask[something[nested]]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -241,7 +241,7 @@ def test_mask_call_filteredMask(self, request): args, kwargs = request.call_args self.assertIn( - "filteredMask[something[nested]]", + "filteredMask[something[nested]]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -256,7 +256,7 @@ def test_mask_call_v2_dot(self, request): self.transport(req) args, kwargs = request.call_args - self.assertIn("mask.something.nested", + self.assertIn("mask.something.nested".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -287,7 +287,7 @@ def test_print_reproduceable(self): def test_ibm_id_call(self, auth, request): request.return_value = self.response - data = ''' + data = ''' getObject @@ -302,7 +302,7 @@ def test_ibm_id_call(self, auth, request): -''' +'''.encode() req = transports.Request() req.service = 'SoftLayer_Service' @@ -360,6 +360,57 @@ def test_call_large_number_response(self, request): resp = self.transport(req) self.assertEqual(resp[0]['bytesUsed'], 2666148982056) + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_nonascii_characters(self, request): + request.return_value = self.response + hostname = 'testé' + data = ''' + +getObject + + + + +headers + + + + + + + + +hostname +testé + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ({'hostname': hostname},) + req.transport_user = "testUser" + req.transport_password = "testApiKey" + resp = self.transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=mock.ANY) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + @mock.patch('SoftLayer.transports.requests.Session.request') @pytest.mark.parametrize( From dcbc9fe57f95480ff790bcbc8dc9ac37185ca0fc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 1 Sep 2021 13:43:42 -0500 Subject: [PATCH 0515/1385] Fixing a bunch of tox related issues, moslty unspecified-encoding and use-list-literal errors --- SoftLayer/CLI/autoscale/__init__.py | 1 + SoftLayer/CLI/autoscale/edit.py | 2 +- SoftLayer/CLI/block/count.py | 2 +- SoftLayer/CLI/block/replication/partners.py | 22 +++---------------- SoftLayer/CLI/dns/zone_import.py | 2 +- SoftLayer/CLI/file/count.py | 2 +- SoftLayer/CLI/file/replication/partners.py | 24 +++------------------ SoftLayer/CLI/firewall/edit.py | 2 +- SoftLayer/CLI/hardware/edit.py | 2 +- SoftLayer/CLI/hardware/upgrade.py | 2 +- SoftLayer/CLI/order/quote.py | 2 +- SoftLayer/CLI/sshkey/add.py | 2 +- SoftLayer/CLI/sshkey/print.py | 2 +- SoftLayer/CLI/ssl/add.py | 8 +++---- SoftLayer/CLI/ssl/download.py | 2 +- SoftLayer/CLI/ssl/edit.py | 8 +++---- SoftLayer/CLI/storage_utils.py | 21 ++++++++++++++++++ SoftLayer/CLI/template.py | 4 ++-- SoftLayer/CLI/virt/create.py | 2 +- SoftLayer/CLI/virt/edit.py | 2 +- SoftLayer/CLI/virt/upgrade.py | 2 +- SoftLayer/config.py | 2 +- SoftLayer/fixtures/SoftLayer_Account.py | 2 +- SoftLayer/managers/vs_capacity.py | 2 +- 24 files changed, 55 insertions(+), 67 deletions(-) diff --git a/SoftLayer/CLI/autoscale/__init__.py b/SoftLayer/CLI/autoscale/__init__.py index e69de29bb..80cd82747 100644 --- a/SoftLayer/CLI/autoscale/__init__.py +++ b/SoftLayer/CLI/autoscale/__init__.py @@ -0,0 +1 @@ +"""Autoscale""" diff --git a/SoftLayer/CLI/autoscale/edit.py b/SoftLayer/CLI/autoscale/edit.py index c470aebbf..94e2165af 100644 --- a/SoftLayer/CLI/autoscale/edit.py +++ b/SoftLayer/CLI/autoscale/edit.py @@ -32,7 +32,7 @@ def cli(env, identifier, name, minimum, maximum, userdata, userfile, cpu, memory if userdata: virt_template['userData'] = [{"value": userdata}] elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: virt_template['userData'] = [{"value": userfile_obj.read()}] virt_template['startCpus'] = cpu virt_template['maxMemory'] = memory diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py index 9f5fd35cf..ecfba0a53 100644 --- a/SoftLayer/CLI/block/count.py +++ b/SoftLayer/CLI/block/count.py @@ -25,7 +25,7 @@ def cli(env, sortby, datacenter): mask=mask) # cycle through all block volumes and count datacenter occurences. - datacenters = dict() + datacenters = {} for volume in block_volumes: service_resource = volume['serviceResource'] if 'datacenter' in service_resource: diff --git a/SoftLayer/CLI/block/replication/partners.py b/SoftLayer/CLI/block/replication/partners.py index f19be0af5..ade8f6b0f 100644 --- a/SoftLayer/CLI/block/replication/partners.py +++ b/SoftLayer/CLI/block/replication/partners.py @@ -6,26 +6,10 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils -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' -] +COLUMNS = storage_utils.REPLICATION_PARTNER_COLUMNS +DEFAULT_COLUMNS = storage_utils.REPLICATION_PARTNER_DEFAULT @click.command() diff --git a/SoftLayer/CLI/dns/zone_import.py b/SoftLayer/CLI/dns/zone_import.py index 7b47cdb12..e0e3d72d4 100644 --- a/SoftLayer/CLI/dns/zone_import.py +++ b/SoftLayer/CLI/dns/zone_import.py @@ -26,7 +26,7 @@ def cli(env, zonefile, dry_run): """Import zone based off a BIND zone file.""" manager = SoftLayer.DNSManager(env.client) - with open(zonefile) as zone_f: + with open(zonefile, encoding="utf-8") as zone_f: zone_contents = zone_f.read() zone, records, bad_lines = parse_zone_details(zone_contents) diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py index 215d5c4d7..cb6ed1a0a 100644 --- a/SoftLayer/CLI/file/count.py +++ b/SoftLayer/CLI/file/count.py @@ -24,7 +24,7 @@ def cli(env, sortby, datacenter): file_volumes = file_manager.list_file_volumes(datacenter=datacenter, mask=mask) - datacenters = dict() + datacenters = {} for volume in file_volumes: service_resource = volume['serviceResource'] if 'datacenter' in service_resource: diff --git a/SoftLayer/CLI/file/replication/partners.py b/SoftLayer/CLI/file/replication/partners.py index 866248fdf..b48418b1a 100644 --- a/SoftLayer/CLI/file/replication/partners.py +++ b/SoftLayer/CLI/file/replication/partners.py @@ -6,28 +6,10 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils -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' -] +COLUMNS = storage_utils.REPLICATION_PARTNER_COLUMNS +DEFAULT_COLUMNS = storage_utils.REPLICATION_PARTNER_DEFAULT @click.command() diff --git a/SoftLayer/CLI/firewall/edit.py b/SoftLayer/CLI/firewall/edit.py index d2bb5bb2a..0e1f38dac 100644 --- a/SoftLayer/CLI/firewall/edit.py +++ b/SoftLayer/CLI/firewall/edit.py @@ -23,7 +23,7 @@ def parse_rules(content=None): :returns: a list of rules """ rules = content.split(DELIMITER) - parsed_rules = list() + parsed_rules = [] order = 1 for rule in rules: if rule.strip() == '': diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index dc1152c6f..32b1ba5d2 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -40,7 +40,7 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed if userdata: data['userdata'] = userdata elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: data['userdata'] = userfile_obj.read() if tag: diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index a5b432e82..a3f197d24 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -51,7 +51,7 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') - disk_list = list() + disk_list = [] if add_disk: for guest_disk in add_disk: disks = {'description': 'add_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py index 498dbdc56..f129c02ad 100644 --- a/SoftLayer/CLI/order/quote.py +++ b/SoftLayer/CLI/order/quote.py @@ -43,7 +43,7 @@ def _parse_create_args(client, args): if args.get('userdata'): userdata = args['userdata'] elif args.get('userfile'): - with open(args['userfile'], 'r') as userfile: + with open(args['userfile'], 'r', encoding="utf-8") as userfile: userdata = userfile.read() if userdata: for hardware in data['hardware']: diff --git a/SoftLayer/CLI/sshkey/add.py b/SoftLayer/CLI/sshkey/add.py index d80330d7c..570cfcea8 100644 --- a/SoftLayer/CLI/sshkey/add.py +++ b/SoftLayer/CLI/sshkey/add.py @@ -33,7 +33,7 @@ def cli(env, label, in_file, key, note): if key: key_text = key else: - with open(path.expanduser(in_file), 'rU') as key_file: + with open(path.expanduser(in_file), 'rU', encoding="utf-8") as key_file: key_text = key_file.read().strip() key_file.close() diff --git a/SoftLayer/CLI/sshkey/print.py b/SoftLayer/CLI/sshkey/print.py index 2bcdcd3be..2e1444d64 100644 --- a/SoftLayer/CLI/sshkey/print.py +++ b/SoftLayer/CLI/sshkey/print.py @@ -26,7 +26,7 @@ def cli(env, identifier, out_file): key = mgr.get_key(key_id) if out_file: - with open(path.expanduser(out_file), 'w') as pub_file: + with open(path.expanduser(out_file), 'w', encoding="utf-8") as pub_file: pub_file.write(key['key']) table = formatting.KeyValueTable(['name', 'value']) diff --git a/SoftLayer/CLI/ssl/add.py b/SoftLayer/CLI/ssl/add.py index 161b48c5e..9a579d899 100644 --- a/SoftLayer/CLI/ssl/add.py +++ b/SoftLayer/CLI/ssl/add.py @@ -28,16 +28,16 @@ def cli(env, crt, csr, icc, key, notes): 'certificateSigningRequest': '', 'notes': notes, } - with open(crt) as file_crt: + with open(crt, encoding="utf-8") as file_crt: template['certificate'] = file_crt.read() - with open(key) as file_key: + with open(key, encoding="utf-8") as file_key: template['privateKey'] = file_key.read() - with open(csr) as file_csr: + with open(csr, encoding="utf-8") as file_csr: if csr: body = file_csr.read() template['certificateSigningRequest'] = body - with open(icc) as file_icc: + with open(icc, encoding="utf-8") as file_icc: if icc: body = file_icc.read() template['intermediateCertificate'] = body diff --git a/SoftLayer/CLI/ssl/download.py b/SoftLayer/CLI/ssl/download.py index 77b5247b2..3c7c2dfb6 100644 --- a/SoftLayer/CLI/ssl/download.py +++ b/SoftLayer/CLI/ssl/download.py @@ -30,5 +30,5 @@ def cli(env, identifier): def write_cert(filename, content): """Writes certificate body to the given file path.""" - with open(filename, 'w') as cert_file: + with open(filename, 'w', encoding="utf-8") as cert_file: cert_file.write(content) diff --git a/SoftLayer/CLI/ssl/edit.py b/SoftLayer/CLI/ssl/edit.py index da899f34f..b6dc08e7b 100644 --- a/SoftLayer/CLI/ssl/edit.py +++ b/SoftLayer/CLI/ssl/edit.py @@ -24,16 +24,16 @@ def cli(env, identifier, crt, csr, icc, key, notes): """Edit SSL certificate.""" template = {'id': identifier} - with open(crt) as file_crt: + with open(crt, encoding="utf-8") as file_crt: if crt: template['certificate'] = file_crt.read() - with open(key) as file_key: + with open(key, encoding="utf-8") as file_key: if key: template['privateKey'] = file_key.read() - with open(csr) as file_csr: + with open(csr, encoding="utf-8") as file_csr: if csr: template['certificateSigningRequest'] = file_csr.read() - with open(icc) as file_icc: + with open(icc, encoding="utf-8") as file_icc: if icc: template['intermediateCertificate'] = file_icc.read() if notes: diff --git a/SoftLayer/CLI/storage_utils.py b/SoftLayer/CLI/storage_utils.py index aa24585eb..3d23e0941 100644 --- a/SoftLayer/CLI/storage_utils.py +++ b/SoftLayer/CLI/storage_utils.py @@ -144,3 +144,24 @@ def _format_name(obj): 'password', 'allowed_host_id', ] + + +REPLICATION_PARTNER_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"), +] + +REPLICATION_PARTNER_DEFAULT = [ + 'ID', + 'Username', + 'Account ID', + 'Capacity (GB)', + 'Hardware ID', + 'Guest ID', + 'Host ID' +] diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 012644aa6..9ecbd7172 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -24,7 +24,7 @@ def __call__(self, ctx, param, value): if value is None: return - with open(os.path.expanduser(value), 'r') as file_handle: + with open(os.path.expanduser(value), 'r', encoding="utf-8") as file_handle: config = configparser.ConfigParser() ini_str = '[settings]\n' + file_handle.read() ini_fp = io.StringIO(ini_str) @@ -58,7 +58,7 @@ def export_to_template(filename, args, exclude=None): exclude.append('format') exclude.append('debug') - with open(filename, "w") as template_file: + with open(filename, "w", encoding="utf-8") as template_file: for k, val in args.items(): if val and k not in exclude: if isinstance(val, tuple): diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 9d07f01d2..ad8b3b35b 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -118,7 +118,7 @@ def _parse_create_args(client, args): if args.get('userdata'): data['userdata'] = args['userdata'] elif args.get('userfile'): - with open(args['userfile'], 'r') as userfile: + with open(args['userfile'], 'r', encoding="utf-8") as userfile: data['userdata'] = userfile.read() # Get the SSH keys diff --git a/SoftLayer/CLI/virt/edit.py b/SoftLayer/CLI/virt/edit.py index 7c7e07db9..a72caa585 100644 --- a/SoftLayer/CLI/virt/edit.py +++ b/SoftLayer/CLI/virt/edit.py @@ -43,7 +43,7 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, if userdata: data['userdata'] = userdata elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: data['userdata'] = userfile_obj.read() data['hostname'] = hostname diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index fdfa37822..45e60e573 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -42,7 +42,7 @@ def cli(env, identifier, cpu, private, memory, network, flavor, add_disk, resize if not (env.skip_confirmations or formatting.confirm("This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') - disk_json = list() + disk_json = [] if memory: memory = int(memory / 1024) if resize_disk: diff --git a/SoftLayer/config.py b/SoftLayer/config.py index caa8def10..5ae8c7131 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -118,5 +118,5 @@ def write_config(configuration, config_file=None): if config_file is None: config_file = '~/.softlayer' config_file = os.path.expanduser(config_file) - with open(config_file, 'w') as file: + with open(config_file, 'w', encoding="utf-8") as file: configuration.write(file) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 7ca2d7e67..35216be76 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -14,7 +14,7 @@ 'globalIdentifier': 'F9329795-4220-4B0A-B970-C86B950667FA', 'id': 201, # 'name': 'private_image2', - 'name': u'a¬ሴ€耀', + 'name': 'a¬ሴ€耀', 'parentId': '', 'publicFlag': False, }] diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 727a881b9..433f2b63a 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -89,7 +89,7 @@ def get_available_routers(self, dc=None): for region in regions: routers[region['keyname']] = [] for location in region['locations']: - location['location']['pods'] = list() + location['location']['pods'] = [] for pod in pods: if pod['datacenterName'] == location['location']['name']: location['location']['pods'].append(pod) From 849a1e4e0fead06e1aead249c3ff27d3211c61d0 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 2 Sep 2021 17:09:07 -0400 Subject: [PATCH 0516/1385] Add sensor data to hardware --- SoftLayer/CLI/hardware/sensor.py | 69 ++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Hardware.py | 28 ++++++++++ SoftLayer/managers/hardware.py | 4 ++ docs/cli/hardware.rst | 4 ++ tests/CLI/modules/server_tests.py | 8 +++ tests/managers/hardware_tests.py | 12 +++-- 7 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 SoftLayer/CLI/hardware/sensor.py diff --git a/SoftLayer/CLI/hardware/sensor.py b/SoftLayer/CLI/hardware/sensor.py new file mode 100644 index 000000000..cba0f097c --- /dev/null +++ b/SoftLayer/CLI/hardware/sensor.py @@ -0,0 +1,69 @@ +"""Hardware internal Sensor .""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import click +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@click.option('--discrete', is_flag=True, default=False, help='Show discrete units associated hardware sensor') +@environment.pass_env +def cli(env, identifier, discrete): + """Retrieve a server’s hardware state via its internal sensors.""" + mgr = SoftLayer.HardwareManager(env.client) + sensors = mgr.get_sensors(identifier) + + temperature_table = formatting.Table(["sensor", "status", "Reading", "min", "Max"], + title='temperature Degree c') + + volts_table = formatting.Table(["sensor", "status", "Reading", "min", "Max"], + title='volts') + + watts_table = formatting.Table(["sensor", "status", "Reading"], + title='Watts') + + rpm_table = formatting.Table(["sensor", "status", "Reading", "min"], + title='RPM') + + discrete_table = formatting.Table(["sensor", "status", "Reading"], + title='discrete') + + for sensor in sensors: + if sensor.get('sensorUnits') == 'degrees C': + temperature_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('upperNonCritical'), + sensor.get('upperCritical')]) + + if sensor.get('sensorUnits') == 'volts': + volts_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('lowerNonCritical'), + sensor.get('lowerCritical')]) + + if sensor.get('sensorUnits') == 'Watts': + watts_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading')]) + + if sensor.get('sensorUnits') == 'RPM': + rpm_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('lowerCritical')]) + + if sensor.get('sensorUnits') == 'discrete': + discrete_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading')]) + env.fout(temperature_table) + env.fout(rpm_table) + env.fout(volts_table) + env.fout(watts_table) + if discrete: + env.fout(discrete_table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a6e42539c..fd53cc43c 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -272,6 +272,7 @@ ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), + ('hardware:sensor', 'SoftLayer.CLI.hardware.sensor:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index fd6bf9535..770de045c 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -59,3 +59,31 @@ } allowAccessToNetworkStorageList = True + +getSensorData = [ + { + "sensorId": "Ambient 1 Temperature", + "sensorReading": "25.000", + "sensorUnits": "degrees C", + "status": "ok", + "upperCritical": "43.000", + "upperNonCritical": "41.000", + "upperNonRecoverable": "46.000" + }, + { + "lowerCritical": "3500.000", + "sensorId": "Fan 1 Tach", + "sensorReading": "6580.000", + "sensorUnits": "RPM", + "status": "ok" + }, { + "sensorId": "IPMI Watchdog", + "sensorReading": "0x0", + "sensorUnits": "discrete", + "status": "0x0080" + }, { + "sensorId": "Avg Power", + "sensorReading": "70.000", + "sensorUnits": "Watts", + "status": "ok" + }] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0f410b322..fe494f83d 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1098,6 +1098,10 @@ def get_components(self, hardware_id, mask=None, filter_component=None): return self.client.call('Hardware_Server', 'getComponents', mask=mask, filter=filter_component, id=hardware_id) + def get_sensors(self, hardware_id): + """Returns Hardware sensor data""" + return self.client.call('Hardware', 'getSensorData', id=hardware_id) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index aa95d9141..1f8375cfe 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -127,3 +127,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.upgrade:cli :prog: hardware upgrade :show-nested: + +.. click:: SoftLayer.CLI.hardware.sensor:cli + :prog: hardware sensor + :show-nested: diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 3b5498d1a..b558811a2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -996,3 +996,11 @@ def test_upgrade(self, confirm_mock): def test_components(self): result = self.run_command(['hardware', 'detail', '100', '--components']) self.assert_no_fail(result) + + def test_sensor(self): + result = self.run_command(['hardware', 'sensor', '100']) + self.assert_no_fail(result) + + def test_sensor_discrete(self): + result = self.run_command(['hardware', 'sensor', '100', '--discrete']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 307b38250..51a6e510d 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): @@ -948,6 +948,10 @@ def test_get_components(self): self.assertEqual(result[0]['hardwareComponentModel']['name'], 'IMM2 - Onboard') self.assertEqual(result[0]['hardwareComponentModel']['firmwares'][0]['version'], '5.60') + def test_sensor(self): + self.hardware.get_sensors(100) + self.assert_called_with('SoftLayer_Hardware', 'getSensorData') + class HardwareHelperTests(testing.TestCase): From 9317fe356b83d1fea1638f6b7db1b1f28dc6f842 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 2 Sep 2021 18:01:26 -0400 Subject: [PATCH 0517/1385] fix the tox tool --- tests/managers/hardware_tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 51a6e510d..a9eada76c 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): From 6b17d25b03ba231bd4618b2758d49b3d490ca551 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 16 Sep 2021 15:44:34 -0400 Subject: [PATCH 0518/1385] #1545 remove erroneous print statement in unplanned_event_table function --- SoftLayer/CLI/account/events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index b5d8960cb..7d4803b42 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -69,7 +69,6 @@ def unplanned_event_table(events): unplanned_table.align['Subject'] = 'l' unplanned_table.align['Impacted Resources'] = 'l' for event in events: - print(event.get('modifyDate')) unplanned_table.add_row([ event.get('id'), event.get('systemTicketId'), From caa096f4d3cac7735808657ed087022797db5d00 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 16 Sep 2021 16:23:27 -0400 Subject: [PATCH 0519/1385] #1545 add test to validate cli account event json output --- tests/CLI/modules/account_tests.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index fe9c11b0b..8428d3306 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -4,6 +4,8 @@ Tests for the user cli command """ +import json + from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account from SoftLayer import testing @@ -37,8 +39,20 @@ def test_event_ack_all(self): self.assert_called_with(self.SLNOE, 'getAllObjects') self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=1234) - # slcli account invoice-detail + def test_event_jsonraw_output(self): + # https://github.com/softlayer/softlayer-python/issues/1545 + command = '--format jsonraw account events' + command_params = command.split() + result = self.run_command(command_params) + json_text_tables = result.stdout.split('\n') + # removing an extra item due to an additional Newline at the end of the output + json_text_tables.pop() + # each item in the json_text_tables should be a list + for json_text_table in json_text_tables: + json_table = json.loads(json_text_table) + self.assertIsInstance(json_table, list) + # slcli account invoice-detail def test_invoice_detail(self): result = self.run_command(['account', 'invoice-detail', '1234']) self.assert_no_fail(result) From e94ee4d64ec0b939de41392096943d48bf7b2c20 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 17 Sep 2021 14:39:48 -0500 Subject: [PATCH 0520/1385] Ignoring f-string related messages for tox for now --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 73b9a0007..54cfb633d 100644 --- a/tox.ini +++ b/tox.ini @@ -40,6 +40,7 @@ commands = -d consider-using-in \ -d consider-using-dict-comprehension \ -d useless-import-alias \ + -d consider-using-f-string \ --max-args=25 \ --max-branches=20 \ --max-statements=65 \ @@ -60,4 +61,4 @@ commands = [testenv:docs] commands = - python ./docCheck.py \ No newline at end of file + python ./docCheck.py From 3090070a28e67b149157302f10337b9e93fb0075 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:33:10 -0400 Subject: [PATCH 0521/1385] #1541 fix to loadbal details duplicate columns error in members table --- SoftLayer/CLI/loadbal/detail.py | 108 +++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 35e10f376..6712c3ce2 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -29,12 +29,33 @@ def lbaas_table(this_lb): table.align['value'] = 'l' table.add_row(['Id', this_lb.get('id')]) table.add_row(['UUI', this_lb.get('uuid')]) + table.add_row(['Name', this_lb.get('name')]) table.add_row(['Address', this_lb.get('address')]) + table.add_row(['Type', SoftLayer.LoadBalancerManager.TYPE.get(this_lb.get('type'))]) table.add_row(['Location', utils.lookup(this_lb, 'datacenter', 'longName')]) table.add_row(['Description', this_lb.get('description')]) - table.add_row(['Name', this_lb.get('name')]) table.add_row(['Status', "{} / {}".format(this_lb.get('provisioningStatus'), this_lb.get('operatingStatus'))]) + listener_table, pools = get_listener_table(this_lb) + table.add_row(['Protocols', listener_table]) + + member_table = get_member_table(this_lb, pools) + table.add_row(['Members', member_table]) + + hp_table = get_hp_table(this_lb) + table.add_row(['Health Checks', hp_table]) + + l7pool_table = get_l7pool_table(this_lb) + table.add_row(['L7 Pools', l7pool_table]) + + ssl_table = get_ssl_table(this_lb) + table.add_row(['Ciphers', ssl_table]) + + return table + + +def get_hp_table(this_lb): + """Generates a table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_HealthMonitor/ hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'Timeout', 'Modify', 'Active']) for health in this_lb.get('healthMonitors', []): @@ -47,44 +68,17 @@ def lbaas_table(this_lb): utils.clean_time(health.get('modifyDate')), health.get('provisioningStatus') ]) - table.add_row(['Checks', hp_table]) + return hp_table - # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ - l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active']) - for layer7 in this_lb.get('l7Pools', []): - l7_table.add_row([ - layer7.get('id'), - layer7.get('uuid'), - layer7.get('loadBalancingAlgorithm'), - layer7.get('name'), - layer7.get('protocol'), - utils.clean_time(layer7.get('modifyDate')), - layer7.get('provisioningStatus') - ]) - table.add_row(['L7 Pools', l7_table]) - - pools = {} - # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ - listener_table = formatting.Table(['UUID', 'Max Connection', 'Mapping', 'Balancer', 'Modify', 'Active']) - for listener in this_lb.get('listeners', []): - pool = listener.get('defaultPool') - priv_map = "{}:{}".format(pool['protocol'], pool['protocolPort']) - pools[pool['uuid']] = priv_map - mapping = "{}:{} -> {}".format(listener.get('protocol'), listener.get('protocolPort'), priv_map) - listener_table.add_row([ - listener.get('uuid'), - listener.get('connectionLimit', 'None'), - mapping, - pool.get('loadBalancingAlgorithm', 'None'), - utils.clean_time(listener.get('modifyDate')), - listener.get('provisioningStatus') - ]) - table.add_row(['Pools', listener_table]) +def get_member_table(this_lb, pools): + """Generates a members table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Member/ member_col = ['UUID', 'Address', 'Weight', 'Modify', 'Active'] + counter = 0 for uuid in pools.values(): - member_col.append(uuid) + member_col.append(f'P{counter}-> {uuid}') + counter += 1 member_table = formatting.Table(member_col) for member in this_lb.get('members', []): row = [ @@ -97,14 +91,54 @@ def lbaas_table(this_lb): for uuid in pools: row.append(get_member_hp(this_lb.get('health'), member.get('uuid'), uuid)) member_table.add_row(row) - table.add_row(['Members', member_table]) + return member_table + +def get_ssl_table(this_lb): + """Generates a ssl table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_SSLCipher/ ssl_table = formatting.Table(['Id', 'Name']) for ssl in this_lb.get('sslCiphers', []): ssl_table.add_row([ssl.get('id'), ssl.get('name')]) - table.add_row(['Ciphers', ssl_table]) - return table + return ssl_table + + +def get_listener_table(this_lb): + """Generates a protocols table from a list of LBaaS devices""" + pools = {} + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ + listener_table = formatting.Table(['UUID', 'Max Connection', 'Mapping', 'Balancer', 'Modify', 'Active']) + for listener in this_lb.get('listeners', []): + pool = listener.get('defaultPool') + priv_map = "{}:{}".format(pool['protocol'], pool['protocolPort']) + pools[pool['uuid']] = priv_map + mapping = "{}:{} -> {}".format(listener.get('protocol'), listener.get('protocolPort'), priv_map) + listener_table.add_row([ + listener.get('uuid'), + listener.get('connectionLimit', 'None'), + mapping, + pool.get('loadBalancingAlgorithm', 'None'), + utils.clean_time(listener.get('modifyDate')), + listener.get('provisioningStatus') + ]) + return listener_table, pools + + +def get_l7pool_table(this_lb): + """Generates a l7Pools table from a list of LBaaS devices""" + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ + l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active']) + for layer7 in this_lb.get('l7Pools', []): + l7_table.add_row([ + layer7.get('id'), + layer7.get('uuid'), + layer7.get('loadBalancingAlgorithm'), + layer7.get('name'), + layer7.get('protocol'), + utils.clean_time(layer7.get('modifyDate')), + layer7.get('provisioningStatus') + ]) + return l7_table def get_member_hp(checks, member_uuid, pool_uuid): From a7690bd7d813fe6038c9a9e1c9160af27b99425b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:36:10 -0400 Subject: [PATCH 0522/1385] 1541 add const type of load balancer as UI, and ibmcloud cli shows --- SoftLayer/managers/load_balancer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 3ffa09594..051d2a4e7 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -18,6 +18,11 @@ class LoadBalancerManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ + TYPE = { + 1: "Public to Private", + 0: "Private to Private", + 2: "Public to Public", + } def __init__(self, client): self.client = client From 474f5fb70bcc711f54b744f286ba575b4288d434 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:38:46 -0400 Subject: [PATCH 0523/1385] 1541 add validation of some fields to loadbal detail test --- .../fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 11 +++++++++++ tests/CLI/modules/loadbal_tests.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 70e8b64cb..d2a919ae1 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -58,6 +58,17 @@ 'uuid': 'ab1a1abc-0e83-4690-b5d4-1359625dba8f', } }, + { + 'clientTimeout': 15, + 'defaultPool': { + 'healthMonitor': { + 'uuid': '222222ab-bbcc-4f32-9b31-1b6d3a1959c0' + }, + 'protocol': 'HTTP', + 'protocolPort': 256, + 'uuid': 'ab1a1abc-0e83-4690-b5d4-1359625dba8x', + } + }, {'connectionLimit': None, 'createDate': '2019-08-21T17:19:25-04:00', 'defaultPool': {'createDate': '2019-08-21T17:19:25-04:00', diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 0fba53cea..8c5cb1147 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -215,6 +215,9 @@ def test_lb_health_update_fails(self, update_lb_health_monitors): def test_lb_detail(self): result = self.run_command(['lb', 'detail', '1111111']) self.assert_no_fail(result) + self.assertIn('Id', result.output) + self.assertIn('UUI', result.output) + self.assertIn('Address', result.output) def test_lb_detail_by_name(self): name = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('name') From 046d9be7cb44f74ac7cccd331fa8ac86b14385f8 Mon Sep 17 00:00:00 2001 From: Gonza Rafuls Date: Wed, 6 Oct 2021 16:52:27 +0200 Subject: [PATCH 0524/1385] fix: initialized accountmanger closes:https://github.com/softlayer/softlayer-python/issues/1551 --- SoftLayer/managers/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 1b2ddf6f9..2a8955408 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -7,6 +7,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.managers.account import AccountManager from SoftLayer.managers.block import BlockStorageManager from SoftLayer.managers.cdn import CDNManager from SoftLayer.managers.dedicated_host import DedicatedHostManager @@ -33,6 +34,7 @@ from SoftLayer.managers.vs_placement import PlacementManager __all__ = [ + 'AccountManager', 'BlockStorageManager', 'CapacityManager', 'CDNManager', From 297e0de57bdf1d21b6652b3da24c5583e774fc13 Mon Sep 17 00:00:00 2001 From: Gonza Rafuls Date: Fri, 8 Oct 2021 11:26:51 +0200 Subject: [PATCH 0525/1385] fix: SoftLayerAPIError import on managers/account.py --- SoftLayer/managers/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index e6337e716..51c9c889c 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -8,7 +8,7 @@ import logging -from SoftLayer import SoftLayerAPIError +from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import utils # Invalid names are ignored due to long method names and short argument names From 42d9d7ae5722848f6321f334b964717a2fcf5eb4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 11 Oct 2021 20:46:27 -0400 Subject: [PATCH 0526/1385] #1550 add loadbal l7policies --- SoftLayer/CLI/loadbal/layer7_policy_list.py | 56 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + .../SoftLayer_Network_LBaaS_Listener.py | 23 ++++++++ SoftLayer/managers/load_balancer.py | 26 +++++++++ tests/CLI/modules/loadbal_tests.py | 10 ++++ tests/managers/loadbal_tests.py | 10 ++++ 6 files changed, 126 insertions(+) create mode 100644 SoftLayer/CLI/loadbal/layer7_policy_list.py diff --git a/SoftLayer/CLI/loadbal/layer7_policy_list.py b/SoftLayer/CLI/loadbal/layer7_policy_list.py new file mode 100644 index 000000000..80c408ed1 --- /dev/null +++ b/SoftLayer/CLI/loadbal/layer7_policy_list.py @@ -0,0 +1,56 @@ +"""List Layer7 policies""" +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@click.option('--protocol-id', '-p', + required=False, + type=int, + help="Front-end Protocol identifier") +@environment.pass_env +def policies(env, protocol_id): + """List policies of the front-end protocol (listener).""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + if protocol_id: + l7policies = mgr.get_l7policies(protocol_id) + table = generate_l7policies_table(l7policies, protocol_id) + else: + l7policies = mgr.get_all_l7policies() + table = l7policies_table(l7policies) + env.fout(table) + + +def generate_l7policies_table(l7policies, identifier): + """Takes a list of Layer7 policies and makes a table""" + table = formatting.Table([ + 'Id', 'UUID', 'Name', 'Action', 'Redirect', 'Priority', 'Create Date' + ], title=f"Layer7 policies - protocol ID {identifier}") + + table.align['Name'] = 'l' + table.align['Action'] = 'l' + table.align['Redirect'] = 'l' + for l7policy in sorted(l7policies, key=lambda data: data.get('priority')): + table.add_row([ + l7policy.get('id'), + l7policy.get('uuid'), + l7policy.get('name'), + l7policy.get('action'), + l7policy.get('redirectL7PoolId') or l7policy.get('redirectUrl') or formatting.blank(), + l7policy.get('priority'), + l7policy.get('createDate'), + ]) + return table + + +def l7policies_table(listeners): + """Takes a dict of (protocols: policies list) and makes a list of tables""" + tables = [] + for listener_id, list_policy in listeners.items(): + tables.append(generate_l7policies_table(list_policy, listener_id)) + return tables diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index fd53cc43c..c051ad3fd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -204,6 +204,7 @@ ('loadbal:health', 'SoftLayer.CLI.loadbal.health:cli'), ('loadbal:member-add', 'SoftLayer.CLI.loadbal.members:add'), ('loadbal:member-del', 'SoftLayer.CLI.loadbal.members:remove'), + ('loadbal:l7policies', 'SoftLayer.CLI.loadbal.layer7_policy_list:policies'), ('loadbal:pool-add', 'SoftLayer.CLI.loadbal.pools:add'), ('loadbal:pool-edit', 'SoftLayer.CLI.loadbal.pools:edit'), ('loadbal:pool-del', 'SoftLayer.CLI.loadbal.pools:delete'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py index 57a2459e8..ba814c730 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -41,3 +41,26 @@ 'name': 'ams01', 'statusId': 2 }} + +getL7Policies = [ + {'action': 'REJECT', + 'createDate': '2021-09-08T15:08:35-06:00', + 'id': 123456, + 'modifyDate': None, + 'name': 'test-reject', + 'priority': 2, + 'redirectL7PoolId': None, + 'uuid': '123mock-1234-43c9-b659-12345678mock' + }, + {'action': 'REDIRECT_HTTPS', + 'createDate': '2021-09-08T15:03:53-06:00', + 'id': 432922, + 'modifyDate': None, + 'name': 'test-policy-https-1', + 'priority': 0, + 'redirectL7PoolId': None, + 'redirectUrl': 'url-test-uuid-mock-1234565', + 'uuid': 'test-uuid-mock-1234565' + } +] + diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 051d2a4e7..c2c6b20de 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -160,6 +160,32 @@ def add_lb_listener(self, identifier, listener): return self.client.call('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', identifier, [listener]) + def get_l7policies(self, identifier): + """Gets Layer7 policies from a listener + + :param identifier: id + """ + + return self.client.call('SoftLayer_Network_LBaaS_Listener', 'getL7Policies', id=identifier) + + def get_all_l7policies(self): + """Gets all Layer7 policies + :returns: Dictionary of (protocol_id: policies list). + """ + + mask = 'mask[listeners[l7Policies]]' + lbaas = self.get_lbaas(mask=mask) + listeners = [] + for lb in lbaas: + listeners.extend(lb.get('listeners')) + policies = {} + for protocol in listeners: + if protocol.get('l7Policies'): + listener_id = protocol.get('id') + l7policies = protocol.get('l7Policies') + policies[listener_id] = l7policies + return policies + def add_lb_l7_pool(self, identifier, pool, members, health, session): """Creates a new l7 pool for a LBaaS instance diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 8c5cb1147..cbba6266c 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -181,6 +181,16 @@ def test_lb_member_del(self, click): self.assert_no_fail(result) click.secho.assert_called_with(output, fg='green') + def test_lb_l7policies_list(self): + command = 'loadbal l7policies' + result = self.run_command(command.split(' ')) + self.assert_no_fail(result) + + def test_lb_l7policies_protocol_list(self): + command = 'loadbal l7policies -p 123456' + result = self.run_command(command.split(' ')) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.loadbal.health.click') def test_lb_health_manage(self, click): lb_id = '1111111' diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index d8058edcc..7f580474a 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -110,6 +110,16 @@ def test_add_lb_listener(self): self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', args=(uuid, [listener])) + def test_get_l7policies(self): + my_id = 1111111 + self.lb_mgr.get_l7policies(my_id) + self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'getL7Policies', identifier=my_id) + + def test_get_all_l7policies(self): + policies = self.lb_mgr.get_all_l7policies() + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + self.assertIsInstance(policies, dict) + def test_add_lb_l7_pool(self): uuid = 'aa-bb-cc' pool = {'id': 1} From f621b6f47061dfdad9471c68178db824bd02b7fc Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 11 Oct 2021 20:46:56 -0400 Subject: [PATCH 0527/1385] #1550 add loadbal l7policies docs --- docs/cli/loadbal.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/cli/loadbal.rst b/docs/cli/loadbal.rst index a4116b877..e019e2aa0 100644 --- a/docs/cli/loadbal.rst +++ b/docs/cli/loadbal.rst @@ -40,6 +40,9 @@ LBaaS Commands .. click:: SoftLayer.CLI.loadbal.pools:l7pool_del :prog: loadbal l7pool-del :show-nested: +.. click:: SoftLayer.CLI.loadbal.layer7_policy_list:policies + :prog: loadbal l7policies + :show-nested: .. click:: SoftLayer.CLI.loadbal.order:order :prog: loadbal order :show-nested: From 26f2052f2054d53a731280838c32505cfebf2623 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 12 Oct 2021 11:14:32 -0400 Subject: [PATCH 0528/1385] #1550 fix tox issues --- SoftLayer/CLI/loadbal/layer7_policy_list.py | 1 - SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py | 1 - SoftLayer/managers/load_balancer.py | 5 +++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/loadbal/layer7_policy_list.py b/SoftLayer/CLI/loadbal/layer7_policy_list.py index 80c408ed1..563d1c35f 100644 --- a/SoftLayer/CLI/loadbal/layer7_policy_list.py +++ b/SoftLayer/CLI/loadbal/layer7_policy_list.py @@ -4,7 +4,6 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer import utils @click.command() diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py index ba814c730..69cee2113 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -63,4 +63,3 @@ 'uuid': 'test-uuid-mock-1234565' } ] - diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index c2c6b20de..f7f4cedd5 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -170,14 +170,15 @@ def get_l7policies(self, identifier): def get_all_l7policies(self): """Gets all Layer7 policies + :returns: Dictionary of (protocol_id: policies list). """ mask = 'mask[listeners[l7Policies]]' lbaas = self.get_lbaas(mask=mask) listeners = [] - for lb in lbaas: - listeners.extend(lb.get('listeners')) + for load_bal in lbaas: + listeners.extend(load_bal.get('listeners')) policies = {} for protocol in listeners: if protocol.get('l7Policies'): From 677f4349abf21b3edd4a6bcd23609b6a0bb0c979 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Mon, 18 Oct 2021 09:51:24 +0530 Subject: [PATCH 0529/1385] Changes for snapshot notification enable and disable option from slcli Changes for snapshot notification enable and disable option from slcli --- .../CLI/block/snapshot/get_notify_status.py | 28 +++++++++++++++++++ .../CLI/block/snapshot/set_notify_status.py | 28 +++++++++++++++++++ .../CLI/file/snapshot/get_notify_status.py | 27 ++++++++++++++++++ .../CLI/file/snapshot/set_notify_status.py | 28 +++++++++++++++++++ SoftLayer/CLI/routes.py | 4 +++ SoftLayer/managers/block.py | 19 +++++++++++++ SoftLayer/managers/file.py | 17 +++++++++++ setup.py | 2 +- 8 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/block/snapshot/get_notify_status.py create mode 100644 SoftLayer/CLI/block/snapshot/set_notify_status.py create mode 100644 SoftLayer/CLI/file/snapshot/get_notify_status.py create mode 100644 SoftLayer/CLI/file/snapshot/set_notify_status.py diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py new file mode 100644 index 000000000..854662dfc --- /dev/null +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -0,0 +1,28 @@ +"""Get the snapshots space usage threshold warning flag setting for specific volume""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume_id') +@environment.pass_env +def cli(env, volume_id): + """Get snapshots space usage threshold warning flag setting for a given volume""" + + block_manager = SoftLayer.BlockStorageManager(env.client) + enabled = block_manager.get_block_snapshots_notification_status(volume_id) + + + if (enabled == ''): + click.echo('Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + % (volume_id)) + elif (enabled == 'True'): + click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' + % (volume_id)) + else: + click.echo('DISABLED: Snapshots space usage threshold warning flag setting is disabled for volume %s' + % (volume_id)) \ No newline at end of file diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py new file mode 100644 index 000000000..bef38a40a --- /dev/null +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -0,0 +1,28 @@ +"""Disable/Enable snapshots space usage threshold warning for a specific volume""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume_id') +@click.option('--notification_flag', + help='Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + required=True) +@environment.pass_env +def cli(env, volume_id, notification_flag): + """Enables/Disables snapshot space usage threshold warning for a given volume""" + + if (notification_flag not in ['True', 'False']): + raise exceptions.CLIAbort( + '--notification-flag must be True or False') + + block_manager = SoftLayer.BlockStorageManager(env.client) + disabled = block_manager.set_block_volume_snapshot_notification(volume_id, notification_flag) + + if disabled: + click.echo('Snapshots space usage threshold warning notification has bee set to %s for volume %s' + % (notification-flag, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py new file mode 100644 index 000000000..707365b3a --- /dev/null +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -0,0 +1,27 @@ +"""Get the snapshots space usage threshold warning flag setting for specific volume""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume_id') +@environment.pass_env +def cli(env, volume_id): + """Get snapshots space usage threshold warning flag setting for a given volume""" + + file_manager = SoftLayer.FileStorageManager(env.client) + enabled = file_manager.get_file_snapshots_notification_status(volume_id) + + if (enabled == ''): + click.echo('Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + % (volume_id)) + elif (enabled == 'True'): + click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' + % (volume_id)) + else: + click.echo('Snapshots space usage threshold warning flag setting is disabled for volume %s' + % (volume_id)) \ No newline at end of file diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py new file mode 100644 index 000000000..83155a5ff --- /dev/null +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -0,0 +1,28 @@ +"""Disable/Enable snapshots space usage threshold warning for a specific volume""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume_id') +@click.option('--notification_flag', + help='Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + required=True) +@environment.pass_env +def cli(env, volume_id, notification_flag): + """Enables/Disables snapshot space usage threshold warning for a given volume""" + + if (notification_flag not in ['True', 'False']): + raise exceptions.CLIAbort( + '--notification-flag must be True or False') + + file_manager = SoftLayer.FileStorageManager(env.client) + disabled = file_manager.set_file_volume_snapshot_notification(volume_id, notification_flag) + + if disabled: + click.echo('Snapshots space usage threshold warning notification has bee set to %s for volume %s' + % (notification-flag, volume_id)) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index fd53cc43c..67cbf4cfa 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -103,6 +103,8 @@ ('block:snapshot-create', 'SoftLayer.CLI.block.snapshot.create:cli'), ('block:snapshot-delete', 'SoftLayer.CLI.block.snapshot.delete:cli'), ('block:snapshot-disable', 'SoftLayer.CLI.block.snapshot.disable:cli'), + ('block:snapshot-set-notification', 'SoftLayer.CLI.block.snapshot.set_notify_status:cli'), + ('block:snapshot-get-notification-status', 'SoftLayer.CLI.block.snapshot.get_notify_status:cli'), ('block:snapshot-enable', 'SoftLayer.CLI.block.snapshot.enable:cli'), ('block:snapshot-schedule-list', 'SoftLayer.CLI.block.snapshot.schedule_list:cli'), ('block:snapshot-list', 'SoftLayer.CLI.block.snapshot.list:cli'), @@ -148,6 +150,8 @@ ('file:snapshot-delete', 'SoftLayer.CLI.file.snapshot.delete:cli'), ('file:snapshot-disable', 'SoftLayer.CLI.file.snapshot.disable:cli'), ('file:snapshot-enable', 'SoftLayer.CLI.file.snapshot.enable:cli'), + ('file:snapshot-set-notification', 'SoftLayer.CLI.file.snapshot.set_notify_status:cli'), + ('file:snapshot-get-notification-status', 'SoftLayer.CLI.file.snapshot.get_notify_status:cli'), ('file:snapshot-schedule-list', 'SoftLayer.CLI.file.snapshot.schedule_list:cli'), ('file:snapshot-list', 'SoftLayer.CLI.file.snapshot.list:cli'), ('file:snapshot-order', 'SoftLayer.CLI.file.snapshot.order:cli'), diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 871c9cb27..b38c0c8b7 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -102,6 +102,25 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs): """ return self.get_volume_snapshot_list(volume_id, **kwargs) + def set_block_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): + """Enables/Disables snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :param kwargs: + :param notification-flag: Enable/Disable flag for snapshot warning notification. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id,) + + def get_block_snapshots_notification_status(self, volume_id, **kwargs): + """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id,) + def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 734e54081..2a2e017ea 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -129,7 +129,24 @@ def get_file_volume_snapshot_list(self, volume_id, **kwargs): :return: Returns a list of snapshots for the specified volume. """ return self.get_volume_snapshot_list(volume_id, **kwargs) + def set_file_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): + """Enables/Disables snapshot space usage threshold warning for a given volume. + :param volume_id: ID of volume. + :param kwargs: + :param notification-flag: Enable/Disable flag for snapshot warning notification. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id,) + + def get_file_snapshots_notification_status(self, volume_id, **kwargs): + """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id,) def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, service_offering='storage_as_a_service', diff --git a/setup.py b/setup.py index 4eec2cad5..5248bdb15 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.7', + version='5.9.7-dev', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 727cc90ad8899d25bcca9e93f3a0a871c53bbda9 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Tue, 19 Oct 2021 13:01:01 +0530 Subject: [PATCH 0530/1385] Minor cosmetic and parameter updates --- SoftLayer/CLI/block/snapshot/get_notify_status.py | 2 +- SoftLayer/CLI/block/snapshot/set_notify_status.py | 1 - SoftLayer/managers/block.py | 4 ++-- SoftLayer/managers/file.py | 4 ++-- SoftLayer/managers/storage.py | 9 +++++++++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index 854662dfc..56fae80be 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -24,5 +24,5 @@ def cli(env, volume_id): click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: - click.echo('DISABLED: Snapshots space usage threshold warning flag setting is disabled for volume %s' + click.echo('Snapshots space usage threshold warning flag setting is disabled for volume %s' % (volume_id)) \ No newline at end of file diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index bef38a40a..6cd7084dc 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -15,7 +15,6 @@ @environment.pass_env def cli(env, volume_id, notification_flag): """Enables/Disables snapshot space usage threshold warning for a given volume""" - if (notification_flag not in ['True', 'False']): raise exceptions.CLIAbort( '--notification-flag must be True or False') diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index b38c0c8b7..9094bfb32 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -110,7 +110,7 @@ def set_block_volume_snapshot_notification(self, volume_id, notification_flag, * :param notification-flag: Enable/Disable flag for snapshot warning notification. :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id,) + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id, **kwargs) def get_block_snapshots_notification_status(self, volume_id, **kwargs): """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. @@ -119,7 +119,7 @@ def get_block_snapshots_notification_status(self, volume_id, **kwargs): :param kwargs: :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id,) + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id, **kwargs) def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 2a2e017ea..5b9fb345d 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -137,7 +137,7 @@ def set_file_volume_snapshot_notification(self, volume_id, notification_flag, ** :param notification-flag: Enable/Disable flag for snapshot warning notification. :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id,) + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id, **kwargs) def get_file_snapshots_notification_status(self, volume_id, **kwargs): """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. @@ -146,7 +146,7 @@ def get_file_snapshots_notification_status(self, volume_id, **kwargs): :param kwargs: :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id,) + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id, **kwargs) def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, service_offering='storage_as_a_service', diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 44e76c138..4ac9a6ff7 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -112,6 +112,15 @@ def get_volume_snapshot_list(self, volume_id, **kwargs): kwargs['mask'] = ','.join(items) return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs) + def set_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): + """Returns a list of snapshots for the specified volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Returns a list of snapshots for the specified volume. + """ + + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id , **kwargs) def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, ip_address_ids=None, subnet_ids=None): From f266dccb24ed744b64e3563c90ab5e4f8317c035 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Wed, 20 Oct 2021 21:10:24 +0530 Subject: [PATCH 0531/1385] Removing setup tagging --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5248bdb15..4eec2cad5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.7-dev', + version='5.9.7', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From f7eea013e0ea903e572a3970235f51068979ef30 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Thu, 21 Oct 2021 21:49:09 +0530 Subject: [PATCH 0532/1385] Formatting errors resolution Formatting errors mentioned in https://github.com/softlayer/softlayer-python/pull/1554 being resolved. --- .../CLI/block/snapshot/get_notify_status.py | 12 +++++++----- .../CLI/block/snapshot/set_notify_status.py | 19 +++++++++++-------- .../CLI/file/snapshot/get_notify_status.py | 11 +++++++---- .../CLI/file/snapshot/set_notify_status.py | 19 +++++++++++-------- docs/cli/block.rst | 9 +++++++++ docs/cli/file.rst | 11 ++++++++++- 6 files changed, 55 insertions(+), 26 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index 56fae80be..9ca845637 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -16,13 +16,15 @@ def cli(env, volume_id): block_manager = SoftLayer.BlockStorageManager(env.client) enabled = block_manager.get_block_snapshots_notification_status(volume_id) - if (enabled == ''): - click.echo('Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + click.echo( + 'Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): - click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' + click.echo( + 'Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: - click.echo('Snapshots space usage threshold warning flag setting is disabled for volume %s' - % (volume_id)) \ No newline at end of file + click.echo( + 'Snapshots space usage threshold warning flag setting is disabled for volume %s' + % (volume_id)) diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index 6cd7084dc..cdb260c89 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -9,19 +9,22 @@ @click.command() @click.argument('volume_id') -@click.option('--notification_flag', - help='Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', - required=True) +@click.option( + '--notification_flag', + help= + 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + required=True) @environment.pass_env def cli(env, volume_id, notification_flag): """Enables/Disables snapshot space usage threshold warning for a given volume""" if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort( - '--notification-flag must be True or False') + raise exceptions.CLIAbort('--notification-flag must be True or False') block_manager = SoftLayer.BlockStorageManager(env.client) - disabled = block_manager.set_block_volume_snapshot_notification(volume_id, notification_flag) + disabled = block_manager.set_block_volume_snapshot_notification( + volume_id, notification_flag) if disabled: - click.echo('Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification-flag, volume_id)) + click.echo( + 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' + % (notification - flag, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index 707365b3a..d4afe6654 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -17,11 +17,14 @@ def cli(env, volume_id): enabled = file_manager.get_file_snapshots_notification_status(volume_id) if (enabled == ''): - click.echo('Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + click.echo( + 'Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): - click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' + click.echo( + 'Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: - click.echo('Snapshots space usage threshold warning flag setting is disabled for volume %s' - % (volume_id)) \ No newline at end of file + click.echo( + 'Snapshots space usage threshold warning flag setting is disabled for volume %s' + % (volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 83155a5ff..112939cc3 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -9,20 +9,23 @@ @click.command() @click.argument('volume_id') -@click.option('--notification_flag', - help='Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', - required=True) +@click.option( + '--notification_flag', + help= + 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + required=True) @environment.pass_env def cli(env, volume_id, notification_flag): """Enables/Disables snapshot space usage threshold warning for a given volume""" if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort( - '--notification-flag must be True or False') + raise exceptions.CLIAbort('--notification-flag must be True or False') file_manager = SoftLayer.FileStorageManager(env.client) - disabled = file_manager.set_file_volume_snapshot_notification(volume_id, notification_flag) + disabled = file_manager.set_file_volume_snapshot_notification( + volume_id, notification_flag) if disabled: - click.echo('Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification-flag, volume_id)) + click.echo( + 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' + % (notification - flag, volume_id)) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 18a324397..b5655709f 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -146,3 +146,12 @@ Block Commands .. click:: SoftLayer.CLI.block.replication.disaster_recovery_failover:cli :prog: block disaster-recovery-failover :show-nested: + + +.. click:: SoftLayer.CLI.block.snapshot.set_notify_status:cli + :prog: block snapshot-set-notification + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.get_notify_status:cli + :prog: block snapshot-get-notification + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 6c914b1a4..7ecc0bc75 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -125,4 +125,13 @@ File Commands .. click:: SoftLayer.CLI.file.replication.disaster_recovery_failover:cli :prog: file disaster-recovery-failover - :show-nested: \ No newline at end of file + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.set_notify_status:cli + :prog: file snapshot-set-notification + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.get_notify_status:cli + :prog: file snapshot-get-notification + :show-nested: + From 854087ed6ac5244e9ed024387a3dd5a43b24ed4d Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Thu, 21 Oct 2021 22:02:14 +0530 Subject: [PATCH 0533/1385] updating get command --- docs/cli/block.rst | 2 +- docs/cli/file.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index b5655709f..2f3cbfcfc 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -153,5 +153,5 @@ Block Commands :show-nested: .. click:: SoftLayer.CLI.block.snapshot.get_notify_status:cli - :prog: block snapshot-get-notification + :prog: block snapshot-get-notification-status :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 7ecc0bc75..93b5c5331 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -132,6 +132,6 @@ File Commands :show-nested: .. click:: SoftLayer.CLI.file.snapshot.get_notify_status:cli - :prog: file snapshot-get-notification + :prog: file snapshot-get-notification-status :show-nested: From 564e2417bb2d01c91594359e0a200000032f5a29 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 22 Oct 2021 14:13:27 -0400 Subject: [PATCH 0534/1385] #1555 Fix hw billing reports 0 items --- SoftLayer/CLI/hardware/billing.py | 2 +- SoftLayer/fixtures/SoftLayer_Hardware.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index 55c68c485..6989489bf 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -31,7 +31,7 @@ def cli(env, identifier): table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) price_table = formatting.Table(['Item', 'Recurring Price']) - for item in utils.lookup(result, 'billingItem', 'children') or []: + for item in utils.lookup(result, 'billingItem', 'nextInvoiceChildren') or []: price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 770de045c..18fc5a7d5 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -7,7 +7,7 @@ 'id': 6327, 'recurringFee': 1.54, 'nextInvoiceTotalRecurringAmount': 16.08, - 'children': [ + 'nextInvoiceChildren': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], 'orderItem': { From b3d28a262bea0b092a5d71e1168aa9864609c686 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 22 Oct 2021 15:16:40 -0400 Subject: [PATCH 0535/1385] #1555 update hw billing test --- SoftLayer/fixtures/SoftLayer_Hardware.py | 2 +- tests/CLI/modules/server_tests.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 18fc5a7d5..770de045c 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -7,7 +7,7 @@ 'id': 6327, 'recurringFee': 1.54, 'nextInvoiceTotalRecurringAmount': 16.08, - 'nextInvoiceChildren': [ + 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], 'orderItem': { diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index b558811a2..d688a6a90 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -868,19 +868,25 @@ def test_hardware_storage(self): def test_billing(self): result = self.run_command(['hw', 'billing', '123456']) - billing_json = { + hardware_server_billing = { 'Billing Item Id': 6327, 'Id': '123456', 'Provision Date': None, 'Recurring Fee': 1.54, 'Total': 16.08, - 'prices': [{ - 'Item': 'test', - 'Recurring Price': 1 - }] + 'prices': [ + { + 'Item': 'test', + 'Recurring Price': 1 + }, + { + 'Item': 'test2', + 'Recurring Price': 2 + }, + ] } self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), billing_json) + self.assertEqual(json.loads(result.output), hardware_server_billing) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_hw_no_confirm(self, confirm_mock): From da4593ec14163a5ec46195b486aa1b78d62e2576 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Sat, 23 Oct 2021 12:33:49 +0530 Subject: [PATCH 0536/1385] Incorporating review comments --- .../CLI/block/snapshot/set_notify_status.py | 23 +++++++++--------- .../CLI/file/snapshot/set_notify_status.py | 24 +++++++++---------- SoftLayer/managers/storage.py | 18 ++++++++++---- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index cdb260c89..9adffec87 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -9,22 +9,23 @@ @click.command() @click.argument('volume_id') -@click.option( - '--notification_flag', +@click.option('--enable/--disable', default=True, help= - 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable', required=True) @environment.pass_env -def cli(env, volume_id, notification_flag): +def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" - if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort('--notification-flag must be True or False') - block_manager = SoftLayer.BlockStorageManager(env.client) - disabled = block_manager.set_block_volume_snapshot_notification( - volume_id, notification_flag) - if disabled: + if enable: + enabled = 'True' + else: + enabled = 'False' + status = block_manager.set_block_volume_snapshot_notification( + volume_id, enabled) + + if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification - flag, volume_id)) + % (enabled, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 112939cc3..80b8494fa 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -9,23 +9,23 @@ @click.command() @click.argument('volume_id') -@click.option( - '--notification_flag', +@click.option('--enable/--disable', default=True, help= - 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable' , required=True) @environment.pass_env -def cli(env, volume_id, notification_flag): +def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" - - if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort('--notification-flag must be True or False') - file_manager = SoftLayer.FileStorageManager(env.client) - disabled = file_manager.set_file_volume_snapshot_notification( - volume_id, notification_flag) - if disabled: + if enable: + enabled = 'True' + else: + enabled = 'False' + + status = file_manager.set_file_volume_snapshot_notification( + volume_id, enabled) + if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification - flag, volume_id)) + % (enable, volume_id)) \ No newline at end of file diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 4ac9a6ff7..734e8f9e2 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -112,15 +112,23 @@ def get_volume_snapshot_list(self, volume_id, **kwargs): kwargs['mask'] = ','.join(items) return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs) - def set_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): - """Returns a list of snapshots for the specified volume. + def set_volume_snapshot_notification(self, volume_id, enable): + """Enables/Disables snapshot space usage threshold warning for a given volume. :param volume_id: ID of volume. - :param kwargs: - :return: Returns a list of snapshots for the specified volume. + :param enable: Enable/Disable flag for snapshot warning notification. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id , **kwargs) + return self.client.call('Network_Storage', 'setSnapshotNotification', enable, id=volume_id) + + def get_volume_snapshot_notification_status(self, volume_id): + """returns Enabled/Disabled status of snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id) def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, ip_address_ids=None, subnet_ids=None): From cce0849fb600447774daf7ff44f33e9b21bf7a3b Mon Sep 17 00:00:00 2001 From: cmp Date: Thu, 28 Oct 2021 23:07:48 -0500 Subject: [PATCH 0537/1385] Update API docs link and remove travisCI mention Fixes #1538 --- docs/dev/index.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/dev/index.rst b/docs/dev/index.rst index a0abdcc13..9174186cd 100644 --- a/docs/dev/index.rst +++ b/docs/dev/index.rst @@ -72,10 +72,7 @@ your code. You can run only the linting checks by using this command: The project's configuration instructs tox to test against many different versions of Python. A tox test will use as many of those as it can find on your -local computer. Rather than installing all those versions, we recommend that -you point the `Travis `_ continuous integration tool at -your GitHub fork. Travis will run the test against the full suite of Python -versions every time you push new code. +local computer. Using tox to run tests in multiple environments can be very time consuming. If you wish to quickly run the tests in your own environment, you @@ -178,7 +175,7 @@ Developer Resources ------------------- .. toctree:: - SoftLayer API Documentation + SoftLayer API Documentation Source on GitHub Issues Pull Requests From 4ccb9b823048d80a3b06124b6a0255f1ae0bf0b0 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Wed, 3 Nov 2021 22:00:09 +0530 Subject: [PATCH 0538/1385] Incorporating review comments --- .../CLI/block/snapshot/get_notify_status.py | 8 ++++---- .../CLI/block/snapshot/set_notify_status.py | 8 ++------ .../CLI/file/snapshot/get_notify_status.py | 8 ++++---- .../CLI/file/snapshot/set_notify_status.py | 9 ++------- SoftLayer/managers/block.py | 19 ------------------- SoftLayer/managers/file.py | 17 ----------------- 6 files changed, 12 insertions(+), 57 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index 9ca845637..d7f04ff7f 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -14,17 +14,17 @@ def cli(env, volume_id): """Get snapshots space usage threshold warning flag setting for a given volume""" block_manager = SoftLayer.BlockStorageManager(env.client) - enabled = block_manager.get_block_snapshots_notification_status(volume_id) + enabled = block_manager.get_volume_snapshot_notification_status(volume_id) if (enabled == ''): click.echo( - 'Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + 'Enabled:Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): click.echo( - 'Snapshots space usage threshold warning flag setting is enabled for volume %s' + 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: click.echo( - 'Snapshots space usage threshold warning flag setting is disabled for volume %s' + 'Disabled:Snapshots space usage threshold warning flag setting is disabled for volume %s' % (volume_id)) diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index 9adffec87..82d30056d 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -18,12 +18,8 @@ def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" block_manager = SoftLayer.BlockStorageManager(env.client) - if enable: - enabled = 'True' - else: - enabled = 'False' - status = block_manager.set_block_volume_snapshot_notification( - volume_id, enabled) + status = block_manager.set_volume_snapshot_notification( + volume_id, enable) if status: click.echo( diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index d4afe6654..f18b41aba 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -14,17 +14,17 @@ def cli(env, volume_id): """Get snapshots space usage threshold warning flag setting for a given volume""" file_manager = SoftLayer.FileStorageManager(env.client) - enabled = file_manager.get_file_snapshots_notification_status(volume_id) + enabled = file_manager.get_volume_snapshot_notification_status(volume_id) if (enabled == ''): click.echo( - 'Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + 'Enabled:Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): click.echo( - 'Snapshots space usage threshold warning flag setting is enabled for volume %s' + 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: click.echo( - 'Snapshots space usage threshold warning flag setting is disabled for volume %s' + 'Disabled:Snapshots space usage threshold warning flag setting is disabled for volume %s' % (volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 80b8494fa..90b3b7d34 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -18,13 +18,8 @@ def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" file_manager = SoftLayer.FileStorageManager(env.client) - if enable: - enabled = 'True' - else: - enabled = 'False' - - status = file_manager.set_file_volume_snapshot_notification( - volume_id, enabled) + status = file_manager.set_volume_snapshot_notification( + volume_id, enable) if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 9094bfb32..871c9cb27 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -102,25 +102,6 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs): """ return self.get_volume_snapshot_list(volume_id, **kwargs) - def set_block_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): - """Enables/Disables snapshot space usage threshold warning for a given volume. - - :param volume_id: ID of volume. - :param kwargs: - :param notification-flag: Enable/Disable flag for snapshot warning notification. - :return: Enables/Disables snapshot space usage threshold warning for a given volume. - """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id, **kwargs) - - def get_block_snapshots_notification_status(self, volume_id, **kwargs): - """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. - - :param volume_id: ID of volume. - :param kwargs: - :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. - """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id, **kwargs) - def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 5b9fb345d..734e54081 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -129,24 +129,7 @@ def get_file_volume_snapshot_list(self, volume_id, **kwargs): :return: Returns a list of snapshots for the specified volume. """ return self.get_volume_snapshot_list(volume_id, **kwargs) - def set_file_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): - """Enables/Disables snapshot space usage threshold warning for a given volume. - :param volume_id: ID of volume. - :param kwargs: - :param notification-flag: Enable/Disable flag for snapshot warning notification. - :return: Enables/Disables snapshot space usage threshold warning for a given volume. - """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id, **kwargs) - - def get_file_snapshots_notification_status(self, volume_id, **kwargs): - """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. - - :param volume_id: ID of volume. - :param kwargs: - :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. - """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id, **kwargs) def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, service_offering='storage_as_a_service', From 84b88aae2c18ca312c54b9b884938ab643adc732 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Nov 2021 17:26:26 -0400 Subject: [PATCH 0539/1385] return error internal in vs usage feature when sent the valid-type in lowercase --- SoftLayer/CLI/virt/usage.py | 4 ++-- tests/CLI/modules/vs/vs_tests.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/usage.py b/SoftLayer/CLI/virt/usage.py index 9936d9416..c7404fd45 100644 --- a/SoftLayer/CLI/virt/usage.py +++ b/SoftLayer/CLI/virt/usage.py @@ -30,7 +30,7 @@ def cli(env, identifier, start_date, end_date, valid_type, summary_period): vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') result = vsi.get_summary_data_usage(vs_id, start_date=start_date, end_date=end_date, - valid_type=valid_type, summary_period=summary_period) + valid_type=valid_type.upper(), summary_period=summary_period) if len(result) == 0: raise exceptions.CLIAbort('No metric data for this range of dates provided') @@ -38,7 +38,7 @@ def cli(env, identifier, start_date, end_date, valid_type, summary_period): count = 0 counter = 0.00 for data in result: - if valid_type == "MEMORY_USAGE": + if valid_type.upper() == "MEMORY_USAGE": usage_counter = data['counter'] / 2 ** 30 else: usage_counter = data['counter'] diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 97dc52ea4..892a823fd 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -736,6 +736,13 @@ def test_usage_vs_cpu(self): self.assert_no_fail(result) + def test_usage_vs_cpu_lower_case(self): + result = self.run_command( + ['vs', 'usage', '100', '--start_date=2019-3-4', '--end_date=2019-4-2', '--valid_type=cpu0', + '--summary_period=300']) + + self.assert_no_fail(result) + def test_usage_vs_memory(self): result = self.run_command( ['vs', 'usage', '100', '--start_date=2019-3-4', '--end_date=2019-4-2', '--valid_type=MEMORY_USAGE', From d2ac1a62b8bd745334add524aba3a26ca9a4b531 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Thu, 4 Nov 2021 17:37:47 +0530 Subject: [PATCH 0540/1385] TOX formatting resolution --- .../CLI/block/snapshot/get_notify_status.py | 8 +- .../CLI/block/snapshot/set_notify_status.py | 15 +- .../CLI/file/snapshot/get_notify_status.py | 3 +- .../CLI/file/snapshot/set_notify_status.py | 13 +- SoftLayer/managers/storage.py | 253 +++++++++++++----- 5 files changed, 197 insertions(+), 95 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index d7f04ff7f..f247b3c6d 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -4,7 +4,6 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions @click.command() @@ -12,14 +11,13 @@ @environment.pass_env def cli(env, volume_id): """Get snapshots space usage threshold warning flag setting for a given volume""" - block_manager = SoftLayer.BlockStorageManager(env.client) enabled = block_manager.get_volume_snapshot_notification_status(volume_id) if (enabled == ''): - click.echo( - 'Enabled:Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' - % (volume_id)) + click.echo(""" + Enabled:Snapshots space usage warning flag is null. Set to default value enable. For volume %s + """ % (volume_id)) elif (enabled == 'True'): click.echo( 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index 82d30056d..51733b381 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -4,24 +4,25 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions @click.command() @click.argument('volume_id') -@click.option('--enable/--disable', default=True, - help= - 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable', +@click.option( + '--enable/--disable', + default=True, + help=""" + Enable/Disable snapshot notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable + """, required=True) @environment.pass_env def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" block_manager = SoftLayer.BlockStorageManager(env.client) - status = block_manager.set_volume_snapshot_notification( - volume_id, enable) + status = block_manager.set_volume_snapshot_notification(volume_id, enable) if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (enabled, volume_id)) + % (enable, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index f18b41aba..32616f6cd 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -4,7 +4,6 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions @click.command() @@ -18,7 +17,7 @@ def cli(env, volume_id): if (enabled == ''): click.echo( - 'Enabled:Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + 'Enabled:Snapshots space usage threshold warning flag is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): click.echo( diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 90b3b7d34..7d5b3e5b7 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -4,23 +4,22 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions @click.command() @click.argument('volume_id') -@click.option('--enable/--disable', default=True, - help= - 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable' , +@click.option( + '--enable/--disable', + default=True, + help='Enable/Disable snapshot notification. Use `slcli file snapshot-set-notification volumeId --enable` to enable', required=True) @environment.pass_env def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" file_manager = SoftLayer.FileStorageManager(env.client) - status = file_manager.set_volume_snapshot_notification( - volume_id, enable) + status = file_manager.set_volume_snapshot_notification(volume_id, enable) if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (enable, volume_id)) \ No newline at end of file + % (enable, volume_id)) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 734e8f9e2..a3176583f 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -9,7 +9,6 @@ from SoftLayer.managers import storage_utils from SoftLayer import utils - # pylint: disable=too-many-public-methods @@ -20,7 +19,6 @@ class StorageManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ - def __init__(self, client): self.configuration = {} self.client = client @@ -69,7 +67,10 @@ def get_volume_details(self, volume_id, **kwargs): 'notes', ] kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) + return self.client.call('Network_Storage', + 'getObject', + id=volume_id, + **kwargs) def get_volume_access_list(self, volume_id, **kwargs): """Returns a list of authorized hosts for a specified volume. @@ -87,7 +88,10 @@ def get_volume_access_list(self, volume_id, **kwargs): 'allowedIpAddresses[allowedHost[credential]]', ] kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) + return self.client.call('Network_Storage', + 'getObject', + id=volume_id, + **kwargs) def get_volume_snapshot_list(self, volume_id, **kwargs): """Returns a list of snapshots for the specified volume. @@ -98,20 +102,18 @@ def get_volume_snapshot_list(self, volume_id, **kwargs): """ if 'mask' not in kwargs: items = [ - 'id', - 'notes', - 'snapshotSizeBytes', - 'storageType[keyName]', - 'snapshotCreationTimestamp', - 'intervalSchedule', - 'hourlySchedule', - 'dailySchedule', - 'weeklySchedule' + 'id', 'notes', 'snapshotSizeBytes', 'storageType[keyName]', + 'snapshotCreationTimestamp', 'intervalSchedule', + 'hourlySchedule', 'dailySchedule', 'weeklySchedule' ] kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs) + return self.client.call('Network_Storage', + 'getSnapshots', + id=volume_id, + **kwargs) + def set_volume_snapshot_notification(self, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume. @@ -120,7 +122,10 @@ def set_volume_snapshot_notification(self, volume_id, enable): :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', enable, id=volume_id) + return self.client.call('Network_Storage', + 'setSnapshotNotification', + enable, + id=volume_id) def get_volume_snapshot_notification_status(self, volume_id): """returns Enabled/Disabled status of snapshot space usage threshold warning for a given volume. @@ -128,10 +133,16 @@ def get_volume_snapshot_notification_status(self, volume_id): :param volume_id: ID of volume. :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id) - - def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, - ip_address_ids=None, subnet_ids=None): + return self.client.call('Network_Storage', + 'getSnapshotNotificationStatus', + id=volume_id) + + def authorize_host_to_volume(self, + volume_id, + hardware_ids=None, + virtual_guest_ids=None, + ip_address_ids=None, + subnet_ids=None): """Authorizes hosts to Storage Volumes :param volume_id: The File volume to authorize hosts to @@ -142,13 +153,20 @@ def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_i :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects which now have access to the given volume """ - host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids, - ip_address_ids, subnet_ids) - - return self.client.call('Network_Storage', 'allowAccessFromHostList', host_templates, id=volume_id) - - def deauthorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, - ip_address_ids=None, subnet_ids=None): + host_templates = storage_utils.populate_host_templates( + hardware_ids, virtual_guest_ids, ip_address_ids, subnet_ids) + + return self.client.call('Network_Storage', + 'allowAccessFromHostList', + host_templates, + id=volume_id) + + def deauthorize_host_to_volume(self, + volume_id, + hardware_ids=None, + virtual_guest_ids=None, + ip_address_ids=None, + subnet_ids=None): """Revokes authorization of hosts to File Storage Volumes :param volume_id: The File volume to deauthorize hosts to @@ -159,10 +177,13 @@ def deauthorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects which have access to the given File volume """ - host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids, - ip_address_ids, subnet_ids) + host_templates = storage_utils.populate_host_templates( + hardware_ids, virtual_guest_ids, ip_address_ids, subnet_ids) - return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id) + return self.client.call('Network_Storage', + 'removeAccessFromHostList', + host_templates, + id=volume_id) def get_replication_partners(self, volume_id): """Acquires list of replicant volumes pertaining to the given volume. @@ -170,7 +191,9 @@ def get_replication_partners(self, volume_id): :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) + 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. @@ -178,9 +201,16 @@ def get_replication_locations(self, volume_id): :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): + 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 volume. :param volume_id: The ID of the primary volume to be replicated @@ -199,15 +229,17 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No 'weeklySchedule,storageType[keyName],provisionedIops' block_volume = self.get_volume_details(volume_id, mask=block_mask) - storage_class = storage_utils.block_or_file(block_volume['storageType']['keyName']) + storage_class = storage_utils.block_or_file( + block_volume['storageType']['keyName']) order = storage_utils.prepare_replicant_order_object( - self, snapshot_schedule, location, tier, block_volume, storage_class - ) + self, snapshot_schedule, location, tier, block_volume, + storage_class) if storage_class == 'block': if os_type is None: - if isinstance(utils.lookup(block_volume, 'osType', 'keyName'), str): + if isinstance(utils.lookup(block_volume, 'osType', 'keyName'), + str): os_type = block_volume['osType']['keyName'] else: raise exceptions.SoftLayerError( @@ -217,9 +249,15 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No return self.client.call('Product_Order', 'placeOrder', order) - def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, duplicate_size=None, - duplicate_iops=None, duplicate_tier_level=None, duplicate_snapshot_size=None, - hourly_billing_flag=False, dependent_duplicate=False): + def order_duplicate_volume(self, + origin_volume_id, + origin_snapshot_id=None, + duplicate_size=None, + duplicate_iops=None, + duplicate_tier_level=None, + duplicate_snapshot_size=None, + hourly_billing_flag=False, + dependent_duplicate=False): """Places an order for a duplicate volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -236,19 +274,23 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, dupl 'storageType[keyName],capacityGb,originalVolumeSize,' \ 'provisionedIops,storageTierLevel,osType[keyName],' \ 'staasVersion,hasEncryptionAtRest' - origin_volume = self.get_volume_details(origin_volume_id, mask=block_mask) - storage_class = storage_utils.block_or_file(origin_volume['storageType']['keyName']) + origin_volume = self.get_volume_details(origin_volume_id, + mask=block_mask) + storage_class = storage_utils.block_or_file( + origin_volume['storageType']['keyName']) order = storage_utils.prepare_duplicate_order_object( self, origin_volume, duplicate_iops, duplicate_tier_level, - duplicate_size, duplicate_snapshot_size, storage_class, hourly_billing_flag - ) + duplicate_size, duplicate_snapshot_size, storage_class, + hourly_billing_flag) if storage_class == 'block': - if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), str): + if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), + str): os_type = origin_volume['osType']['keyName'] else: - raise exceptions.SoftLayerError("Cannot find origin volume's os-type") + raise exceptions.SoftLayerError( + "Cannot find origin volume's os-type") order['osFormatType'] = {'keyName': os_type} @@ -260,7 +302,11 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, dupl return self.client.call('Product_Order', 'placeOrder', order) - def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None): + def order_modified_volume(self, + volume_id, + new_size=None, + new_iops=None, + new_tier_level=None): """Places an order for modifying an existing block volume. :param volume_id: The ID of the volume to be modified @@ -284,8 +330,7 @@ def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tie volume = self.get_volume_details(volume_id, mask=block_mask) order = storage_utils.prepare_modify_order_object( - self, volume, new_iops, new_tier_level, new_size - ) + self, volume, new_iops, new_tier_level, new_size) return self.client.call('Product_Order', 'placeOrder', order) @@ -297,14 +342,19 @@ def volume_set_note(self, volume_id, note): :return: Returns true if success """ template = {'notes': note} - return self.client.call('SoftLayer_Network_Storage', 'editObject', template, id=volume_id) + return self.client.call('SoftLayer_Network_Storage', + 'editObject', + template, + id=volume_id) def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. :param snapshot_id: The ID of the snapshot object to delete. """ - return self.client.call('Network_Storage', 'deleteObject', id=snapshot_id) + return self.client.call('Network_Storage', + 'deleteObject', + id=snapshot_id) def create_snapshot(self, volume_id, notes='', **kwargs): """Creates a snapshot on the given block volume. @@ -313,9 +363,14 @@ def create_snapshot(self, volume_id, notes='', **kwargs): :param string notes: The notes or "name" to assign the snapshot :return: Returns the id of the new snapshot """ - return self.client.call('Network_Storage', 'createSnapshot', notes, id=volume_id, **kwargs) - - def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): + return self.client.call('Network_Storage', + 'createSnapshot', + notes, + id=volume_id, + **kwargs) + + def order_snapshot_space(self, volume_id, capacity, tier, upgrade, + **kwargs): """Orders snapshot space for the given block volume. :param integer volume_id: The id of the volume @@ -329,11 +384,15 @@ def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): 'staasVersion,hasEncryptionAtRest' volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs) - order = storage_utils.prepare_snapshot_order_object(self, volume, capacity, tier, upgrade) + order = storage_utils.prepare_snapshot_order_object( + self, volume, capacity, tier, upgrade) return self.client.call('Product_Order', 'placeOrder', order) - def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate=False): + def cancel_snapshot_space(self, + volume_id, + reason='No longer needed', + immediate=False): """Cancels snapshot space for a given volume. :param integer volume_id: The volume ID @@ -345,7 +404,8 @@ def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate= volume = self.get_volume_details(volume_id, mask=object_mask) if 'activeChildren' not in volume['billingItem']: - raise exceptions.SoftLayerError('No snapshot space found to cancel') + raise exceptions.SoftLayerError( + 'No snapshot space found to cancel') children_array = volume['billingItem']['activeChildren'] billing_item_id = None @@ -356,14 +416,21 @@ def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate= break if not billing_item_id: - raise exceptions.SoftLayerError('No snapshot space found to cancel') + raise exceptions.SoftLayerError( + 'No snapshot space found to cancel') if utils.lookup(volume, 'billingItem', 'hourlyFlag'): immediate = True - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) + return self.client.call('SoftLayer_Billing_Item', + 'cancelItem', + immediate, + True, + reason, + id=billing_item_id) - def enable_snapshots(self, volume_id, schedule_type, retention_count, minute, hour, day_of_week, **kwargs): + def enable_snapshots(self, volume_id, schedule_type, retention_count, + minute, hour, day_of_week, **kwargs): """Enables snapshots for a specific block volume at a given schedule :param integer volume_id: The id of the volume @@ -374,8 +441,15 @@ def enable_snapshots(self, volume_id, schedule_type, retention_count, minute, ho :param string day_of_week: Day when to take snapshot :return: Returns whether successfully scheduled or not """ - return self.client.call('Network_Storage', 'enableSnapshots', schedule_type, retention_count, - minute, hour, day_of_week, id=volume_id, **kwargs) + return self.client.call('Network_Storage', + 'enableSnapshots', + schedule_type, + retention_count, + minute, + hour, + day_of_week, + id=volume_id, + **kwargs) def disable_snapshots(self, volume_id, schedule_type): """Disables snapshots for a specific block volume at a given schedule @@ -384,7 +458,10 @@ def disable_snapshots(self, volume_id, schedule_type): :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' :return: Returns whether successfully disabled or not """ - return self.client.call('Network_Storage', 'disableSnapshots', schedule_type, id=volume_id) + return self.client.call('Network_Storage', + 'disableSnapshots', + schedule_type, + id=volume_id) def list_volume_schedules(self, volume_id): """Lists schedules for a given volume @@ -393,7 +470,10 @@ def list_volume_schedules(self, volume_id): :return: Returns list of schedules assigned to a given volume """ object_mask = 'schedules[type,properties[type]]' - volume_detail = self.client.call('Network_Storage', 'getObject', id=volume_id, mask=object_mask) + volume_detail = self.client.call('Network_Storage', + 'getObject', + id=volume_id, + mask=object_mask) return utils.lookup(volume_detail, 'schedules') @@ -404,7 +484,10 @@ def restore_from_snapshot(self, volume_id, snapshot_id): :param integer snapshot_id: The id of the restore point :return: Returns whether succesfully restored or not """ - return self.client.call('Network_Storage', 'restoreFromSnapshot', snapshot_id, id=volume_id) + return self.client.call('Network_Storage', + 'restoreFromSnapshot', + snapshot_id, + id=volume_id) def failover_to_replicant(self, volume_id, replicant_id): """Failover to a volume replicant. @@ -413,7 +496,10 @@ def failover_to_replicant(self, volume_id, replicant_id): :param integer replicant_id: ID of replicant to failover to :return: Returns whether failover was successful or not """ - return self.client.call('Network_Storage', 'failoverToReplicant', replicant_id, id=volume_id) + return self.client.call('Network_Storage', + 'failoverToReplicant', + replicant_id, + id=volume_id) def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): """Disaster Recovery Failover to a volume replicant. @@ -422,7 +508,10 @@ def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): :param integer replicant: ID of replicant to failover to :return: Returns whether failover to successful or not """ - return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) + return self.client.call('Network_Storage', + 'disasterRecoveryFailoverToReplicant', + replicant_id, + id=volume_id) def failback_from_replicant(self, volume_id): """Failback from a volume replicant. @@ -430,9 +519,14 @@ def failback_from_replicant(self, volume_id): :param integer volume_id: The id of the volume :return: Returns whether failback was successful or not """ - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) - - def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): + return self.client.call('Network_Storage', + 'failbackFromReplicant', + id=volume_id) + + def cancel_volume(self, + volume_id, + reason='No longer needed', + immediate=False): """Cancels the given storage volume. :param integer volume_id: The volume ID @@ -443,14 +537,20 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): volume = self.get_volume_details(volume_id, mask=object_mask) if 'billingItem' not in volume: - raise exceptions.SoftLayerError("Storage Volume was already cancelled") + raise exceptions.SoftLayerError( + "Storage Volume was already cancelled") billing_item_id = volume['billingItem']['id'] if utils.lookup(volume, 'billingItem', 'hourlyFlag'): immediate = True - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) + return self.client.call('SoftLayer_Billing_Item', + 'cancelItem', + immediate, + True, + reason, + id=billing_item_id) def refresh_dupe(self, volume_id, snapshot_id): """"Refresh a duplicate volume with a snapshot from its parent. @@ -458,11 +558,16 @@ def refresh_dupe(self, volume_id, snapshot_id): :param integer volume_id: The id of the volume :param integer snapshot_id: The id of the snapshot """ - return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id) + return self.client.call('Network_Storage', + 'refreshDuplicate', + snapshot_id, + id=volume_id) def convert_dep_dupe(self, volume_id): """Convert a dependent duplicate volume to an independent volume. :param integer volume_id: The id of the volume. """ - return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', id=volume_id) + return self.client.call('Network_Storage', + 'convertCloneDependentToIndependent', + id=volume_id) From e23440d06641357a55406cb79e9ca85d4e2b07f2 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Thu, 4 Nov 2021 22:46:11 +0530 Subject: [PATCH 0541/1385] TOX issue resolution --- SoftLayer/CLI/block/snapshot/get_notify_status.py | 4 ++-- SoftLayer/CLI/file/snapshot/get_notify_status.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index f247b3c6d..25344f812 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -14,11 +14,11 @@ def cli(env, volume_id): block_manager = SoftLayer.BlockStorageManager(env.client) enabled = block_manager.get_volume_snapshot_notification_status(volume_id) - if (enabled == ''): + if enabled == '': click.echo(""" Enabled:Snapshots space usage warning flag is null. Set to default value enable. For volume %s """ % (volume_id)) - elif (enabled == 'True'): + elif enabled == 'True': click.echo( 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index 32616f6cd..9724046ee 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -15,11 +15,11 @@ def cli(env, volume_id): file_manager = SoftLayer.FileStorageManager(env.client) enabled = file_manager.get_volume_snapshot_notification_status(volume_id) - if (enabled == ''): + if enabled == '': click.echo( 'Enabled:Snapshots space usage threshold warning flag is null. Set to default value enable. For volume %s' % (volume_id)) - elif (enabled == 'True'): + elif enabled == 'True': click.echo( 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) From 90631f8ff48185674de97b90c61364f737c62c0e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 8 Nov 2021 16:33:21 -0600 Subject: [PATCH 0542/1385] #1561 added a warning about vs bandwidth summary period, and specifically send in None to the API so the command will at least work --- SoftLayer/CLI/virt/bandwidth.py | 9 ++++++++- tests/CLI/modules/vs/vs_tests.py | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index 68d3d986c..c91ff6ffc 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -36,7 +36,14 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): """ vsi = SoftLayer.VSManager(env.client) vsi_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') - data = vsi.get_bandwidth_data(vsi_id, start_date, end_date, None, summary_period) + + # Summary period is broken for virtual guests, check VIRT-11733 for a resolution. + # For now, we are going to ignore summary_period and set it to the default the API imposes + if summary_period != 300: + click.secho("""The Summary Period option is currently set to the 300s as the backend API will throw an exception +any other value. This should be resolved in the next version of the slcli.""", fg='yellow') + summary_period = 300 + data = vsi.get_bandwidth_data(vsi_id, start_date, end_date, None, None) title = "Bandwidth Report: %s - %s" % (start_date, end_date) table, sum_table = create_bandwidth_table(data, summary_period, title) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 97dc52ea4..f438a1bb1 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -754,6 +754,7 @@ def test_usage_metric_data_empty(self): self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_bandwidth_vs(self): + self.skipTest("Skipping until VIRT-11733 is released") if sys.version_info < (3, 6): self.skipTest("Test requires python 3.6+") @@ -782,6 +783,7 @@ def test_bandwidth_vs(self): self.assertEqual(output_list[0]['Pub In'], 1.3503) def test_bandwidth_vs_quite(self): + self.skipTest("Skipping until VIRT-11733 is released") result = self.run_command(['vs', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01', '-q']) self.assert_no_fail(result) From a02f4913ddf1a68e395af28c29bf32e558717994 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 11 Nov 2021 15:09:08 -0400 Subject: [PATCH 0543/1385] Add Item names to vs billing report --- SoftLayer/CLI/virt/billing.py | 6 +-- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 35 ++++++++++---- SoftLayer/managers/vs.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 48 +++++++++---------- 4 files changed, 53 insertions(+), 38 deletions(-) diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py index 7312de8d8..ddc785361 100644 --- a/SoftLayer/CLI/virt/billing.py +++ b/SoftLayer/CLI/virt/billing.py @@ -28,11 +28,11 @@ def cli(env, identifier): table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) table.add_row(['Recurring Fee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) - table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) + table.add_row(['Provision Date', utils.lookup(result, 'provisionDate')]) - price_table = formatting.Table(['Recurring Price']) + price_table = formatting.Table(['description', 'Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['nextInvoiceTotalRecurringAmount']]) + price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 5957949ce..4662b68f8 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -8,16 +8,31 @@ 'id': 6327, 'nextInvoiceTotalRecurringAmount': 1.54, 'children': [ - {'categoryCode': 'port_speed', - 'nextInvoiceTotalRecurringAmount': 1}, - {'categoryCode': 'guest_core', - 'nextInvoiceTotalRecurringAmount': 1}, - {'categoryCode': 'ram', - 'nextInvoiceTotalRecurringAmount': 1}, - {'categoryCode': 'guest_core', - 'nextInvoiceTotalRecurringAmount': 1}, - {'categoryCode': 'guest_disk1', - 'nextInvoiceTotalRecurringAmount': 1}, + { + 'categoryCode': 'ram', + 'description': '1 GB', + 'nextInvoiceTotalRecurringAmount': 1 + }, + { + 'categoryCode': 'remote_management', + 'description': 'Reboot / Remote Console', + 'nextInvoiceTotalRecurringAmount': 1 + }, + { + 'categoryCode': 'port_speed', + 'description': '1 Gbps Public & Private Network Uplinks', + 'nextInvoiceTotalRecurringAmount': 1 + }, + { + 'categoryCode': 'public_port', + 'description': '1 Gbps Public Uplink', + 'nextInvoiceTotalRecurringAmount': 1 + }, + { + 'categoryCode': 'service_port', + 'description': '1 Gbps Private Uplink', + 'nextInvoiceTotalRecurringAmount': 1 + } ], 'package': { "id": 835, diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 77410ff4a..75c00127a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -245,7 +245,7 @@ def get_instance(self, instance_id, **kwargs): 'userData,' '''billingItem[id,nextInvoiceTotalRecurringAmount, package[id,keyName], - children[categoryCode,nextInvoiceTotalRecurringAmount], + children[description,categoryCode,nextInvoiceTotalRecurringAmount], orderItem[id, order.userRecord[username], preset.keyName]],''' diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 892a823fd..29007f154 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -323,7 +323,7 @@ def test_create_options_prices(self): def test_create_options_prices_location(self): result = self.run_command(['vs', 'create-options', '--prices', 'dal13', - '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -345,19 +345,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -400,12 +400,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) @@ -820,11 +820,11 @@ def test_billing(self): 'Recurring Fee': None, 'Total': 1.54, 'prices': [ - {'Recurring Price': 1}, - {'Recurring Price': 1}, - {'Recurring Price': 1}, - {'Recurring Price': 1}, - {'Recurring Price': 1} + {'description': '1 GB', 'Recurring Price': 1}, + {'description': 'Reboot / Remote Console', 'Recurring Price': 1}, + {'description': '1 Gbps Public & Private Network Uplinks', 'Recurring Price': 1}, + {'description': '1 Gbps Public Uplink', 'Recurring Price': 1}, + {'description': '1 Gbps Private Uplink', 'Recurring Price': 1} ] } self.assert_no_fail(result) From f6677a7fedafc9c14e58eebdd84dbde064103be8 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Nov 2021 14:50:49 -0400 Subject: [PATCH 0544/1385] fix the team code review --- SoftLayer/CLI/virt/billing.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py index ddc785361..7ef1e0884 100644 --- a/SoftLayer/CLI/virt/billing.py +++ b/SoftLayer/CLI/virt/billing.py @@ -30,7 +30,7 @@ def cli(env, identifier): table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) table.add_row(['Provision Date', utils.lookup(result, 'provisionDate')]) - price_table = formatting.Table(['description', 'Recurring Price']) + price_table = formatting.Table(['Description', 'Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 29007f154..187445063 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -820,11 +820,11 @@ def test_billing(self): 'Recurring Fee': None, 'Total': 1.54, 'prices': [ - {'description': '1 GB', 'Recurring Price': 1}, - {'description': 'Reboot / Remote Console', 'Recurring Price': 1}, - {'description': '1 Gbps Public & Private Network Uplinks', 'Recurring Price': 1}, - {'description': '1 Gbps Public Uplink', 'Recurring Price': 1}, - {'description': '1 Gbps Private Uplink', 'Recurring Price': 1} + {'Description': '1 GB', 'Recurring Price': 1}, + {'Description': 'Reboot / Remote Console', 'Recurring Price': 1}, + {'Description': '1 Gbps Public & Private Network Uplinks', 'Recurring Price': 1}, + {'Description': '1 Gbps Public Uplink', 'Recurring Price': 1}, + {'Description': '1 Gbps Private Uplink', 'Recurring Price': 1} ] } self.assert_no_fail(result) From 34cad8a28df006cb9379cfe9784a21fd272588fb Mon Sep 17 00:00:00 2001 From: Sandro Tosi Date: Sun, 21 Nov 2021 01:05:00 -0500 Subject: [PATCH 0545/1385] Mapping is now in collections.abc this fixes an error running tests: ``` __________________________ TestUtils.test_dict_merge ___________________________ self = def test_dict_merge(self): filter1 = {"virtualGuests": {"hostname": {"operation": "etst"}}} filter2 = {"virtualGuests": {"id": {"operation": "orderBy", "options": [{"name": "sort", "value": ["DESC"]}]}}} > result = SoftLayer.utils.dict_merge(filter1, filter2) tests/basic_tests.py:85: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ dct1 = {'virtualGuests': {'hostname': {'operation': 'etst'}}} dct2 = {'virtualGuests': {'id': {'operation': 'orderBy', 'options': [{'name': 'sort', 'value': ['DESC']}]}}} def dict_merge(dct1, dct2): """Recursively merges dct2 and dct1, ideal for merging objectFilter together. :param dct1: A dictionary :param dct2: A dictionary :return: dct1 + dct2 """ dct = dct1.copy() for k, _ in dct2.items(): > if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): E AttributeError: module 'collections' has no attribute 'Mapping' SoftLayer/utils.py:71: AttributeError ``` --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 929b6524b..05ef50471 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -68,7 +68,7 @@ def dict_merge(dct1, dct2): dct = dct1.copy() for k, _ in dct2.items(): - if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): + if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.abc.Mapping)): dct[k] = dict_merge(dct1[k], dct2[k]) else: dct[k] = dct2[k] From 91d72205e8a376a721afd6dcc2c83b666025700f Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 25 Nov 2021 17:22:34 -0400 Subject: [PATCH 0546/1385] fix vs placementgroup list --- SoftLayer/CLI/virt/placementgroup/list.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/virt/placementgroup/list.py b/SoftLayer/CLI/virt/placementgroup/list.py index 94f72af1d..3a7098b23 100644 --- a/SoftLayer/CLI/virt/placementgroup/list.py +++ b/SoftLayer/CLI/virt/placementgroup/list.py @@ -5,6 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.vs_placement import PlacementManager as PlacementManager +from SoftLayer import utils @click.command() @@ -19,12 +20,12 @@ def cli(env): ) for group in result: table.add_row([ - group['id'], - group['name'], - group['backendRouter']['hostname'], - group['rule']['name'], - group['guestCount'], - group['createDate'] + utils.lookup(group, 'id'), + utils.lookup(group, 'name'), + utils.lookup(group, 'backendRouter', 'hostname'), + utils.lookup(group, 'rule', 'name'), + utils.lookup(group, 'guestCount'), + utils.lookup(group, 'createDate') ]) env.fout(table) From 52ee904ab320952598fc0a6757100093307f32f1 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 3 Dec 2021 16:25:02 -0600 Subject: [PATCH 0547/1385] #1568 fixed up snapshot-notification cli commands --- .../CLI/block/snapshot/get_notify_status.py | 14 ++------ .../CLI/file/snapshot/get_notify_status.py | 14 ++------ SoftLayer/managers/storage.py | 20 +++++++---- tests/CLI/modules/block_tests.py | 10 ++++++ tests/CLI/modules/file_tests.py | 10 ++++++ tests/managers/storage_generic_tests.py | 35 +++++++++++++++++++ 6 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 tests/managers/storage_generic_tests.py diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index 25344f812..f16ef9804 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -14,15 +14,7 @@ def cli(env, volume_id): block_manager = SoftLayer.BlockStorageManager(env.client) enabled = block_manager.get_volume_snapshot_notification_status(volume_id) - if enabled == '': - click.echo(""" - Enabled:Snapshots space usage warning flag is null. Set to default value enable. For volume %s - """ % (volume_id)) - elif enabled == 'True': - click.echo( - 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' - % (volume_id)) + if enabled == 0: + click.echo("Disabled: Snapshots space usage threshold is disabled for volume {}".format(volume_id)) else: - click.echo( - 'Disabled:Snapshots space usage threshold warning flag setting is disabled for volume %s' - % (volume_id)) + click.echo("Enabled: Snapshots space usage threshold is enabled for volume {}".format(volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index 9724046ee..1cddb6a28 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -15,15 +15,7 @@ def cli(env, volume_id): file_manager = SoftLayer.FileStorageManager(env.client) enabled = file_manager.get_volume_snapshot_notification_status(volume_id) - if enabled == '': - click.echo( - 'Enabled:Snapshots space usage threshold warning flag is null. Set to default value enable. For volume %s' - % (volume_id)) - elif enabled == 'True': - click.echo( - 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' - % (volume_id)) + if enabled == 0: + click.echo("Disabled: Snapshots space usage threshold is disabled for volume {}".format(volume_id)) else: - click.echo( - 'Disabled:Snapshots space usage threshold warning flag setting is disabled for volume %s' - % (volume_id)) + click.echo("Enabled: Snapshots space usage threshold is enabled for volume {}".format(volume_id)) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index a3176583f..980736876 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -19,11 +19,16 @@ class StorageManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ + def __init__(self, client): self.configuration = {} self.client = client self.resolvers = [self._get_ids_from_username] + def _get_ids_from_username(self, username): + """Should only be actually called from the block/file manager""" + return [] + def get_volume_count_limits(self): """Returns a list of block volume count limit. @@ -122,10 +127,7 @@ def set_volume_snapshot_notification(self, volume_id, enable): :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', - 'setSnapshotNotification', - enable, - id=volume_id) + return self.client.call('Network_Storage', 'setSnapshotNotification', enable, id=volume_id) def get_volume_snapshot_notification_status(self, volume_id): """returns Enabled/Disabled status of snapshot space usage threshold warning for a given volume. @@ -133,9 +135,13 @@ def get_volume_snapshot_notification_status(self, volume_id): :param volume_id: ID of volume. :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', - 'getSnapshotNotificationStatus', - id=volume_id) + status = self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id) + # A None status is enabled as well. + if status is None: + status = 1 + # We need to force int on the return because otherwise the API will return the string '0' + # instead of either a boolean or real int... + return int(status) def authorize_host_to_volume(self, volume_id, diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 1c6b22e14..9f4499782 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -812,3 +812,13 @@ def test_volume_not_set_note(self, set_note): self.assert_no_fail(result) self.assertIn("Note could not be set!", result.output) + + @mock.patch('SoftLayer.BlockStorageManager.get_volume_snapshot_notification_status') + def test_snapshot_get_notification_status(self, status): + status.side_effect = [None, 1, 0] + expected = ['Enabled', 'Enabled', 'Disabled'] + + for expect in expected: + result = self.run_command(['block', 'snapshot-get-notification-status', '999']) + self.assert_no_fail(result) + self.assertIn(expect, result.output) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index cbe73818c..06595dee0 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -791,3 +791,13 @@ def test_volume_not_set_note(self, set_note): self.assert_no_fail(result) self.assertIn("Note could not be set!", result.output) + + @mock.patch('SoftLayer.FileStorageManager.get_volume_snapshot_notification_status') + def test_snapshot_get_notification_status(self, status): + status.side_effect = [None, 1, 0] + expected = ['Enabled', 'Enabled', 'Disabled'] + + for expect in expected: + result = self.run_command(['file', 'snapshot-get-notification-status', '999']) + self.assert_no_fail(result) + self.assertIn(expect, result.output) diff --git a/tests/managers/storage_generic_tests.py b/tests/managers/storage_generic_tests.py new file mode 100644 index 000000000..6585bd721 --- /dev/null +++ b/tests/managers/storage_generic_tests.py @@ -0,0 +1,35 @@ +""" + SoftLayer.tests.managers.storage_generic_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +import copy +import SoftLayer +from SoftLayer import exceptions +from SoftLayer import testing + + +class StorageGenericTests(testing.TestCase): + def set_up(self): + self.storage = SoftLayer.managers.storage.StorageManager(self.client) + + def test_get_volume_snapshot_notification_status(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'getSnapshotNotificationStatus') + # These are the values we expect from the API as of 2021-12-01, FBLOCK4193 + mock.side_effect = [None, '1', '0'] + expected = [1, 1, 0] + + for expect in expected: + result = self.storage.get_volume_snapshot_notification_status(12345) + self.assert_called_with('SoftLayer_Network_Storage', 'getSnapshotNotificationStatus', identifier=12345) + self.assertEqual(expect, result) + + def test_set_volume_snapshot_notification(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'setSnapshotNotification') + mock.return_value = None + + result = self.storage.set_volume_snapshot_notification(12345, False) + self.assert_called_with('SoftLayer_Network_Storage', 'setSnapshotNotification', + identifier=12345, args=(False,)) From 1545608dd5fae7dda11438f78a2d68b033f54d2b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 3 Dec 2021 16:34:44 -0600 Subject: [PATCH 0548/1385] fixed tox issues --- SoftLayer/managers/storage.py | 2 +- tests/managers/storage_generic_tests.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 980736876..f666a8222 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -25,7 +25,7 @@ def __init__(self, client): self.client = client self.resolvers = [self._get_ids_from_username] - def _get_ids_from_username(self, username): + def _get_ids_from_username(self, username): # pylint: disable=unused-argument,no-self-use """Should only be actually called from the block/file manager""" return [] diff --git a/tests/managers/storage_generic_tests.py b/tests/managers/storage_generic_tests.py index 6585bd721..1658ff0cf 100644 --- a/tests/managers/storage_generic_tests.py +++ b/tests/managers/storage_generic_tests.py @@ -5,9 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import copy import SoftLayer -from SoftLayer import exceptions from SoftLayer import testing @@ -33,3 +31,4 @@ def test_set_volume_snapshot_notification(self): result = self.storage.set_volume_snapshot_notification(12345, False) self.assert_called_with('SoftLayer_Network_Storage', 'setSnapshotNotification', identifier=12345, args=(False,)) + self.assertEqual(None, result) From 552784ea906ca71847b8074849ad3851d78e8b69 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 3 Dec 2021 16:50:20 -0600 Subject: [PATCH 0549/1385] tox fixes --- SoftLayer/CLI/block/count.py | 2 +- SoftLayer/CLI/file/count.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py index ecfba0a53..cbd3d23b9 100644 --- a/SoftLayer/CLI/block/count.py +++ b/SoftLayer/CLI/block/count.py @@ -30,7 +30,7 @@ def cli(env, sortby, datacenter): service_resource = volume['serviceResource'] if 'datacenter' in service_resource: datacenter_name = service_resource['datacenter']['name'] - if datacenter_name not in datacenters.keys(): + if datacenter_name not in datacenters.keys(): # pylint: disable=consider-iterating-dictionary datacenters[datacenter_name] = 1 else: datacenters[datacenter_name] += 1 diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py index cb6ed1a0a..325758538 100644 --- a/SoftLayer/CLI/file/count.py +++ b/SoftLayer/CLI/file/count.py @@ -29,7 +29,7 @@ def cli(env, sortby, datacenter): service_resource = volume['serviceResource'] if 'datacenter' in service_resource: datacenter_name = service_resource['datacenter']['name'] - if datacenter_name not in datacenters.keys(): + if datacenter_name not in datacenters.keys(): # pylint: disable=consider-iterating-dictionary datacenters[datacenter_name] = 1 else: datacenters[datacenter_name] += 1 From cb5f2f91cb070b992ac5550af3abe7c55c0d2197 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Dec 2021 15:01:12 -0600 Subject: [PATCH 0550/1385] Version and changelog update to 5.9.8 --- CHANGELOG.md | 36 +++++++++++++++++++++++++++++++++++- SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf0eb2848..21dd2223b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Change Log +## [5.9.8] - 2021-12-07 + +https://github.com/softlayer/softlayer-python/compare/v5.9.7...v5.9.8 + +#### Improvements + +- Fix code blocks formatting of The Solution section docs #1534 +- Add retry decorator to documentation #1535 +- Updated utility docs #1536 +- Add Exceptions to Documentation #1537 +- Forces specific encoding on XMLRPC requests #1543 +- Add sensor data to hardware #1544 +- Ignoring f-string related messages for tox for now #1548 +- Fix account events #1546 +- Improved loadbal details #1549 +- Fix initialized accountmanger #1552 +- Fix hw billing reports 0 items #1556 +- Update API docs link and remove travisCI mention #1557 +- Fix errors with vs bandwidth #1563 +- Add Item names to vs billing report #1564 +- Mapping is now in collections.abc #1565 +- fix vs placementgroup list #1567 +- fixed up snapshot-notification cli commands #1569 + +#### New Commands +- loadbal l7policies #1553 + + ` slcli loadbal l7policies --protocol-id` + + `slcli loadbal l7policies` +- Snapshot notify #1554 + + `slcli file|block snapshot-set-notification` + + `slcli file|block snapshot-get-notification-status` + + + ## [5.9.7] - 2021-08-04 https://github.com/softlayer/softlayer-python/compare/v5.9.6...v5.9.7 @@ -173,7 +207,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 - #1318 add Drive number in guest drives details using the device number - #1323 add vs list hardware and all option -## [5.8.9] - 2020-07-06 +## [5.8.9] - 2020-07-06 https://github.com/softlayer/softlayer-python/compare/v5.8.8...v5.8.9 - #1252 Automated Snap publisher diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 6d55ed2df..9f6d4b6da 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.7' +VERSION = 'v5.9.8' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 4eec2cad5..165223b88 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.7', + version='5.9.8', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From dc4a43e87548730d9c83b3ccb1f608814e9e8fbb Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 31 Dec 2021 08:58:57 -0400 Subject: [PATCH 0551/1385] add new feature on vlan --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/create_options.py | 33 +++++++++++++++++++ .../fixtures/SoftLayer_Location_Datacenter.py | 2 ++ SoftLayer/managers/network.py | 14 ++++++++ tests/CLI/modules/vlan_tests.py | 4 +++ 5 files changed, 54 insertions(+) create mode 100644 SoftLayer/CLI/vlan/create_options.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2d44a824f..2dbb519e6 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -354,6 +354,7 @@ ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:create', 'SoftLayer.CLI.vlan.create:cli'), + ('vlan:create-options', 'SoftLayer.CLI.vlan.create_options:cli'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), diff --git a/SoftLayer/CLI/vlan/create_options.py b/SoftLayer/CLI/vlan/create_options.py new file mode 100644 index 000000000..5e79e1774 --- /dev/null +++ b/SoftLayer/CLI/vlan/create_options.py @@ -0,0 +1,33 @@ +"""Vlan order options.""" +# :license: MIT, see LICENSE for more details. +# pylint: disable=too-many-statements +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(short_help="Get options to use for creating Vlan servers.") +@environment.pass_env +def cli(env): + """Vlan order options.""" + + mgr = SoftLayer.NetworkManager(env.client) + datacenters = mgr.get_list_datacenter() + + table = formatting.Table(['name', 'Value'], title="Datacenters") + router_table = formatting.Table(['datacenter', 'hostname']) + dc_table = formatting.Table(['Datacenters']) + table.add_row(['VLAN type', 'Private, Public']) + + for datacenter in datacenters: + dc_table.add_row([datacenter['name']]) + routers = mgr.get_routers(datacenter['id']) + for router in routers: + router_table.add_row([datacenter['name'], router['hostname']]) + + table.add_row(['Datacenters', dc_table]) + table.add_row(['Routers', router_table]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py index e9aa9b48e..b0b937cf4 100644 --- a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py +++ b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py @@ -10,3 +10,5 @@ "name": "dal09" } ] + +getHardwareRouters = [] diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 4bdb1c18a..eb648f518 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -789,3 +789,17 @@ def get_pods(self, datacenter=None): _filter = {"datacenterName": {"operation": datacenter}} return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) + + def get_list_datacenter(self): + """Calls SoftLayer_Location::getDatacenters() + + returns all datacenter locations. + """ + return self.client.call('SoftLayer_Location_Datacenter', 'getDatacenters') + + def get_routers(self, identifier): + """Calls SoftLayer_Location::getRouters() + + returns all routers locations. + """ + return self.client.call('SoftLayer_Location_Datacenter', 'getHardwareRouters', id=identifier) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 204788d4d..6c8fbce01 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -18,6 +18,10 @@ def test_detail(self): result = self.run_command(['vlan', 'detail', '1234']) self.assert_no_fail(result) + def test_create_options(self): + result = self.run_command(['vlan', 'create-options']) + self.assert_no_fail(result) + def test_detail_no_vs(self): result = self.run_command(['vlan', 'detail', '1234', '--no-vs']) self.assert_no_fail(result) From 746ca96a3c18b599b8a165bf8c5a068d5c9ed42e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 31 Dec 2021 09:08:19 -0400 Subject: [PATCH 0552/1385] add documentation --- docs/cli/vlan.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 72c9a54cf..6a9927ca1 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -7,6 +7,10 @@ VLANs :prog: vlan create :show-nested: +.. click:: SoftLayer.CLI.vlan.create-options:cli + :prog: vlan create-options + :show-nested: + .. click:: SoftLayer.CLI.vlan.detail:cli :prog: vlan detail :show-nested: From 666a86c254ff87cb4ae51b2837ed497d93a52db3 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 31 Dec 2021 09:14:26 -0400 Subject: [PATCH 0553/1385] add documentation --- docs/cli/vlan.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 6a9927ca1..be4890ce3 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -7,7 +7,7 @@ VLANs :prog: vlan create :show-nested: -.. click:: SoftLayer.CLI.vlan.create-options:cli +.. click:: SoftLayer.CLI.vlan.create_options:cli :prog: vlan create-options :show-nested: From 00b7d8187de80858e7170e3a1705c9184afb7982 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 7 Jan 2022 17:57:34 -0400 Subject: [PATCH 0554/1385] fix the team code review comments --- SoftLayer/CLI/vlan/create_options.py | 6 +++--- SoftLayer/managers/network.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/vlan/create_options.py b/SoftLayer/CLI/vlan/create_options.py index 5e79e1774..0b03b06af 100644 --- a/SoftLayer/CLI/vlan/create_options.py +++ b/SoftLayer/CLI/vlan/create_options.py @@ -11,13 +11,13 @@ @click.command(short_help="Get options to use for creating Vlan servers.") @environment.pass_env def cli(env): - """Vlan order options.""" + """List all the options for creating VLAN""" mgr = SoftLayer.NetworkManager(env.client) datacenters = mgr.get_list_datacenter() - table = formatting.Table(['name', 'Value'], title="Datacenters") - router_table = formatting.Table(['datacenter', 'hostname']) + table = formatting.Table(['Options', 'Value'], title="Datacenters") + router_table = formatting.Table(['Datacenter', 'Router/Pod']) dc_table = formatting.Table(['Datacenters']) table.add_row(['VLAN type', 'Private, Public']) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index eb648f518..6638a29d3 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -800,6 +800,6 @@ def get_list_datacenter(self): def get_routers(self, identifier): """Calls SoftLayer_Location::getRouters() - returns all routers locations. - """ + returns all routers locations. + """ return self.client.call('SoftLayer_Location_Datacenter', 'getHardwareRouters', id=identifier) From 994b5c7fe863fbd25d27fcaf78389703c426e5f1 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 21 Jan 2022 18:04:43 -0400 Subject: [PATCH 0555/1385] Add loadbalancer timeout values --- SoftLayer/CLI/loadbal/detail.py | 38 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 6712c3ce2..ef850e029 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -39,17 +39,29 @@ def lbaas_table(this_lb): listener_table, pools = get_listener_table(this_lb) table.add_row(['Protocols', listener_table]) - member_table = get_member_table(this_lb, pools) - table.add_row(['Members', member_table]) - - hp_table = get_hp_table(this_lb) - table.add_row(['Health Checks', hp_table]) - - l7pool_table = get_l7pool_table(this_lb) - table.add_row(['L7 Pools', l7pool_table]) - - ssl_table = get_ssl_table(this_lb) - table.add_row(['Ciphers', ssl_table]) + if pools.get('members') is not None: + member_table = get_member_table(this_lb, pools) + table.add_row(['Members', member_table]) + else: + table.add_row(['Members', "Not Found"]) + + if this_lb.get('healthMonitors') != []: + hp_table = get_hp_table(this_lb) + table.add_row(['Health Checks', hp_table]) + else: + table.add_row(['Health Checks', "Not Found"]) + + if this_lb.get('l7Pools') != []: + l7pool_table = get_l7pool_table(this_lb) + table.add_row(['L7 Pools', l7pool_table]) + else: + table.add_row(['L7 Pools', "Not Found"]) + + if this_lb.get('sslCiphers') != []: + ssl_table = get_ssl_table(this_lb) + table.add_row(['Ciphers', ssl_table]) + else: + table.add_row(['Ciphers', "Not Found"]) return table @@ -57,14 +69,14 @@ def lbaas_table(this_lb): def get_hp_table(this_lb): """Generates a table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_HealthMonitor/ - hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'Timeout', 'Modify', 'Active']) + hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'ServerTimeout ', 'Modify', 'Active']) for health in this_lb.get('healthMonitors', []): hp_table.add_row([ health.get('uuid'), health.get('interval'), health.get('maxRetries'), health.get('monitorType'), - health.get('timeout'), + health.get('serverTimeout'), utils.clean_time(health.get('modifyDate')), health.get('provisioningStatus') ]) From c93fa575b2d6be8d8b7faf4cc84d057b1390d46d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 24 Jan 2022 14:27:25 -0600 Subject: [PATCH 0556/1385] #1575 added account bandwidth-pools and updated reports bandwidth to support the --pool option --- SoftLayer/CLI/account/bandwidth_pools.py | 43 +++++++++++++++++ SoftLayer/CLI/report/bandwidth.py | 60 ++++++++++++------------ SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/account.py | 20 ++++++++ 4 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 SoftLayer/CLI/account/bandwidth_pools.py diff --git a/SoftLayer/CLI/account/bandwidth_pools.py b/SoftLayer/CLI/account/bandwidth_pools.py new file mode 100644 index 000000000..29dc9492a --- /dev/null +++ b/SoftLayer/CLI/account/bandwidth_pools.py @@ -0,0 +1,43 @@ +"""Displays information about the accounts bandwidth pools""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + +from pprint import pprint as pp + +@click.command() +@environment.pass_env +def cli(env): + """Lists billing items with some other useful information. + + Similiar to https://cloud.ibm.com/billing/billing-items + """ + + manager = AccountManager(env.client) + items = manager.get_bandwidth_pools() + # table = item_table(items) + pp(items) + table = formatting.Table([ + "Pool Name", + "Region", + "Servers", + "Allocation", + "Current Usage", + "Projected Usage" + ], title="Bandwidth Pools") + table.align = 'l' + + for item in items: + name = item.get('name') + region = utils.lookup(item, 'locationGroup', 'name') + servers = manager.get_bandwidth_pool_counts(identifier=item.get('id')) + allocation = item.get('totalBandwidthAllocated', 0) + current = item.get('billingCyclePublicUsageTotal', 0) + projected = item.get('projectedPublicBandwidthUsage', 0) + + table.add_row([name, region, servers, allocation, current, projected,]) + env.fout(table) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 4ae2d0f68..9ada68e14 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -9,6 +9,7 @@ from SoftLayer.CLI import formatting from SoftLayer import utils +from pprint import pprint as pp # pylint: disable=unused-argument def _validate_datetime(ctx, param, value): @@ -47,23 +48,25 @@ def _get_pooled_bandwidth(env, start, end): label='Calculating for bandwidth pools', file=sys.stderr) as pools: for pool in pools: - if not pool.get('metricTrackingObjectId'): - continue - - yield { - 'id': pool['id'], + pool_detail = { + 'id': pool.get('id'), 'type': 'pool', - 'name': pool['name'], - 'data': env.client.call( + 'name': pool.get('name'), + 'data': [] + } + if pool.get('metricTrackingObjectId'): + bw_data = env.client.call( 'Metric_Tracking_Object', 'getSummaryData', start.strftime('%Y-%m-%d %H:%M:%S %Z'), end.strftime('%Y-%m-%d %H:%M:%S %Z'), types, 300, - id=pool['metricTrackingObjectId'], - ), - } + id=pool.get('metricTrackingObjectId'), + ) + pool_detail['data'] = bw_data + + yield pool_detail def _get_hardware_bandwidth(env, start, end): @@ -172,28 +175,20 @@ def _get_virtual_bandwidth(env, start, end): @click.command(short_help="Bandwidth report for every pool/server") -@click.option( - '--start', - callback=_validate_datetime, - default=(datetime.datetime.now() - datetime.timedelta(days=30) - ).strftime('%Y-%m-%d'), - help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") -@click.option( - '--end', - callback=_validate_datetime, - default=datetime.datetime.now().strftime('%Y-%m-%d'), - help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") -@click.option('--sortby', help='Column to sort by', - default='hostname', - show_default=True) -@click.option('--virtual', is_flag=True, - help='Show the all bandwidth summary for each virtual server', - default=False) -@click.option('--server', is_flag=True, - help='show the all bandwidth summary for each hardware server', - default=False) +@click.option('--start', callback=_validate_datetime, + default=(datetime.datetime.now() - datetime.timedelta(days=30)).strftime('%Y-%m-%d'), + help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") +@click.option('--end', callback=_validate_datetime, default=datetime.datetime.now().strftime('%Y-%m-%d'), + help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") +@click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) +@click.option('--virtual', is_flag=True, default=False, + help='Show only the bandwidth summary for each virtual server') +@click.option('--server', is_flag=True, default=False, + help='Show only the bandwidth summary for each hardware server') +@click.option('--pool', is_flag=True, default=False, + help='Show only the bandwidth pool summary.') @environment.pass_env -def cli(env, start, end, sortby, virtual, server): +def cli(env, start, end, sortby, virtual, server, pool): """Bandwidth report for every pool/server. This reports on the total data transfered for each virtual sever, hardware @@ -243,6 +238,9 @@ def _input_to_table(item): for item in itertools.chain(_get_pooled_bandwidth(env, start, end), _get_hardware_bandwidth(env, start, end)): _input_to_table(item) + elif pool: + for item in _get_pooled_bandwidth(env, start, end): + _input_to_table(item) else: for item in itertools.chain(_get_pooled_bandwidth(env, start, end), _get_hardware_bandwidth(env, start, end), diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2dbb519e6..02d3420d3 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -22,6 +22,7 @@ ('account:item-detail', 'SoftLayer.CLI.account.item_detail:cli'), ('account:cancel-item', 'SoftLayer.CLI.account.cancel_item:cli'), ('account:orders', 'SoftLayer.CLI.account.orders:cli'), + ('account:bandwidth-pools', 'SoftLayer.CLI.account.bandwidth_pools:cli'), ('virtual', 'SoftLayer.CLI.virt'), ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 51c9c889c..9307ea1d5 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -326,3 +326,23 @@ def get_active_account_licenses(self): _mask = """billingItem,softwareDescription""" return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) + + def get_bandwidth_pools(self, mask=None): + """Gets all the bandwidth pools on an account""" + + if mask is None: + mask = """mask[totalBandwidthAllocated,locationGroup, id, name, billingCyclePublicUsageTotal, + projectedPublicBandwidthUsage] + """ + + return self.client.call('SoftLayer_Account', 'getBandwidthAllotments', mask=mask, iter=True) + + def get_bandwidth_pool_counts(self, identifier): + """Gets a count of all servers in a bandwidth pool""" + mask = "mask[id, bareMetalInstanceCount, hardwareCount, virtualGuestCount]" + counts = self.client.call('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject', + id=identifier, mask=mask) + total = counts.get('bareMetalInstanceCount', 0) + \ + counts.get('hardwareCount', 0) + \ + counts.get('virtualGuestCount', 0) + return total \ No newline at end of file From 2e7ac4ae1ec66b08260739f63b002e6e656f8cc2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 Jan 2022 14:35:27 -0600 Subject: [PATCH 0557/1385] #1575 fixed some style and output issues --- SoftLayer/CLI/account/bandwidth_pools.py | 16 +++++++--------- SoftLayer/CLI/report/bandwidth.py | 1 - SoftLayer/managers/account.py | 16 ++++++++++------ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools.py b/SoftLayer/CLI/account/bandwidth_pools.py index 29dc9492a..358bfd4d0 100644 --- a/SoftLayer/CLI/account/bandwidth_pools.py +++ b/SoftLayer/CLI/account/bandwidth_pools.py @@ -7,20 +7,18 @@ from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils -from pprint import pprint as pp @click.command() @environment.pass_env def cli(env): - """Lists billing items with some other useful information. + """Displays bandwidth pool information - Similiar to https://cloud.ibm.com/billing/billing-items + Similiar to https://cloud.ibm.com/classic/network/bandwidth/vdr """ manager = AccountManager(env.client) items = manager.get_bandwidth_pools() - # table = item_table(items) - pp(items) + table = formatting.Table([ "Pool Name", "Region", @@ -35,9 +33,9 @@ def cli(env): name = item.get('name') region = utils.lookup(item, 'locationGroup', 'name') servers = manager.get_bandwidth_pool_counts(identifier=item.get('id')) - allocation = item.get('totalBandwidthAllocated', 0) - current = item.get('billingCyclePublicUsageTotal', 0) - projected = item.get('projectedPublicBandwidthUsage', 0) + allocation = "{} GB".format(item.get('totalBandwidthAllocated', 0)) + current = "{} GB".format(utils.lookup(item, 'billingCyclePublicBandwidthUsage', 'amountOut')) + projected = "{} GB".format(item.get('projectedPublicBandwidthUsage', 0)) - table.add_row([name, region, servers, allocation, current, projected,]) + table.add_row([name, region, servers, allocation, current, projected]) env.fout(table) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 9ada68e14..50b002304 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -9,7 +9,6 @@ from SoftLayer.CLI import formatting from SoftLayer import utils -from pprint import pprint as pp # pylint: disable=unused-argument def _validate_datetime(ctx, param, value): diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 9307ea1d5..4e6a3a26a 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -331,18 +331,22 @@ def get_bandwidth_pools(self, mask=None): """Gets all the bandwidth pools on an account""" if mask is None: - mask = """mask[totalBandwidthAllocated,locationGroup, id, name, billingCyclePublicUsageTotal, - projectedPublicBandwidthUsage] + mask = """mask[totalBandwidthAllocated,locationGroup, id, name, projectedPublicBandwidthUsage, + billingCyclePublicBandwidthUsage[amountOut,amountIn]] """ return self.client.call('SoftLayer_Account', 'getBandwidthAllotments', mask=mask, iter=True) def get_bandwidth_pool_counts(self, identifier): - """Gets a count of all servers in a bandwidth pool""" + """Gets a count of all servers in a bandwidth pool + + Getting the server counts individually is significantly faster than pulling them in + with the get_bandwidth_pools api call. + """ mask = "mask[id, bareMetalInstanceCount, hardwareCount, virtualGuestCount]" counts = self.client.call('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject', id=identifier, mask=mask) total = counts.get('bareMetalInstanceCount', 0) + \ - counts.get('hardwareCount', 0) + \ - counts.get('virtualGuestCount', 0) - return total \ No newline at end of file + counts.get('hardwareCount', 0) + \ + counts.get('virtualGuestCount', 0) + return total From 25fac1496d0d9a7c859dba28a0e10dd6208a94c3 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 26 Jan 2022 09:38:24 -0400 Subject: [PATCH 0558/1385] Add pricing date to slcli order preset-list --- SoftLayer/CLI/order/preset_list.py | 45 +++++++++++++++---- .../fixtures/SoftLayer_Product_Package.py | 33 ++++++++++++-- SoftLayer/managers/ordering.py | 2 +- tests/CLI/modules/order_tests.py | 5 +++ 4 files changed, 73 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/order/preset_list.py b/SoftLayer/CLI/order/preset_list.py index 412d95ee7..d40d42093 100644 --- a/SoftLayer/CLI/order/preset_list.py +++ b/SoftLayer/CLI/order/preset_list.py @@ -16,8 +16,9 @@ @click.argument('package_keyname') @click.option('--keyword', help="A word (or string) used to filter preset names.") +@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, e.g. --prices') @environment.pass_env -def cli(env, package_keyname, keyword): +def cli(env, package_keyname, keyword, prices): """List package presets. .. Note:: @@ -33,6 +34,8 @@ def cli(env, package_keyname, keyword): slcli order preset-list BARE_METAL_SERVER --keyword gpu """ + + tables = [] table = formatting.Table(COLUMNS) manager = ordering.OrderingManager(env.client) @@ -41,10 +44,36 @@ def cli(env, package_keyname, keyword): _filter = {'activePresets': {'name': {'operation': '*= %s' % keyword}}} presets = manager.list_presets(package_keyname, filter=_filter) - for preset in presets: - table.add_row([ - str(preset['name']).strip(), - str(preset['keyName']).strip(), - str(preset['description']).strip() - ]) - env.fout(table) + if prices: + table_prices = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction', 'Location']) + for price in presets: + locations = [] + if price['locations'] != []: + for location in price['locations']: + locations.append(location['name']) + cr_max = get_item_price_data(price['prices'][0], 'capacityRestrictionMaximum') + cr_min = get_item_price_data(price['prices'][0], 'capacityRestrictionMinimum') + cr_type = get_item_price_data(price['prices'][0], 'capacityRestrictionType') + table_prices.add_row([price['keyName'], price['id'], + get_item_price_data(price['prices'][0], 'hourlyRecurringFee'), + get_item_price_data(price['prices'][0], 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type), str(locations)]) + tables.append(table_prices) + + else: + for preset in presets: + table.add_row([ + str(preset['name']).strip(), + str(preset['keyName']).strip(), + str(preset['description']).strip() + ]) + tables.append(table) + env.fout(tables) + + +def get_item_price_data(price, item_attribute): + """Given an SoftLayer_Product_Item_Price, returns its default price data""" + result = '-' + if item_attribute in price: + result = price[item_attribute] + return result diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 95fa34ed2..c4c45985d 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1847,7 +1847,16 @@ "isActive": "1", "keyName": "M1_64X512X25", "name": "M1.64x512x25", - "packageId": 835 + "packageId": 835, + "locations": [], + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 258963, + "itemId": 8195, + "recurringFee": "0", + "setupFee": "0" + }] }, { "description": "M1.56x448x100", @@ -1855,7 +1864,16 @@ "isActive": "1", "keyName": "M1_56X448X100", "name": "M1.56x448x100", - "packageId": 835 + "packageId": 835, + "locations": [], + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 698563, + "itemId": 8195, + "recurringFee": "0", + "setupFee": "0" + }] }, { "description": "M1.64x512x100", @@ -1863,7 +1881,16 @@ "isActive": "1", "keyName": "M1_64X512X100", "name": "M1.64x512x100", - "packageId": 835 + "packageId": 835, + "locations": [], + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 963258, + "itemId": 8195, + "recurringFee": "0", + "setupFee": "0" + }] } ] diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 18513e1e4..2bb7bae0a 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -17,7 +17,7 @@ PACKAGE_MASK = '''id, name, keyName, isActive, type''' -PRESET_MASK = '''id, name, keyName, description''' +PRESET_MASK = '''id, name, keyName, description, categories, prices, locations''' class OrderingManager(object): diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 24495d0ff..0e8878093 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -300,6 +300,11 @@ def test_preset_list_keywork(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Package', 'getActivePresets', filter=_filter) + def test_preset_list_prices(self): + result = self.run_command(['order', 'preset-list', 'package', '--prices']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Package', 'getActivePresets') + def test_location_list(self): result = self.run_command(['order', 'package-locations', 'package']) self.assert_no_fail(result) From ad4186c0164eaa519d0c662a57b62476be26400c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 26 Jan 2022 13:37:54 -0600 Subject: [PATCH 0559/1385] #1575 unit tests for bandwidth pooling code --- SoftLayer/fixtures/SoftLayer_Account.py | 18 ++ ...er_Network_Bandwidth_Version1_Allotment.py | 6 + docs/cli/account.rst | 4 + tests/CLI/modules/account_tests.py | 6 + tests/CLI/modules/report_tests.py | 233 ++++-------------- tests/managers/account_tests.py | 10 + 6 files changed, 86 insertions(+), 191 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 35216be76..3f7d3cf4c 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1207,3 +1207,21 @@ "version": 4 } }] + +getBandwidthAllotments = [{ + 'billingCyclePublicBandwidthUsage': { + 'amountIn': '6.94517', + 'amountOut': '6.8859' + }, + 'id': 309961, + 'locationGroup': { + 'description': 'All Datacenters in Mexico', + 'id': 262, + 'locationGroupTypeId': 1, + 'name': 'MEX', + 'securityLevelId': None + }, + 'name': 'MexRegion', + 'projectedPublicBandwidthUsage': 9.88, + 'totalBandwidthAllocated': 3361 +}] \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py new file mode 100644 index 000000000..d784b7e7b --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py @@ -0,0 +1,6 @@ +getObject = { + 'id': 309961, + 'bareMetalInstanceCount': 0, + 'hardwareCount': 2, + 'virtualGuestCount': 0 +} \ No newline at end of file diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 719c44fde..8cb855f13 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -43,3 +43,7 @@ Account Commands .. click:: SoftLayer.CLI.account.licenses:cli :prog: account licenses :show-nested: + +.. click:: SoftLayer.CLI.account.bandwidth_pools:cli + :prog: account bandwidth-pools + :show-nested: diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 8428d3306..9ac821ace 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -136,3 +136,9 @@ def test_acccount_licenses(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getActiveVirtualLicenses') self.assert_called_with('SoftLayer_Account', 'getActiveAccountLicenses') + + def test_bandwidth_pools(self): + result = self.run_command(['account', 'bandwidth-pools']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getBandwidthAllotments') + self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject') \ No newline at end of file diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 8489aeeab..11c7448e7 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -8,6 +8,7 @@ import json +from pprint import pprint as pp class ReportTests(testing.TestCase): @@ -76,8 +77,7 @@ def test_bandwidth_report(self): 'hostname': 'host3', 'virtualRack': {'id': 2, 'bandwidthAllotmentTypeId': 2}, }] - summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', - 'getSummaryData') + summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', 'getSummaryData') summary_data.return_value = [ {'type': 'publicIn_net_octet', 'counter': 10}, {'type': 'publicOut_net_octet', 'counter': 20}, @@ -93,86 +93,23 @@ def test_bandwidth_report(self): ]) self.assert_no_fail(result) + stripped_output = '[' + result.output.split('[', 1)[1] - self.assertEqual([ - { - 'hostname': 'pool1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool' - }, { - 'hostname': 'pool3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool' - }, { - 'hostname': 'host1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'hardware' - }, { - 'hostname': 'host3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'hardware' - }, { - 'hostname': 'host1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'virtual' - }, { - 'hostname': 'host3', - 'pool': 2, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'virtual'}], - json.loads(stripped_output), - ) - self.assertEqual( - 6, - len(self.calls('SoftLayer_Metric_Tracking_Object', - 'getSummaryData')), - ) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=1) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=3) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=101) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=103) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=201) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=203) - call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', - identifier=1)[0] - expected_args = ( - '2016-02-04 00:00:00 ', - '2016-03-04 12:34:56 ', + json_output = json.loads(stripped_output) + pp(json.loads(stripped_output)) + print("======= ^^^^^^^^^ ==============") + self.assertEqual(json_output[0]['hostname'], 'pool1') + self.assertEqual(json_output[0]['private_in'], 30) + + self.assertEqual(6, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=3) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=101) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=103) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=201) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=203) + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] + expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', [{ 'keyName': 'PUBLICIN', 'name': 'publicIn', @@ -242,64 +179,19 @@ def test_virtual_bandwidth_report(self): self.assert_no_fail(result) stripped_output = '[' + result.output.split('[', 1)[1] - self.assertEqual([ - { - 'hostname': 'pool1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool', - }, { - 'hostname': 'pool3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool', - }, { - 'hostname': 'host1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'virtual', - }, { - 'hostname': 'host3', - 'pool': 2, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'virtual', - }], - json.loads(stripped_output), - ) - self.assertEqual( - 4, - len(self.calls('SoftLayer_Metric_Tracking_Object', - 'getSummaryData')), - ) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=1) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=3) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=201) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=203) - call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', - identifier=1)[0] - expected_args = ( - '2016-02-04 00:00:00 ', - '2016-03-04 12:34:56 ', + json_output = json.loads(stripped_output) + self.assertEqual(json_output[0]['hostname'], 'pool1') + self.assertEqual(json_output[1]['private_in'], 0) + self.assertEqual(json_output[2]['private_in'], 30) + self.assertEqual(json_output[3]['type'], 'virtual') + + self.assertEqual(4, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=3) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=201) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=203) + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] + expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', [{ 'keyName': 'PUBLICIN', 'name': 'publicIn', @@ -370,59 +262,18 @@ def test_server_bandwidth_report(self): self.assert_no_fail(result) stripped_output = '[' + result.output.split('[', 1)[1] - self.assertEqual([ - { - 'hostname': 'pool1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool', - }, { - 'hostname': 'pool3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool', - }, { - 'hostname': 'host1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'hardware', - }, { - 'hostname': 'host3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'hardware', - }, ], - json.loads(stripped_output), - ) - self.assertEqual( - 4, - len(self.calls('SoftLayer_Metric_Tracking_Object', - 'getSummaryData')), - ) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=101) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=103) + json_output = json.loads(stripped_output) + self.assertEqual(json_output[0]['hostname'], 'pool1') + self.assertEqual(json_output[1]['private_in'], 0) + self.assertEqual(json_output[2]['private_in'], 30) + self.assertEqual(json_output[3]['type'], 'hardware') + + self.assertEqual(4, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=101) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=103) - call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', - identifier=1)[0] - expected_args = ( - '2016-02-04 00:00:00 ', - '2016-03-04 12:34:56 ', + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] + expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', [{ 'keyName': 'PUBLICIN', 'name': 'publicIn', diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 6d515de38..11380e370 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -3,6 +3,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ +from unittest import mock as mock from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import SoftLayerAPIError @@ -166,3 +167,12 @@ def test_get_routers_with_datacenter(self): self.manager.get_routers(location='dal13') object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) + + def test_get_bandwidth_pools(self): + self.manager.get_bandwidth_pools() + self.assert_called_with('SoftLayer_Account', 'getBandwidthAllotments', mask=mock.ANY) + + def test_get_bandwidth_pool_counts(self): + total = self.manager.get_bandwidth_pool_counts(1234) + self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject', identifier=1234) + self.assertEqual(total, 2) \ No newline at end of file From 2665d367fd5ee567fed7a6fa92439481c39a2fc3 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 26 Jan 2022 16:06:15 -0600 Subject: [PATCH 0560/1385] tox fixes --- SoftLayer/fixtures/SoftLayer_Account.py | 2 +- ...er_Network_Bandwidth_Version1_Allotment.py | 2 +- tests/CLI/modules/account_tests.py | 2 +- tests/CLI/modules/report_tests.py | 123 +++++++++--------- tests/managers/account_tests.py | 2 +- 5 files changed, 66 insertions(+), 65 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 3f7d3cf4c..fb5aedb67 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1224,4 +1224,4 @@ 'name': 'MexRegion', 'projectedPublicBandwidthUsage': 9.88, 'totalBandwidthAllocated': 3361 -}] \ No newline at end of file +}] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py index d784b7e7b..422e7721d 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py @@ -3,4 +3,4 @@ 'bareMetalInstanceCount': 0, 'hardwareCount': 2, 'virtualGuestCount': 0 -} \ No newline at end of file +} diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 9ac821ace..b33c38c6e 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -141,4 +141,4 @@ def test_bandwidth_pools(self): result = self.run_command(['account', 'bandwidth-pools']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getBandwidthAllotments') - self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject') \ No newline at end of file + self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject') diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 11c7448e7..f756704c0 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -8,7 +8,8 @@ import json -from pprint import pprint as pp +from pprint import pprint as pp + class ReportTests(testing.TestCase): @@ -93,14 +94,14 @@ def test_bandwidth_report(self): ]) self.assert_no_fail(result) - + stripped_output = '[' + result.output.split('[', 1)[1] json_output = json.loads(stripped_output) pp(json.loads(stripped_output)) print("======= ^^^^^^^^^ ==============") self.assertEqual(json_output[0]['hostname'], 'pool1') self.assertEqual(json_output[0]['private_in'], 30) - + self.assertEqual(6, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=3) @@ -110,25 +111,25 @@ def test_bandwidth_report(self): self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=203) call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', - [{ - 'keyName': 'PUBLICIN', - 'name': 'publicIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PUBLICOUT', - 'name': 'publicOut', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEIN', - 'name': 'privateIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEOUT', - 'name': 'privateOut', - 'summaryType': 'sum', - }], - 300, - ) + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) self.assertEqual(expected_args, call.args) def test_virtual_bandwidth_report(self): @@ -192,25 +193,25 @@ def test_virtual_bandwidth_report(self): self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=203) call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', - [{ - 'keyName': 'PUBLICIN', - 'name': 'publicIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PUBLICOUT', - 'name': 'publicOut', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEIN', - 'name': 'privateIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEOUT', - 'name': 'privateOut', - 'summaryType': 'sum', - }], - 300, - ) + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) self.assertEqual(expected_args, call.args) def test_server_bandwidth_report(self): @@ -267,30 +268,30 @@ def test_server_bandwidth_report(self): self.assertEqual(json_output[1]['private_in'], 0) self.assertEqual(json_output[2]['private_in'], 30) self.assertEqual(json_output[3]['type'], 'hardware') - + self.assertEqual(4, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=101) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=103) call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', - [{ - 'keyName': 'PUBLICIN', - 'name': 'publicIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PUBLICOUT', - 'name': 'publicOut', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEIN', - 'name': 'privateIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEOUT', - 'name': 'privateOut', - 'summaryType': 'sum', - }], - 300, - ) + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) self.assertEqual(expected_args, call.args) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 11380e370..b32c223f6 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -175,4 +175,4 @@ def test_get_bandwidth_pools(self): def test_get_bandwidth_pool_counts(self): total = self.manager.get_bandwidth_pool_counts(1234) self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject', identifier=1234) - self.assertEqual(total, 2) \ No newline at end of file + self.assertEqual(total, 2) From 8c5fd3c0ce5ff339feaf21b10f20def171614284 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 4 Feb 2022 15:26:17 -0600 Subject: [PATCH 0561/1385] v5.9.9 changelog and updates --- CHANGELOG.md | 13 +++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21dd2223b..7f9166bc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log + +## [5.9.9] - 2021-12-07 + +https://github.com/softlayer/softlayer-python/compare/v5.9.8...v5.9.9 + +#### Improvements +- Add loadbalancer timeout values #1576 +- Add pricing date to slcli order preset-list #1578 + +#### New Commands +- `slcli vlan create-options` add new feature on vlan #1572 +- `slcli account bandwidth-pools` Bandwidth pool features #1579 + ## [5.9.8] - 2021-12-07 https://github.com/softlayer/softlayer-python/compare/v5.9.7...v5.9.8 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 9f6d4b6da..e7749589d 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.8' +VERSION = 'v5.9.9' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 165223b88..37af1f9b5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.8', + version='5.9.9', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From fd579dd13fe7f4814577979b00714abbd3d7b4b6 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 8 Feb 2022 10:59:02 -0400 Subject: [PATCH 0562/1385] Bandwidth pool management --- .../CLI/account/bandwidth_pools_detail.py | 66 ++++++++ SoftLayer/CLI/routes.py | 1 + ...er_Network_Bandwidth_Version1_Allotment.py | 145 ++++++++++++++++++ SoftLayer/managers/account.py | 11 ++ docs/cli/account.rst | 4 + tests/CLI/modules/account_tests.py | 5 + 6 files changed, 232 insertions(+) create mode 100644 SoftLayer/CLI/account/bandwidth_pools_detail.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py new file mode 100644 index 000000000..1878acde5 --- /dev/null +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -0,0 +1,66 @@ +"""Get bandwidth pools.""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer import AccountManager +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get bandwidth about a VLAN.""" + + manager = AccountManager(env.client) + bandwidths = manager.getBandwidthDetail(identifier) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['Id', bandwidths['id']]) + table.add_row(['Name', bandwidths['name']]) + table.add_row(['Create Date', bandwidths['createDate']]) + table.add_row(['Current Usage', bandwidths['billingCyclePublicBandwidthUsage']['amountOut']]) + table.add_row(['Projected Usage', bandwidths['projectedPublicBandwidthUsage']]) + table.add_row(['Inbound Usage', bandwidths['inboundPublicBandwidthUsage']]) + if bandwidths['hardware'] != []: + table.add_row(['hardware', _bw_table(bandwidths['hardware'])]) + else: + table.add_row(['hardware', 'not found']) + + if bandwidths['virtualGuests'] != []: + table.add_row(['virtualGuests', _virtual_table(bandwidths['virtualGuests'])]) + else: + table.add_row(['virtualGuests', 'Not Found']) + + if bandwidths['bareMetalInstances'] != []: + table.add_row(['Netscale', _bw_table(bandwidths['bareMetalInstances'])]) + else: + table.add_row(['Netscale', 'Not Found']) + + env.fout(table) + + +def _bw_table(bw_data): + """Generates a bandwidth useage table""" + table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) + for bw_point in bw_data: + amount = bw_point['bandwidthAllotmentDetail']['allocation']['amount'] + current = utils.lookup(bw_point, 'outboundBandwidthUsage') + ip_address = bw_point['primaryIpAddress'] + table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) + return [table_data] + + +def _virtual_table(bw_data): + """Generates a virtual bandwidth usage table""" + table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) + for bw_point in bw_data: + amount = bw_point['bandwidthAllotmentDetail']['allocation']['amount'] + current = utils.lookup(bw_point, 'outboundPublicBandwidthUsage') + ip_address = bw_point['primaryIpAddress'] + table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) + return [table_data] diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2d44a824f..85a6ee336 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -15,6 +15,7 @@ ('account:invoice-detail', 'SoftLayer.CLI.account.invoice_detail:cli'), ('account:invoices', 'SoftLayer.CLI.account.invoices:cli'), ('account:events', 'SoftLayer.CLI.account.events:cli'), + ('account:bandwidth-pools-detail', 'SoftLayer.CLI.account.bandwidth_pools_detail:cli'), ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), ('account:licenses', 'SoftLayer.CLI.account.licenses:cli'), ('account:summary', 'SoftLayer.CLI.account.summary:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py new file mode 100644 index 000000000..ddf8752a3 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py @@ -0,0 +1,145 @@ +getObject = { + 'bandwidthAllotmentTypeId': 2, + 'createDate': '2016-07-25T08:31:17-07:00', + 'id': 123456, + 'locationGroupId': 262, + 'name': 'MexRegion', + 'serviceProviderId': 1, + 'activeDetails': [ + { + 'allocationId': 48293300, + 'bandwidthAllotmentId': 309961, + 'effectiveDate': '2022-02-04T00:00:00-06:00', + 'id': 48882086, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '5000', + 'id': 48293300, + } + }, + { + 'allocationId': 48293302, + 'bandwidthAllotmentId': 309961, + 'effectiveDate': '2022-02-04T00:00:00-06:00', + 'id': 48882088, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '5000', + 'id': 48293302, + } + } + ], + 'bareMetalInstances': [], + 'billingCyclePublicBandwidthUsage': { + 'amountIn': '.23642', + 'amountOut': '.05475', + 'bandwidthUsageDetailTypeId': '1', + 'trackingObject': { + 'id': 258963, + 'resourceTableId': 309961, + 'startDate': '2021-03-10T11:04:56-06:00', + } + }, + 'hardware': [ + { + 'domain': 'test.com', + 'fullyQualifiedDomainName': 'testpooling.test.com', + 'hardwareStatusId': 5, + 'hostname': 'testpooling', + 'id': 36589, + 'manufacturerSerialNumber': 'J122Y7N', + 'provisionDate': '2022-01-24T15:17:03-06:00', + 'serialNumber': 'SL018EA8', + 'serviceProviderId': 1, + 'bandwidthAllotmentDetail': { + 'allocationId': 48293302, + 'bandwidthAllotmentId': 309961, + 'effectiveDate': '2022-02-04T00:00:00-06:00', + 'id': 48882088, + 'allocation': { + 'amount': '5000', + 'id': 48293302, + } + }, + 'globalIdentifier': '36e63026-5fa1-456d-a04f-adf34e60e2f4', + 'hardwareStatus': { + 'id': 5, + 'status': 'ACTIVE' + }, + 'networkManagementIpAddress': '10.130.97.247', + 'outboundBandwidthUsage': '.02594', + 'primaryBackendIpAddress': '10.130.97.227', + 'primaryIpAddress': '169.57.4.70', + 'privateIpAddress': '10.130.97.227' + }, + { + 'domain': 'testtest.com', + 'fullyQualifiedDomainName': 'testpooling2.test.com', + 'hardwareStatusId': 5, + 'hostname': 'testpooling2', + 'id': 25478, + 'manufacturerSerialNumber': 'J12935M', + 'notes': '', + 'provisionDate': '2022-01-24T15:44:20-06:00', + 'serialNumber': 'SL01HIIB', + 'serviceProviderId': 1, + 'bandwidthAllotmentDetail': { + 'allocationId': 48293300, + 'bandwidthAllotmentId': 309961, + 'effectiveDate': '2022-02-04T00:00:00-06:00', + 'id': 48882086, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '5000', + 'id': 478965, + } + }, + 'globalIdentifier': '6ea407bd-9c07-4129-9103-9fda8a9e7028', + 'hardwareStatus': { + 'id': 5, + 'status': 'ACTIVE' + }, + 'networkManagementIpAddress': '10.130.97.252', + 'outboundBandwidthUsage': '.02884', + 'primaryBackendIpAddress': '10.130.97.248', + 'primaryIpAddress': '169.57.4.73', + 'privateIpAddress': '10.130.97.248' + } + ], + 'inboundPublicBandwidthUsage': '.23642', + 'projectedPublicBandwidthUsage': 0.43, + 'virtualGuests': [{ + 'createDate': '2021-06-09T13:49:28-07:00', + 'deviceStatusId': 8, + 'domain': 'cgallo.com', + 'fullyQualifiedDomainName': 'KVM-Test.test.com', + 'hostname': 'KVM-Test', + 'id': 3578963, + 'maxCpu': 2, + 'maxCpuUnits': 'CORE', + 'maxMemory': 4096, + 'startCpus': 2, + 'statusId': 1001, + 'typeId': 1, + 'uuid': '15951561-6171-0dfc-f3d2-be039e51cc10', + 'bandwidthAllotmentDetail': { + 'allocationId': 45907006, + 'bandwidthAllotmentId': 138442, + 'effectiveDate': '2021-06-09T13:49:31-07:00', + 'id': 46467342, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '0', + 'id': 45907006, + } + }, + 'globalIdentifier': 'a245a7dd-acd1-4d1a-9356-cc1ac6b55b98', + 'outboundPublicBandwidthUsage': '.02845', + 'primaryBackendIpAddress': '10.208.73.53', + 'primaryIpAddress': '169.48.96.27', + 'status': { + 'keyName': 'ACTIVE', + 'name': 'Active' + } + }] +} diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 51c9c889c..905fc6cd0 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -326,3 +326,14 @@ def get_active_account_licenses(self): _mask = """billingItem,softwareDescription""" return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) + + def getBandwidthDetail(self, identifier): + """Gets bandwidth pool detail. + + :returns: bandwidth pool detail + """ + _mask = """activeDetails[allocation],projectedPublicBandwidthUsage, billingCyclePublicBandwidthUsage, + hardware[outboundBandwidthUsage,bandwidthAllotmentDetail[allocation]],inboundPublicBandwidthUsage, + virtualGuests[outboundPublicBandwidthUsage,bandwidthAllotmentDetail[allocation]], + bareMetalInstances[outboundBandwidthUsage,bandwidthAllotmentDetail[allocation]]""" + return self.client['SoftLayer_Network_Bandwidth_Version1_Allotment'].getObject(id=identifier, mask=_mask) diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 719c44fde..e7a248baf 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -43,3 +43,7 @@ Account Commands .. click:: SoftLayer.CLI.account.licenses:cli :prog: account licenses :show-nested: + +.. click:: SoftLayer.CLI.account.bandwidth_pools_detail:cli + :prog: account bandwidth-pools-detail + :show-nested: diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 8428d3306..aca62218c 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -136,3 +136,8 @@ def test_acccount_licenses(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getActiveVirtualLicenses') self.assert_called_with('SoftLayer_Account', 'getActiveAccountLicenses') + + def test_acccount_bandwidth_pool_detail(self): + result = self.run_command(['account', 'bandwidth-pools-detail', '123456']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject') From bc42a742a2615f1aba24f95350133b117f344d0f Mon Sep 17 00:00:00 2001 From: David Runge Date: Thu, 10 Feb 2022 00:18:42 +0100 Subject: [PATCH 0563/1385] Replace the use of ptable with prettytable {README.rst,tools/*}: Replace ptable with prettytable >= 2.0.0. SoftLayer/CLI/formatting.py: Only consider the import of prettytable. --- README.rst | 2 +- SoftLayer/CLI/formatting.py | 6 +----- setup.py | 2 +- tools/requirements.txt | 4 ++-- tools/test-requirements.txt | 4 ++-- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 15d5bcca3..3cb2219f8 100644 --- a/README.rst +++ b/README.rst @@ -167,7 +167,7 @@ If you cannot install python 3.6+ for some reason, you will need to use a versio Python Packages --------------- -* ptable >= 0.9.2 +* prettytable >= 2.0.0 * click >= 7 * requests >= 2.20.0 * prompt_toolkit >= 2 diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b28c54fe6..fcb5fe625 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -11,11 +11,7 @@ import click -# If both PTable and prettytable are installed, its impossible to use the new version -try: - from prettytable import prettytable -except ImportError: - import prettytable +import prettytable from SoftLayer.CLI import exceptions from SoftLayer import utils diff --git a/setup.py b/setup.py index 37af1f9b5..945dcf95b 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ }, python_requires='>=3.5', install_requires=[ - 'ptable >= 0.9.2', + 'prettytable >= 2.0.0', 'click >= 7', 'requests >= 2.20.0', 'prompt_toolkit >= 2', diff --git a/tools/requirements.txt b/tools/requirements.txt index ad902bc39..09f985d84 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,6 +1,6 @@ -ptable >= 0.9.2 +prettytable >= 2.0.0 click >= 7 requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 -urllib3 >= 1.24 \ No newline at end of file +urllib3 >= 1.24 diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index 0f1ec684c..3cc0f32e6 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -4,9 +4,9 @@ pytest pytest-cov mock sphinx -ptable >= 0.9.2 +prettytable >= 2.0.0 click >= 7 requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 -urllib3 >= 1.24 \ No newline at end of file +urllib3 >= 1.24 From eee36d53381d34c427c81f050b06c5e0664c70e5 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 11 Feb 2022 08:59:37 -0400 Subject: [PATCH 0564/1385] fix the some problems and fix the team code review comments --- .../CLI/account/bandwidth_pools_detail.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index 1878acde5..e4303c99d 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -12,7 +12,7 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Get bandwidth about a VLAN.""" + """Get bandwidth pool details.""" manager = AccountManager(env.client) bandwidths = manager.getBandwidthDetail(identifier) @@ -23,9 +23,12 @@ def cli(env, identifier): table.add_row(['Id', bandwidths['id']]) table.add_row(['Name', bandwidths['name']]) table.add_row(['Create Date', bandwidths['createDate']]) - table.add_row(['Current Usage', bandwidths['billingCyclePublicBandwidthUsage']['amountOut']]) - table.add_row(['Projected Usage', bandwidths['projectedPublicBandwidthUsage']]) - table.add_row(['Inbound Usage', bandwidths['inboundPublicBandwidthUsage']]) + current = "{} GB".format(bandwidths['billingCyclePublicBandwidthUsage']['amountOut'], 0) + table.add_row(['Current Usage', current]) + projected = "{} GB".format(bandwidths.get('projectedPublicBandwidthUsage', 0)) + table.add_row(['Projected Usage', projected]) + inbound = "{} GB".format(bandwidths.get('inboundPublicBandwidthUsage', 0)) + table.add_row(['Inbound Usage', inbound]) if bandwidths['hardware'] != []: table.add_row(['hardware', _bw_table(bandwidths['hardware'])]) else: @@ -48,8 +51,8 @@ def _bw_table(bw_data): """Generates a bandwidth useage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = bw_point['bandwidthAllotmentDetail']['allocation']['amount'] - current = utils.lookup(bw_point, 'outboundBandwidthUsage') + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut'), 0) + current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage'), 0) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] @@ -59,8 +62,8 @@ def _virtual_table(bw_data): """Generates a virtual bandwidth usage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = bw_point['bandwidthAllotmentDetail']['allocation']['amount'] - current = utils.lookup(bw_point, 'outboundPublicBandwidthUsage') + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut'), 0) + current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage'), 0) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] From 255ff24a7f6a038100ed5c17c353f07da9b2cdc0 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 11 Feb 2022 10:38:23 -0400 Subject: [PATCH 0565/1385] fix tox tool --- SoftLayer/CLI/account/bandwidth_pools_detail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index e4303c99d..4110a04df 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -23,7 +23,7 @@ def cli(env, identifier): table.add_row(['Id', bandwidths['id']]) table.add_row(['Name', bandwidths['name']]) table.add_row(['Create Date', bandwidths['createDate']]) - current = "{} GB".format(bandwidths['billingCyclePublicBandwidthUsage']['amountOut'], 0) + current = "{} GB".format(utils.lookup(bandwidths, 'billingCyclePublicBandwidthUsage', 'amountOut', 0)) table.add_row(['Current Usage', current]) projected = "{} GB".format(bandwidths.get('projectedPublicBandwidthUsage', 0)) table.add_row(['Projected Usage', projected]) @@ -51,8 +51,8 @@ def _bw_table(bw_data): """Generates a bandwidth useage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut'), 0) - current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage'), 0) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut', 0)) + current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage', 0)) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] @@ -62,8 +62,8 @@ def _virtual_table(bw_data): """Generates a virtual bandwidth usage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut'), 0) - current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage'), 0) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut', 0)) + current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage', 0)) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] From fc5614c040cccff6411c87ad6cc191f7ec0b6971 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 11 Feb 2022 10:42:14 -0400 Subject: [PATCH 0566/1385] Add id in the result in the command bandwidth-pools --- SoftLayer/CLI/account/bandwidth_pools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools.py b/SoftLayer/CLI/account/bandwidth_pools.py index 358bfd4d0..ddcfd0a86 100644 --- a/SoftLayer/CLI/account/bandwidth_pools.py +++ b/SoftLayer/CLI/account/bandwidth_pools.py @@ -20,6 +20,7 @@ def cli(env): items = manager.get_bandwidth_pools() table = formatting.Table([ + "Id", "Pool Name", "Region", "Servers", @@ -28,8 +29,8 @@ def cli(env): "Projected Usage" ], title="Bandwidth Pools") table.align = 'l' - for item in items: + id = item.get('id') name = item.get('name') region = utils.lookup(item, 'locationGroup', 'name') servers = manager.get_bandwidth_pool_counts(identifier=item.get('id')) @@ -37,5 +38,5 @@ def cli(env): current = "{} GB".format(utils.lookup(item, 'billingCyclePublicBandwidthUsage', 'amountOut')) projected = "{} GB".format(item.get('projectedPublicBandwidthUsage', 0)) - table.add_row([name, region, servers, allocation, current, projected]) + table.add_row([id, name, region, servers, allocation, current, projected]) env.fout(table) From f03fdb5f4194b1e3b89c300a8c0ae75352a0c995 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 11 Feb 2022 10:52:26 -0400 Subject: [PATCH 0567/1385] id renamed to id_bandwidth --- SoftLayer/CLI/account/bandwidth_pools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools.py b/SoftLayer/CLI/account/bandwidth_pools.py index ddcfd0a86..2d94c7bbb 100644 --- a/SoftLayer/CLI/account/bandwidth_pools.py +++ b/SoftLayer/CLI/account/bandwidth_pools.py @@ -30,7 +30,7 @@ def cli(env): ], title="Bandwidth Pools") table.align = 'l' for item in items: - id = item.get('id') + id_bandwidth = item.get('id') name = item.get('name') region = utils.lookup(item, 'locationGroup', 'name') servers = manager.get_bandwidth_pool_counts(identifier=item.get('id')) @@ -38,5 +38,5 @@ def cli(env): current = "{} GB".format(utils.lookup(item, 'billingCyclePublicBandwidthUsage', 'amountOut')) projected = "{} GB".format(item.get('projectedPublicBandwidthUsage', 0)) - table.add_row([id, name, region, servers, allocation, current, projected]) + table.add_row([id_bandwidth, name, region, servers, allocation, current, projected]) env.fout(table) From 43b33de5f130dbd86daf6c468665b5e227bfe0a7 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 11 Feb 2022 11:32:17 -0400 Subject: [PATCH 0568/1385] fix tox tool --- SoftLayer/CLI/account/bandwidth_pools_detail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index 4110a04df..a555be065 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -23,7 +23,7 @@ def cli(env, identifier): table.add_row(['Id', bandwidths['id']]) table.add_row(['Name', bandwidths['name']]) table.add_row(['Create Date', bandwidths['createDate']]) - current = "{} GB".format(utils.lookup(bandwidths, 'billingCyclePublicBandwidthUsage', 'amountOut', 0)) + current = "{} GB".format(utils.lookup(bandwidths, 'billingCyclePublicBandwidthUsage', 'amountOut')) table.add_row(['Current Usage', current]) projected = "{} GB".format(bandwidths.get('projectedPublicBandwidthUsage', 0)) table.add_row(['Projected Usage', projected]) @@ -51,8 +51,8 @@ def _bw_table(bw_data): """Generates a bandwidth useage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut', 0)) - current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage', 0)) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut')) + current = "{} GB".format(bw_point.get('outboundBandwidthUsage', 0)) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] @@ -62,8 +62,8 @@ def _virtual_table(bw_data): """Generates a virtual bandwidth usage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut', 0)) - current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage', 0)) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut')) + current = "{} GB".format(bw_point.get('outboundBandwidthUsage', 0)) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] From 6f8590690b22d3e076a6f9b2baf4cf42656bf326 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Feb 2022 09:59:13 -0400 Subject: [PATCH 0569/1385] fix tox tool and fix the some problems --- .../CLI/account/bandwidth_pools_detail.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index a555be065..7e609e961 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -22,17 +22,23 @@ def cli(env, identifier): table.align['value'] = 'l' table.add_row(['Id', bandwidths['id']]) table.add_row(['Name', bandwidths['name']]) - table.add_row(['Create Date', bandwidths['createDate']]) + table.add_row(['Create Date', utils.clean_time(bandwidths.get('createDate'), '%Y-%m-%d')]) current = "{} GB".format(utils.lookup(bandwidths, 'billingCyclePublicBandwidthUsage', 'amountOut')) + if current is None: + current = '-' table.add_row(['Current Usage', current]) projected = "{} GB".format(bandwidths.get('projectedPublicBandwidthUsage', 0)) + if projected is None: + projected = '-' table.add_row(['Projected Usage', projected]) inbound = "{} GB".format(bandwidths.get('inboundPublicBandwidthUsage', 0)) + if inbound is None: + inbound = '-' table.add_row(['Inbound Usage', inbound]) if bandwidths['hardware'] != []: table.add_row(['hardware', _bw_table(bandwidths['hardware'])]) else: - table.add_row(['hardware', 'not found']) + table.add_row(['hardware', 'Not Found']) if bandwidths['virtualGuests'] != []: table.add_row(['virtualGuests', _virtual_table(bandwidths['virtualGuests'])]) @@ -40,9 +46,9 @@ def cli(env, identifier): table.add_row(['virtualGuests', 'Not Found']) if bandwidths['bareMetalInstances'] != []: - table.add_row(['Netscale', _bw_table(bandwidths['bareMetalInstances'])]) + table.add_row(['Netscaler', _bw_table(bandwidths['bareMetalInstances'])]) else: - table.add_row(['Netscale', 'Not Found']) + table.add_row(['Netscaler', 'Not Found']) env.fout(table) @@ -51,9 +57,11 @@ def _bw_table(bw_data): """Generates a bandwidth useage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut')) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amount')) current = "{} GB".format(bw_point.get('outboundBandwidthUsage', 0)) - ip_address = bw_point['primaryIpAddress'] + ip_address = bw_point.get('primaryIpAddress') + if ip_address is None: + ip_address = '-' table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] @@ -62,8 +70,10 @@ def _virtual_table(bw_data): """Generates a virtual bandwidth usage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut')) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amount')) current = "{} GB".format(bw_point.get('outboundBandwidthUsage', 0)) - ip_address = bw_point['primaryIpAddress'] + ip_address = bw_point.get('primaryIpAddress') + if ip_address is None: + ip_address = '-' table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] From 377387bceb1cc52a2277b17a824bb70420ec3819 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 28 Feb 2022 17:06:12 -0600 Subject: [PATCH 0570/1385] #1590 basic structure for the DC closure report --- SoftLayer/CLI/report/dc_closures.py | 125 ++++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + 2 files changed, 126 insertions(+) create mode 100644 SoftLayer/CLI/report/dc_closures.py diff --git a/SoftLayer/CLI/report/dc_closures.py b/SoftLayer/CLI/report/dc_closures.py new file mode 100644 index 000000000..1f4e307c4 --- /dev/null +++ b/SoftLayer/CLI/report/dc_closures.py @@ -0,0 +1,125 @@ +"""Metric Utilities""" +import datetime +import itertools +import sys + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +from pprint import pprint as pp + +@click.command(short_help="""Report on Resources in closing datacenters""") +@environment.pass_env +def cli(env): + """Report on Resources in closing datacenters + + Displays a list of Datacenters soon to be shutdown, and any resources on the account +in those locations + """ + + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, +backendRouterName, frontendRouterName]""" + closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask) + # Find all VLANs in the POD that is going to close. + search = "_objectType:SoftLayer_Network_Vlan primaryRouter.hostname: \"{}\" || primaryRouter.hostname: \"{}\"" + resource_mask = """mask[ + resource(SoftLayer_Network_Vlan)[ + id,fullyQualifiedName,name,note,vlanNumber,networkSpace, + virtualGuests[id,fullyQualifiedDomainName,billingItem[cancellationDate]], + hardware[id,fullyQualifiedDomainName,billingItem[cancellationDate]], + networkVlanFirewall[id,primaryIpAddress,billingItem[cancellationDate]], + privateNetworkGateways[id,name,networkSpace], + publicNetworkGateways[id,name,networkSpace] + ] + ] + """ + table_title = "Resources in closing datacenters" + resource_table = formatting.Table(["Id", "Name", "Public VLAN", "Private VLAN", "Type", "Datacenter", + "POD", "Cancellation Date"], title=table_title) + resource_table.align = 'l' + for pod in closing_pods: + resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} + vlans = env.client.call('SoftLayer_Search', 'advancedSearch', + search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), + iter=True, mask=resource_mask) + for vlan in vlans: + resources = process_vlan(vlan.get('resource', {}), resources) + + for resource_type in resources.keys(): + + for resource_object in resources[resource_type].values(): + resource_table.add_row([ + resource_object['id'], + resource_object['name'], + resource_object['vlan'].get('PUBLIC', '-'), + resource_object['vlan'].get('PRIVATE', '-'), + resource_type, + pod.get('datacenterLongName'), + pod.get('backendRouterName'), + resource_object['cancelDate'] + ]) + + env.fout(resource_table) + + +# returns a Table Row for a given resource +def process_vlan(vlan, resources=None): + if resources is None: + resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} + + type_x = "virtual" + for x in vlan.get('virtualGuests', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + + type_x = 'hardware' + for x in vlan.get('hardware', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + + type_x = 'firewall' + for x in vlan.get('networkVlanFirewall', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('primaryIpAddress', vlan, x, existing) + + type_x = 'gateway' + for x in vlan.get('privateNetworkGateways', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + for x in vlan.get('publicNetworkGateways', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + + return resources + +# name_property is what property to use as the name from resource +# vlan is the vlan object +# resource has the data we want +# entry is for any existing data +def build_resource_object(name_property, vlan, resource, entry): + new_entry = { + 'id': resource.get('id'), + 'name': resource.get(name_property), + 'vlan': {vlan.get('networkSpace'): vlan.get('vlanNumber')}, + 'cancelDate': utils.clean_time(utils.lookup(resource, 'billingItem', 'cancellationDate')) + } + if entry: + entry['vlan'][vlan.get('networkSpace')] = vlan.get('vlanNumber') + else: + entry = new_entry + + return entry \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 02d3420d3..995845419 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -365,6 +365,7 @@ ('report', 'SoftLayer.CLI.report'), ('report:bandwidth', 'SoftLayer.CLI.report.bandwidth:cli'), + ('report:datacenter-closures', 'SoftLayer.CLI.report.dc_closures:cli'), ('autoscale', 'SoftLayer.CLI.autoscale'), ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), From da2273ee50e983868065270f0a3445f108b59526 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 1 Mar 2022 17:49:45 -0600 Subject: [PATCH 0571/1385] #1590 added docs and unit tests --- SoftLayer/CLI/report/dc_closures.py | 76 ++++++++++----------- docs/cli/reports.rst | 12 +++- tests/CLI/modules/report_tests.py | 102 ++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 39 deletions(-) diff --git a/SoftLayer/CLI/report/dc_closures.py b/SoftLayer/CLI/report/dc_closures.py index 1f4e307c4..563533ab1 100644 --- a/SoftLayer/CLI/report/dc_closures.py +++ b/SoftLayer/CLI/report/dc_closures.py @@ -1,8 +1,4 @@ -"""Metric Utilities""" -import datetime -import itertools -import sys - +"""Report on Resources in closing datacenters""" import click from SoftLayer.CLI import environment @@ -10,14 +6,12 @@ from SoftLayer import utils -from pprint import pprint as pp - @click.command(short_help="""Report on Resources in closing datacenters""") @environment.pass_env def cli(env): """Report on Resources in closing datacenters - Displays a list of Datacenters soon to be shutdown, and any resources on the account + Displays a list of Datacenters soon to be shutdown, and any resources on the account in those locations """ @@ -33,7 +27,7 @@ def cli(env): } mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" - closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask) + closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) # Find all VLANs in the POD that is going to close. search = "_objectType:SoftLayer_Network_Vlan primaryRouter.hostname: \"{}\" || primaryRouter.hostname: \"{}\"" resource_mask = """mask[ @@ -54,16 +48,17 @@ def cli(env): for pod in closing_pods: resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} vlans = env.client.call('SoftLayer_Search', 'advancedSearch', - search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), - iter=True, mask=resource_mask) + search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), + iter=True, mask=resource_mask) + # Go through the vlans and coalate the resources into a data structure that is easy to print out for vlan in vlans: resources = process_vlan(vlan.get('resource', {}), resources) - - for resource_type in resources.keys(): - - for resource_object in resources[resource_type].values(): + + # Go through each resource and add it to the table + for resource_type, resource_values in resources.items(): + for resource_id, resource_object in resource_values.items(): resource_table.add_row([ - resource_object['id'], + resource_id, resource_object['name'], resource_object['vlan'].get('PUBLIC', '-'), resource_object['vlan'].get('PRIVATE', '-'), @@ -72,46 +67,51 @@ def cli(env): pod.get('backendRouterName'), resource_object['cancelDate'] ]) - + env.fout(resource_table) # returns a Table Row for a given resource def process_vlan(vlan, resources=None): + """Takes in a vlan object and pulls out the needed resources""" if resources is None: resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} type_x = "virtual" - for x in vlan.get('virtualGuests', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + for obj_x in vlan.get('virtualGuests', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, obj_x, existing) type_x = 'hardware' - for x in vlan.get('hardware', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + for obj_x in vlan.get('hardware', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, obj_x, existing) type_x = 'firewall' - for x in vlan.get('networkVlanFirewall', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('primaryIpAddress', vlan, x, existing) + for obj_x in vlan.get('networkVlanFirewall', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('primaryIpAddress', vlan, obj_x, existing) type_x = 'gateway' - for x in vlan.get('privateNetworkGateways', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) - for x in vlan.get('publicNetworkGateways', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + for obj_x in vlan.get('privateNetworkGateways', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('name', vlan, obj_x, existing) + for obj_x in vlan.get('publicNetworkGateways', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('name', vlan, obj_x, existing) return resources -# name_property is what property to use as the name from resource -# vlan is the vlan object -# resource has the data we want -# entry is for any existing data + def build_resource_object(name_property, vlan, resource, entry): - new_entry = { + """builds out a resource object and puts the required values in the right place. + + :param: name_property is what property to use as the name from resource + :param: vlan is the vlan object + :param: resource has the data we want + :param: entry is for any existing data + """ + new_entry = { 'id': resource.get('id'), 'name': resource.get(name_property), 'vlan': {vlan.get('networkSpace'): vlan.get('vlanNumber')}, @@ -122,4 +122,4 @@ def build_resource_object(name_property, vlan, resource, entry): else: entry = new_entry - return entry \ No newline at end of file + return entry diff --git a/docs/cli/reports.rst b/docs/cli/reports.rst index f62de5882..39299e99b 100644 --- a/docs/cli/reports.rst +++ b/docs/cli/reports.rst @@ -14,4 +14,14 @@ A list of datacenters, and how many servers, VSI, vlans, subnets and public_ips .. click:: SoftLayer.CLI.report.bandwidth:cli :prog: report bandwidth - :show-nested: \ No newline at end of file + :show-nested: + + +.. click:: SoftLayer.CLI.report.dc_closures:cli + :prog: report datacenter-closures + :show-nested: + +Displays some basic information about the Servers and other resources that are in Datacenters scheduled to be +decommissioned in the near future. +See `IBM Cloud Datacenter Consolidation `_ for +more information \ No newline at end of file diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index f756704c0..f2012ab34 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -7,6 +7,7 @@ from SoftLayer import testing import json +from unittest import mock as mock from pprint import pprint as pp @@ -295,3 +296,104 @@ def test_server_bandwidth_report(self): 300, ) self.assertEqual(expected_args, call.args) + + def test_dc_closure_report(self): + search_mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + search_mock.side_effect = [_advanced_search(), [], [], []] + result = self.run_command(['report', 'datacenter-closures']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects', filter=mock.ANY, mask=mock.ANY) + self.assert_called_with('SoftLayer_Search', 'advancedSearch') + json_output = json.loads(result.output) + pp(json_output) + self.assertEqual(5, len(json_output)) + self.assertEqual('bcr01a.ams01', json_output[0]['POD']) + + +def _advanced_search(): + results = [{'matchedTerms': ['primaryRouter.hostname:|fcr01a.mex01|'], + 'relevanceScore': '5.4415264', + 'resource': {'fullyQualifiedName': 'mex01.fcr01.858', + 'hardware': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testpooling2.ibmtest.com', + 'id': 1676221}, + {'billingItem': {'cancellationDate': '2022-03-03T23:59:59-06:00'}, + 'fullyQualifiedDomainName': 'testpooling.ibmtest.com', + 'id': 1534033}], + 'id': 1133383, + 'name': 'Mex-BM-Public', + 'networkSpace': 'PUBLIC', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 858}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|fcr01a.mex01|'], + 'relevanceScore': '5.4415264', + 'resource': {'fullyQualifiedName': 'mex01.fcr01.1257', + 'hardware': [], + 'id': 2912280, + 'networkSpace': 'PUBLIC', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'imageTest.ibmtest.com', + 'id': 127270182}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'test.deleteme.com', + 'id': 106291032}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testslack.test.com', + 'id': 127889958}], + 'vlanNumber': 1257}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '5.003179', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1472', + 'hardware': [], + 'id': 2912282, + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'imageTest.ibmtest.com', + 'id': 127270182}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'test.deleteme.com', + 'id': 106291032}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testslack.test.com', + 'id': 127889958}], + 'vlanNumber': 1472}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '4.9517627', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1664', + 'hardware': [{'billingItem': {'cancellationDate': '2022-03-03T23:59:59-06:00'}, + 'fullyQualifiedDomainName': 'testpooling.ibmtest.com', + 'id': 1534033}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testpooling2.ibmtest.com', + 'id': 1676221}], + 'id': 3111644, + 'name': 'testmex', + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 1664}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '4.9517627', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1414', + 'hardware': [], + 'id': 2933662, + 'name': 'test-for-trunks', + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 1414}, + 'resourceType': 'SoftLayer_Network_Vlan'}] + return results From 4c85a3e6507f8b7aef71ecb30cead241dbf39358 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 10 Mar 2022 14:59:09 -0400 Subject: [PATCH 0572/1385] New Command slcli hardware|virtual monitoring --- SoftLayer/CLI/hardware/monitoring.py | 37 +++++++++++++++++++ SoftLayer/CLI/routes.py | 2 + SoftLayer/CLI/virt/monitoring.py | 37 +++++++++++++++++++ .../fixtures/SoftLayer_Hardware_Server.py | 32 +++++++++++++++- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 31 ++++++++++++++-- SoftLayer/managers/hardware.py | 2 + SoftLayer/managers/vs.py | 1 + tests/CLI/modules/server_tests.py | 4 ++ tests/CLI/modules/vs/vs_tests.py | 4 ++ 9 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 SoftLayer/CLI/hardware/monitoring.py create mode 100644 SoftLayer/CLI/virt/monitoring.py diff --git a/SoftLayer/CLI/hardware/monitoring.py b/SoftLayer/CLI/hardware/monitoring.py new file mode 100644 index 000000000..81640f3e5 --- /dev/null +++ b/SoftLayer/CLI/hardware/monitoring.py @@ -0,0 +1,37 @@ +"""Get monitoring for a hardware device.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get details for a hardware monitors device.""" + + hardware = SoftLayer.HardwareManager(env.client) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + monitoring = hardware.get_hardware(identifier) + + table.add_row(['domain', monitoring.get('fullyQualifiedDomainName')]) + table.add_row(['public Ip', monitoring.get('primaryIpAddress')]) + table.add_row(['private Ip', monitoring.get('primaryBackendIpAddress')]) + table.add_row(['location', monitoring['datacenter']['longName']]) + + monitoring_table = formatting.Table(['Id', 'ipAddress', 'status', 'type', 'notify']) + for monitor in monitoring['networkMonitors']: + monitoring_table.add_row([monitor.get('id'), monitor.get('ipAddress'), monitor.get('status'), + monitor['queryType']['name'], monitor['responseAction']['actionDescription']]) + + table.add_row(['Devices monitors', monitoring_table]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 3bd02eae9..d8176f1ca 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -52,6 +52,7 @@ ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), + ('virtual:monitoring', 'SoftLayer.CLI.virt.monitoring:cli'), ('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'), ('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'), @@ -280,6 +281,7 @@ ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), ('hardware:sensor', 'SoftLayer.CLI.hardware.sensor:cli'), + ('hardware:monitoring', 'SoftLayer.CLI.hardware.monitoring:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/CLI/virt/monitoring.py b/SoftLayer/CLI/virt/monitoring.py new file mode 100644 index 000000000..4e76549cf --- /dev/null +++ b/SoftLayer/CLI/virt/monitoring.py @@ -0,0 +1,37 @@ +"""Get monitoring for a vSI device.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get details for a vsi monitors device.""" + + vsi = SoftLayer.VSManager(env.client) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + monitoring = vsi.get_instance(identifier) + + table.add_row(['domain', monitoring.get('fullyQualifiedDomainName')]) + table.add_row(['public Ip', monitoring.get('primaryIpAddress')]) + table.add_row(['private Ip', monitoring.get('primaryBackendIpAddress')]) + table.add_row(['location', monitoring['datacenter']['longName']]) + + monitoring_table = formatting.Table(['Id', 'ipAddress', 'status', 'type', 'notify']) + for monitor in monitoring['networkMonitors']: + monitoring_table.add_row([monitor.get('id'), monitor.get('ipAddress'), monitor.get('status'), + monitor['queryType']['name'], monitor['responseAction']['actionDescription']]) + + table.add_row(['Devices monitors', monitoring_table]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 938c5cebc..0b3a6c748 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -1,7 +1,7 @@ getObject = { 'id': 1000, 'globalIdentifier': '1a2b3c-1701', - 'datacenter': {'id': 50, 'name': 'TEST00', + 'datacenter': {'id': 50, 'name': 'TEST00', 'longName': 'test 00', 'description': 'Test Data Center'}, 'billingItem': { 'id': 6327, @@ -74,7 +74,35 @@ 'friendlyName': 'Friendly Transaction Name', 'id': 6660 } - } + }, + 'networkMonitors': [ + { + 'hardwareId': 3123796, + 'hostId': 3123796, + 'id': 19016454, + 'ipAddress': '169.53.167.199', + 'queryTypeId': 1, + 'responseActionId': 2, + 'status': 'ON', + 'waitCycles': 0, + 'lastResult': { + 'finishTime': '2022-03-10T08:31:40-06:00', + 'responseStatus': 2, + 'responseTime': 159.15, + }, + 'queryType': { + 'description': 'Test ping to address', + 'id': 1, + 'monitorLevel': 0, + 'name': 'SERVICE PING' + }, + 'responseAction': { + 'actionDescription': 'Notify Users', + 'id': 2, + 'level': 0 + } + } + ] } editObject = True setTags = True diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 4662b68f8..f7e422d22 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -47,7 +47,7 @@ 'preset': {'keyName': 'B1_8X16X100'} } }, - 'datacenter': {'id': 50, 'name': 'TEST00', + 'datacenter': {'id': 50, 'name': 'TEST00', 'longName': 'test 00', 'description': 'Test Data Center'}, 'powerState': {'keyName': 'RUNNING', 'name': 'Running'}, 'maxCpu': 2, @@ -83,6 +83,29 @@ 'softwareDescription': {'name': 'Ubuntu'}} }], 'tagReferences': [{'tag': {'name': 'production'}}], + 'networkMonitors': [ + { + 'guestId': 116114480, + 'hostId': 116114480, + 'id': 17653845, + 'ipAddress': '52.116.23.73', + 'queryTypeId': 1, + 'responseActionId': 1, + 'status': 'ON', + 'waitCycles': 0, + 'queryType': { + 'description': 'Test ping to address', + 'id': 1, + 'monitorLevel': 0, + 'name': 'SERVICE PING' + }, + 'responseAction': { + 'actionDescription': 'Do Nothing', + 'id': 1, + 'level': 0 + } + } + ] } getCreateObjectOptions = { 'flavors': [ @@ -894,6 +917,6 @@ allowAccessToNetworkStorageList = True attachDiskImage = { - "createDate": "2021-03-22T13:15:31-06:00", - "id": 1234567 - } + "createDate": "2021-03-22T13:15:31-06:00", + "id": 1234567 +} diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fe494f83d..4ef7333aa 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -231,6 +231,7 @@ def get_hardware(self, hardware_id, **kwargs): 'domain,' 'provisionDate,' 'hardwareStatus,' + 'bareMetalInstanceFlag,' 'processorPhysicalCoreAmount,' 'memoryCapacity,' 'notes,' @@ -269,6 +270,7 @@ def get_hardware(self, hardware_id, **kwargs): 'hourlyBillingFlag,' 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' + 'monitoringServiceComponent,networkMonitors[queryType,lastResult,responseAction],' 'remoteManagementAccounts[username,password]' ) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 75c00127a..2fa698dce 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -252,6 +252,7 @@ def get_instance(self, instance_id, **kwargs): 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' 'dedicatedHost.id,' + 'monitoringServiceComponent,networkMonitors[queryType,lastResult,responseAction],' 'placementGroupId' ) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index d688a6a90..a150217e2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -1010,3 +1010,7 @@ def test_sensor(self): def test_sensor_discrete(self): result = self.run_command(['hardware', 'sensor', '100', '--discrete']) self.assert_no_fail(result) + + def test_monitoring(self): + result = self.run_command(['hardware', 'monitoring', '100']) + self.assert_no_fail(result) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 982ed0879..4ae31fd6d 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -937,3 +937,7 @@ def test_authorize_volume_and_portable_storage_vs(self): result = self.run_command(['vs', 'authorize-storage', '--username-storage=SL01SEL301234-11', '--portable-id=12345', '1234']) self.assert_no_fail(result) + + def test_monitoring_vs(self): + result = self.run_command(['vs', 'monitoring', '1234']) + self.assert_no_fail(result) From d5e4f1ed197462a8bf28fb9ec6e2625456ee5f76 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 10 Mar 2022 15:13:35 -0400 Subject: [PATCH 0573/1385] add documentation --- docs/cli/hardware.rst | 4 ++++ docs/cli/vs.rst | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 1f8375cfe..6f7ed344e 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -24,6 +24,10 @@ Interacting with Hardware :prog: hardware create :show-nested: +.. click:: SoftLayer.CLI.hardware.monitoring:cli + :prog: hardware monitoring + :show-nested: + Provides some basic functionality to order a server. `slcli order` has a more full featured method of ordering servers. This command only supports the FAST_PROVISION type. diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 6227e0570..3dd34a405 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -271,6 +271,10 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual authorize-storage :show-nested: +.. click:: SoftLayer.CLI.virt.monitoring:cli + :prog: virtual monitoring + :show-nested: + Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity From b6c3336fc0120b59088b5ee4f0feba31dbe9a36e Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 10 Mar 2022 18:00:52 -0400 Subject: [PATCH 0574/1385] fix to errors in slcli hw create-options --- SoftLayer/managers/account.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index aadb4af94..15e1ddfef 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -284,6 +284,11 @@ def get_routers(self, location=None, mask=None): :param string location: location string :returns: Routers """ + + if mask is None: + mask = """ + topLevelLocation + """ object_filter = '' if location: object_filter = { From 2abe0e6427ba6840ba3dfbc3c87066b4bcb424c6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 14:22:50 -0600 Subject: [PATCH 0575/1385] v6.0.0 release --- CHANGELOG.md | 16 +++++++++++++++- SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9166bc3..799351a6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,21 @@ # Change Log -## [5.9.9] - 2021-12-07 +## [6.0.0] - 2022-03-11 + + +## What's Changed +* Replace the use of ptable with prettytable by @dvzrv in https://github.com/softlayer/softlayer-python/pull/1584 +* Bandwidth pool management by @caberos in https://github.com/softlayer/softlayer-python/pull/1582 +* Add id in the result in the command bandwidth-pools by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1586 +* Datacenter closure report by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1592 +* fix to errors in slcli hw create-options by @caberos in https://github.com/softlayer/softlayer-python/pull/1594 + + +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v5.9.9...v6.0.0 + + +## [5.9.9] - 2022-02-04 https://github.com/softlayer/softlayer-python/compare/v5.9.8...v5.9.9 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index e7749589d..3c37a4af7 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.9' +VERSION = 'v5.6.0' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 945dcf95b..cdb4e2500 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.9', + version='5.6.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 09f63db7189272183a58dc4b255f707fbf9a68b4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 14:24:13 -0600 Subject: [PATCH 0576/1385] v6.0.0 version updates --- SoftLayer/consts.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 3c37a4af7..38a8289bf 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.6.0' +VERSION = 'v6.0.0' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index cdb4e2500..211c077d5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.6.0', + version='6.0.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 83915de22ea85b5701cf2c6e25fe48f64f82a196 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:02:09 -0600 Subject: [PATCH 0577/1385] added long_description_content_type to the setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 211c077d5..39bf801f6 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,7 @@ version='6.0.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, + long_description_content_type='text/x-rst', author='SoftLayer, Inc., an IBM Company', author_email='SLDNDeveloperRelations@wwpdl.vnet.ibm.com', packages=find_packages(exclude=['tests']), From a289994f4c076e2e604b86c19ffdd9875a021a20 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:04:20 -0600 Subject: [PATCH 0578/1385] normalized line endings --- .github/workflows/documentation.yml | 54 +-- .github/workflows/test_pypi_release.yml | 72 ++-- SoftLayer/CLI/account/billing_items.py | 120 +++--- SoftLayer/CLI/account/cancel_item.py | 36 +- SoftLayer/CLI/autoscale/__init__.py | 2 +- SoftLayer/CLI/tags/__init__.py | 2 +- SoftLayer/CLI/tags/cleanup.py | 52 +-- SoftLayer/CLI/tags/details.py | 54 +-- SoftLayer/CLI/tags/list.py | 150 ++++---- SoftLayer/CLI/tags/taggable.py | 54 +-- SoftLayer/CLI/virt/migrate.py | 164 ++++---- SoftLayer/fixtures/BluePages_Search.py | 2 +- SoftLayer/fixtures/SoftLayer_Hardware.py | 178 ++++----- SoftLayer/fixtures/SoftLayer_Search.py | 46 +-- SoftLayer/fixtures/SoftLayer_Tag.py | 62 +-- SoftLayer/managers/tags.py | 460 +++++++++++------------ docCheck.py | 188 ++++----- docs/cli/nas.rst | 22 +- docs/cli/tags.rst | 60 +-- tests/CLI/modules/tag_tests.py | 226 +++++------ tests/managers/tag_tests.py | 416 ++++++++++---------- 21 files changed, 1210 insertions(+), 1210 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index c713212ee..d307fd937 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,27 +1,27 @@ -name: documentation - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.8] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Documentation Checks - run: | - python docCheck.py +name: documentation + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Documentation Checks + run: | + python docCheck.py diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index aea906c54..12443d257 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -1,37 +1,37 @@ -# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ - -name: Publish 📦 to TestPyPI - -on: - push: - branches: [test-pypi ] - -jobs: - build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install pypa/build - run: >- - python -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: >- - python -m - build - --sdist - --wheel - --outdir dist/ - . - - name: Publish 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{ secrets.CGALLO_TEST_PYPI }} +# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ + +name: Publish 📦 to TestPyPI + +on: + push: + branches: [test-pypi ] + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.CGALLO_TEST_PYPI }} repository_url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/SoftLayer/CLI/account/billing_items.py b/SoftLayer/CLI/account/billing_items.py index 32bc6c271..48abe4644 100644 --- a/SoftLayer/CLI/account/billing_items.py +++ b/SoftLayer/CLI/account/billing_items.py @@ -1,60 +1,60 @@ -"""Lists all active billing items on this account. See https://cloud.ibm.com/billing/billing-items""" -# :license: MIT, see LICENSE for more details. -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.managers.account import AccountManager as AccountManager -from SoftLayer import utils - - -@click.command() -@environment.pass_env -def cli(env): - """Lists billing items with some other useful information. - - Similiar to https://cloud.ibm.com/billing/billing-items - """ - - manager = AccountManager(env.client) - items = manager.get_account_billing_items() - table = item_table(items) - - env.fout(table) - - -def item_table(items): - """Formats a table for billing items""" - table = formatting.Table([ - "Id", - "Create Date", - "Cost", - "Category Code", - "Ordered By", - "Description", - "Notes" - ], title="Billing Items") - table.align['Description'] = 'l' - table.align['Category Code'] = 'l' - for item in items: - description = item.get('description') - fqdn = "{}.{}".format(item.get('hostName', ''), item.get('domainName', '')) - if fqdn != ".": - description = fqdn - user = utils.lookup(item, 'orderItem', 'order', 'userRecord') - ordered_by = "IBM" - create_date = utils.clean_time(item.get('createDate'), in_format='%Y-%m-%d', out_format='%Y-%m-%d') - if user: - # ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) - ordered_by = user.get('displayName') - - table.add_row([ - item.get('id'), - create_date, - item.get('nextInvoiceTotalRecurringAmount'), - item.get('categoryCode'), - ordered_by, - utils.trim_to(description, 50), - utils.trim_to(item.get('notes', 'None'), 40), - ]) - return table +"""Lists all active billing items on this account. See https://cloud.ibm.com/billing/billing-items""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """Lists billing items with some other useful information. + + Similiar to https://cloud.ibm.com/billing/billing-items + """ + + manager = AccountManager(env.client) + items = manager.get_account_billing_items() + table = item_table(items) + + env.fout(table) + + +def item_table(items): + """Formats a table for billing items""" + table = formatting.Table([ + "Id", + "Create Date", + "Cost", + "Category Code", + "Ordered By", + "Description", + "Notes" + ], title="Billing Items") + table.align['Description'] = 'l' + table.align['Category Code'] = 'l' + for item in items: + description = item.get('description') + fqdn = "{}.{}".format(item.get('hostName', ''), item.get('domainName', '')) + if fqdn != ".": + description = fqdn + user = utils.lookup(item, 'orderItem', 'order', 'userRecord') + ordered_by = "IBM" + create_date = utils.clean_time(item.get('createDate'), in_format='%Y-%m-%d', out_format='%Y-%m-%d') + if user: + # ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + ordered_by = user.get('displayName') + + table.add_row([ + item.get('id'), + create_date, + item.get('nextInvoiceTotalRecurringAmount'), + item.get('categoryCode'), + ordered_by, + utils.trim_to(description, 50), + utils.trim_to(item.get('notes', 'None'), 40), + ]) + return table diff --git a/SoftLayer/CLI/account/cancel_item.py b/SoftLayer/CLI/account/cancel_item.py index de0fa446b..0cc08593d 100644 --- a/SoftLayer/CLI/account/cancel_item.py +++ b/SoftLayer/CLI/account/cancel_item.py @@ -1,18 +1,18 @@ -"""Cancels a billing item.""" -# :license: MIT, see LICENSE for more details. -import click - -from SoftLayer.CLI import environment -from SoftLayer.managers.account import AccountManager as AccountManager - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Cancels a billing item.""" - - manager = AccountManager(env.client) - item = manager.cancel_item(identifier) - - env.fout(item) +"""Cancels a billing item.""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.account import AccountManager as AccountManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancels a billing item.""" + + manager = AccountManager(env.client) + item = manager.cancel_item(identifier) + + env.fout(item) diff --git a/SoftLayer/CLI/autoscale/__init__.py b/SoftLayer/CLI/autoscale/__init__.py index 80cd82747..81d126383 100644 --- a/SoftLayer/CLI/autoscale/__init__.py +++ b/SoftLayer/CLI/autoscale/__init__.py @@ -1 +1 @@ -"""Autoscale""" +"""Autoscale""" diff --git a/SoftLayer/CLI/tags/__init__.py b/SoftLayer/CLI/tags/__init__.py index f8dd3783b..5b257eeec 100644 --- a/SoftLayer/CLI/tags/__init__.py +++ b/SoftLayer/CLI/tags/__init__.py @@ -1 +1 @@ -"""Manage Tags""" +"""Manage Tags""" diff --git a/SoftLayer/CLI/tags/cleanup.py b/SoftLayer/CLI/tags/cleanup.py index 26ddea7ef..54da9b20d 100644 --- a/SoftLayer/CLI/tags/cleanup.py +++ b/SoftLayer/CLI/tags/cleanup.py @@ -1,26 +1,26 @@ -"""Removes unused Tags""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI import environment -from SoftLayer.managers.tags import TagManager - - -@click.command() -@click.option('--dry-run', '-d', is_flag=True, default=False, - help="Don't delete, just show what will be deleted.") -@environment.pass_env -def cli(env, dry_run): - """Removes all empty tags.""" - - tag_manager = TagManager(env.client) - empty_tags = tag_manager.get_unattached_tags() - - for tag in empty_tags: - if dry_run: - click.secho("(Dry Run) Removing {}".format(tag.get('name')), fg='yellow') - else: - result = tag_manager.delete_tag(tag.get('name')) - color = 'green' if result else 'red' - click.secho("Removing {}".format(tag.get('name')), fg=color) +"""Removes unused Tags""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.tags import TagManager + + +@click.command() +@click.option('--dry-run', '-d', is_flag=True, default=False, + help="Don't delete, just show what will be deleted.") +@environment.pass_env +def cli(env, dry_run): + """Removes all empty tags.""" + + tag_manager = TagManager(env.client) + empty_tags = tag_manager.get_unattached_tags() + + for tag in empty_tags: + if dry_run: + click.secho("(Dry Run) Removing {}".format(tag.get('name')), fg='yellow') + else: + result = tag_manager.delete_tag(tag.get('name')) + color = 'green' if result else 'red' + click.secho("Removing {}".format(tag.get('name')), fg=color) diff --git a/SoftLayer/CLI/tags/details.py b/SoftLayer/CLI/tags/details.py index 7c397f431..6e75013d5 100644 --- a/SoftLayer/CLI/tags/details.py +++ b/SoftLayer/CLI/tags/details.py @@ -1,27 +1,27 @@ -"""Details of a Tag.""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI.tags.list import detailed_table -from SoftLayer.managers.tags import TagManager - - -@click.command() -@click.argument('identifier') -@click.option('--name', required=False, default=False, is_flag=True, show_default=False, - help='Assume identifier is a tag name. Useful if your tag name is a number.') -@environment.pass_env -def cli(env, identifier, name): - """Get details for a Tag. Identifier can be either a name or tag-id""" - - tag_manager = TagManager(env.client) - - # If the identifier is a int, and user didn't tell us it was a name. - if str.isdigit(identifier) and not name: - tags = [tag_manager.get_tag(identifier)] - else: - tags = tag_manager.get_tag_by_name(identifier) - table = detailed_table(tag_manager, tags) - env.fout(table) +"""Details of a Tag.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI.tags.list import detailed_table +from SoftLayer.managers.tags import TagManager + + +@click.command() +@click.argument('identifier') +@click.option('--name', required=False, default=False, is_flag=True, show_default=False, + help='Assume identifier is a tag name. Useful if your tag name is a number.') +@environment.pass_env +def cli(env, identifier, name): + """Get details for a Tag. Identifier can be either a name or tag-id""" + + tag_manager = TagManager(env.client) + + # If the identifier is a int, and user didn't tell us it was a name. + if str.isdigit(identifier) and not name: + tags = [tag_manager.get_tag(identifier)] + else: + tags = tag_manager.get_tag_by_name(identifier) + table = detailed_table(tag_manager, tags) + env.fout(table) diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index bc8662764..f811d573c 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -1,75 +1,75 @@ -"""List Tags.""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer.managers.tags import TagManager -from SoftLayer import utils - -# pylint: disable=unnecessary-lambda - - -@click.command() -@click.option('--detail', '-d', is_flag=True, default=False, - help="Show information about the resources using this tag.") -@environment.pass_env -def cli(env, detail): - """List Tags.""" - - tag_manager = TagManager(env.client) - - if detail: - tables = detailed_table(tag_manager, tag_manager.get_attached_tags()) - for table in tables: - env.fout(table) - else: - table = simple_table(tag_manager) - env.fout(table) - # pp(tags.list_tags()) - - -def tag_row(tag): - """Format a tag table row""" - return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] - - -def detailed_table(tag_manager, tags): - """Creates a table for each tag, with details about resources using it""" - tables = [] - for tag in tags: - references = tag_manager.get_tag_references(tag.get('id')) - # pp(references) - new_table = formatting.Table(['Id', 'Type', 'Resource'], title=tag.get('name')) - for reference in references: - tag_type = utils.lookup(reference, 'tagType', 'keyName') - resource_id = reference.get('resourceTableId') - resource_row = get_resource_name(tag_manager, resource_id, tag_type) - new_table.add_row([resource_id, tag_type, resource_row]) - tables.append(new_table) - - return tables - - -def simple_table(tag_manager): - """Just tags and how many resources on each""" - tags = tag_manager.list_tags() - table = formatting.Table(['Id', 'Tag', 'Count'], title='Tags') - for tag in tags.get('attached', []): - table.add_row(tag_row(tag)) - for tag in tags.get('unattached', []): - table.add_row(tag_row(tag)) - return table - - -def get_resource_name(tag_manager, resource_id, tag_type): - """Returns a string to identify a resource""" - name = None - try: - resource = tag_manager.reference_lookup(resource_id, tag_type) - name = tag_manager.get_resource_name(resource, tag_type) - except SoftLayerAPIError as exception: - name = "{}".format(exception.reason) - return name +"""List Tags.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers.tags import TagManager +from SoftLayer import utils + +# pylint: disable=unnecessary-lambda + + +@click.command() +@click.option('--detail', '-d', is_flag=True, default=False, + help="Show information about the resources using this tag.") +@environment.pass_env +def cli(env, detail): + """List Tags.""" + + tag_manager = TagManager(env.client) + + if detail: + tables = detailed_table(tag_manager, tag_manager.get_attached_tags()) + for table in tables: + env.fout(table) + else: + table = simple_table(tag_manager) + env.fout(table) + # pp(tags.list_tags()) + + +def tag_row(tag): + """Format a tag table row""" + return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] + + +def detailed_table(tag_manager, tags): + """Creates a table for each tag, with details about resources using it""" + tables = [] + for tag in tags: + references = tag_manager.get_tag_references(tag.get('id')) + # pp(references) + new_table = formatting.Table(['Id', 'Type', 'Resource'], title=tag.get('name')) + for reference in references: + tag_type = utils.lookup(reference, 'tagType', 'keyName') + resource_id = reference.get('resourceTableId') + resource_row = get_resource_name(tag_manager, resource_id, tag_type) + new_table.add_row([resource_id, tag_type, resource_row]) + tables.append(new_table) + + return tables + + +def simple_table(tag_manager): + """Just tags and how many resources on each""" + tags = tag_manager.list_tags() + table = formatting.Table(['Id', 'Tag', 'Count'], title='Tags') + for tag in tags.get('attached', []): + table.add_row(tag_row(tag)) + for tag in tags.get('unattached', []): + table.add_row(tag_row(tag)) + return table + + +def get_resource_name(tag_manager, resource_id, tag_type): + """Returns a string to identify a resource""" + name = None + try: + resource = tag_manager.reference_lookup(resource_id, tag_type) + name = tag_manager.get_resource_name(resource, tag_type) + except SoftLayerAPIError as exception: + name = "{}".format(exception.reason) + return name diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py index 0c08acdb0..4bbd4a926 100644 --- a/SoftLayer/CLI/tags/taggable.py +++ b/SoftLayer/CLI/tags/taggable.py @@ -1,27 +1,27 @@ -"""List everything that could be tagged.""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.managers.tags import TagManager - - -@click.command() -@environment.pass_env -def cli(env): - """List everything that could be tagged.""" - - tag_manager = TagManager(env.client) - tag_types = tag_manager.get_all_tag_types() - for tag_type in tag_types: - title = "{} ({})".format(tag_type['description'], tag_type['keyName']) - table = formatting.Table(['Id', 'Name'], title=title) - resources = tag_manager.taggable_by_type(tag_type['keyName']) - for resource in resources: - table.add_row([ - resource['resource']['id'], - tag_manager.get_resource_name(resource['resource'], tag_type['keyName']) - ]) - env.fout(table) +"""List everything that could be tagged.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.tags import TagManager + + +@click.command() +@environment.pass_env +def cli(env): + """List everything that could be tagged.""" + + tag_manager = TagManager(env.client) + tag_types = tag_manager.get_all_tag_types() + for tag_type in tag_types: + title = "{} ({})".format(tag_type['description'], tag_type['keyName']) + table = formatting.Table(['Id', 'Name'], title=title) + resources = tag_manager.taggable_by_type(tag_type['keyName']) + for resource in resources: + table.add_row([ + resource['resource']['id'], + tag_manager.get_resource_name(resource['resource'], tag_type['keyName']) + ]) + env.fout(table) diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py index d06673365..df44245f7 100644 --- a/SoftLayer/CLI/virt/migrate.py +++ b/SoftLayer/CLI/virt/migrate.py @@ -1,82 +1,82 @@ -"""Manage Migrations of Virtual Guests""" -# :license: MIT, see LICENSE for more details. -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer import utils - - -@click.command() -@click.option('--guest', '-g', type=click.INT, help="Guest ID to immediately migrate.") -@click.option('--all', '-a', 'migrate_all', is_flag=True, default=False, - help="Migrate ALL guests that require migration immediately.") -@click.option('--host', '-h', type=click.INT, - help="Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host.") -@environment.pass_env -def cli(env, guest, migrate_all, host): - """Manage VSIs that require migration. Can migrate Dedicated Host VSIs as well.""" - - vsi = SoftLayer.VSManager(env.client) - pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}} - dedicated_filter = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} - mask = """mask[ - id, hostname, domain, datacenter, pendingMigrationFlag, powerState, - primaryIpAddress,primaryBackendIpAddress, dedicatedHost - ]""" - - # No options, just print out a list of guests that can be migrated - if not (guest or migrate_all): - require_migration = vsi.list_instances(filter=pending_filter, mask=mask) - require_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter'], title="Require Migration") - - for vsi_object in require_migration: - require_table.add_row([ - vsi_object.get('id'), - vsi_object.get('hostname'), - vsi_object.get('domain'), - utils.lookup(vsi_object, 'datacenter', 'name') - ]) - - if require_migration: - env.fout(require_table) - else: - click.secho("No guests require migration at this time", fg='green') - - migrateable = vsi.list_instances(filter=dedicated_filter, mask=mask) - migrateable_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter', 'Host Name', 'Host Id'], - title="Dedicated Guests") - for vsi_object in migrateable: - migrateable_table.add_row([ - vsi_object.get('id'), - vsi_object.get('hostname'), - vsi_object.get('domain'), - utils.lookup(vsi_object, 'datacenter', 'name'), - utils.lookup(vsi_object, 'dedicatedHost', 'name'), - utils.lookup(vsi_object, 'dedicatedHost', 'id') - ]) - env.fout(migrateable_table) - # Migrate all guests with pendingMigrationFlag=True - elif migrate_all: - require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]") - if not require_migration: - click.secho("No guests require migration at this time", fg='green') - for vsi_object in require_migration: - migrate(vsi, vsi_object['id']) - # Just migrate based on the options - else: - migrate(vsi, guest, host) - - -def migrate(vsi_manager, vsi_id, host_id=None): - """Handles actually migrating virtual guests and handling the exception""" - - try: - if host_id: - vsi_manager.migrate_dedicated(vsi_id, host_id) - else: - vsi_manager.migrate(vsi_id) - click.secho("Started a migration on {}".format(vsi_id), fg='green') - except SoftLayer.exceptions.SoftLayerAPIError as ex: - click.secho("Failed to migrate {}. {}".format(vsi_id, str(ex)), fg='red') +"""Manage Migrations of Virtual Guests""" +# :license: MIT, see LICENSE for more details. +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@click.option('--guest', '-g', type=click.INT, help="Guest ID to immediately migrate.") +@click.option('--all', '-a', 'migrate_all', is_flag=True, default=False, + help="Migrate ALL guests that require migration immediately.") +@click.option('--host', '-h', type=click.INT, + help="Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host.") +@environment.pass_env +def cli(env, guest, migrate_all, host): + """Manage VSIs that require migration. Can migrate Dedicated Host VSIs as well.""" + + vsi = SoftLayer.VSManager(env.client) + pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}} + dedicated_filter = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} + mask = """mask[ + id, hostname, domain, datacenter, pendingMigrationFlag, powerState, + primaryIpAddress,primaryBackendIpAddress, dedicatedHost + ]""" + + # No options, just print out a list of guests that can be migrated + if not (guest or migrate_all): + require_migration = vsi.list_instances(filter=pending_filter, mask=mask) + require_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter'], title="Require Migration") + + for vsi_object in require_migration: + require_table.add_row([ + vsi_object.get('id'), + vsi_object.get('hostname'), + vsi_object.get('domain'), + utils.lookup(vsi_object, 'datacenter', 'name') + ]) + + if require_migration: + env.fout(require_table) + else: + click.secho("No guests require migration at this time", fg='green') + + migrateable = vsi.list_instances(filter=dedicated_filter, mask=mask) + migrateable_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter', 'Host Name', 'Host Id'], + title="Dedicated Guests") + for vsi_object in migrateable: + migrateable_table.add_row([ + vsi_object.get('id'), + vsi_object.get('hostname'), + vsi_object.get('domain'), + utils.lookup(vsi_object, 'datacenter', 'name'), + utils.lookup(vsi_object, 'dedicatedHost', 'name'), + utils.lookup(vsi_object, 'dedicatedHost', 'id') + ]) + env.fout(migrateable_table) + # Migrate all guests with pendingMigrationFlag=True + elif migrate_all: + require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]") + if not require_migration: + click.secho("No guests require migration at this time", fg='green') + for vsi_object in require_migration: + migrate(vsi, vsi_object['id']) + # Just migrate based on the options + else: + migrate(vsi, guest, host) + + +def migrate(vsi_manager, vsi_id, host_id=None): + """Handles actually migrating virtual guests and handling the exception""" + + try: + if host_id: + vsi_manager.migrate_dedicated(vsi_id, host_id) + else: + vsi_manager.migrate(vsi_id) + click.secho("Started a migration on {}".format(vsi_id), fg='green') + except SoftLayer.exceptions.SoftLayerAPIError as ex: + click.secho("Failed to migrate {}. {}".format(vsi_id, str(ex)), fg='red') diff --git a/SoftLayer/fixtures/BluePages_Search.py b/SoftLayer/fixtures/BluePages_Search.py index 9682f63dc..756b02afd 100644 --- a/SoftLayer/fixtures/BluePages_Search.py +++ b/SoftLayer/fixtures/BluePages_Search.py @@ -1 +1 @@ -findBluePagesProfile = True +findBluePagesProfile = True diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 770de045c..3c74dc439 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -1,89 +1,89 @@ -getObject = { - 'id': 1234, - 'globalIdentifier': 'xxxxc-asd', - 'datacenter': {'id': 12, 'name': 'DALLAS21', - 'description': 'Dallas 21'}, - 'billingItem': { - 'id': 6327, - 'recurringFee': 1.54, - 'nextInvoiceTotalRecurringAmount': 16.08, - 'children': [ - {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, - ], - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'bob', - } - } - } - }, - 'primaryIpAddress': '4.4.4.4', - 'hostname': 'testtest1', - 'domain': 'test.sftlyr.ws', - 'bareMetalInstanceFlag': True, - 'fullyQualifiedDomainName': 'testtest1.test.sftlyr.ws', - 'processorPhysicalCoreAmount': 4, - 'memoryCapacity': 4, - 'primaryBackendIpAddress': '10.4.4.4', - 'networkManagementIpAddress': '10.4.4.4', - 'hardwareStatus': {'status': 'ACTIVE'}, - 'primaryNetworkComponent': {'maxSpeed': 1000, 'speed': 1000}, - 'provisionDate': '2020-08-01 15:23:45', - 'notes': 'NOTES NOTES NOTES', - 'operatingSystem': { - 'softwareLicense': { - 'softwareDescription': { - 'referenceCode': 'UBUNTU_20_64', - 'name': 'Ubuntu', - 'version': 'Ubuntu 20.04 LTS', - } - }, - 'passwords': [ - {'username': 'root', 'password': 'xxxxxxxxxxxx'} - ], - }, - 'remoteManagementAccounts': [ - {'username': 'root', 'password': 'zzzzzzzzzzzzzz'} - ], - 'networkVlans': [ - { - 'networkSpace': 'PRIVATE', - 'vlanNumber': 1234, - 'id': 11111 - }, - ], - 'tagReferences': [ - {'tag': {'name': 'a tag'}} - ], -} - -allowAccessToNetworkStorageList = True - -getSensorData = [ - { - "sensorId": "Ambient 1 Temperature", - "sensorReading": "25.000", - "sensorUnits": "degrees C", - "status": "ok", - "upperCritical": "43.000", - "upperNonCritical": "41.000", - "upperNonRecoverable": "46.000" - }, - { - "lowerCritical": "3500.000", - "sensorId": "Fan 1 Tach", - "sensorReading": "6580.000", - "sensorUnits": "RPM", - "status": "ok" - }, { - "sensorId": "IPMI Watchdog", - "sensorReading": "0x0", - "sensorUnits": "discrete", - "status": "0x0080" - }, { - "sensorId": "Avg Power", - "sensorReading": "70.000", - "sensorUnits": "Watts", - "status": "ok" - }] +getObject = { + 'id': 1234, + 'globalIdentifier': 'xxxxc-asd', + 'datacenter': {'id': 12, 'name': 'DALLAS21', + 'description': 'Dallas 21'}, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'nextInvoiceTotalRecurringAmount': 16.08, + 'children': [ + {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, + ], + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'bob', + } + } + } + }, + 'primaryIpAddress': '4.4.4.4', + 'hostname': 'testtest1', + 'domain': 'test.sftlyr.ws', + 'bareMetalInstanceFlag': True, + 'fullyQualifiedDomainName': 'testtest1.test.sftlyr.ws', + 'processorPhysicalCoreAmount': 4, + 'memoryCapacity': 4, + 'primaryBackendIpAddress': '10.4.4.4', + 'networkManagementIpAddress': '10.4.4.4', + 'hardwareStatus': {'status': 'ACTIVE'}, + 'primaryNetworkComponent': {'maxSpeed': 1000, 'speed': 1000}, + 'provisionDate': '2020-08-01 15:23:45', + 'notes': 'NOTES NOTES NOTES', + 'operatingSystem': { + 'softwareLicense': { + 'softwareDescription': { + 'referenceCode': 'UBUNTU_20_64', + 'name': 'Ubuntu', + 'version': 'Ubuntu 20.04 LTS', + } + }, + 'passwords': [ + {'username': 'root', 'password': 'xxxxxxxxxxxx'} + ], + }, + 'remoteManagementAccounts': [ + {'username': 'root', 'password': 'zzzzzzzzzzzzzz'} + ], + 'networkVlans': [ + { + 'networkSpace': 'PRIVATE', + 'vlanNumber': 1234, + 'id': 11111 + }, + ], + 'tagReferences': [ + {'tag': {'name': 'a tag'}} + ], +} + +allowAccessToNetworkStorageList = True + +getSensorData = [ + { + "sensorId": "Ambient 1 Temperature", + "sensorReading": "25.000", + "sensorUnits": "degrees C", + "status": "ok", + "upperCritical": "43.000", + "upperNonCritical": "41.000", + "upperNonRecoverable": "46.000" + }, + { + "lowerCritical": "3500.000", + "sensorId": "Fan 1 Tach", + "sensorReading": "6580.000", + "sensorUnits": "RPM", + "status": "ok" + }, { + "sensorId": "IPMI Watchdog", + "sensorReading": "0x0", + "sensorUnits": "discrete", + "status": "0x0080" + }, { + "sensorId": "Avg Power", + "sensorReading": "70.000", + "sensorUnits": "Watts", + "status": "ok" + }] diff --git a/SoftLayer/fixtures/SoftLayer_Search.py b/SoftLayer/fixtures/SoftLayer_Search.py index ccb45fe55..2bac09e42 100644 --- a/SoftLayer/fixtures/SoftLayer_Search.py +++ b/SoftLayer/fixtures/SoftLayer_Search.py @@ -1,23 +1,23 @@ -advancedSearch = [ - { - "relevanceScore": "4", - "resourceType": "SoftLayer_Hardware", - "resource": { - "accountId": 307608, - "domain": "vmware.test.com", - "fullyQualifiedDomainName": "host14.vmware.test.com", - "hardwareStatusId": 5, - "hostname": "host14", - "id": 123456, - "manufacturerSerialNumber": "AAAAAAAAA", - "notes": "A test notes", - "provisionDate": "2018-08-24T12:32:10-06:00", - "serialNumber": "SL12345678", - "serviceProviderId": 1, - "hardwareStatus": { - "id": 5, - "status": "ACTIVE" - } - } - } -] +advancedSearch = [ + { + "relevanceScore": "4", + "resourceType": "SoftLayer_Hardware", + "resource": { + "accountId": 307608, + "domain": "vmware.test.com", + "fullyQualifiedDomainName": "host14.vmware.test.com", + "hardwareStatusId": 5, + "hostname": "host14", + "id": 123456, + "manufacturerSerialNumber": "AAAAAAAAA", + "notes": "A test notes", + "provisionDate": "2018-08-24T12:32:10-06:00", + "serialNumber": "SL12345678", + "serviceProviderId": 1, + "hardwareStatus": { + "id": 5, + "status": "ACTIVE" + } + } + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 9f6aeaec4..79310b354 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -1,31 +1,31 @@ -getUnattachedTagsForCurrentUser = [{'id': 287895, 'name': 'coreos', 'referenceCount': 0}] -getAttachedTagsForCurrentUser = [{'id': 1286571, 'name': 'bs_test_instance', 'referenceCount': 5}] -getReferences = [ - { - 'id': 73009305, - 'resourceTableId': 33488921, - 'tag': { - 'id': 1286571, - 'name': 'bs_test_instance', - }, - 'tagId': 1286571, - 'tagType': {'description': 'CCI', 'keyName': 'GUEST'}, - 'tagTypeId': 2, - 'usrRecordId': 6625205 - } -] - -deleteTag = True - -setTags = True - -getObject = getAttachedTagsForCurrentUser[0] - -getTagByTagName = getAttachedTagsForCurrentUser - -getAllTagTypes = [ - { - "description": "Hardware", - "keyName": "HARDWARE" - } -] +getUnattachedTagsForCurrentUser = [{'id': 287895, 'name': 'coreos', 'referenceCount': 0}] +getAttachedTagsForCurrentUser = [{'id': 1286571, 'name': 'bs_test_instance', 'referenceCount': 5}] +getReferences = [ + { + 'id': 73009305, + 'resourceTableId': 33488921, + 'tag': { + 'id': 1286571, + 'name': 'bs_test_instance', + }, + 'tagId': 1286571, + 'tagType': {'description': 'CCI', 'keyName': 'GUEST'}, + 'tagTypeId': 2, + 'usrRecordId': 6625205 + } +] + +deleteTag = True + +setTags = True + +getObject = getAttachedTagsForCurrentUser[0] + +getTagByTagName = getAttachedTagsForCurrentUser + +getAllTagTypes = [ + { + "description": "Hardware", + "keyName": "HARDWARE" + } +] diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 818b0547d..eda95790a 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -1,230 +1,230 @@ -""" - SoftLayer.tags - ~~~~~~~~~~~~ - Tag Manager - - :license: MIT, see LICENSE for more details. -""" -import re - -from SoftLayer.exceptions import SoftLayerAPIError - - -class TagManager(object): - """Manager for Tag functions.""" - - def __init__(self, client): - self.client = client - - def list_tags(self, mask=None): - """Returns a list of all tags for the Current User - - :param str mask: Object mask to use if you do not want the default. - """ - if mask is None: - mask = "mask[id,name,referenceCount]" - unattached = self.get_unattached_tags(mask) - attached = self.get_attached_tags(mask) - return {'attached': attached, 'unattached': unattached} - - def get_unattached_tags(self, mask=None): - """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser() - - :params string mask: Mask to use. - """ - return self.client.call('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', - mask=mask, iter=True) - - def get_attached_tags(self, mask=None): - """Calls SoftLayer_Tag::getAttachedTagsForCurrentUser() - - :params string mask: Mask to use. - """ - return self.client.call('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', - mask=mask, iter=True) - - def get_tag_references(self, tag_id, mask=None): - """Calls SoftLayer_Tag::getReferences(id=tag_id) - - :params int tag_id: Tag id to get references from - :params string mask: Mask to use. - """ - if mask is None: - mask = "mask[tagType]" - return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) - - def get_tag(self, tag_id, mask=None): - """Calls SoftLayer_Tag::getObject(id=tag_id) - - :params int tag_id: Tag id to get object from - :params string mask: Mask to use. - """ - if mask is None: - mask = "mask[id,name]" - result = self.client.call('SoftLayer_Tag', 'getObject', id=tag_id, mask=mask) - return result - - def get_tag_by_name(self, tag_name, mask=None): - """Calls SoftLayer_Tag::getTagByTagName(tag_name) - - :params string tag_name: Tag name to get object from - :params string mask: Mask to use. - """ - if mask is None: - mask = "mask[id,name]" - result = self.client.call('SoftLayer_Tag', 'getTagByTagName', tag_name, mask=mask) - return result - - def reference_lookup(self, resource_table_id, tag_type): - """Returns the SoftLayer Service for the corresponding type - - :param int resource_table_id: Tag_Reference::resourceTableId - :param string tag_type: Tag_Reference->tagType->keyName - - From SoftLayer_Tag::getAllTagTypes() - - |Type |Service | - | ----------------------------- | ------ | - |Hardware |HARDWARE| - |CCI |GUEST| - |Account Document |ACCOUNT_DOCUMENT| - |Ticket |TICKET| - |Vlan Firewall |NETWORK_VLAN_FIREWALL| - |Contract |CONTRACT| - |Image Template |IMAGE_TEMPLATE| - |Application Delivery Controller |APPLICATION_DELIVERY_CONTROLLER| - |Vlan |NETWORK_VLAN| - |Dedicated Host |DEDICATED_HOST| - """ - service = self.type_to_service(tag_type) - if service is None: - raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) - return self.client.call(service, 'getObject', id=resource_table_id) - - def delete_tag(self, name): - """Calls SoftLayer_Tag::deleteTag - - :param string name: tag name to delete - """ - return self.client.call('SoftLayer_Tag', 'deleteTag', name) - - def set_tags(self, tags, key_name, resource_id): - """Calls SoftLayer_Tag::setTags() - - :param string tags: List of tags. - :param string key_name: Key name of a tag type. - :param int resource_id: ID of the object being tagged. - """ - return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id) - - def get_all_tag_types(self): - """Calls SoftLayer_Tag::getAllTagTypes()""" - types = self.client.call('SoftLayer_Tag', 'getAllTagTypes') - useable_types = [] - for tag_type in types: - service = self.type_to_service(tag_type['keyName']) - # Mostly just to remove the types that are not user taggable. - if service is not None: - temp_type = tag_type - temp_type['service'] = service - useable_types.append(temp_type) - return useable_types - - def taggable_by_type(self, tag_type): - """Returns a list of resources that can be tagged, that are of the given type - - :param string tag_type: Key name of a tag type. See SoftLayer_Tag::getAllTagTypes - """ - service = self.type_to_service(tag_type) - search_term = "_objectType:SoftLayer_{}".format(service) - if tag_type == 'TICKET': - search_term = "{} status.name: open".format(search_term) - elif tag_type == 'IMAGE_TEMPLATE': - mask = "mask[id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,imageType]" - resources = self.client.call('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups', - mask=mask, iter=True) - to_return = [] - # Fake search result output - for resource in resources: - to_return.append({'resourceType': service, 'resource': resource}) - return to_return - elif tag_type == 'NETWORK_SUBNET': - resources = self.client.call('SoftLayer_Account', 'getSubnets', iter=True) - to_return = [] - # Fake search result output - for resource in resources: - to_return.append({'resourceType': service, 'resource': resource}) - return to_return - resources = self.client.call('SoftLayer_Search', 'advancedSearch', search_term, iter=True) - return resources - - @staticmethod - def type_to_service(tag_type): - """Returns the SoftLayer service for the given tag_type""" - service = None - if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: - return None - - if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': - service = 'Network_Application_Delivery_Controller' - elif tag_type == 'GUEST': - service = 'Virtual_Guest' - elif tag_type == 'DEDICATED_HOST': - service = 'Virtual_DedicatedHost' - elif tag_type == 'IMAGE_TEMPLATE': - service = 'Virtual_Guest_Block_Device_Template_Group' - else: - - tag_type = tag_type.lower() - # Sets the First letter, and any letter preceeded by a '_' to uppercase - # HARDWARE -> Hardware, NETWORK_VLAN -> Network_Vlan for example. - service = re.sub(r'(^[a-z]|\_[a-z])', lambda x: x.group().upper(), tag_type) - return service - - @staticmethod - def get_resource_name(resource, tag_type): - """Returns a string that names a resource - - :param dict resource: A SoftLayer datatype for the given tag_type - :param string tag_type: Key name for the tag_type - """ - if tag_type == 'NETWORK_VLAN_FIREWALL': - return resource.get('primaryIpAddress') - elif tag_type == 'NETWORK_VLAN': - return "{} ({})".format(resource.get('vlanNumber'), resource.get('name')) - elif tag_type == 'IMAGE_TEMPLATE' or tag_type == 'APPLICATION_DELIVERY_CONTROLLER': - return resource.get('name') - elif tag_type == 'TICKET': - return resource.get('subjet') - elif tag_type == 'NETWORK_SUBNET': - return resource.get('networkIdentifier') - else: - return resource.get('fullyQualifiedDomainName') - - # @staticmethod - # def type_to_datatype(tag_type): - # """Returns the SoftLayer datatye for the given tag_type""" - # datatye = None - # if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: - # return None - - # if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': - # datatye = 'adcLoadBalancers' - # elif tag_type == 'GUEST': - # datatye = 'virtualGuests' - # elif tag_type == 'DEDICATED_HOST': - # datatye = 'dedicatedHosts' - # elif tag_type == 'HARDWARE': - # datatye = 'hardware' - # elif tag_type == 'TICKET': - # datatye = 'openTickets' - # elif tag_type == 'NETWORK_SUBNET': - # datatye = 'subnets' - # elif tag_type == 'NETWORK_VLAN': - # datatye = 'networkVlans' - # elif tag_type == 'NETWORK_VLAN_FIREWALL': - # datatye = 'networkVlans' - # elif tag_type == 'IMAGE_TEMPLATE': - # datatye = 'blockDeviceTemplateGroups' - - # return datatye +""" + SoftLayer.tags + ~~~~~~~~~~~~ + Tag Manager + + :license: MIT, see LICENSE for more details. +""" +import re + +from SoftLayer.exceptions import SoftLayerAPIError + + +class TagManager(object): + """Manager for Tag functions.""" + + def __init__(self, client): + self.client = client + + def list_tags(self, mask=None): + """Returns a list of all tags for the Current User + + :param str mask: Object mask to use if you do not want the default. + """ + if mask is None: + mask = "mask[id,name,referenceCount]" + unattached = self.get_unattached_tags(mask) + attached = self.get_attached_tags(mask) + return {'attached': attached, 'unattached': unattached} + + def get_unattached_tags(self, mask=None): + """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser() + + :params string mask: Mask to use. + """ + return self.client.call('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', + mask=mask, iter=True) + + def get_attached_tags(self, mask=None): + """Calls SoftLayer_Tag::getAttachedTagsForCurrentUser() + + :params string mask: Mask to use. + """ + return self.client.call('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', + mask=mask, iter=True) + + def get_tag_references(self, tag_id, mask=None): + """Calls SoftLayer_Tag::getReferences(id=tag_id) + + :params int tag_id: Tag id to get references from + :params string mask: Mask to use. + """ + if mask is None: + mask = "mask[tagType]" + return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) + + def get_tag(self, tag_id, mask=None): + """Calls SoftLayer_Tag::getObject(id=tag_id) + + :params int tag_id: Tag id to get object from + :params string mask: Mask to use. + """ + if mask is None: + mask = "mask[id,name]" + result = self.client.call('SoftLayer_Tag', 'getObject', id=tag_id, mask=mask) + return result + + def get_tag_by_name(self, tag_name, mask=None): + """Calls SoftLayer_Tag::getTagByTagName(tag_name) + + :params string tag_name: Tag name to get object from + :params string mask: Mask to use. + """ + if mask is None: + mask = "mask[id,name]" + result = self.client.call('SoftLayer_Tag', 'getTagByTagName', tag_name, mask=mask) + return result + + def reference_lookup(self, resource_table_id, tag_type): + """Returns the SoftLayer Service for the corresponding type + + :param int resource_table_id: Tag_Reference::resourceTableId + :param string tag_type: Tag_Reference->tagType->keyName + + From SoftLayer_Tag::getAllTagTypes() + + |Type |Service | + | ----------------------------- | ------ | + |Hardware |HARDWARE| + |CCI |GUEST| + |Account Document |ACCOUNT_DOCUMENT| + |Ticket |TICKET| + |Vlan Firewall |NETWORK_VLAN_FIREWALL| + |Contract |CONTRACT| + |Image Template |IMAGE_TEMPLATE| + |Application Delivery Controller |APPLICATION_DELIVERY_CONTROLLER| + |Vlan |NETWORK_VLAN| + |Dedicated Host |DEDICATED_HOST| + """ + service = self.type_to_service(tag_type) + if service is None: + raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + return self.client.call(service, 'getObject', id=resource_table_id) + + def delete_tag(self, name): + """Calls SoftLayer_Tag::deleteTag + + :param string name: tag name to delete + """ + return self.client.call('SoftLayer_Tag', 'deleteTag', name) + + def set_tags(self, tags, key_name, resource_id): + """Calls SoftLayer_Tag::setTags() + + :param string tags: List of tags. + :param string key_name: Key name of a tag type. + :param int resource_id: ID of the object being tagged. + """ + return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id) + + def get_all_tag_types(self): + """Calls SoftLayer_Tag::getAllTagTypes()""" + types = self.client.call('SoftLayer_Tag', 'getAllTagTypes') + useable_types = [] + for tag_type in types: + service = self.type_to_service(tag_type['keyName']) + # Mostly just to remove the types that are not user taggable. + if service is not None: + temp_type = tag_type + temp_type['service'] = service + useable_types.append(temp_type) + return useable_types + + def taggable_by_type(self, tag_type): + """Returns a list of resources that can be tagged, that are of the given type + + :param string tag_type: Key name of a tag type. See SoftLayer_Tag::getAllTagTypes + """ + service = self.type_to_service(tag_type) + search_term = "_objectType:SoftLayer_{}".format(service) + if tag_type == 'TICKET': + search_term = "{} status.name: open".format(search_term) + elif tag_type == 'IMAGE_TEMPLATE': + mask = "mask[id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,imageType]" + resources = self.client.call('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups', + mask=mask, iter=True) + to_return = [] + # Fake search result output + for resource in resources: + to_return.append({'resourceType': service, 'resource': resource}) + return to_return + elif tag_type == 'NETWORK_SUBNET': + resources = self.client.call('SoftLayer_Account', 'getSubnets', iter=True) + to_return = [] + # Fake search result output + for resource in resources: + to_return.append({'resourceType': service, 'resource': resource}) + return to_return + resources = self.client.call('SoftLayer_Search', 'advancedSearch', search_term, iter=True) + return resources + + @staticmethod + def type_to_service(tag_type): + """Returns the SoftLayer service for the given tag_type""" + service = None + if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: + return None + + if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + service = 'Network_Application_Delivery_Controller' + elif tag_type == 'GUEST': + service = 'Virtual_Guest' + elif tag_type == 'DEDICATED_HOST': + service = 'Virtual_DedicatedHost' + elif tag_type == 'IMAGE_TEMPLATE': + service = 'Virtual_Guest_Block_Device_Template_Group' + else: + + tag_type = tag_type.lower() + # Sets the First letter, and any letter preceeded by a '_' to uppercase + # HARDWARE -> Hardware, NETWORK_VLAN -> Network_Vlan for example. + service = re.sub(r'(^[a-z]|\_[a-z])', lambda x: x.group().upper(), tag_type) + return service + + @staticmethod + def get_resource_name(resource, tag_type): + """Returns a string that names a resource + + :param dict resource: A SoftLayer datatype for the given tag_type + :param string tag_type: Key name for the tag_type + """ + if tag_type == 'NETWORK_VLAN_FIREWALL': + return resource.get('primaryIpAddress') + elif tag_type == 'NETWORK_VLAN': + return "{} ({})".format(resource.get('vlanNumber'), resource.get('name')) + elif tag_type == 'IMAGE_TEMPLATE' or tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + return resource.get('name') + elif tag_type == 'TICKET': + return resource.get('subjet') + elif tag_type == 'NETWORK_SUBNET': + return resource.get('networkIdentifier') + else: + return resource.get('fullyQualifiedDomainName') + + # @staticmethod + # def type_to_datatype(tag_type): + # """Returns the SoftLayer datatye for the given tag_type""" + # datatye = None + # if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: + # return None + + # if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + # datatye = 'adcLoadBalancers' + # elif tag_type == 'GUEST': + # datatye = 'virtualGuests' + # elif tag_type == 'DEDICATED_HOST': + # datatye = 'dedicatedHosts' + # elif tag_type == 'HARDWARE': + # datatye = 'hardware' + # elif tag_type == 'TICKET': + # datatye = 'openTickets' + # elif tag_type == 'NETWORK_SUBNET': + # datatye = 'subnets' + # elif tag_type == 'NETWORK_VLAN': + # datatye = 'networkVlans' + # elif tag_type == 'NETWORK_VLAN_FIREWALL': + # datatye = 'networkVlans' + # elif tag_type == 'IMAGE_TEMPLATE': + # datatye = 'blockDeviceTemplateGroups' + + # return datatye diff --git a/docCheck.py b/docCheck.py index f6b11ba36..90a8da3f4 100644 --- a/docCheck.py +++ b/docCheck.py @@ -1,94 +1,94 @@ -"""Makes sure all routes have documentation""" -import SoftLayer -from SoftLayer.CLI import routes -from pprint import pprint as pp -import glob -import logging -import os -import sys -import re - -class Checker(): - - def __init__(self): - pass - - def getDocFiles(self, path=None): - files = [] - if path is None: - path = ".{seper}docs{seper}cli".format(seper=os.path.sep) - for file in glob.glob(path + '/*', recursive=True): - if os.path.isdir(file): - files = files + self.getDocFiles(file) - else: - files.append(file) - return files - - def readDocs(self, path=None): - files = self.getDocFiles(path) - commands = {} - click_regex = re.compile(r"\.\. click:: ([a-zA-Z0-9_\.:]*)") - prog_regex = re.compile(r"\W*:prog: (.*)") - - for file in files: - click_line = '' - prog_line = '' - with open(file, 'r') as f: - for line in f: - click_match = re.match(click_regex, line) - prog_match = False - if click_match: - click_line = click_match.group(1) - - # Prog line should always be directly after click line. - prog_match = re.match(prog_regex, f.readline()) - if prog_match: - prog_line = prog_match.group(1).replace(" ", ":") - commands[prog_line] = click_line - click_line = '' - prog_line = '' - # pp(commands) - return commands - - def checkCommand(self, command, documented_commands): - """Sees if a command is documented - - :param tuple command: like the entry in the routes file ('command:action', 'SoftLayer.CLI.module.function') - :param documented_commands: dictionary of commands found to be auto-documented. - """ - - # These commands use a slightly different loader. - ignored = [ - 'virtual:capacity', - 'virtual:placementgroup', - 'object-storage:credential' - ] - if command[0] in ignored: - return True - if documented_commands.get(command[0], False) == command[1]: - return True - return False - - - def main(self, debug=0): - existing_commands = routes.ALL_ROUTES - documented_commands = self.readDocs() - # pp(documented_commands) - exitCode = 0 - for command in existing_commands: - if (command[1].find(":") == -1): # Header commands in the routes file, dont need documentaiton. - continue - else: - if self.checkCommand(command, documented_commands): - if debug: - print("{} is documented".format(command[0])) - - else: - print("===> {} {} IS UNDOCUMENTED <===".format(command[0], command[1])) - exitCode = 1 - sys.exit(exitCode) - - -if __name__ == "__main__": - main = Checker() - main.main() +"""Makes sure all routes have documentation""" +import SoftLayer +from SoftLayer.CLI import routes +from pprint import pprint as pp +import glob +import logging +import os +import sys +import re + +class Checker(): + + def __init__(self): + pass + + def getDocFiles(self, path=None): + files = [] + if path is None: + path = ".{seper}docs{seper}cli".format(seper=os.path.sep) + for file in glob.glob(path + '/*', recursive=True): + if os.path.isdir(file): + files = files + self.getDocFiles(file) + else: + files.append(file) + return files + + def readDocs(self, path=None): + files = self.getDocFiles(path) + commands = {} + click_regex = re.compile(r"\.\. click:: ([a-zA-Z0-9_\.:]*)") + prog_regex = re.compile(r"\W*:prog: (.*)") + + for file in files: + click_line = '' + prog_line = '' + with open(file, 'r') as f: + for line in f: + click_match = re.match(click_regex, line) + prog_match = False + if click_match: + click_line = click_match.group(1) + + # Prog line should always be directly after click line. + prog_match = re.match(prog_regex, f.readline()) + if prog_match: + prog_line = prog_match.group(1).replace(" ", ":") + commands[prog_line] = click_line + click_line = '' + prog_line = '' + # pp(commands) + return commands + + def checkCommand(self, command, documented_commands): + """Sees if a command is documented + + :param tuple command: like the entry in the routes file ('command:action', 'SoftLayer.CLI.module.function') + :param documented_commands: dictionary of commands found to be auto-documented. + """ + + # These commands use a slightly different loader. + ignored = [ + 'virtual:capacity', + 'virtual:placementgroup', + 'object-storage:credential' + ] + if command[0] in ignored: + return True + if documented_commands.get(command[0], False) == command[1]: + return True + return False + + + def main(self, debug=0): + existing_commands = routes.ALL_ROUTES + documented_commands = self.readDocs() + # pp(documented_commands) + exitCode = 0 + for command in existing_commands: + if (command[1].find(":") == -1): # Header commands in the routes file, dont need documentaiton. + continue + else: + if self.checkCommand(command, documented_commands): + if debug: + print("{} is documented".format(command[0])) + + else: + print("===> {} {} IS UNDOCUMENTED <===".format(command[0], command[1])) + exitCode = 1 + sys.exit(exitCode) + + +if __name__ == "__main__": + main = Checker() + main.main() diff --git a/docs/cli/nas.rst b/docs/cli/nas.rst index 024744919..2e0f7079e 100644 --- a/docs/cli/nas.rst +++ b/docs/cli/nas.rst @@ -1,12 +1,12 @@ -.. _cli_nas: - -NAS Commands -============ - -.. click:: SoftLayer.CLI.nas.list:cli - :prog: nas list - :show-nested: - -.. click:: SoftLayer.CLI.nas.credentials:cli - :prog: nas credentials +.. _cli_nas: + +NAS Commands +============ + +.. click:: SoftLayer.CLI.nas.list:cli + :prog: nas list + :show-nested: + +.. click:: SoftLayer.CLI.nas.credentials:cli + :prog: nas credentials :show-nested: \ No newline at end of file diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst index a5a99b694..d997760de 100644 --- a/docs/cli/tags.rst +++ b/docs/cli/tags.rst @@ -1,30 +1,30 @@ -.. _cli_tags: - -Tag Commands -============ - -These commands will allow you to interact with the **IMS** provier tagging service. The `IBM Global Search and Tagging API `_ can be used to interact with both the **GHOST** provider and **IMS** provider. The **GHOST** provider will handle tags for things outside of the Classic Infrastructure (aka SoftLayer) space. - -.. click:: SoftLayer.CLI.tags.list:cli - :prog: tags list - :show-nested: - -.. click:: SoftLayer.CLI.tags.set:cli - :prog: tags set - :show-nested: - -.. click:: SoftLayer.CLI.tags.details:cli - :prog: tags details - :show-nested: - -.. click:: SoftLayer.CLI.tags.delete:cli - :prog: tags delete - :show-nested: - -.. click:: SoftLayer.CLI.tags.taggable:cli - :prog: tags taggable - :show-nested: - -.. click:: SoftLayer.CLI.tags.cleanup:cli - :prog: tags cleanup - :show-nested: +.. _cli_tags: + +Tag Commands +============ + +These commands will allow you to interact with the **IMS** provier tagging service. The `IBM Global Search and Tagging API `_ can be used to interact with both the **GHOST** provider and **IMS** provider. The **GHOST** provider will handle tags for things outside of the Classic Infrastructure (aka SoftLayer) space. + +.. click:: SoftLayer.CLI.tags.list:cli + :prog: tags list + :show-nested: + +.. click:: SoftLayer.CLI.tags.set:cli + :prog: tags set + :show-nested: + +.. click:: SoftLayer.CLI.tags.details:cli + :prog: tags details + :show-nested: + +.. click:: SoftLayer.CLI.tags.delete:cli + :prog: tags delete + :show-nested: + +.. click:: SoftLayer.CLI.tags.taggable:cli + :prog: tags taggable + :show-nested: + +.. click:: SoftLayer.CLI.tags.cleanup:cli + :prog: tags cleanup + :show-nested: diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index 364201181..12fc85768 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -1,113 +1,113 @@ -""" - SoftLayer.tests.CLI.modules.tag_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Tests for the user cli command -""" -from unittest import mock as mock - -from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer import testing - - -class TagCLITests(testing.TestCase): - - def test_list(self): - result = self.run_command(['tags', 'list']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') - self.assertIn('coreos', result.output) - - def test_list_detail(self): - result = self.run_command(['tags', 'list', '-d']) - self.assert_no_fail(result) - self.assertIn('"vs-test1.test.sftlyr.ws', result.output) # From fixtures/virutal_guest.getObject - # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) - self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) - - def test_list_detail_ungettable(self): - mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') - mock.side_effect = SoftLayerAPIError(404, "TEST ERROR") - result = self.run_command(['tags', 'list', '-d']) - self.assert_no_fail(result) - self.assertIn("TEST ERROR", result.output) # From fixtures/virutal_guest.getObject - # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) - self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) - - @mock.patch('SoftLayer.CLI.tags.set.click') - def test_set_tags(self, click): - result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) - click.secho.assert_called_with('Set tags successfully', fg='green') - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) - - @mock.patch('SoftLayer.CLI.tags.set.click') - def test_set_tags_failure(self, click): - mock = self.set_mock('SoftLayer_Tag', 'setTags') - mock.return_value = False - result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) - click.secho.assert_called_with('Failed to set tags', fg='red') - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) - - def test_details_by_name(self): - tag_name = 'bs_test_instance' - result = self.run_command(['tags', 'details', tag_name]) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=(tag_name,)) - - def test_details_by_id(self): - tag_id = '1286571' - result = self.run_command(['tags', 'details', tag_id]) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) - - def test_deleteTags_by_name(self): - result = self.run_command(['tags', 'delete', 'test']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('test',)) - - def test_deleteTags_by_id(self): - result = self.run_command(['tags', 'delete', '123456']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getObject', identifier='123456') - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('bs_test_instance',)) - - def test_deleteTags_by_number_name(self): - result = self.run_command(['tags', 'delete', '123456', '--name']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) - - @mock.patch('SoftLayer.CLI.tags.delete.click') - def test_deleteTags_fail(self, click): - mock = self.set_mock('SoftLayer_Tag', 'deleteTag') - mock.return_value = False - result = self.run_command(['tags', 'delete', '123456', '--name']) - click.secho.assert_called_with('Failed to remove tag 123456', fg='red') - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) - - def test_taggable(self): - result = self.run_command(['tags', 'taggable']) - self.assert_no_fail(result) - self.assertIn('"host14.vmware.test.com', result.output) - self.assert_called_with('SoftLayer_Tag', 'getAllTagTypes') - self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) - - def test_cleanup(self): - result = self.run_command(['tags', 'cleanup']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('coreos',)) - - def test_cleanup_dry(self): - result = self.run_command(['tags', 'cleanup', '-d']) - self.assert_no_fail(result) - self.assertIn('(Dry Run)', result.output) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assertEqual([], self.calls(service='SoftLayer_Tag', method='deleteTag')) +""" + SoftLayer.tests.CLI.modules.tag_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from unittest import mock as mock + +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer import testing + + +class TagCLITests(testing.TestCase): + + def test_list(self): + result = self.run_command(['tags', 'list']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assertIn('coreos', result.output) + + def test_list_detail(self): + result = self.run_command(['tags', 'list', '-d']) + self.assert_no_fail(result) + self.assertIn('"vs-test1.test.sftlyr.ws', result.output) # From fixtures/virutal_guest.getObject + # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) + + def test_list_detail_ungettable(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') + mock.side_effect = SoftLayerAPIError(404, "TEST ERROR") + result = self.run_command(['tags', 'list', '-d']) + self.assert_no_fail(result) + self.assertIn("TEST ERROR", result.output) # From fixtures/virutal_guest.getObject + # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) + + @mock.patch('SoftLayer.CLI.tags.set.click') + def test_set_tags(self, click): + result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) + click.secho.assert_called_with('Set tags successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) + + @mock.patch('SoftLayer.CLI.tags.set.click') + def test_set_tags_failure(self, click): + mock = self.set_mock('SoftLayer_Tag', 'setTags') + mock.return_value = False + result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) + click.secho.assert_called_with('Failed to set tags', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) + + def test_details_by_name(self): + tag_name = 'bs_test_instance' + result = self.run_command(['tags', 'details', tag_name]) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=(tag_name,)) + + def test_details_by_id(self): + tag_id = '1286571' + result = self.run_command(['tags', 'details', tag_id]) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) + + def test_deleteTags_by_name(self): + result = self.run_command(['tags', 'delete', 'test']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('test',)) + + def test_deleteTags_by_id(self): + result = self.run_command(['tags', 'delete', '123456']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier='123456') + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('bs_test_instance',)) + + def test_deleteTags_by_number_name(self): + result = self.run_command(['tags', 'delete', '123456', '--name']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) + + @mock.patch('SoftLayer.CLI.tags.delete.click') + def test_deleteTags_fail(self, click): + mock = self.set_mock('SoftLayer_Tag', 'deleteTag') + mock.return_value = False + result = self.run_command(['tags', 'delete', '123456', '--name']) + click.secho.assert_called_with('Failed to remove tag 123456', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) + + def test_taggable(self): + result = self.run_command(['tags', 'taggable']) + self.assert_no_fail(result) + self.assertIn('"host14.vmware.test.com', result.output) + self.assert_called_with('SoftLayer_Tag', 'getAllTagTypes') + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) + + def test_cleanup(self): + result = self.run_command(['tags', 'cleanup']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('coreos',)) + + def test_cleanup_dry(self): + result = self.run_command(['tags', 'cleanup', '-d']) + self.assert_no_fail(result) + self.assertIn('(Dry Run)', result.output) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assertEqual([], self.calls(service='SoftLayer_Tag', method='deleteTag')) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index 67c817a6f..51b0d2198 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -1,208 +1,208 @@ -""" - SoftLayer.tests.managers.tag_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" - -from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer.managers import tags -from SoftLayer import testing - - -class TagTests(testing.TestCase): - - def set_up(self): - self.tag_manager = tags.TagManager(self.client) - self.test_mask = "mask[id]" - - def test_list_tags(self): - result = self.tag_manager.list_tags() - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') - self.assertIn('attached', result.keys()) - self.assertIn('unattached', result.keys()) - - def test_list_tags_mask(self): - result = self.tag_manager.list_tags(mask=self.test_mask) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) - self.assertIn('attached', result.keys()) - self.assertIn('unattached', result.keys()) - - def test_unattached_tags(self): - result = self.tag_manager.get_unattached_tags() - self.assertEqual('coreos', result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=None) - - def test_unattached_tags_mask(self): - result = self.tag_manager.get_unattached_tags(mask=self.test_mask) - self.assertEqual('coreos', result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) - - def test_attached_tags(self): - result = self.tag_manager.get_attached_tags() - self.assertEqual('bs_test_instance', result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=None) - - def test_attached_tags_mask(self): - result = self.tag_manager.get_attached_tags(mask=self.test_mask) - self.assertEqual('bs_test_instance', result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) - - def test_get_tag_references(self): - tag_id = 1286571 - result = self.tag_manager.get_tag_references(tag_id) - self.assertEqual(tag_id, result[0].get('tagId')) - self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id) - - def test_get_tag_references_mask(self): - tag_id = 1286571 - result = self.tag_manager.get_tag_references(tag_id, mask=self.test_mask) - self.assertEqual(tag_id, result[0].get('tagId')) - self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id, mask=self.test_mask) - - def test_reference_lookup_hardware(self): - resource_id = 12345 - tag_type = 'HARDWARE' - - self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) - - def test_reference_lookup_guest(self): - resource_id = 12345 - tag_type = 'GUEST' - - self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=resource_id) - - def test_reference_lookup_app_delivery(self): - resource_id = 12345 - tag_type = 'APPLICATION_DELIVERY_CONTROLLER' - - self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', - 'getObject', identifier=resource_id) - - def test_reference_lookup_dedicated(self): - resource_id = 12345 - tag_type = 'DEDICATED_HOST' - - self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getObject', identifier=resource_id) - - def test_reference_lookup_document(self): - resource_id = 12345 - tag_type = 'ACCOUNT_DOCUMENT' - - exception = self.assertRaises( - SoftLayerAPIError, - self.tag_manager.reference_lookup, - resource_id, - tag_type - ) - self.assertEqual(exception.faultCode, 404) - self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") - - def test_set_tags(self): - tags = "tag1,tag2" - key_name = "GUEST" - resource_id = 100 - - self.tag_manager.set_tags(tags, key_name, resource_id) - self.assert_called_with('SoftLayer_Tag', 'setTags') - - def test_get_tag(self): - tag_id = 1286571 - result = self.tag_manager.get_tag(tag_id) - self.assertEqual(tag_id, result.get('id')) - self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) - - def test_get_tag_mask(self): - tag_id = 1286571 - result = self.tag_manager.get_tag(tag_id, mask=self.test_mask) - self.assertEqual(tag_id, result.get('id')) - self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id, mask=self.test_mask) - - def test_get_tag_by_name(self): - tag_name = 'bs_test_instance' - result = self.tag_manager.get_tag_by_name(tag_name) - args = (tag_name,) - self.assertEqual(tag_name, result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=args) - - def test_get_tag_by_name_mask(self): - tag_name = 'bs_test_instance' - result = self.tag_manager.get_tag_by_name(tag_name, mask=self.test_mask) - args = (tag_name,) - self.assertEqual(tag_name, result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', mask=self.test_mask, args=args) - - def test_taggable_by_type_main(self): - result = self.tag_manager.taggable_by_type("HARDWARE") - self.assertEqual("SoftLayer_Hardware", result[0].get('resourceType')) - self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) - - def test_taggable_by_type_ticket(self): - mock = self.set_mock('SoftLayer_Search', 'advancedSearch') - mock.return_value = [ - { - "resourceType": "SoftLayer_Ticket", - "resource": { - "domain": "vmware.test.com", - } - } - ] - - result = self.tag_manager.taggable_by_type("TICKET") - self.assertEqual("SoftLayer_Ticket", result[0].get('resourceType')) - self.assert_called_with('SoftLayer_Search', 'advancedSearch', - args=('_objectType:SoftLayer_Ticket status.name: open',)) - - def test_taggable_by_type_image_template(self): - result = self.tag_manager.taggable_by_type("IMAGE_TEMPLATE") - self.assertEqual("Virtual_Guest_Block_Device_Template_Group", result[0].get('resourceType')) - self.assert_called_with('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups') - - def test_taggable_by_type_network_subnet(self): - result = self.tag_manager.taggable_by_type("NETWORK_SUBNET") - self.assertEqual("Network_Subnet", result[0].get('resourceType')) - self.assert_called_with('SoftLayer_Account', 'getSubnets') - - def test_type_to_service(self): - in_out = [ - {'input': 'ACCOUNT_DOCUMENT', 'output': None}, - {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': 'Network_Application_Delivery_Controller'}, - {'input': 'GUEST', 'output': 'Virtual_Guest'}, - {'input': 'DEDICATED_HOST', 'output': 'Virtual_DedicatedHost'}, - {'input': 'IMAGE_TEMPLATE', 'output': 'Virtual_Guest_Block_Device_Template_Group'}, - {'input': 'HARDWARE', 'output': 'Hardware'}, - {'input': 'NETWORK_VLAN', 'output': 'Network_Vlan'}, - ] - - for test in in_out: - result = self.tag_manager.type_to_service(test.get('input')) - self.assertEqual(test.get('output'), result) - - def test_get_resource_name(self): - resource = { - 'primaryIpAddress': '4.4.4.4', - 'vlanNumber': '12345', - 'name': 'testName', - 'subject': 'TEST SUBJECT', - 'networkIdentifier': '127.0.0.1', - 'fullyQualifiedDomainName': 'test.test.com' - } - in_out = [ - {'input': 'NETWORK_VLAN_FIREWALL', 'output': resource.get('primaryIpAddress')}, - {'input': 'NETWORK_VLAN', 'output': "{} ({})".format(resource.get('vlanNumber'), resource.get('name'))}, - {'input': 'IMAGE_TEMPLATE', 'output': resource.get('name')}, - {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': resource.get('name')}, - {'input': 'TICKET', 'output': resource.get('subjet')}, - {'input': 'NETWORK_SUBNET', 'output': resource.get('networkIdentifier')}, - {'input': 'HARDWARE', 'output': resource.get('fullyQualifiedDomainName')}, - ] - - for test in in_out: - result = self.tag_manager.get_resource_name(resource, test.get('input')) - self.assertEqual(test.get('output'), result) +""" + SoftLayer.tests.managers.tag_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers import tags +from SoftLayer import testing + + +class TagTests(testing.TestCase): + + def set_up(self): + self.tag_manager = tags.TagManager(self.client) + self.test_mask = "mask[id]" + + def test_list_tags(self): + result = self.tag_manager.list_tags() + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assertIn('attached', result.keys()) + self.assertIn('unattached', result.keys()) + + def test_list_tags_mask(self): + result = self.tag_manager.list_tags(mask=self.test_mask) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) + self.assertIn('attached', result.keys()) + self.assertIn('unattached', result.keys()) + + def test_unattached_tags(self): + result = self.tag_manager.get_unattached_tags() + self.assertEqual('coreos', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=None) + + def test_unattached_tags_mask(self): + result = self.tag_manager.get_unattached_tags(mask=self.test_mask) + self.assertEqual('coreos', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) + + def test_attached_tags(self): + result = self.tag_manager.get_attached_tags() + self.assertEqual('bs_test_instance', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=None) + + def test_attached_tags_mask(self): + result = self.tag_manager.get_attached_tags(mask=self.test_mask) + self.assertEqual('bs_test_instance', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) + + def test_get_tag_references(self): + tag_id = 1286571 + result = self.tag_manager.get_tag_references(tag_id) + self.assertEqual(tag_id, result[0].get('tagId')) + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id) + + def test_get_tag_references_mask(self): + tag_id = 1286571 + result = self.tag_manager.get_tag_references(tag_id, mask=self.test_mask) + self.assertEqual(tag_id, result[0].get('tagId')) + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id, mask=self.test_mask) + + def test_reference_lookup_hardware(self): + resource_id = 12345 + tag_type = 'HARDWARE' + + self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) + + def test_reference_lookup_guest(self): + resource_id = 12345 + tag_type = 'GUEST' + + self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=resource_id) + + def test_reference_lookup_app_delivery(self): + resource_id = 12345 + tag_type = 'APPLICATION_DELIVERY_CONTROLLER' + + self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', + 'getObject', identifier=resource_id) + + def test_reference_lookup_dedicated(self): + resource_id = 12345 + tag_type = 'DEDICATED_HOST' + + self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getObject', identifier=resource_id) + + def test_reference_lookup_document(self): + resource_id = 12345 + tag_type = 'ACCOUNT_DOCUMENT' + + exception = self.assertRaises( + SoftLayerAPIError, + self.tag_manager.reference_lookup, + resource_id, + tag_type + ) + self.assertEqual(exception.faultCode, 404) + self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") + + def test_set_tags(self): + tags = "tag1,tag2" + key_name = "GUEST" + resource_id = 100 + + self.tag_manager.set_tags(tags, key_name, resource_id) + self.assert_called_with('SoftLayer_Tag', 'setTags') + + def test_get_tag(self): + tag_id = 1286571 + result = self.tag_manager.get_tag(tag_id) + self.assertEqual(tag_id, result.get('id')) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) + + def test_get_tag_mask(self): + tag_id = 1286571 + result = self.tag_manager.get_tag(tag_id, mask=self.test_mask) + self.assertEqual(tag_id, result.get('id')) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id, mask=self.test_mask) + + def test_get_tag_by_name(self): + tag_name = 'bs_test_instance' + result = self.tag_manager.get_tag_by_name(tag_name) + args = (tag_name,) + self.assertEqual(tag_name, result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=args) + + def test_get_tag_by_name_mask(self): + tag_name = 'bs_test_instance' + result = self.tag_manager.get_tag_by_name(tag_name, mask=self.test_mask) + args = (tag_name,) + self.assertEqual(tag_name, result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', mask=self.test_mask, args=args) + + def test_taggable_by_type_main(self): + result = self.tag_manager.taggable_by_type("HARDWARE") + self.assertEqual("SoftLayer_Hardware", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) + + def test_taggable_by_type_ticket(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = [ + { + "resourceType": "SoftLayer_Ticket", + "resource": { + "domain": "vmware.test.com", + } + } + ] + + result = self.tag_manager.taggable_by_type("TICKET") + self.assertEqual("SoftLayer_Ticket", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Search', 'advancedSearch', + args=('_objectType:SoftLayer_Ticket status.name: open',)) + + def test_taggable_by_type_image_template(self): + result = self.tag_manager.taggable_by_type("IMAGE_TEMPLATE") + self.assertEqual("Virtual_Guest_Block_Device_Template_Group", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups') + + def test_taggable_by_type_network_subnet(self): + result = self.tag_manager.taggable_by_type("NETWORK_SUBNET") + self.assertEqual("Network_Subnet", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Account', 'getSubnets') + + def test_type_to_service(self): + in_out = [ + {'input': 'ACCOUNT_DOCUMENT', 'output': None}, + {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': 'Network_Application_Delivery_Controller'}, + {'input': 'GUEST', 'output': 'Virtual_Guest'}, + {'input': 'DEDICATED_HOST', 'output': 'Virtual_DedicatedHost'}, + {'input': 'IMAGE_TEMPLATE', 'output': 'Virtual_Guest_Block_Device_Template_Group'}, + {'input': 'HARDWARE', 'output': 'Hardware'}, + {'input': 'NETWORK_VLAN', 'output': 'Network_Vlan'}, + ] + + for test in in_out: + result = self.tag_manager.type_to_service(test.get('input')) + self.assertEqual(test.get('output'), result) + + def test_get_resource_name(self): + resource = { + 'primaryIpAddress': '4.4.4.4', + 'vlanNumber': '12345', + 'name': 'testName', + 'subject': 'TEST SUBJECT', + 'networkIdentifier': '127.0.0.1', + 'fullyQualifiedDomainName': 'test.test.com' + } + in_out = [ + {'input': 'NETWORK_VLAN_FIREWALL', 'output': resource.get('primaryIpAddress')}, + {'input': 'NETWORK_VLAN', 'output': "{} ({})".format(resource.get('vlanNumber'), resource.get('name'))}, + {'input': 'IMAGE_TEMPLATE', 'output': resource.get('name')}, + {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': resource.get('name')}, + {'input': 'TICKET', 'output': resource.get('subjet')}, + {'input': 'NETWORK_SUBNET', 'output': resource.get('networkIdentifier')}, + {'input': 'HARDWARE', 'output': resource.get('fullyQualifiedDomainName')}, + ] + + for test in in_out: + result = self.tag_manager.get_resource_name(resource, test.get('input')) + self.assertEqual(test.get('output'), result) From 252fd02881f3e649c3f8d6c3e6d8d9028f5fe916 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:04:40 -0600 Subject: [PATCH 0579/1385] Added mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..0cbadf756 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Christopher Gallo From 9132182ad7809241092935b6735a8262a6b3ab51 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:05:53 -0600 Subject: [PATCH 0580/1385] going to v6.0.1 because the readme was broken so now I have to make a new version --- CHANGELOG.md | 5 +++-- SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 799351a6f..6b6f55a71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [6.0.0] - 2022-03-11 +## [6.0.1] - 2022-03-11 ## What's Changed @@ -12,8 +12,9 @@ * fix to errors in slcli hw create-options by @caberos in https://github.com/softlayer/softlayer-python/pull/1594 -**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v5.9.9...v6.0.0 +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v5.9.9...v6.0.1 +6.0.0 was skipped. ## [5.9.9] - 2022-02-04 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 38a8289bf..54e4dfd22 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v6.0.0' +VERSION = 'v6.0.1' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 39bf801f6..0138ba9de 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='6.0.0', + version='6.0.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', From 37be462aa478037ccc840d2fb9f8543bd4a42727 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:14:37 -0600 Subject: [PATCH 0581/1385] added gitattributes to kinda enforce line endings --- .gitattributes | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..05041c45f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Language aware diff headers +# https://tekin.co.uk/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more +# https://gist.github.com/tekin/12500956bd56784728e490d8cef9cb81 +*.css diff=css +*.html diff=html +*.py diff=python +*.md diff=markdown + + +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary From f543f3d2dc45229cd705be897f11c5f9c169bd39 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Mar 2022 10:16:00 -0400 Subject: [PATCH 0582/1385] fix the code review comments --- SoftLayer/CLI/hardware/monitoring.py | 10 +++++----- SoftLayer/CLI/virt/monitoring.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/CLI/hardware/monitoring.py b/SoftLayer/CLI/hardware/monitoring.py index 81640f3e5..58f4f614f 100644 --- a/SoftLayer/CLI/hardware/monitoring.py +++ b/SoftLayer/CLI/hardware/monitoring.py @@ -22,12 +22,12 @@ def cli(env, identifier): monitoring = hardware.get_hardware(identifier) - table.add_row(['domain', monitoring.get('fullyQualifiedDomainName')]) - table.add_row(['public Ip', monitoring.get('primaryIpAddress')]) - table.add_row(['private Ip', monitoring.get('primaryBackendIpAddress')]) - table.add_row(['location', monitoring['datacenter']['longName']]) + table.add_row(['Domain', monitoring.get('fullyQualifiedDomainName')]) + table.add_row(['Public Ip', monitoring.get('primaryIpAddress')]) + table.add_row(['Private Ip', monitoring.get('primaryBackendIpAddress')]) + table.add_row(['Location', monitoring['datacenter']['longName']]) - monitoring_table = formatting.Table(['Id', 'ipAddress', 'status', 'type', 'notify']) + monitoring_table = formatting.Table(['Id', 'IpAddress', 'Status', 'Type', 'Notify']) for monitor in monitoring['networkMonitors']: monitoring_table.add_row([monitor.get('id'), monitor.get('ipAddress'), monitor.get('status'), monitor['queryType']['name'], monitor['responseAction']['actionDescription']]) diff --git a/SoftLayer/CLI/virt/monitoring.py b/SoftLayer/CLI/virt/monitoring.py index 4e76549cf..27e16d35a 100644 --- a/SoftLayer/CLI/virt/monitoring.py +++ b/SoftLayer/CLI/virt/monitoring.py @@ -1,4 +1,4 @@ -"""Get monitoring for a vSI device.""" +"""Get monitoring for a VSI device.""" # :license: MIT, see LICENSE for more details. import click @@ -22,12 +22,12 @@ def cli(env, identifier): monitoring = vsi.get_instance(identifier) - table.add_row(['domain', monitoring.get('fullyQualifiedDomainName')]) - table.add_row(['public Ip', monitoring.get('primaryIpAddress')]) - table.add_row(['private Ip', monitoring.get('primaryBackendIpAddress')]) - table.add_row(['location', monitoring['datacenter']['longName']]) + table.add_row(['Domain', monitoring.get('fullyQualifiedDomainName')]) + table.add_row(['Public Ip', monitoring.get('primaryIpAddress')]) + table.add_row(['Private Ip', monitoring.get('primaryBackendIpAddress')]) + table.add_row(['Location', monitoring['datacenter']['longName']]) - monitoring_table = formatting.Table(['Id', 'ipAddress', 'status', 'type', 'notify']) + monitoring_table = formatting.Table(['Id', 'IpAddress', 'Status', 'Type', 'Notify']) for monitor in monitoring['networkMonitors']: monitoring_table.add_row([monitor.get('id'), monitor.get('ipAddress'), monitor.get('status'), monitor['queryType']['name'], monitor['responseAction']['actionDescription']]) From 5fef78815ae08ed536948db97b7ba92eac55cef3 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 21 Mar 2022 12:12:15 -0400 Subject: [PATCH 0583/1385] When listing datacenters/pods, mark those that are closing soon. --- SoftLayer/CLI/hardware/create_options.py | 30 ++++++++++++++++++++++-- SoftLayer/CLI/virt/create_options.py | 30 ++++++++++++++++++++++-- SoftLayer/managers/network.py | 8 +++---- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 646d37c09..5a59bf696 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -7,6 +7,7 @@ from SoftLayer.CLI import formatting from SoftLayer.managers import account from SoftLayer.managers import hardware +from SoftLayer.managers import network @click.command() @@ -22,14 +23,39 @@ def cli(env, prices, location=None): account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) routers = account_manager.get_routers(location=location) + network_manager = network.NetworkManager(env.client) + + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + + pods_mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, + backendRouterName, frontendRouterName]""" + pods = network_manager.get_pods(mask=pods_mask, filter=closing_filter) tables = [] # Datacenters - dc_table = formatting.Table(['Datacenter', 'Value'], title="Datacenters") + dc_table = formatting.Table(['Datacenter', 'Value', 'note'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' for location_info in options['locations']: - dc_table.add_row([location_info['name'], location_info['key']]) + closure = [] + for pod in pods: + if ((location_info['key'] in str(pod['name']))): + closure.append(pod['name']) + + if len(closure) == 0: + closure = '' + else: + closure = 'closed soon: %s' % (str(closure)) + dc_table.add_row([location_info['name'], location_info['key'], str(closure)]) tables.append(dc_table) tables.append(_preset_prices_table(options['sizes'], prices)) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index a3ee24314..1db69fe68 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -2,6 +2,7 @@ # :license: MIT, see LICENSE for more details. # pylint: disable=too-many-statements import click +from SoftLayer.managers import network import SoftLayer from SoftLayer.CLI import environment @@ -22,16 +23,41 @@ def cli(env, vsi_type, prices, location=None): """Virtual server order options.""" vsi = SoftLayer.VSManager(env.client) + network_manager = network.NetworkManager(env.client) options = vsi.get_create_options(vsi_type, location) + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + + pods_mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, + backendRouterName, frontendRouterName]""" + pods = network_manager.get_pods(mask=pods_mask, filter=closing_filter) + tables = [] # Datacenters - dc_table = formatting.Table(['datacenter', 'Value'], title="Datacenters") + dc_table = formatting.Table(['Datacenter', 'Value', 'note'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' for location_info in options['locations']: - dc_table.add_row([location_info['name'], location_info['key']]) + closure = [] + for pod in pods: + if ((location_info['key'] in str(pod['name']))): + closure.append(pod['name']) + + if len(closure) == 0: + closure = '' + else: + closure = 'closed soon: %s' % (str(closure)) + dc_table.add_row([location_info['name'], location_info['key'], str(closure)]) tables.append(dc_table) if vsi_type == 'CLOUD_SERVER': diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 6638a29d3..6eaabfc44 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -779,16 +779,16 @@ def cancel_item(self, identifier, cancel_immediately, customer_note, id=identifier) - def get_pods(self, datacenter=None): + def get_pods(self, mask=None, filter=None, datacenter=None): """Calls SoftLayer_Network_Pod::getAllObjects() returns list of all network pods and their routers. """ - _filter = None + if datacenter: - _filter = {"datacenterName": {"operation": datacenter}} + filter = {"datacenterName": {"operation": datacenter}} - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=filter) def get_list_datacenter(self): """Calls SoftLayer_Location::getDatacenters() From c690793800ae5ae903eba3de7283dac11cf48e7a Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Mar 2022 08:26:47 -0400 Subject: [PATCH 0584/1385] fix the team code review comments --- SoftLayer/CLI/hardware/create_options.py | 27 ++++++------------------ SoftLayer/CLI/virt/create_options.py | 26 ++++++----------------- SoftLayer/managers/network.py | 27 +++++++++++++++++++++--- 3 files changed, 37 insertions(+), 43 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 5a59bf696..556c3af75 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -25,24 +25,12 @@ def cli(env, prices, location=None): routers = account_manager.get_routers(location=location) network_manager = network.NetworkManager(env.client) - closing_filter = { - 'capabilities': { - 'operation': 'in', - 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] - }, - 'name': { - 'operation': 'orderBy', - 'options': [{'name': 'sort', 'value': ['DESC']}] - } - } - - pods_mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, - backendRouterName, frontendRouterName]""" - pods = network_manager.get_pods(mask=pods_mask, filter=closing_filter) + + pods = network_manager.get_closed_pods() tables = [] # Datacenters - dc_table = formatting.Table(['Datacenter', 'Value', 'note'], title="Datacenters") + dc_table = formatting.Table(['Datacenter', 'Value', 'Note'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' for location_info in options['locations']: @@ -51,11 +39,10 @@ def cli(env, prices, location=None): if ((location_info['key'] in str(pod['name']))): closure.append(pod['name']) - if len(closure) == 0: - closure = '' - else: - closure = 'closed soon: %s' % (str(closure)) - dc_table.add_row([location_info['name'], location_info['key'], str(closure)]) + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) + dc_table.add_row([location_info['name'], location_info['key'], notes]) tables.append(dc_table) tables.append(_preset_prices_table(options['sizes'], prices)) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 1db69fe68..644e9c900 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -26,25 +26,12 @@ def cli(env, vsi_type, prices, location=None): network_manager = network.NetworkManager(env.client) options = vsi.get_create_options(vsi_type, location) - closing_filter = { - 'capabilities': { - 'operation': 'in', - 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] - }, - 'name': { - 'operation': 'orderBy', - 'options': [{'name': 'sort', 'value': ['DESC']}] - } - } - - pods_mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, - backendRouterName, frontendRouterName]""" - pods = network_manager.get_pods(mask=pods_mask, filter=closing_filter) + pods = network_manager.get_closed_pods() tables = [] # Datacenters - dc_table = formatting.Table(['Datacenter', 'Value', 'note'], title="Datacenters") + dc_table = formatting.Table(['Datacenter', 'Value', 'Note'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' for location_info in options['locations']: @@ -53,11 +40,10 @@ def cli(env, vsi_type, prices, location=None): if ((location_info['key'] in str(pod['name']))): closure.append(pod['name']) - if len(closure) == 0: - closure = '' - else: - closure = 'closed soon: %s' % (str(closure)) - dc_table.add_row([location_info['name'], location_info['key'], str(closure)]) + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) + dc_table.add_row([location_info['name'], location_info['key'], notes]) tables.append(dc_table) if vsi_type == 'CLOUD_SERVER': diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 6eaabfc44..0f550ec3d 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -779,16 +779,17 @@ def cancel_item(self, identifier, cancel_immediately, customer_note, id=identifier) - def get_pods(self, mask=None, filter=None, datacenter=None): + def get_pods(self, datacenter=None): """Calls SoftLayer_Network_Pod::getAllObjects() returns list of all network pods and their routers. """ + _filter = None if datacenter: - filter = {"datacenterName": {"operation": datacenter}} + _filter = {"datacenterName": {"operation": datacenter}} - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=filter) + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) def get_list_datacenter(self): """Calls SoftLayer_Location::getDatacenters() @@ -803,3 +804,23 @@ def get_routers(self, identifier): returns all routers locations. """ return self.client.call('SoftLayer_Location_Datacenter', 'getHardwareRouters', id=identifier) + + def get_closed_pods(self): + """Calls SoftLayer_Network_Pod::getAllObjects() + + returns list of all closing network pods. + """ + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + + mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, + backendRouterName, frontendRouterName]""" + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) From 0567276624979bbee362b5bd00872897c23b8e5b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Mar 2022 08:35:27 -0400 Subject: [PATCH 0585/1385] fix the tox analysis --- SoftLayer/CLI/hardware/create_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 556c3af75..b7183758b 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -25,8 +25,8 @@ def cli(env, prices, location=None): routers = account_manager.get_routers(location=location) network_manager = network.NetworkManager(env.client) - pods = network_manager.get_closed_pods() + tables = [] # Datacenters From 68fa92e770550471045f89b64cf6be7e5a47f977 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Mar 2022 08:40:00 -0400 Subject: [PATCH 0586/1385] fix the tox analysis --- SoftLayer/CLI/hardware/create_options.py | 2 +- SoftLayer/CLI/virt/create_options.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index b7183758b..cf09971da 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -36,7 +36,7 @@ def cli(env, prices, location=None): for location_info in options['locations']: closure = [] for pod in pods: - if ((location_info['key'] in str(pod['name']))): + if location_info['key'] in str(pod['name']): closure.append(pod['name']) notes = '-' diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 644e9c900..ac84eda64 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -37,7 +37,7 @@ def cli(env, vsi_type, prices, location=None): for location_info in options['locations']: closure = [] for pod in pods: - if ((location_info['key'] in str(pod['name']))): + if location_info['key'] in str(pod['name']): closure.append(pod['name']) notes = '-' From 6937a461fa826c21d5d44765c020cf131cc28713 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Mar 2022 12:08:04 -0400 Subject: [PATCH 0587/1385] Add an orderBy filter to slcli vlan list --- SoftLayer/managers/network.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 6638a29d3..bcb73f786 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -506,7 +506,7 @@ def list_subnets(self, identifier=None, datacenter=None, version=0, kwargs['iter'] = True return self.client.call('Account', 'getSubnets', **kwargs) - def list_vlans(self, datacenter=None, vlan_number=None, name=None, **kwargs): + def list_vlans(self, datacenter=None, vlan_number=None, name=None, limit=100, **kwargs): """Display a list of all VLANs on the account. This provides a quick overview of all VLANs including information about @@ -523,6 +523,8 @@ def list_vlans(self, datacenter=None, vlan_number=None, name=None, **kwargs): """ _filter = utils.NestedDict(kwargs.get('filter') or {}) + _filter['networkVlans']['id'] = utils.query_filter_orderby() + if vlan_number: _filter['networkVlans']['vlanNumber'] = ( utils.query_filter(vlan_number)) @@ -540,7 +542,7 @@ def list_vlans(self, datacenter=None, vlan_number=None, name=None, **kwargs): kwargs['mask'] = DEFAULT_VLAN_MASK kwargs['iter'] = True - return self.account.getNetworkVlans(**kwargs) + return self.account.getNetworkVlans(mask=kwargs['mask'], filter=_filter.to_dict(), limit=limit) def list_securitygroups(self, **kwargs): """List security groups.""" From 9083aba4b4884cc2df0dd27e37ae01466854a7d6 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Mar 2022 18:28:34 -0400 Subject: [PATCH 0588/1385] fix the team code review comments --- SoftLayer/CLI/order/package_locations.py | 16 ++++++++++++++-- tests/CLI/modules/order_tests.py | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/order/package_locations.py b/SoftLayer/CLI/order/package_locations.py index 9f8ffb655..984a7f276 100644 --- a/SoftLayer/CLI/order/package_locations.py +++ b/SoftLayer/CLI/order/package_locations.py @@ -4,9 +4,10 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.managers import network from SoftLayer.managers import ordering -COLUMNS = ['id', 'dc', 'description', 'keyName'] +COLUMNS = ['id', 'dc', 'description', 'keyName', 'note'] @click.command() @@ -18,15 +19,26 @@ def cli(env, package_keyname): Use the location Key Name to place orders """ manager = ordering.OrderingManager(env.client) + network_manager = network.NetworkManager(env.client) + + pods = network_manager.get_closed_pods() table = formatting.Table(COLUMNS) locations = manager.package_locations(package_keyname) for region in locations: for datacenter in region['locations']: + closure = [] + for pod in pods: + if datacenter['location']['name'] in str(pod['name']): + closure.append(pod['name']) + + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) table.add_row([ datacenter['location']['id'], datacenter['location']['name'], region['description'], - region['keyname'] + region['keyname'], notes ]) env.fout(table) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 0e8878093..a8040bafa 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -309,7 +309,8 @@ def test_location_list(self): result = self.run_command(['order', 'package-locations', 'package']) self.assert_no_fail(result) expected_results = [ - {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', 'keyName': 'WASHINGTON07'} + {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', + 'keyName': 'WASHINGTON07','note': 'closed soon: wdc07.pod01'} ] print("FUCK") print(result.output) From 3c9cf602a1082b93ca8bc9a4daf05379becd5b84 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Mar 2022 08:22:30 -0400 Subject: [PATCH 0589/1385] fix the team code review comments --- tests/CLI/modules/order_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index a8040bafa..490362b99 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -310,7 +310,7 @@ def test_location_list(self): self.assert_no_fail(result) expected_results = [ {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', - 'keyName': 'WASHINGTON07','note': 'closed soon: wdc07.pod01'} + 'keyName': 'WASHINGTON07', 'note': 'closed soon: wdc07.pod01'} ] print("FUCK") print(result.output) From b8ebc9a625a582843f6f54f1b35d7d34cbee1043 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Mar 2022 10:35:25 -0400 Subject: [PATCH 0590/1385] fix the team code review comments --- SoftLayer/CLI/order/package_locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/order/package_locations.py b/SoftLayer/CLI/order/package_locations.py index 984a7f276..2bddbf66f 100644 --- a/SoftLayer/CLI/order/package_locations.py +++ b/SoftLayer/CLI/order/package_locations.py @@ -7,7 +7,7 @@ from SoftLayer.managers import network from SoftLayer.managers import ordering -COLUMNS = ['id', 'dc', 'description', 'keyName', 'note'] +COLUMNS = ['id', 'dc', 'description', 'keyName', 'Note'] @click.command() From 79a16e5cd123c94c55a5a0772dd59272ec681016 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Mar 2022 15:38:59 -0400 Subject: [PATCH 0591/1385] fix the team code review comments --- tests/CLI/modules/order_tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 490362b99..be03bbd17 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -310,10 +310,9 @@ def test_location_list(self): self.assert_no_fail(result) expected_results = [ {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', - 'keyName': 'WASHINGTON07', 'note': 'closed soon: wdc07.pod01'} + 'keyName': 'WASHINGTON07', 'Note': 'closed soon: wdc07.pod01'} ] - print("FUCK") - print(result.output) + self.assertEqual(expected_results, json.loads(result.output)) def test_quote_verify(self): From 2af5f07d0aef1b34b6261df95c6a1e405c529a9d Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Mar 2022 11:54:07 -0400 Subject: [PATCH 0592/1385] Add a warning if user orders in a POD that is being closed --- SoftLayer/CLI/hardware/create.py | 8 ++++++++ SoftLayer/CLI/virt/create.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index a1d373e14..19d100fc4 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -41,6 +41,10 @@ def cli(env, **args): """Order/create a dedicated server.""" mgr = SoftLayer.HardwareManager(env.client) + network = SoftLayer.NetworkManager(env.client) + + pods = network.get_closed_pods() + closure = [] # Get the SSH keys ssh_keys = [] @@ -99,6 +103,10 @@ def cli(env, **args): return if do_create: + for pod in pods: + if args.get('datacenter') in str(pod['name']): + closure.append(pod['name']) + click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting dedicated server order.') diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index ad8b3b35b..5953364db 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -218,8 +218,16 @@ def cli(env, **args): create_args = _parse_create_args(env.client, args) test = args.get('test', False) do_create = not (args.get('export') or test) + network = SoftLayer.NetworkManager(env.client) + + pods = network.get_closed_pods() + closure = [] if do_create: + for pod in pods: + if args.get('datacenter') in str(pod['name']): + closure.append(pod['name']) + click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting virtual server order.') From 0fa4dfd9f8723ce41d42f4c999f8a71c19b11a9e Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Mar 2022 17:05:06 -0400 Subject: [PATCH 0593/1385] fix the error unit test --- tests/CLI/modules/server_tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index a150217e2..b026fa8cf 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -397,8 +397,9 @@ def test_create_server(self, order_mock): ]) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - {'id': 98765, 'created': '2013-08-02 15:23:47'}) + self.assertEqual( + str(result.output), + 'Warning: Closed soon: TEST00.pod2\n{\n "id": 98765,\n "created": "2013-08-02 15:23:47"\n}\n') @mock.patch('SoftLayer.CLI.template.export_to_template') def test_create_server_with_export(self, export_mock): From 3de562bca57e4a7a23b3d41ed7aec45168a0a23f Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Mar 2022 09:11:47 -0400 Subject: [PATCH 0594/1385] fix the team code review and fix the unit test --- SoftLayer/CLI/order/place.py | 9 +++++++++ tests/CLI/modules/order_tests.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 531bacee5..e11209bdb 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -8,6 +8,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting +from SoftLayer.managers import NetworkManager from SoftLayer.managers import ordering COLUMNS = ['keyName', @@ -64,6 +65,10 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, """ manager = ordering.OrderingManager(env.client) + network = NetworkManager(env.client) + + pods = network.get_closed_pods() + closure = [] if extras: try: @@ -90,6 +95,10 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, ]) else: + print(args) + for pod in pods: + closure.append(pod['name']) + click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort("Aborting order.") diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index be03bbd17..a86257b50 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -141,10 +141,10 @@ def test_place(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertEqual({'id': 1234, - 'created': order_date, - 'status': 'APPROVED'}, - json.loads(result.output)) + self.assertEqual("('package', 'DALLAS13', ('ITEM1',))\n" + 'Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' + '{\n "id": 1234,\n "created": "2017-04-04 07:39:20",\n "status": "APPROVED"\n}\n', + str(result.output)) def test_place_with_quantity(self): order_date = '2017-04-04 07:39:20' @@ -162,10 +162,10 @@ def test_place_with_quantity(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertEqual({'id': 1234, - 'created': order_date, - 'status': 'APPROVED'}, - json.loads(result.output)) + self.assertEqual("('package', 'DALLAS13', ('ITEM1',))\n" + 'Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' + '{\n "id": 1234,\n "created": "2017-04-04 07:39:20",\n "status": "APPROVED"\n}\n', + str(result.output)) def test_place_extras_parameter_fail(self): result = self.run_command(['-y', 'order', 'place', 'package', 'DALLAS13', 'ITEM1', From 86be7809e551314acea2f5a31c92dcf29b1971b6 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Mar 2022 16:39:07 -0400 Subject: [PATCH 0595/1385] slcli licenses is missing the help text --- SoftLayer/CLI/licenses/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/CLI/licenses/__init__.py b/SoftLayer/CLI/licenses/__init__.py index e69de29bb..74c74f884 100644 --- a/SoftLayer/CLI/licenses/__init__.py +++ b/SoftLayer/CLI/licenses/__init__.py @@ -0,0 +1,2 @@ +"""VMware licenses.""" +# :license: MIT, see LICENSE for more details. From 77794a5e6021a78891532f0863b38f0f146564ab Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 30 Mar 2022 16:14:40 -0500 Subject: [PATCH 0596/1385] Version to 6.0.2, locked click to 8.0.4 for now --- CHANGELOG.md | 9 +++++++++ SoftLayer/consts.py | 2 +- setup.py | 4 ++-- tools/requirements.txt | 2 +- tools/test-requirements.txt | 2 +- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6f55a71..1749efaea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log +## [6.0.2] - 2022-03-30 + +## What's Changed +* New Command slcli hardware|virtual monitoring by @caberos in https://github.com/softlayer/softlayer-python/pull/1593 +* When listing datacenters/pods, mark those that are closing soon. by @caberos in https://github.com/softlayer/softlayer-python/pull/1597 + + +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v6.0.1...v6.0.2 + ## [6.0.1] - 2022-03-11 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 54e4dfd22..32ebd4ec4 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v6.0.1' +VERSION = 'v6.0.2' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 0138ba9de..09921feaf 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='6.0.1', + version='6.0.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', @@ -35,7 +35,7 @@ python_requires='>=3.5', install_requires=[ 'prettytable >= 2.0.0', - 'click >= 7', + 'click == 8.0.4', 'requests >= 2.20.0', 'prompt_toolkit >= 2', 'pygments >= 2.0.0', diff --git a/tools/requirements.txt b/tools/requirements.txt index 09f985d84..dd85ec17e 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,5 +1,5 @@ prettytable >= 2.0.0 -click >= 7 +click == 8.0.4 requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index 3cc0f32e6..ac58d1241 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -5,7 +5,7 @@ pytest-cov mock sphinx prettytable >= 2.0.0 -click >= 7 +click == 8.0.4 requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 From 39f82b1136eede3f2e038731f6deb2721ca73fe3 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Mar 2022 19:01:59 -0400 Subject: [PATCH 0597/1385] fix the team code review and unit test --- SoftLayer/CLI/order/place.py | 1 - tests/CLI/modules/order_tests.py | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index e11209bdb..2b20be224 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -95,7 +95,6 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, ]) else: - print(args) for pod in pods: closure.append(pod['name']) click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index a86257b50..5eec6c18b 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -141,8 +141,7 @@ def test_place(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertEqual("('package', 'DALLAS13', ('ITEM1',))\n" - 'Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' + self.assertEqual('Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' '{\n "id": 1234,\n "created": "2017-04-04 07:39:20",\n "status": "APPROVED"\n}\n', str(result.output)) @@ -162,8 +161,7 @@ def test_place_with_quantity(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertEqual("('package', 'DALLAS13', ('ITEM1',))\n" - 'Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' + self.assertEqual('Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' '{\n "id": 1234,\n "created": "2017-04-04 07:39:20",\n "status": "APPROVED"\n}\n', str(result.output)) From d045f0fced445ba1b8b892075981e3d55fedeb52 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Mon, 4 Apr 2022 10:03:27 -0400 Subject: [PATCH 0598/1385] updated number of updates in the command account event-detail --- SoftLayer/CLI/account/event_detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/event_detail.py b/SoftLayer/CLI/account/event_detail.py index 2c1ee80c2..386a41710 100644 --- a/SoftLayer/CLI/account/event_detail.py +++ b/SoftLayer/CLI/account/event_detail.py @@ -65,9 +65,9 @@ def update_table(event): """Formats a basic event update table""" update_number = 0 for update in event.get('updates', []): + update_number = update_number + 1 header = "======= Update #%s on %s =======" % (update_number, utils.clean_time(update.get('startDate'))) click.secho(header, fg='green') - update_number = update_number + 1 text = update.get('contents') # deals with all the \r\n from the API click.secho(utils.clean_splitlines(text)) From 6d5374e3fdfbfb72c2b090fc79006ef81f2fe45e Mon Sep 17 00:00:00 2001 From: edsonarios Date: Tue, 5 Apr 2022 15:16:16 -0400 Subject: [PATCH 0599/1385] added options in command -slcli account events- for show just one o two specific tables --- SoftLayer/CLI/account/events.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 7d4803b42..43d85b537 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -11,8 +11,14 @@ @click.command() @click.option('--ack-all', is_flag=True, default=False, help="Acknowledge every upcoming event. Doing so will turn off the popup in the control portal") +@click.option('--planned', is_flag=True, default=False, + help="Show just planned events") +@click.option('--unplanned', is_flag=True, default=False, + help="Show just unplanned events") +@click.option('--announcement', is_flag=True, default=False, + help="Show just announcement events") @environment.pass_env -def cli(env, ack_all): +def cli(env, ack_all, planned, unplanned, announcement): """Summary and acknowledgement of upcoming and ongoing maintenance events""" manager = AccountManager(env.client) @@ -21,13 +27,22 @@ def cli(env, ack_all): announcement_events = manager.get_upcoming_events("ANNOUNCEMENT") add_ack_flag(planned_events, manager, ack_all) - env.fout(planned_event_table(planned_events)) - add_ack_flag(unplanned_events, manager, ack_all) - env.fout(unplanned_event_table(unplanned_events)) - add_ack_flag(announcement_events, manager, ack_all) - env.fout(announcement_event_table(announcement_events)) + + if planned: + env.fout(planned_event_table(planned_events)) + + if unplanned: + env.fout(unplanned_event_table(unplanned_events)) + + if announcement: + env.fout(announcement_event_table(announcement_events)) + + if not planned and not unplanned and not announcement: + env.fout(planned_event_table(planned_events)) + env.fout(unplanned_event_table(unplanned_events)) + env.fout(announcement_event_table(announcement_events)) def add_ack_flag(events, manager, ack_all): From 62ef9abe68599afc5e98a26eb46360e00760e525 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 5 Apr 2022 17:25:43 -0500 Subject: [PATCH 0600/1385] #1602 groundwork for adding a SOAP style client --- SoftLayer/transports.py | 589 --------------------- SoftLayer/transports/__init__.py | 45 ++ SoftLayer/transports/debug.py | 61 +++ SoftLayer/transports/fixture.py | 30 ++ SoftLayer/transports/rest.py | 182 +++++++ SoftLayer/transports/soap.py | 83 +++ SoftLayer/transports/timing.py | 40 ++ SoftLayer/transports/transport.py | 154 ++++++ SoftLayer/transports/xmlrpc.py | 171 ++++++ setup.py | 3 +- tests/transport_tests.py | 791 ---------------------------- tests/transports/__init__.py | 0 tests/transports/debug_tests.py | 84 +++ tests/transports/rest_tests.py | 365 +++++++++++++ tests/transports/soap_tests.py | 59 +++ tests/transports/transport_tests.py | 73 +++ tests/transports/xmlrpc_tests.py | 467 ++++++++++++++++ tools/requirements.txt | 1 + tools/test-requirements.txt | 1 + 19 files changed, 1818 insertions(+), 1381 deletions(-) delete mode 100644 SoftLayer/transports.py create mode 100644 SoftLayer/transports/__init__.py create mode 100644 SoftLayer/transports/debug.py create mode 100644 SoftLayer/transports/fixture.py create mode 100644 SoftLayer/transports/rest.py create mode 100644 SoftLayer/transports/soap.py create mode 100644 SoftLayer/transports/timing.py create mode 100644 SoftLayer/transports/transport.py create mode 100644 SoftLayer/transports/xmlrpc.py create mode 100644 tests/transports/__init__.py create mode 100644 tests/transports/debug_tests.py create mode 100644 tests/transports/rest_tests.py create mode 100644 tests/transports/soap_tests.py create mode 100644 tests/transports/transport_tests.py create mode 100644 tests/transports/xmlrpc_tests.py diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py deleted file mode 100644 index e243f16f6..000000000 --- a/SoftLayer/transports.py +++ /dev/null @@ -1,589 +0,0 @@ -""" - SoftLayer.transports - ~~~~~~~~~~~~~~~~~~~~ - XML-RPC transport layer that uses the requests library. - - :license: MIT, see LICENSE for more details. -""" -import base64 -import importlib -import json -import logging -import re -from string import Template -import time -import xmlrpc.client - -import requests -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry - -from SoftLayer import consts -from SoftLayer import exceptions -from SoftLayer import utils - -LOGGER = logging.getLogger(__name__) -# transports.Request does have a lot of instance attributes. :( -# pylint: disable=too-many-instance-attributes, no-self-use - -__all__ = [ - 'Request', - 'XmlRpcTransport', - 'RestTransport', - 'TimingTransport', - 'DebugTransport', - 'FixtureTransport', - 'SoftLayerListResult', -] - -REST_SPECIAL_METHODS = { - # 'deleteObject': 'DELETE', - 'createObject': 'POST', - 'createObjects': 'POST', - 'editObject': 'PUT', - 'editObjects': 'PUT', -} - - -def get_session(user_agent): - """Sets up urllib sessions""" - - client = requests.Session() - client.headers.update({ - 'Content-Type': 'application/json', - 'User-Agent': user_agent, - }) - retry = Retry(connect=3, backoff_factor=3) - adapter = HTTPAdapter(max_retries=retry) - client.mount('https://', adapter) - return client - - -class Request(object): - """Transport request object.""" - - def __init__(self): - #: API service name. E.G. SoftLayer_Account - self.service = None - - #: API method name. E.G. getObject - self.method = None - - #: API Parameters. - self.args = tuple() - - #: API headers, used for authentication, masks, limits, offsets, etc. - self.headers = {} - - #: Transport user. - self.transport_user = None - - #: Transport password. - self.transport_password = None - - #: Transport headers. - self.transport_headers = {} - - #: Boolean specifying if the server certificate should be verified. - self.verify = None - - #: Client certificate file path. - self.cert = None - - #: InitParameter/identifier of an object. - self.identifier = None - - #: SoftLayer mask (dict or string). - self.mask = None - - #: SoftLayer Filter (dict). - self.filter = None - - #: Integer result limit. - self.limit = None - - #: Integer result offset. - self.offset = None - - #: Integer call start time - self.start_time = None - - #: Integer call end time - self.end_time = None - - #: String full url - self.url = None - - #: String result of api call - self.result = None - - #: String payload to send in - self.payload = None - - #: Exception any exceptions that got caught - self.exception = None - - def __repr__(self): - """Prints out what this call is all about""" - pretty_mask = utils.clean_string(self.mask) - pretty_filter = self.filter - param_string = "id={id}, mask='{mask}', filter='{filter}', args={args}, limit={limit}, offset={offset}".format( - id=self.identifier, mask=pretty_mask, filter=pretty_filter, - args=self.args, limit=self.limit, offset=self.offset) - return "{service}::{method}({params})".format( - service=self.service, method=self.method, params=param_string) - - -class SoftLayerListResult(list): - """A SoftLayer API list result.""" - - def __init__(self, items=None, total_count=0): - - #: total count of items that exist on the server. This is useful when - #: paginating through a large list of objects. - self.total_count = total_count - super().__init__(items) - - -class XmlRpcTransport(object): - """XML-RPC transport.""" - - def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): - - self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') - self.timeout = timeout or None - self.proxy = proxy - self.user_agent = user_agent or consts.USER_AGENT - self.verify = verify - self._client = None - - @property - def client(self): - """Returns client session object""" - - if self._client is None: - self._client = get_session(self.user_agent) - return self._client - - def __call__(self, request): - """Makes a SoftLayer API call against the XML-RPC endpoint. - - :param request request: Request object - """ - largs = list(request.args) - headers = request.headers - - auth = None - if request.transport_user: - auth = requests.auth.HTTPBasicAuth(request.transport_user, request.transport_password) - - if request.identifier is not None: - header_name = request.service + 'InitParameters' - headers[header_name] = {'id': request.identifier} - - if request.mask is not None: - if isinstance(request.mask, dict): - mheader = '%sObjectMask' % request.service - else: - mheader = 'SoftLayer_ObjectMask' - request.mask = _format_object_mask(request.mask) - headers.update({mheader: {'mask': request.mask}}) - - if request.filter is not None: - headers['%sObjectFilter' % request.service] = request.filter - - if request.limit: - headers['resultLimit'] = { - 'limit': request.limit, - 'offset': request.offset or 0, - } - - largs.insert(0, {'headers': headers}) - request.transport_headers.setdefault('Content-Type', 'application/xml') - request.transport_headers.setdefault('User-Agent', self.user_agent) - - request.url = '/'.join([self.endpoint_url, request.service]) - request.payload = xmlrpc.client.dumps(tuple(largs), - methodname=request.method, - allow_none=True, - encoding="iso-8859-1") - - # Prefer the request setting, if it's not None - verify = request.verify - if verify is None: - request.verify = self.verify - - try: - resp = self.client.request('POST', request.url, - data=request.payload.encode(), - auth=auth, - headers=request.transport_headers, - timeout=self.timeout, - verify=request.verify, - cert=request.cert, - proxies=_proxies_dict(self.proxy)) - - resp.raise_for_status() - result = xmlrpc.client.loads(resp.content)[0][0] - if isinstance(result, list): - return SoftLayerListResult( - result, int(resp.headers.get('softlayer-total-items', 0))) - else: - return result - except xmlrpc.client.Fault as ex: - # These exceptions are formed from the XML-RPC spec - # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php - error_mapping = { - '-32700': exceptions.NotWellFormed, - '-32701': exceptions.UnsupportedEncoding, - '-32702': exceptions.InvalidCharacter, - '-32600': exceptions.SpecViolation, - '-32601': exceptions.MethodNotFound, - '-32602': exceptions.InvalidMethodParameters, - '-32603': exceptions.InternalError, - '-32500': exceptions.ApplicationError, - '-32400': exceptions.RemoteSystemError, - '-32300': exceptions.TransportError, - } - _ex = error_mapping.get(ex.faultCode, exceptions.SoftLayerAPIError) - raise _ex(ex.faultCode, ex.faultString) from ex - except requests.HTTPError as ex: - raise exceptions.TransportError(ex.response.status_code, str(ex)) - except requests.RequestException as ex: - raise exceptions.TransportError(0, str(ex)) - - def print_reproduceable(self, request): - """Prints out the minimal python code to reproduce a specific request - - The will also automatically replace the API key so its not accidently exposed. - - :param request request: Request object - """ - output = Template('''============= testing.py ============= -import requests -from requests.auth import HTTPBasicAuth -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry -from xml.etree import ElementTree -client = requests.Session() -client.headers.update({'Content-Type': 'application/json', 'User-Agent': 'softlayer-python/testing',}) -retry = Retry(connect=3, backoff_factor=3) -adapter = HTTPAdapter(max_retries=retry) -client.mount('https://', adapter) -# This is only needed if you are using an cloud.ibm.com api key -#auth=HTTPBasicAuth('apikey', YOUR_CLOUD_API_KEY) -auth=None -url = '$url' -payload = $payload -transport_headers = $transport_headers -timeout = $timeout -verify = $verify -cert = $cert -proxy = $proxy -response = client.request('POST', url, data=payload, headers=transport_headers, timeout=timeout, - verify=verify, cert=cert, proxies=proxy, auth=auth) -xml = ElementTree.fromstring(response.content) -ElementTree.dump(xml) -==========================''') - - safe_payload = re.sub(r'[a-z0-9]{64}', r'API_KEY_GOES_HERE', request.payload) - safe_payload = re.sub(r'(\s+)', r' ', safe_payload) - safe_payload = safe_payload.encode() - substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers, - timeout=self.timeout, verify=request.verify, cert=request.cert, - proxy=_proxies_dict(self.proxy)) - return output.substitute(substitutions) - - -class RestTransport(object): - """REST transport. - - REST calls should mostly work, but is not fully tested. - XML-RPC should be used when in doubt - """ - - def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): - - self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT_REST).rstrip('/') - self.timeout = timeout or None - self.proxy = proxy - self.user_agent = user_agent or consts.USER_AGENT - self.verify = verify - self._client = None - - @property - def client(self): - """Returns client session object""" - - if self._client is None: - self._client = get_session(self.user_agent) - return self._client - - def __call__(self, request): - """Makes a SoftLayer API call against the REST endpoint. - - REST calls should mostly work, but is not fully tested. - XML-RPC should be used when in doubt - - :param request request: Request object - """ - params = request.headers.copy() - if request.mask: - request.mask = _format_object_mask(request.mask) - params['objectMask'] = request.mask - - if request.limit or request.offset: - limit = request.limit or 0 - offset = request.offset or 0 - params['resultLimit'] = "%d,%d" % (offset, limit) - - if request.filter: - params['objectFilter'] = json.dumps(request.filter) - - request.params = params - - auth = None - if request.transport_user: - auth = requests.auth.HTTPBasicAuth( - request.transport_user, - request.transport_password, - ) - - method = REST_SPECIAL_METHODS.get(request.method) - - if method is None: - method = 'GET' - - body = {} - if request.args: - # NOTE(kmcdonald): force POST when there are arguments because - # the request body is ignored otherwise. - method = 'POST' - body['parameters'] = request.args - - if body: - request.payload = json.dumps(body, cls=ComplexEncoder) - - url_parts = [self.endpoint_url, request.service] - if request.identifier is not None: - url_parts.append(str(request.identifier)) - - if request.method is not None: - url_parts.append(request.method) - - request.url = '%s.%s' % ('/'.join(url_parts), 'json') - - # Prefer the request setting, if it's not None - - if request.verify is None: - request.verify = self.verify - - try: - resp = self.client.request(method, request.url, - auth=auth, - headers=request.transport_headers, - params=request.params, - data=request.payload, - timeout=self.timeout, - verify=request.verify, - cert=request.cert, - proxies=_proxies_dict(self.proxy)) - - request.url = resp.url - - resp.raise_for_status() - - if resp.text != "": - try: - result = json.loads(resp.text) - except ValueError as json_ex: - LOGGER.warning(json_ex) - raise exceptions.SoftLayerAPIError(resp.status_code, str(resp.text)) - else: - raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") - - request.result = result - - if isinstance(result, list): - return SoftLayerListResult( - result, int(resp.headers.get('softlayer-total-items', 0))) - else: - return result - except requests.HTTPError as ex: - try: - message = json.loads(ex.response.text)['error'] - request.url = ex.response.url - except ValueError as json_ex: - if ex.response.text == "": - raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") - LOGGER.warning(json_ex) - raise exceptions.SoftLayerAPIError(resp.status_code, ex.response.text) - - raise exceptions.SoftLayerAPIError(ex.response.status_code, message) - except requests.RequestException as ex: - raise exceptions.TransportError(0, str(ex)) - - def print_reproduceable(self, request): - """Prints out the minimal python code to reproduce a specific request - - The will also automatically replace the API key so its not accidently exposed. - - :param request request: Request object - """ - command = "curl -u $SL_USER:$SL_APIKEY -X {method} -H {headers} {data} '{uri}'" - - method = REST_SPECIAL_METHODS.get(request.method) - - if method is None: - method = 'GET' - if request.args: - method = 'POST' - - data = '' - if request.payload is not None: - data = "-d '{}'".format(request.payload) - - headers = ['"{0}: {1}"'.format(k, v) for k, v in request.transport_headers.items()] - headers = " -H ".join(headers) - return command.format(method=method, headers=headers, data=data, uri=request.url) - - -class DebugTransport(object): - """Transport that records API call timings.""" - - def __init__(self, transport): - self.transport = transport - - #: List All API calls made during a session - self.requests = [] - - def __call__(self, call): - call.start_time = time.time() - - self.pre_transport_log(call) - try: - call.result = self.transport(call) - except (exceptions.SoftLayerAPIError, exceptions.TransportError) as ex: - call.exception = ex - - self.post_transport_log(call) - - call.end_time = time.time() - self.requests.append(call) - - if call.exception is not None: - LOGGER.debug(self.print_reproduceable(call)) - raise call.exception - - return call.result - - def pre_transport_log(self, call): - """Prints a warning before calling the API """ - output = "Calling: {})".format(call) - LOGGER.warning(output) - - def post_transport_log(self, call): - """Prints the result "Returned Data: \n%s" % (call.result)of an API call""" - output = "Returned Data: \n{}".format(call.result) - LOGGER.debug(output) - - def get_last_calls(self): - """Returns all API calls for a session""" - return self.requests - - def print_reproduceable(self, call): - """Prints a reproduceable debugging output""" - return self.transport.print_reproduceable(call) - - -class TimingTransport(object): - """Transport that records API call timings.""" - - def __init__(self, transport): - self.transport = transport - self.last_calls = [] - - def __call__(self, call): - """See Client.call for documentation.""" - start_time = time.time() - - result = self.transport(call) - - end_time = time.time() - self.last_calls.append((call, start_time, end_time - start_time)) - return result - - def get_last_calls(self): - """Retrieves the last_calls property. - - This property will contain a list of tuples in the form - (Request, initiated_utc_timestamp, execution_time) - """ - last_calls = self.last_calls - self.last_calls = [] - return last_calls - - def print_reproduceable(self, call): - """Not Implemented""" - return call.service - - -class FixtureTransport(object): - """Implements a transport which returns fixtures.""" - - def __call__(self, call): - """Load fixture from the default fixture path.""" - try: - module_path = 'SoftLayer.fixtures.%s' % call.service - module = importlib.import_module(module_path) - except ImportError as ex: - message = '{} fixture is not implemented'.format(call.service) - raise NotImplementedError(message) from ex - try: - return getattr(module, call.method) - except AttributeError as ex: - message = '{}::{} fixture is not implemented'.format(call.service, call.method) - raise NotImplementedError(message) from ex - - def print_reproduceable(self, call): - """Not Implemented""" - return call.service - - -def _proxies_dict(proxy): - """Makes a proxy dict appropriate to pass to requests.""" - if not proxy: - return None - return {'http': proxy, 'https': proxy} - - -def _format_object_mask(objectmask): - """Format the new style object mask. - - This wraps the user mask with mask[USER_MASK] if it does not already - have one. This makes it slightly easier for users. - - :param objectmask: a string-based object mask - - """ - objectmask = objectmask.strip() - - if (not objectmask.startswith('mask') and - not objectmask.startswith('[') and - not objectmask.startswith('filteredMask')): - objectmask = "mask[%s]" % objectmask - return objectmask - - -class ComplexEncoder(json.JSONEncoder): - """ComplexEncoder helps jsonencoder deal with byte strings""" - - def default(self, o): - """Encodes o as JSON""" - - # Base64 encode bytes type objects. - if isinstance(o, bytes): - base64_bytes = base64.b64encode(o) - return base64_bytes.decode("utf-8") - # Let the base class default method raise the TypeError - return json.JSONEncoder.default(self, o) diff --git a/SoftLayer/transports/__init__.py b/SoftLayer/transports/__init__.py new file mode 100644 index 000000000..bbecea227 --- /dev/null +++ b/SoftLayer/transports/__init__.py @@ -0,0 +1,45 @@ +""" + SoftLayer.transports + ~~~~~~~~~~~~~~~~~~~~ + XML-RPC transport layer that uses the requests library. + + :license: MIT, see LICENSE for more details. +""" + + +import requests + + +# Required imports to not break existing code. +from .rest import RestTransport +from .xmlrpc import XmlRpcTransport +from .fixture import FixtureTransport +from .timing import TimingTransport +from .debug import DebugTransport + +from .transport import Request +from .transport import SoftLayerListResult as SoftLayerListResult + + +# transports.Request does have a lot of instance attributes. :( +# pylint: disable=too-many-instance-attributes, no-self-use + +__all__ = [ + 'Request', + 'XmlRpcTransport', + 'RestTransport', + 'TimingTransport', + 'DebugTransport', + 'FixtureTransport', + 'SoftLayerListResult' +] + + + + + + + + + + diff --git a/SoftLayer/transports/debug.py b/SoftLayer/transports/debug.py new file mode 100644 index 000000000..31b93b847 --- /dev/null +++ b/SoftLayer/transports/debug.py @@ -0,0 +1,61 @@ +""" + SoftLayer.transports.debug + ~~~~~~~~~~~~~~~~~~~~ + Debugging transport. Will print out verbose logging information. + + :license: MIT, see LICENSE for more details. +""" + +import logging +import time + +from SoftLayer import exceptions + + +class DebugTransport(object): + """Transport that records API call timings.""" + + def __init__(self, transport): + self.transport = transport + + #: List All API calls made during a session + self.requests = [] + self.logger = logging.getLogger(__name__) + + def __call__(self, call): + call.start_time = time.time() + + self.pre_transport_log(call) + try: + call.result = self.transport(call) + except (exceptions.SoftLayerAPIError, exceptions.TransportError) as ex: + call.exception = ex + + self.post_transport_log(call) + + call.end_time = time.time() + self.requests.append(call) + + if call.exception is not None: + self.logger.debug(self.print_reproduceable(call)) + raise call.exception + + return call.result + + def pre_transport_log(self, call): + """Prints a warning before calling the API """ + output = "Calling: {})".format(call) + self.logger.warning(output) + + def post_transport_log(self, call): + """Prints the result "Returned Data: \n%s" % (call.result)of an API call""" + output = "Returned Data: \n{}".format(call.result) + self.logger.debug(output) + + def get_last_calls(self): + """Returns all API calls for a session""" + return self.requests + + def print_reproduceable(self, call): + """Prints a reproduceable debugging output""" + return self.transport.print_reproduceable(call) \ No newline at end of file diff --git a/SoftLayer/transports/fixture.py b/SoftLayer/transports/fixture.py new file mode 100644 index 000000000..3eece28fc --- /dev/null +++ b/SoftLayer/transports/fixture.py @@ -0,0 +1,30 @@ +""" + SoftLayer.transports.fixture + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Fixture transport, used for unit tests + + :license: MIT, see LICENSE for more details. +""" + +import importlib + +class FixtureTransport(object): + """Implements a transport which returns fixtures.""" + + def __call__(self, call): + """Load fixture from the default fixture path.""" + try: + module_path = 'SoftLayer.fixtures.%s' % call.service + module = importlib.import_module(module_path) + except ImportError as ex: + message = '{} fixture is not implemented'.format(call.service) + raise NotImplementedError(message) from ex + try: + return getattr(module, call.method) + except AttributeError as ex: + message = '{}::{} fixture is not implemented'.format(call.service, call.method) + raise NotImplementedError(message) from ex + + def print_reproduceable(self, call): + """Not Implemented""" + return call.service \ No newline at end of file diff --git a/SoftLayer/transports/rest.py b/SoftLayer/transports/rest.py new file mode 100644 index 000000000..e80d5bb35 --- /dev/null +++ b/SoftLayer/transports/rest.py @@ -0,0 +1,182 @@ +""" + SoftLayer.transports.rest + ~~~~~~~~~~~~~~~~~~~~ + REST Style transport library + + :license: MIT, see LICENSE for more details. +""" + +import json +import logging +import requests + +from SoftLayer import consts +from SoftLayer import exceptions + +from .transport import _format_object_mask +from .transport import _proxies_dict +from .transport import ComplexEncoder +from .transport import get_session +from .transport import SoftLayerListResult + +REST_SPECIAL_METHODS = { + # 'deleteObject': 'DELETE', + 'createObject': 'POST', + 'createObjects': 'POST', + 'editObject': 'PUT', + 'editObjects': 'PUT', +} + + +class RestTransport(object): + """REST transport. + + REST calls should mostly work, but is not fully tested. + XML-RPC should be used when in doubt + """ + + def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): + + self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT_REST).rstrip('/') + self.timeout = timeout or None + self.proxy = proxy + self.user_agent = user_agent or consts.USER_AGENT + self.verify = verify + self._client = None + self.logger = logging.getLogger(__name__) + + @property + def client(self): + """Returns client session object""" + + if self._client is None: + self._client = get_session(self.user_agent) + return self._client + + def __call__(self, request): + """Makes a SoftLayer API call against the REST endpoint. + + REST calls should mostly work, but is not fully tested. + XML-RPC should be used when in doubt + + :param request request: Request object + """ + params = request.headers.copy() + if request.mask: + request.mask = _format_object_mask(request.mask) + params['objectMask'] = request.mask + + if request.limit or request.offset: + limit = request.limit or 0 + offset = request.offset or 0 + params['resultLimit'] = "%d,%d" % (offset, limit) + + if request.filter: + params['objectFilter'] = json.dumps(request.filter) + + request.params = params + + auth = None + if request.transport_user: + auth = requests.auth.HTTPBasicAuth( + request.transport_user, + request.transport_password, + ) + + method = REST_SPECIAL_METHODS.get(request.method) + + if method is None: + method = 'GET' + + body = {} + if request.args: + # NOTE(kmcdonald): force POST when there are arguments because + # the request body is ignored otherwise. + method = 'POST' + body['parameters'] = request.args + + if body: + request.payload = json.dumps(body, cls=ComplexEncoder) + + url_parts = [self.endpoint_url, request.service] + if request.identifier is not None: + url_parts.append(str(request.identifier)) + + if request.method is not None: + url_parts.append(request.method) + + request.url = '%s.%s' % ('/'.join(url_parts), 'json') + + # Prefer the request setting, if it's not None + + if request.verify is None: + request.verify = self.verify + + try: + resp = self.client.request(method, request.url, + auth=auth, + headers=request.transport_headers, + params=request.params, + data=request.payload, + timeout=self.timeout, + verify=request.verify, + cert=request.cert, + proxies=_proxies_dict(self.proxy)) + + request.url = resp.url + + resp.raise_for_status() + + if resp.text != "": + try: + result = json.loads(resp.text) + except ValueError as json_ex: + self.logger.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, str(resp.text)) + else: + raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") + + request.result = result + + if isinstance(result, list): + return SoftLayerListResult( + result, int(resp.headers.get('softlayer-total-items', 0))) + else: + return result + except requests.HTTPError as ex: + try: + message = json.loads(ex.response.text)['error'] + request.url = ex.response.url + except ValueError as json_ex: + if ex.response.text == "": + raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") + self.logger.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, ex.response.text) + + raise exceptions.SoftLayerAPIError(ex.response.status_code, message) + except requests.RequestException as ex: + raise exceptions.TransportError(0, str(ex)) + + def print_reproduceable(self, request): + """Prints out the minimal python code to reproduce a specific request + + The will also automatically replace the API key so its not accidently exposed. + + :param request request: Request object + """ + command = "curl -u $SL_USER:$SL_APIKEY -X {method} -H {headers} {data} '{uri}'" + + method = REST_SPECIAL_METHODS.get(request.method) + + if method is None: + method = 'GET' + if request.args: + method = 'POST' + + data = '' + if request.payload is not None: + data = "-d '{}'".format(request.payload) + + headers = ['"{0}: {1}"'.format(k, v) for k, v in request.transport_headers.items()] + headers = " -H ".join(headers) + return command.format(method=method, headers=headers, data=data, uri=request.url) \ No newline at end of file diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py new file mode 100644 index 000000000..b89fb090c --- /dev/null +++ b/SoftLayer/transports/soap.py @@ -0,0 +1,83 @@ +""" + SoftLayer.transports.soap + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SOAP Style transport library + + :license: MIT, see LICENSE for more details. +""" +import logging +import re +from string import Template +from zeep import Client, Settings, Transport, xsd +from zeep.helpers import serialize_object +from zeep.cache import SqliteCache + +import requests + +from SoftLayer import consts +from SoftLayer import exceptions + +from .transport import _format_object_mask +from .transport import _proxies_dict +from .transport import ComplexEncoder +from .transport import get_session +from .transport import SoftLayerListResult + +from pprint import pprint as pp +class SoapTransport(object): + """XML-RPC transport.""" + + def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): + + # Throw an error for py < 3.6 because of f-strings + logging.getLogger('zeep').setLevel(logging.ERROR) + self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') + self.timeout = timeout or None + self.proxy = proxy + self.user_agent = user_agent or consts.USER_AGENT + self.verify = verify + self._client = None + + def __call__(self, request): + """Makes a SoftLayer API call against the SOAP endpoint. + + :param request request: Request object + """ + print("Making a SOAP API CALL...") + # client = Client(f"{self.endpoint_url}/{request.service}?wsdl") + zeep_settings = Settings(strict=False, xml_huge_tree=True) + zeep_transport = Transport(cache=SqliteCache(timeout=86400)) + client = Client(f"{self.endpoint_url}/{request.service}?wsdl", + settings=zeep_settings, transport=zeep_transport) + # authXsd = xsd.Element( + # f"{self.endpoint_url}/authenticate", + # xsd.ComplexType([ + # xsd.Element(f"{self.endpoint_url}/username", xsd.String()), + # xsd.Element(f"{self.endpoint_url}/apiKey", xsd.String()) + # ]) + # ) + xsdUserAuth = xsd.Element( + '{http://api.softlayer.com/soap/v3.1/}authenticate', + xsd.ComplexType([ + xsd.Element('{http://api.softlayer.com/soap/v3.1/}username', xsd.String()), + xsd.Element('{http://api.softlayer.com/soap/v3.1/}apiKey', xsd.String()) + ]) + ) + # transport = Transport(session=get_session()) + + authHeader = xsdUserAuth(username=request.transport_user, apiKey=request.transport_password) + method = getattr(client.service, request.method) + result = client.service.getObject(_soapheaders=[authHeader]) + return serialize_object(result) + # result = transport.post(f"{self.endpoint_url}/{request.service}") + + + def print_reproduceable(self, request): + """Prints out the minimal python code to reproduce a specific request + + The will also automatically replace the API key so its not accidently exposed. + + :param request request: Request object + """ + + return "THE SOAP API CALL..." diff --git a/SoftLayer/transports/timing.py b/SoftLayer/transports/timing.py new file mode 100644 index 000000000..5b9345276 --- /dev/null +++ b/SoftLayer/transports/timing.py @@ -0,0 +1,40 @@ +""" + SoftLayer.transports.timing + ~~~~~~~~~~~~~~~~~~~~ + Timing transport, used when you want to know how long an API call took. + + :license: MIT, see LICENSE for more details. +""" +import time + + +class TimingTransport(object): + """Transport that records API call timings.""" + + def __init__(self, transport): + self.transport = transport + self.last_calls = [] + + def __call__(self, call): + """See Client.call for documentation.""" + start_time = time.time() + + result = self.transport(call) + + end_time = time.time() + self.last_calls.append((call, start_time, end_time - start_time)) + return result + + def get_last_calls(self): + """Retrieves the last_calls property. + + This property will contain a list of tuples in the form + (Request, initiated_utc_timestamp, execution_time) + """ + last_calls = self.last_calls + self.last_calls = [] + return last_calls + + def print_reproduceable(self, call): + """Not Implemented""" + return call.service diff --git a/SoftLayer/transports/transport.py b/SoftLayer/transports/transport.py new file mode 100644 index 000000000..40a8e872b --- /dev/null +++ b/SoftLayer/transports/transport.py @@ -0,0 +1,154 @@ +""" + SoftLayer.transports.transport + ~~~~~~~~~~~~~~~~~~~~ + Common functions for transporting API requests + + :license: MIT, see LICENSE for more details. +""" +import base64 +import json +import requests +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + + +from SoftLayer import utils + + +def get_session(user_agent): + """Sets up urllib sessions""" + + client = requests.Session() + client.headers.update({ + 'Content-Type': 'application/json', + 'User-Agent': user_agent, + }) + retry = Retry(connect=3, backoff_factor=3) + adapter = HTTPAdapter(max_retries=retry) + client.mount('https://', adapter) + return client + + +# transports.Request does have a lot of instance attributes. :( +# pylint: disable=too-many-instance-attributes, no-self-use +class Request(object): + """Transport request object.""" + + def __init__(self): + #: API service name. E.G. SoftLayer_Account + self.service = None + + #: API method name. E.G. getObject + self.method = None + + #: API Parameters. + self.args = tuple() + + #: API headers, used for authentication, masks, limits, offsets, etc. + self.headers = {} + + #: Transport user. + self.transport_user = None + + #: Transport password. + self.transport_password = None + + #: Transport headers. + self.transport_headers = {} + + #: Boolean specifying if the server certificate should be verified. + self.verify = None + + #: Client certificate file path. + self.cert = None + + #: InitParameter/identifier of an object. + self.identifier = None + + #: SoftLayer mask (dict or string). + self.mask = None + + #: SoftLayer Filter (dict). + self.filter = None + + #: Integer result limit. + self.limit = None + + #: Integer result offset. + self.offset = None + + #: Integer call start time + self.start_time = None + + #: Integer call end time + self.end_time = None + + #: String full url + self.url = None + + #: String result of api call + self.result = None + + #: String payload to send in + self.payload = None + + #: Exception any exceptions that got caught + self.exception = None + + def __repr__(self): + """Prints out what this call is all about""" + pretty_mask = utils.clean_string(self.mask) + pretty_filter = self.filter + param_string = "id={id}, mask='{mask}', filter='{filter}', args={args}, limit={limit}, offset={offset}".format( + id=self.identifier, mask=pretty_mask, filter=pretty_filter, + args=self.args, limit=self.limit, offset=self.offset) + return "{service}::{method}({params})".format( + service=self.service, method=self.method, params=param_string) + +class SoftLayerListResult(list): + """A SoftLayer API list result.""" + + def __init__(self, items=None, total_count=0): + + #: total count of items that exist on the server. This is useful when + #: paginating through a large list of objects. + self.total_count = total_count + super().__init__(items) + +def _proxies_dict(proxy): + """Makes a proxy dict appropriate to pass to requests.""" + if not proxy: + return None + return {'http': proxy, 'https': proxy} + + +def _format_object_mask(objectmask): + """Format the new style object mask. + + This wraps the user mask with mask[USER_MASK] if it does not already + have one. This makes it slightly easier for users. + + :param objectmask: a string-based object mask + + """ + objectmask = objectmask.strip() + + if (not objectmask.startswith('mask') and + not objectmask.startswith('[') and + not objectmask.startswith('filteredMask')): + objectmask = "mask[%s]" % objectmask + return objectmask + + +class ComplexEncoder(json.JSONEncoder): + """ComplexEncoder helps jsonencoder deal with byte strings""" + + def default(self, o): + """Encodes o as JSON""" + + # Base64 encode bytes type objects. + if isinstance(o, bytes): + base64_bytes = base64.b64encode(o) + return base64_bytes.decode("utf-8") + # Let the base class default method raise the TypeError + return json.JSONEncoder.default(self, o) diff --git a/SoftLayer/transports/xmlrpc.py b/SoftLayer/transports/xmlrpc.py new file mode 100644 index 000000000..31afaf868 --- /dev/null +++ b/SoftLayer/transports/xmlrpc.py @@ -0,0 +1,171 @@ +""" + SoftLayer.transports.xmlrpc + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + XML-RPC Style transport library + + :license: MIT, see LICENSE for more details. +""" +import logging +import re +from string import Template +import xmlrpc.client + +import requests + +from SoftLayer import consts +from SoftLayer import exceptions + +from .transport import _format_object_mask +from .transport import _proxies_dict +from .transport import ComplexEncoder +from .transport import get_session +from .transport import SoftLayerListResult + +class XmlRpcTransport(object): + """XML-RPC transport.""" + + def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): + + self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') + self.timeout = timeout or None + self.proxy = proxy + self.user_agent = user_agent or consts.USER_AGENT + self.verify = verify + self._client = None + + @property + def client(self): + """Returns client session object""" + + if self._client is None: + self._client = get_session(self.user_agent) + return self._client + + def __call__(self, request): + """Makes a SoftLayer API call against the XML-RPC endpoint. + + :param request request: Request object + """ + largs = list(request.args) + headers = request.headers + + auth = None + if request.transport_user: + auth = requests.auth.HTTPBasicAuth(request.transport_user, request.transport_password) + + if request.identifier is not None: + header_name = request.service + 'InitParameters' + headers[header_name] = {'id': request.identifier} + + if request.mask is not None: + if isinstance(request.mask, dict): + mheader = '%sObjectMask' % request.service + else: + mheader = 'SoftLayer_ObjectMask' + request.mask = _format_object_mask(request.mask) + headers.update({mheader: {'mask': request.mask}}) + + if request.filter is not None: + headers['%sObjectFilter' % request.service] = request.filter + + if request.limit: + headers['resultLimit'] = { + 'limit': request.limit, + 'offset': request.offset or 0, + } + + largs.insert(0, {'headers': headers}) + request.transport_headers.setdefault('Content-Type', 'application/xml') + request.transport_headers.setdefault('User-Agent', self.user_agent) + + request.url = '/'.join([self.endpoint_url, request.service]) + request.payload = xmlrpc.client.dumps(tuple(largs), + methodname=request.method, + allow_none=True, + encoding="iso-8859-1") + + # Prefer the request setting, if it's not None + verify = request.verify + if verify is None: + request.verify = self.verify + + try: + resp = self.client.request('POST', request.url, + data=request.payload.encode(), + auth=auth, + headers=request.transport_headers, + timeout=self.timeout, + verify=request.verify, + cert=request.cert, + proxies=_proxies_dict(self.proxy)) + + resp.raise_for_status() + result = xmlrpc.client.loads(resp.content)[0][0] + if isinstance(result, list): + return SoftLayerListResult( + result, int(resp.headers.get('softlayer-total-items', 0))) + else: + return result + except xmlrpc.client.Fault as ex: + # These exceptions are formed from the XML-RPC spec + # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php + error_mapping = { + '-32700': exceptions.NotWellFormed, + '-32701': exceptions.UnsupportedEncoding, + '-32702': exceptions.InvalidCharacter, + '-32600': exceptions.SpecViolation, + '-32601': exceptions.MethodNotFound, + '-32602': exceptions.InvalidMethodParameters, + '-32603': exceptions.InternalError, + '-32500': exceptions.ApplicationError, + '-32400': exceptions.RemoteSystemError, + '-32300': exceptions.TransportError, + } + _ex = error_mapping.get(ex.faultCode, exceptions.SoftLayerAPIError) + raise _ex(ex.faultCode, ex.faultString) from ex + except requests.HTTPError as ex: + raise exceptions.TransportError(ex.response.status_code, str(ex)) + except requests.RequestException as ex: + raise exceptions.TransportError(0, str(ex)) + + def print_reproduceable(self, request): + """Prints out the minimal python code to reproduce a specific request + + The will also automatically replace the API key so its not accidently exposed. + + :param request request: Request object + """ + output = Template('''============= testing.py ============= +import requests +from requests.auth import HTTPBasicAuth +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry +from xml.etree import ElementTree +client = requests.Session() +client.headers.update({'Content-Type': 'application/json', 'User-Agent': 'softlayer-python/testing',}) +retry = Retry(connect=3, backoff_factor=3) +adapter = HTTPAdapter(max_retries=retry) +client.mount('https://', adapter) +# This is only needed if you are using an cloud.ibm.com api key +#auth=HTTPBasicAuth('apikey', YOUR_CLOUD_API_KEY) +auth=None +url = '$url' +payload = $payload +transport_headers = $transport_headers +timeout = $timeout +verify = $verify +cert = $cert +proxy = $proxy +response = client.request('POST', url, data=payload, headers=transport_headers, timeout=timeout, + verify=verify, cert=cert, proxies=proxy, auth=auth) +xml = ElementTree.fromstring(response.content) +ElementTree.dump(xml) +==========================''') + + safe_payload = re.sub(r'[a-z0-9]{64}', r'API_KEY_GOES_HERE', request.payload) + safe_payload = re.sub(r'(\s+)', r' ', safe_payload) + safe_payload = safe_payload.encode() + substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers, + timeout=self.timeout, verify=request.verify, cert=request.cert, + proxy=_proxies_dict(self.proxy)) + return output.substitute(substitutions) diff --git a/setup.py b/setup.py index 09921feaf..97514d838 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,8 @@ 'requests >= 2.20.0', 'prompt_toolkit >= 2', 'pygments >= 2.0.0', - 'urllib3 >= 1.24' + 'urllib3 >= 1.24', + 'zeep' ], keywords=['softlayer', 'cloud', 'slcli'], classifiers=[ diff --git a/tests/transport_tests.py b/tests/transport_tests.py index d09a65c51..5ae0a448c 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -18,797 +18,6 @@ from SoftLayer import transports -def get_xmlrpc_response(): - response = requests.Response() - list_body = b''' - - - - - - - - -''' - response.raw = io.BytesIO(list_body) - response.headers['SoftLayer-Total-Items'] = 10 - response.status_code = 200 - return response - - -class TestXmlRpcAPICall(testing.TestCase): - - def set_up(self): - self.transport = transports.XmlRpcTransport( - endpoint_url='http://something9999999999999999999999.com', - ) - self.response = get_xmlrpc_response() - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_call(self, request): - request.return_value = self.response - - data = ''' - -getObject - - - - -headers - - - - - - - -'''.encode() - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - resp = self.transport(req) - - request.assert_called_with('POST', - 'http://something9999999999999999999999.com/SoftLayer_Service', - headers={'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT}, - proxies=None, - data=data, - timeout=None, - cert=None, - verify=True, - auth=None) - self.assertEqual(resp, []) - self.assertIsInstance(resp, transports.SoftLayerListResult) - self.assertEqual(resp.total_count, 10) - - def test_proxy_without_protocol(self): - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - req.proxy = 'localhost:3128' - - try: - self.assertRaises(SoftLayer.TransportError, self.transport, req) - except AssertionError: - warnings.warn("Incorrect Exception raised. Expected a " - "SoftLayer.TransportError error") - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_valid_proxy(self, request): - request.return_value = self.response - self.transport.proxy = 'http://localhost:3128' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.transport(req) - - request.assert_called_with( - 'POST', - mock.ANY, - proxies={'https': 'http://localhost:3128', - 'http': 'http://localhost:3128'}, - data=mock.ANY, - headers=mock.ANY, - timeout=None, - cert=None, - verify=True, - auth=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_identifier(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.identifier = 1234 - self.transport(req) - - _, kwargs = request.call_args - self.assertIn( - """ -id -1234 -""".encode(), kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_filter(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} - self.transport(req) - - args, kwargs = request.call_args - self.assertIn( - """ -operation -^= prefix -""".encode(), kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_limit_offset(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.limit = 10 - self.transport(req) - - args, kwargs = request.call_args - self.assertIn(""" -resultLimit - -""".encode(), kwargs['data']) - self.assertIn("""limit -10 -""".encode(), kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_old_mask(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = {"something": "nested"} - self.transport(req) - - args, kwargs = request.call_args - self.assertIn(""" -mask - - -something -nested - - -""".encode(), kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_mask_call_no_mask_prefix(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = "something.nested" - self.transport(req) - - args, kwargs = request.call_args - self.assertIn( - "mask[something.nested]".encode(), - kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_mask_call_v2(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = "mask[something[nested]]" - self.transport(req) - - args, kwargs = request.call_args - self.assertIn( - "mask[something[nested]]".encode(), - kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_mask_call_filteredMask(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = "filteredMask[something[nested]]" - self.transport(req) - - args, kwargs = request.call_args - self.assertIn( - "filteredMask[something[nested]]".encode(), - kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_mask_call_v2_dot(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = "mask.something.nested" - self.transport(req) - - args, kwargs = request.call_args - self.assertIn("mask.something.nested".encode(), - kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_request_exception(self, request): - # Test Text Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.content = 'Error Code' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - - self.assertRaises(SoftLayer.TransportError, self.transport, req) - - def test_print_reproduceable(self): - req = transports.Request() - req.url = "https://test.com" - req.payload = "testing" - req.transport_headers = {"test-headers": 'aaaa'} - output_text = self.transport.print_reproduceable(req) - self.assertIn("https://test.com", output_text) - - @mock.patch('SoftLayer.transports.requests.Session.request') - @mock.patch('requests.auth.HTTPBasicAuth') - def test_ibm_id_call(self, auth, request): - request.return_value = self.response - - data = ''' - -getObject - - - - -headers - - - - - - - -'''.encode() - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.transport_user = 'apikey' - req.transport_password = '1234567890qweasdzxc' - resp = self.transport(req) - - auth.assert_called_with('apikey', '1234567890qweasdzxc') - request.assert_called_with('POST', - 'http://something9999999999999999999999.com/SoftLayer_Service', - headers={'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT}, - proxies=None, - data=data, - timeout=None, - cert=None, - verify=True, - auth=mock.ANY) - self.assertEqual(resp, []) - self.assertIsInstance(resp, transports.SoftLayerListResult) - self.assertEqual(resp.total_count, 10) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_call_large_number_response(self, request): - response = requests.Response() - body = b''' - - - - - - - - - bytesUsed - 2666148982056 - - - - - - - - - ''' - response.raw = io.BytesIO(body) - response.headers['SoftLayer-Total-Items'] = 1 - response.status_code = 200 - request.return_value = response - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - resp = self.transport(req) - self.assertEqual(resp[0]['bytesUsed'], 2666148982056) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_nonascii_characters(self, request): - request.return_value = self.response - hostname = 'testé' - data = ''' - -getObject - - - - -headers - - - - - - - - -hostname -testé - - - - - -'''.encode() - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.args = ({'hostname': hostname},) - req.transport_user = "testUser" - req.transport_password = "testApiKey" - resp = self.transport(req) - - request.assert_called_with('POST', - 'http://something9999999999999999999999.com/SoftLayer_Service', - headers={'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT}, - proxies=None, - data=data, - timeout=None, - cert=None, - verify=True, - auth=mock.ANY) - self.assertEqual(resp, []) - self.assertIsInstance(resp, transports.SoftLayerListResult) - self.assertEqual(resp.total_count, 10) - - -@mock.patch('SoftLayer.transports.requests.Session.request') -@pytest.mark.parametrize( - "transport_verify,request_verify,expected", - [ - (True, True, True), - (True, False, False), - (True, None, True), - - (False, True, True), - (False, False, False), - (False, None, False), - - (None, True, True), - (None, False, False), - (None, None, True), - ] -) -def test_verify(request, - transport_verify, - request_verify, - expected): - request.return_value = get_xmlrpc_response() - - transport = transports.XmlRpcTransport( - endpoint_url='http://something9999999999999999999999.com', - ) - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - - if request_verify is not None: - req.verify = request_verify - - if transport_verify is not None: - transport.verify = transport_verify - - transport(req) - - request.assert_called_with('POST', - 'http://something9999999999999999999999.com/SoftLayer_Service', - data=mock.ANY, - headers=mock.ANY, - cert=mock.ANY, - proxies=mock.ANY, - timeout=mock.ANY, - verify=expected, - auth=None) - - -class TestRestAPICall(testing.TestCase): - - def set_up(self): - self.transport = transports.RestTransport( - endpoint_url='http://something9999999999999999999999.com', - ) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_basic(self, request): - request().content = '[]' - request().text = '[]' - request().headers = requests.structures.CaseInsensitiveDict({ - 'SoftLayer-Total-Items': '10', - }) - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - resp = self.transport(req) - - self.assertEqual(resp, []) - self.assertIsInstance(resp, transports.SoftLayerListResult) - self.assertEqual(resp.total_count, 10) - request.assert_called_with( - 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', - headers=mock.ANY, - auth=None, - data=None, - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_http_and_json_error(self, request): - # Test JSON Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.text = ''' - "error": "description", - "code": "Error Code" - ''' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_http_and_empty_error(self, request): - # Test JSON Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.text = '' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_empty_error(self, request): - # Test empty response error. - request().text = '' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_json_error(self, request): - # Test non-json response error. - request().text = 'Not JSON' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - - def test_proxy_without_protocol(self): - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - req.proxy = 'localhost:3128' - try: - self.assertRaises(SoftLayer.TransportError, self.transport, req) - except AssertionError: - warnings.warn("AssertionError raised instead of a SoftLayer.TransportError error") - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_valid_proxy(self, request): - request().text = '{}' - self.transport.proxy = 'http://localhost:3128' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - - self.transport(req) - - request.assert_called_with( - 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', - proxies={'https': 'http://localhost:3128', - 'http': 'http://localhost:3128'}, - auth=None, - data=None, - params={}, - verify=True, - cert=None, - timeout=mock.ANY, - headers=mock.ANY) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_id(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.identifier = 2 - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', - headers=mock.ANY, - auth=None, - data=None, - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_args(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.args = ('test', 1) - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'POST', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - headers=mock.ANY, - auth=None, - data='{"parameters": ["test", 1]}', - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_args_bytes(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.args = ('test', b'asdf') - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'POST', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - headers=mock.ANY, - auth=None, - data='{"parameters": ["test", "YXNkZg=="]}', - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_filter(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - params={'objectFilter': - '{"TYPE": {"attribute": {"operation": "^= prefix"}}}'}, - headers=mock.ANY, - auth=None, - data=None, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_mask(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.mask = 'id,property' - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - params={'objectMask': 'mask[id,property]'}, - headers=mock.ANY, - auth=None, - data=None, - verify=True, - cert=None, - proxies=None, - timeout=None) - - # Now test with mask[] prefix - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.mask = 'mask[id,property]' - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - params={'objectMask': 'mask[id,property]'}, - headers=mock.ANY, - auth=None, - data=None, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_limit_offset(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.identifier = 2 - req.limit = 10 - req.offset = 5 - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', - headers=mock.ANY, - auth=None, - data=None, - params={'resultLimit': '5,10'}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_unknown_error(self, request): - e = requests.RequestException('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.content = 'Error Code' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - - self.assertRaises(SoftLayer.TransportError, self.transport, req) - - @mock.patch('SoftLayer.transports.requests.Session.request') - @mock.patch('requests.auth.HTTPBasicAuth') - def test_with_special_auth(self, auth, request): - request().text = '{}' - - user = 'asdf' - password = 'zxcv' - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.identifier = 2 - req.transport_user = user - req.transport_password = password - - resp = self.transport(req) - self.assertEqual(resp, {}) - auth.assert_called_with(user, password) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', - headers=mock.ANY, - auth=mock.ANY, - data=None, - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - def test_print_reproduceable(self): - req = transports.Request() - req.url = "https://test.com" - req.payload = "testing" - req.transport_headers = {"test-headers": 'aaaa'} - output_text = self.transport.print_reproduceable(req) - self.assertIn("https://test.com", output_text) - - def test_complex_encoder_bytes(self): - to_encode = { - 'test': ['array', 0, 1, False], - 'bytes': b'ASDASDASD' - } - result = json.dumps(to_encode, cls=transports.ComplexEncoder) - # result = '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}' - # encode doesn't always encode in the same order, so testing exact match SOMETIMES breaks. - self.assertIn("QVNEQVNEQVNE", result) - class TestFixtureTransport(testing.TestCase): diff --git a/tests/transports/__init__.py b/tests/transports/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/transports/debug_tests.py b/tests/transports/debug_tests.py new file mode 100644 index 000000000..2527cb302 --- /dev/null +++ b/tests/transports/debug_tests.py @@ -0,0 +1,84 @@ +""" + SoftLayer.tests.transports.debug + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer import transports + + +class TestDebugTransport(testing.TestCase): + + def set_up(self): + fixture_transport = transports.FixtureTransport() + self.transport = transports.DebugTransport(fixture_transport) + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + self.req = req + + def test_call(self): + + resp = self.transport(self.req) + self.assertEqual(resp['accountId'], 1234) + + def test_get_last_calls(self): + + resp = self.transport(self.req) + self.assertEqual(resp['accountId'], 1234) + calls = self.transport.get_last_calls() + self.assertEqual(calls[0].service, 'SoftLayer_Account') + + def test_print_reproduceable(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + output_text = self.transport.print_reproduceable(self.req) + self.assertEqual('SoftLayer_Account', output_text) + + def test_print_reproduceable_post(self): + req = transports.Request() + req.url = "https://test.com" + req.payload = "testing" + req.transport_headers = {"test-headers": 'aaaa'} + req.args = 'createObject' + + rest_transport = transports.RestTransport() + transport = transports.DebugTransport(rest_transport) + + output_text = transport.print_reproduceable(req) + + self.assertIn("https://test.com", output_text) + self.assertIn("-X POST", output_text) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_error(self, request): + # Test JSON Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.text = '''{ + "error": "description", + "code": "Error Code" + }''' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + rest_transport = transports.RestTransport() + transport = transports.DebugTransport(rest_transport) + self.assertRaises(SoftLayer.SoftLayerAPIError, transport, req) + calls = transport.get_last_calls() + self.assertEqual(404, calls[0].exception.faultCode) diff --git a/tests/transports/rest_tests.py b/tests/transports/rest_tests.py new file mode 100644 index 000000000..ec634ec6c --- /dev/null +++ b/tests/transports/rest_tests.py @@ -0,0 +1,365 @@ +""" + SoftLayer.tests.transports.rest + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer import transports + +class TestRestAPICall(testing.TestCase): + + def set_up(self): + self.transport = transports.RestTransport( + endpoint_url='http://something9999999999999999999999.com', + ) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_basic(self, request): + request().content = '[]' + request().text = '[]' + request().headers = requests.structures.CaseInsensitiveDict({ + 'SoftLayer-Total-Items': '10', + }) + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + resp = self.transport(req) + + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + request.assert_called_with( + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', + headers=mock.ANY, + auth=None, + data=None, + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_http_and_json_error(self, request): + # Test JSON Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.text = ''' + "error": "description", + "code": "Error Code" + ''' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_http_and_empty_error(self, request): + # Test JSON Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.text = '' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_empty_error(self, request): + # Test empty response error. + request().text = '' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_json_error(self, request): + # Test non-json response error. + request().text = 'Not JSON' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) + + def test_proxy_without_protocol(self): + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + req.proxy = 'localhost:3128' + try: + self.assertRaises(SoftLayer.TransportError, self.transport, req) + except AssertionError: + warnings.warn("AssertionError raised instead of a SoftLayer.TransportError error") + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_valid_proxy(self, request): + request().text = '{}' + self.transport.proxy = 'http://localhost:3128' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + + self.transport(req) + + request.assert_called_with( + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', + proxies={'https': 'http://localhost:3128', + 'http': 'http://localhost:3128'}, + auth=None, + data=None, + params={}, + verify=True, + cert=None, + timeout=mock.ANY, + headers=mock.ANY) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_id(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.identifier = 2 + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', + headers=mock.ANY, + auth=None, + data=None, + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_args(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ('test', 1) + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'POST', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + headers=mock.ANY, + auth=None, + data='{"parameters": ["test", 1]}', + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_args_bytes(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ('test', b'asdf') + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'POST', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + headers=mock.ANY, + auth=None, + data='{"parameters": ["test", "YXNkZg=="]}', + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_filter(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + params={'objectFilter': + '{"TYPE": {"attribute": {"operation": "^= prefix"}}}'}, + headers=mock.ANY, + auth=None, + data=None, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_mask(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.mask = 'id,property' + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + params={'objectMask': 'mask[id,property]'}, + headers=mock.ANY, + auth=None, + data=None, + verify=True, + cert=None, + proxies=None, + timeout=None) + + # Now test with mask[] prefix + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.mask = 'mask[id,property]' + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + params={'objectMask': 'mask[id,property]'}, + headers=mock.ANY, + auth=None, + data=None, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_limit_offset(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.identifier = 2 + req.limit = 10 + req.offset = 5 + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', + headers=mock.ANY, + auth=None, + data=None, + params={'resultLimit': '5,10'}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_unknown_error(self, request): + e = requests.RequestException('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.content = 'Error Code' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + self.assertRaises(SoftLayer.TransportError, self.transport, req) + + @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('requests.auth.HTTPBasicAuth') + def test_with_special_auth(self, auth, request): + request().text = '{}' + + user = 'asdf' + password = 'zxcv' + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.identifier = 2 + req.transport_user = user + req.transport_password = password + + resp = self.transport(req) + self.assertEqual(resp, {}) + auth.assert_called_with(user, password) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', + headers=mock.ANY, + auth=mock.ANY, + data=None, + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + def test_print_reproduceable(self): + req = transports.Request() + req.url = "https://test.com" + req.payload = "testing" + req.transport_headers = {"test-headers": 'aaaa'} + output_text = self.transport.print_reproduceable(req) + self.assertIn("https://test.com", output_text) + + def test_complex_encoder_bytes(self): + to_encode = { + 'test': ['array', 0, 1, False], + 'bytes': b'ASDASDASD' + } + result = json.dumps(to_encode, cls=transports.transport.ComplexEncoder) + # result = '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}' + # encode doesn't always encode in the same order, so testing exact match SOMETIMES breaks. + self.assertIn("QVNEQVNEQVNE", result) \ No newline at end of file diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py new file mode 100644 index 000000000..bfc0d4623 --- /dev/null +++ b/tests/transports/soap_tests.py @@ -0,0 +1,59 @@ +""" + SoftLayer.tests.transports.xmlrc + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import os +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer.transports.soap import SoapTransport +from SoftLayer.transports import Request + + +from pprint import pprint as pp +def get_soap_response(): + response = requests.Response() + list_body = b''' + + + + + + + + +''' + response.raw = io.BytesIO(list_body) + response.headers['SoftLayer-Total-Items'] = 10 + response.status_code = 200 + return response + + +class TestXmlRpcAPICall(testing.TestCase): + + def set_up(self): + self.transport = SoapTransport(endpoint_url='https://api.softlayer.com/soap/v3.1/') + self.response = get_soap_response() + self.user = os.getenv('SL_USER') + self.password = os.environ.get('SL_APIKEY') + + def test_call(self): + request = Request() + request.service = 'SoftLayer_Account' + request.method = 'getObject' + request.transport_user = self.user + request.transport_password = self.password + data = self.transport(request) + pp(data) + self.assertEqual(data.get('id'), 307608) + diff --git a/tests/transports/transport_tests.py b/tests/transports/transport_tests.py new file mode 100644 index 000000000..32b1eaad9 --- /dev/null +++ b/tests/transports/transport_tests.py @@ -0,0 +1,73 @@ +""" + SoftLayer.tests.transports.debug + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer import transports + + +class TestFixtureTransport(testing.TestCase): + + def set_up(self): + self.transport = transports.FixtureTransport() + + def test_basic(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp['accountId'], 1234) + + def test_no_module(self): + req = transports.Request() + req.service = 'Doesnt_Exist' + req.method = 'getObject' + self.assertRaises(NotImplementedError, self.transport, req) + + def test_no_method(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObjectzzzz' + self.assertRaises(NotImplementedError, self.transport, req) + + +class TestTimingTransport(testing.TestCase): + + def set_up(self): + fixture_transport = transports.FixtureTransport() + self.transport = transports.TimingTransport(fixture_transport) + + def test_call(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp['accountId'], 1234) + + def test_get_last_calls(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp['accountId'], 1234) + calls = self.transport.get_last_calls() + self.assertEqual(calls[0][0].service, 'SoftLayer_Account') + + def test_print_reproduceable(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + output_text = self.transport.print_reproduceable(req) + self.assertEqual('SoftLayer_Account', output_text) diff --git a/tests/transports/xmlrpc_tests.py b/tests/transports/xmlrpc_tests.py new file mode 100644 index 000000000..c59eded0c --- /dev/null +++ b/tests/transports/xmlrpc_tests.py @@ -0,0 +1,467 @@ +""" + SoftLayer.tests.transports.xmlrc + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer import transports + + +def get_xmlrpc_response(): + response = requests.Response() + list_body = b''' + + + + + + + + +''' + response.raw = io.BytesIO(list_body) + response.headers['SoftLayer-Total-Items'] = 10 + response.status_code = 200 + return response + + +class TestXmlRpcAPICall(testing.TestCase): + + def set_up(self): + self.transport = transports.XmlRpcTransport( + endpoint_url='http://something9999999999999999999999.com', + ) + self.response = get_xmlrpc_response() + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_call(self, request): + request.return_value = self.response + + data = ''' + +getObject + + + + +headers + + + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + resp = self.transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=None) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + + def test_proxy_without_protocol(self): + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + req.proxy = 'localhost:3128' + + try: + self.assertRaises(SoftLayer.TransportError, self.transport, req) + except AssertionError: + warnings.warn("Incorrect Exception raised. Expected a " + "SoftLayer.TransportError error") + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_valid_proxy(self, request): + request.return_value = self.response + self.transport.proxy = 'http://localhost:3128' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.transport(req) + + request.assert_called_with( + 'POST', + mock.ANY, + proxies={'https': 'http://localhost:3128', + 'http': 'http://localhost:3128'}, + data=mock.ANY, + headers=mock.ANY, + timeout=None, + cert=None, + verify=True, + auth=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_identifier(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.identifier = 1234 + self.transport(req) + + _, kwargs = request.call_args + self.assertIn( + """ +id +1234 +""".encode(), kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_filter(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + """ +operation +^= prefix +""".encode(), kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_limit_offset(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.limit = 10 + self.transport(req) + + args, kwargs = request.call_args + self.assertIn(""" +resultLimit + +""".encode(), kwargs['data']) + self.assertIn("""limit +10 +""".encode(), kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_old_mask(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = {"something": "nested"} + self.transport(req) + + args, kwargs = request.call_args + self.assertIn(""" +mask + + +something +nested + + +""".encode(), kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_no_mask_prefix(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "something.nested" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + "mask[something.nested]".encode(), + kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_v2(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "mask[something[nested]]" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + "mask[something[nested]]".encode(), + kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_filteredMask(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "filteredMask[something[nested]]" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + "filteredMask[something[nested]]".encode(), + kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_v2_dot(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "mask.something.nested" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn("mask.something.nested".encode(), + kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_request_exception(self, request): + # Test Text Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.content = 'Error Code' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + self.assertRaises(SoftLayer.TransportError, self.transport, req) + + def test_print_reproduceable(self): + req = transports.Request() + req.url = "https://test.com" + req.payload = "testing" + req.transport_headers = {"test-headers": 'aaaa'} + output_text = self.transport.print_reproduceable(req) + self.assertIn("https://test.com", output_text) + + @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('requests.auth.HTTPBasicAuth') + def test_ibm_id_call(self, auth, request): + request.return_value = self.response + + data = ''' + +getObject + + + + +headers + + + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.transport_user = 'apikey' + req.transport_password = '1234567890qweasdzxc' + resp = self.transport(req) + + auth.assert_called_with('apikey', '1234567890qweasdzxc') + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=mock.ANY) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_call_large_number_response(self, request): + response = requests.Response() + body = b''' + + + + + + + + + bytesUsed + 2666148982056 + + + + + + + + + ''' + response.raw = io.BytesIO(body) + response.headers['SoftLayer-Total-Items'] = 1 + response.status_code = 200 + request.return_value = response + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp[0]['bytesUsed'], 2666148982056) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_nonascii_characters(self, request): + request.return_value = self.response + hostname = 'testé' + data = ''' + +getObject + + + + +headers + + + + + + + + +hostname +testé + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ({'hostname': hostname},) + req.transport_user = "testUser" + req.transport_password = "testApiKey" + resp = self.transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=mock.ANY) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + + +@mock.patch('SoftLayer.transports.requests.Session.request') +@pytest.mark.parametrize( + "transport_verify,request_verify,expected", + [ + (True, True, True), + (True, False, False), + (True, None, True), + + (False, True, True), + (False, False, False), + (False, None, False), + + (None, True, True), + (None, False, False), + (None, None, True), + ] +) +def test_verify(request, + transport_verify, + request_verify, + expected): + request.return_value = get_xmlrpc_response() + + transport = transports.XmlRpcTransport( + endpoint_url='http://something9999999999999999999999.com', + ) + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + if request_verify is not None: + req.verify = request_verify + + if transport_verify is not None: + transport.verify = transport_verify + + transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + data=mock.ANY, + headers=mock.ANY, + cert=mock.ANY, + proxies=mock.ANY, + timeout=mock.ANY, + verify=expected, + auth=None) + + + + + diff --git a/tools/requirements.txt b/tools/requirements.txt index dd85ec17e..880148ffd 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -4,3 +4,4 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 +zeep diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index ac58d1241..ab52a13fa 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -10,3 +10,4 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 +zeep \ No newline at end of file From 6e4e5683fcbb0072c2dbf9c5c6a27846cbb2c6fe Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 6 Apr 2022 09:23:47 -0400 Subject: [PATCH 0601/1385] fix the team code review comments and fix the unit test --- SoftLayer/managers/network.py | 5 ++++- tests/managers/network_tests.py | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index bcb73f786..12d58fede 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -542,7 +542,10 @@ def list_vlans(self, datacenter=None, vlan_number=None, name=None, limit=100, ** kwargs['mask'] = DEFAULT_VLAN_MASK kwargs['iter'] = True - return self.account.getNetworkVlans(mask=kwargs['mask'], filter=_filter.to_dict(), limit=limit) + if limit > 0: + return self.account.getNetworkVlans(mask=kwargs['mask'], filter=_filter.to_dict(), limit=limit) + else: + return self.account.getNetworkVlans(mask=kwargs['mask'], filter=_filter.to_dict(), iter=True) def list_securitygroups(self, **kwargs): """List security groups.""" diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a578fd604..acb58bd21 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -360,13 +360,14 @@ def test_list_vlans_with_filters(self): self.assertEqual(result, fixtures.SoftLayer_Account.getNetworkVlans) _filter = { 'networkVlans': { - 'primaryRouter': { - 'datacenter': { - 'name': {'operation': '_= dal00'}}, - }, + 'id': { + 'operation': 'orderBy', + 'options': [ + {'name': 'sort', 'value': ['ASC']}]}, 'vlanNumber': {'operation': 5}, 'name': {'operation': '_= primary-vlan'}, - }, + 'primaryRouter': { + 'datacenter': {'name': {'operation': '_= dal00'}}}} } self.assert_called_with('SoftLayer_Account', 'getNetworkVlans', filter=_filter) From 9715c0be9c476c878815acba1c394b67f4c310be Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 6 Apr 2022 17:39:16 -0500 Subject: [PATCH 0602/1385] objectMask support --- SoftLayer/transports/soap.py | 45 ++++++++++++++++++++-------------- tests/transports/soap_tests.py | 29 +++++++++++++++++++--- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index b89fb090c..9a6eb499b 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -11,6 +11,7 @@ from zeep import Client, Settings, Transport, xsd from zeep.helpers import serialize_object from zeep.cache import SqliteCache +from zeep.plugins import HistoryPlugin import requests @@ -31,43 +32,51 @@ def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, # Throw an error for py < 3.6 because of f-strings logging.getLogger('zeep').setLevel(logging.ERROR) + logging.getLogger('zeep.transports').setLevel(logging.DEBUG) self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') self.timeout = timeout or None self.proxy = proxy self.user_agent = user_agent or consts.USER_AGENT self.verify = verify self._client = None + self.history = HistoryPlugin() def __call__(self, request): """Makes a SoftLayer API call against the SOAP endpoint. :param request request: Request object """ - print("Making a SOAP API CALL...") - # client = Client(f"{self.endpoint_url}/{request.service}?wsdl") + zeep_settings = Settings(strict=False, xml_huge_tree=True) zeep_transport = Transport(cache=SqliteCache(timeout=86400)) client = Client(f"{self.endpoint_url}/{request.service}?wsdl", - settings=zeep_settings, transport=zeep_transport) - # authXsd = xsd.Element( - # f"{self.endpoint_url}/authenticate", - # xsd.ComplexType([ - # xsd.Element(f"{self.endpoint_url}/username", xsd.String()), - # xsd.Element(f"{self.endpoint_url}/apiKey", xsd.String()) - # ]) - # ) + settings=zeep_settings, transport=zeep_transport, plugins=[self.history]) + + # MUST define headers like this because otherwise the objectMask header doesn't work + # because it isn't sent in with a namespace. xsdUserAuth = xsd.Element( - '{http://api.softlayer.com/soap/v3.1/}authenticate', + '{http://api.softlayer.com/soap/v3/}authenticate', xsd.ComplexType([ - xsd.Element('{http://api.softlayer.com/soap/v3.1/}username', xsd.String()), - xsd.Element('{http://api.softlayer.com/soap/v3.1/}apiKey', xsd.String()) + xsd.Element('{http://api.service.softlayer.com/soap/v3/}username', xsd.String()), + xsd.Element('{http://api.service.softlayer.com/soap/v3/}apiKey', xsd.String()) ]) ) - # transport = Transport(session=get_session()) - - authHeader = xsdUserAuth(username=request.transport_user, apiKey=request.transport_password) + xsdMask = xsd.Element( + '{http://api.service.softlayer.com/soap/v3.1/}SoftLayer_ObjectMask', + xsd.ComplexType([ + xsd.Element('mask', xsd.String()), + ]) + ) + + headers = [ + xsdMask(mask=request.mask or ''), + xsdUserAuth(username=request.transport_user, apiKey=request.transport_password) + ] + + pp(headers) + print("HEADERS ^^^^^") method = getattr(client.service, request.method) - result = client.service.getObject(_soapheaders=[authHeader]) + result = client.service.getObject(_soapheaders=headers) return serialize_object(result) # result = transport.post(f"{self.endpoint_url}/{request.service}") @@ -80,4 +89,4 @@ def print_reproduceable(self, request): :param request request: Request object """ - return "THE SOAP API CALL..." + return self.history.last_sent diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index bfc0d4623..af52f2e98 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -39,21 +39,42 @@ def get_soap_response(): return response -class TestXmlRpcAPICall(testing.TestCase): +class TestSoapAPICall(testing.TestCase): def set_up(self): self.transport = SoapTransport(endpoint_url='https://api.softlayer.com/soap/v3.1/') self.response = get_soap_response() self.user = os.getenv('SL_USER') self.password = os.environ.get('SL_APIKEY') - - def test_call(self): request = Request() request.service = 'SoftLayer_Account' request.method = 'getObject' request.transport_user = self.user request.transport_password = self.password - data = self.transport(request) + self.request = request + + def test_call(self): + + data = self.transport(self.request) + pp(data) + self.assertEqual(data.get('id'), 307608) + self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") + + # def test_debug_call(self): + + # self.request.mask = "mask[id,accountName,companyName]" + # data = self.transport(self.request) + + # self.assertEqual(data.get('id'), 307608) + # debug_data = self.transport.print_reproduceable(self.request) + # print(debug_data['envelope']) + # self.assertEqual(":sdfsdf", debug_data) + + def test_objectMask(self): + self.request.mask = "mask[id,companyName]" + data = self.transport(self.request) pp(data) + self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") + self.assertIsNone(data.get('address1')) self.assertEqual(data.get('id'), 307608) From 37f416cb1d2dbf9ce53cf94318d11c5a9aab140b Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 8 Apr 2022 10:58:25 -0400 Subject: [PATCH 0603/1385] solved comments --- SoftLayer/CLI/account/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 43d85b537..8d1048000 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -12,11 +12,11 @@ @click.option('--ack-all', is_flag=True, default=False, help="Acknowledge every upcoming event. Doing so will turn off the popup in the control portal") @click.option('--planned', is_flag=True, default=False, - help="Show just planned events") + help="Show only planned events") @click.option('--unplanned', is_flag=True, default=False, - help="Show just unplanned events") + help="Show only unplanned events") @click.option('--announcement', is_flag=True, default=False, - help="Show just announcement events") + help="Show only announcement events") @environment.pass_env def cli(env, ack_all, planned, unplanned, announcement): """Summary and acknowledgement of upcoming and ongoing maintenance events""" From 357ba70b96b3762a2d8336cb88b9eab1c9b263f3 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 11 Apr 2022 11:00:56 -0400 Subject: [PATCH 0604/1385] Update global ip assign/unassign to use new API --- SoftLayer/CLI/globalip/assign.py | 14 +++++++------- SoftLayer/fixtures/SoftLayer_Network_Subnet.py | 1 + SoftLayer/managers/network.py | 10 ++++++++++ tests/CLI/modules/globalip_tests.py | 2 +- tests/managers/network_tests.py | 4 ++++ 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/globalip/assign.py b/SoftLayer/CLI/globalip/assign.py index ea9a3d12f..1595ccdba 100644 --- a/SoftLayer/CLI/globalip/assign.py +++ b/SoftLayer/CLI/globalip/assign.py @@ -5,17 +5,17 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers -@click.command() +@click.command(epilog="More information about types and ") @click.argument('identifier') -@click.argument('target') +@click.option('--target', + help='See SLDN docs. ' + 'E.g SoftLayer_Network_Subnet_IpAddress, SoftLayer_Hardware_Server,SoftLayer_Virtual_Guest') +@click.option('--router', help='An appropriate identifier for the specified $type. Some types have multiple identifier') @environment.pass_env -def cli(env, identifier, target): +def cli(env, identifier, target, router): """Assigns the global IP to a target.""" mgr = SoftLayer.NetworkManager(env.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, - name='global ip') - mgr.assign_global_ip(global_ip_id, target) + mgr.route(identifier, target, router) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index ac3b9d74a..d0b22b8e5 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -44,3 +44,4 @@ editNote = True setTags = True cancel = True +route = True \ No newline at end of file diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 0f550ec3d..73cd4430c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -824,3 +824,13 @@ def get_closed_pods(self): mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) + + def route(self, subnet_id, type_serv, target): + """Assigns a global IP address to a specified target. + + :param int subnet_id: The ID of the global IP being assigned + :param string type_serv: The type service to assign + :param string target: The instance to assign + """ + return self.client.call('SoftLayer_Network_Subnet', 'route', + type_serv, target, id=subnet_id, ) diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index e12b7c3f6..f46bd5ef7 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -15,7 +15,7 @@ class DnsTests(testing.TestCase): def test_ip_assign(self): - result = self.run_command(['globalip', 'assign', '1', '127.0.0.1']) + result = self.run_command(['globalip', 'assign', '1']) self.assert_no_fail(result) self.assertEqual(result.output, "") diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a578fd604..24a2bbb0b 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -628,3 +628,7 @@ def test_vlan_edit(self): def test_get_all_pods(self): self.network.get_pods() self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') + + def test_route(self): + self.network.route('SoftLayer_Hardware_Server', 123456, 100) + self.assert_called_with('SoftLayer_Network_Subnet', 'route') From 86173b04641d0fe59a2f1f13a810a068e2e92b20 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 11 Apr 2022 15:42:38 -0400 Subject: [PATCH 0605/1385] fix the tox tool --- SoftLayer/fixtures/SoftLayer_Network_Subnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index d0b22b8e5..9ecf8164e 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -44,4 +44,4 @@ editNote = True setTags = True cancel = True -route = True \ No newline at end of file +route = True From d8f219d099adaab4c19082dce180647ab9244d9f Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 13 Apr 2022 12:10:34 -0400 Subject: [PATCH 0606/1385] Ability to route/unroute subnets --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/subnet/route.py | 26 +++++++++++++++++++ .../fixtures/SoftLayer_Network_Subnet.py | 1 + SoftLayer/managers/network.py | 10 +++++++ tests/CLI/modules/subnet_tests.py | 6 +++++ tests/managers/network_tests.py | 4 +++ 6 files changed, 48 insertions(+) create mode 100644 SoftLayer/CLI/subnet/route.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2d5bb9d65..ac2d5084d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -323,6 +323,7 @@ ('subnet:list', 'SoftLayer.CLI.subnet.list:cli'), ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), ('subnet:edit-ip', 'SoftLayer.CLI.subnet.edit_ip:cli'), + ('subnet:route', 'SoftLayer.CLI.subnet.route:cli'), ('tags', 'SoftLayer.CLI.tags'), ('tags:cleanup', 'SoftLayer.CLI.tags.cleanup:cli'), diff --git a/SoftLayer/CLI/subnet/route.py b/SoftLayer/CLI/subnet/route.py new file mode 100644 index 000000000..e4d4acd4e --- /dev/null +++ b/SoftLayer/CLI/subnet/route.py @@ -0,0 +1,26 @@ +"""allows you to change the route of your Account Owned subnets.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + +target_types = {'vlan': 'SoftLayer_Network_Vlan', + 'ip': 'SoftLayer_Network_Subnet_IpAddress', + 'hardware': 'SoftLayer_Hardware_Server', + 'vsi': 'SoftLayer_Virtual_Guest'} + + +@click.command(epilog="More information about types and identifiers " + "on https://sldn.softlayer.com/reference/services/SoftLayer_Network_Subnet/route/") +@click.argument('identifier') +@click.option('--target', type=click.Choice(['vlan', 'ip', 'hardware', 'vsi']), + help='choose the type. vlan, ip, hardware, vsi') +@click.option('--target-id', help='The identifier for the destination resource to route this subnet to. ') +@environment.pass_env +def cli(env, identifier, target, target_id): + """Assigns the subnet to a target.""" + + mgr = SoftLayer.NetworkManager(env.client) + mgr.route(identifier, target_types.get(target), target_id) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index ac3b9d74a..9ecf8164e 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -44,3 +44,4 @@ editNote = True setTags = True cancel = True +route = True diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 0f550ec3d..3fb1e1725 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -824,3 +824,13 @@ def get_closed_pods(self): mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) + + def route(self, subnet_id, type_serv, target): + """Assigns a subnet to a specified target. + + :param int subnet_id: The ID of the global IP being assigned + :param string type_serv: The type service to assign + :param string target: The instance to assign + """ + return self.client.call('SoftLayer_Network_Subnet', 'route', + type_serv, target, id=subnet_id, ) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 65a4cc5c8..03166c9f9 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -184,3 +184,9 @@ def test_cancel(self, confirm_mock): def test_cancel_fail(self): result = self.run_command(['subnet', 'cancel', '1234']) self.assertEqual(result.exit_code, 2) + + def test_route(self): + result = self.run_command(['subnet', 'route', '1']) + + self.assert_no_fail(result) + self.assertEqual(result.exit_code, 0) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a578fd604..24a2bbb0b 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -628,3 +628,7 @@ def test_vlan_edit(self): def test_get_all_pods(self): self.network.get_pods() self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') + + def test_route(self): + self.network.route('SoftLayer_Hardware_Server', 123456, 100) + self.assert_called_with('SoftLayer_Network_Subnet', 'route') From 1e5e998e3c334fcbf55c699ebbfb28d5925a5dc6 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 13 Apr 2022 14:33:31 -0400 Subject: [PATCH 0607/1385] fix the tox analysis --- docs/cli/subnet.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 38bda428d..7d22b8246 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -30,3 +30,6 @@ Subnets .. click:: SoftLayer.CLI.subnet.edit_ip:cli :prog: subnet edit-ip :show-nested: +.. click:: SoftLayer.CLI.subnet.route:cli + :prog: subnet route + :show-nested: From afc6ec2cdb5cb9cd040d817fd1c80146279fa6bd Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 13 Apr 2022 14:53:02 -0500 Subject: [PATCH 0608/1385] #1602 got objectFilter kinda working, at least for simple things. Need to figure out how to deal with href entries though --- SoftLayer/transports/soap.py | 35 +++++++++++++++++++++++++++------- tests/transports/soap_tests.py | 12 ++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 9a6eb499b..1d2ab4284 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -40,6 +40,7 @@ def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, self.verify = verify self._client = None self.history = HistoryPlugin() + self.soapNS = "http://api.service.softlayer.com/soap/v3.1/" def __call__(self, request): """Makes a SoftLayer API call against the SOAP endpoint. @@ -55,29 +56,49 @@ def __call__(self, request): # MUST define headers like this because otherwise the objectMask header doesn't work # because it isn't sent in with a namespace. xsdUserAuth = xsd.Element( - '{http://api.softlayer.com/soap/v3/}authenticate', + f"{{{self.soapNS}}}authenticate", xsd.ComplexType([ - xsd.Element('{http://api.service.softlayer.com/soap/v3/}username', xsd.String()), - xsd.Element('{http://api.service.softlayer.com/soap/v3/}apiKey', xsd.String()) + xsd.Element(f'{{{self.soapNS}}}username', xsd.String()), + xsd.Element(f'{{{self.soapNS}}}apiKey', xsd.String()) ]) ) + factory = client.type_factory(f"{self.soapNS}") + theMask = client.get_type(f"{{{self.soapNS}}}SoftLayer_ObjectMask") xsdMask = xsd.Element( '{http://api.service.softlayer.com/soap/v3.1/}SoftLayer_ObjectMask', + factory['SoftLayer_ObjectMask'] + ) + + # Object Filter + filterType = client.get_type(f"{{{self.soapNS}}}{request.service}ObjectFilter") + xsdFilter = xsd.Element( + f"{{{self.soapNS}}}{request.service}ObjectFilter", filterType + ) + + # Result Limit + xsdResultLimit = xsd.Element( + f"{{{self.soapNS}}}resultLimit", xsd.ComplexType([ - xsd.Element('mask', xsd.String()), + xsd.Element('limit', xsd.String()), + xsd.Element('offset', xsd.String()), ]) ) + test = {"type":{"keyName":{"operation":"BARE_METAL_CPU"}} } headers = [ xsdMask(mask=request.mask or ''), - xsdUserAuth(username=request.transport_user, apiKey=request.transport_password) + xsdUserAuth(username=request.transport_user, apiKey=request.transport_password), + xsdResultLimit(limit=2, offset=0), + xsdFilter(**request.filter or '') # The ** here forces python to treat this dict as properties ] pp(headers) print("HEADERS ^^^^^") method = getattr(client.service, request.method) - result = client.service.getObject(_soapheaders=headers) - return serialize_object(result) + + # result = client.service.getObject(_soapheaders=headers) + result = method(_soapheaders=headers) + return serialize_object(result['body']['getAllObjectsReturn']) # result = transport.post(f"{self.endpoint_url}/{request.service}") diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index af52f2e98..df86321c2 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -78,3 +78,15 @@ def test_objectMask(self): self.assertIsNone(data.get('address1')) self.assertEqual(data.get('id'), 307608) + def test_objectFilter(self): + self.request.service = "SoftLayer_Product_Package" + self.request.method = "getAllObjects" + self.request.mask = "mask[id,description,keyName,type[id,keyName],name]" + self.request.filter = {'type': {'keyName': {'operation': 'BARE_METAL_CPU'}}} + data = self.transport(self.request) + # pp(data) + # print("^^^ DATA **** ") + for package in data: + pp(package) + print("^^^ PACKAGE **** ") + self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") \ No newline at end of file From b32b8a979ad5b2115ac00682ea6aa1c3ee0e46b9 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 14 Apr 2022 08:33:10 -0400 Subject: [PATCH 0609/1385] fix the team code review comments --- SoftLayer/CLI/globalip/assign.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/globalip/assign.py b/SoftLayer/CLI/globalip/assign.py index 1595ccdba..3f58d7125 100644 --- a/SoftLayer/CLI/globalip/assign.py +++ b/SoftLayer/CLI/globalip/assign.py @@ -6,16 +6,21 @@ import SoftLayer from SoftLayer.CLI import environment +target_types = {'vlan': 'SoftLayer_Network_Vlan', + 'ip': 'SoftLayer_Network_Subnet_IpAddress', + 'hardware': 'SoftLayer_Hardware_Server', + 'vsi': 'SoftLayer_Virtual_Guest'} -@click.command(epilog="More information about types and ") + +@click.command(epilog="More information about types and identifiers " + "on https://sldn.softlayer.com/reference/services/SoftLayer_Network_Subnet/route/") @click.argument('identifier') -@click.option('--target', - help='See SLDN docs. ' - 'E.g SoftLayer_Network_Subnet_IpAddress, SoftLayer_Hardware_Server,SoftLayer_Virtual_Guest') -@click.option('--router', help='An appropriate identifier for the specified $type. Some types have multiple identifier') +@click.option('--target', type=click.Choice(['vlan', 'ip', 'hardware', 'vsi']), + help='choose the type. vlan, ip, hardware, vsi') +@click.option('--target-id', help='The identifier for the destination resource to route this subnet to. ') @environment.pass_env -def cli(env, identifier, target, router): - """Assigns the global IP to a target.""" +def cli(env, identifier, target, target_id): + """Assigns the subnet to a target.""" mgr = SoftLayer.NetworkManager(env.client) - mgr.route(identifier, target, router) + mgr.route(identifier, target_types.get(target), target_id) From 1e47c3401ff64dd1096a7a057b18b62ecddfa972 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Tue, 19 Apr 2022 09:59:57 -0400 Subject: [PATCH 0610/1385] Improved successful response to command - slcli account cancel-item --- SoftLayer/CLI/account/cancel_item.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/cancel_item.py b/SoftLayer/CLI/account/cancel_item.py index 0cc08593d..626643446 100644 --- a/SoftLayer/CLI/account/cancel_item.py +++ b/SoftLayer/CLI/account/cancel_item.py @@ -15,4 +15,5 @@ def cli(env, identifier): manager = AccountManager(env.client) item = manager.cancel_item(identifier) - env.fout(item) + if item: + env.fout("Item: {} was cancelled.".format(identifier)) From d4aac0d8306bc3b3fe95b543837aef9ea1b6c487 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 20 Apr 2022 10:54:45 -0400 Subject: [PATCH 0611/1385] Improved successful response to command - slcli virtual edit --- SoftLayer/CLI/virt/edit.py | 13 +++++++++---- tests/CLI/modules/vs/vs_tests.py | 8 +++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/virt/edit.py b/SoftLayer/CLI/virt/edit.py index a72caa585..93f9c6694 100644 --- a/SoftLayer/CLI/virt/edit.py +++ b/SoftLayer/CLI/virt/edit.py @@ -54,11 +54,16 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, vsi = SoftLayer.VSManager(env.client) vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') - if not vsi.edit(vs_id, **data): - raise exceptions.CLIAbort("Failed to update virtual server") + + if vsi.edit(vs_id, **data): + for key, value in data.items(): + if value is not None: + env.fout("The {} of virtual server instance: {} was updated.".format(key, vs_id)) if public_speed is not None: - vsi.change_port_speed(vs_id, True, int(public_speed)) + if vsi.change_port_speed(vs_id, True, int(public_speed)): + env.fout("The public speed of virtual server instance: {} was updated.".format(vs_id)) if private_speed is not None: - vsi.change_port_speed(vs_id, False, int(private_speed)) + if vsi.change_port_speed(vs_id, False, int(private_speed)): + env.fout("The private speed of virtual server instance: {} was updated.".format(vs_id)) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 4ae31fd6d..99fafc0bf 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -609,7 +609,13 @@ def test_edit(self): '100']) self.assert_no_fail(result) - self.assertEqual(result.output, '') + expected_output = '"The userdata of virtual server instance: 100 was updated."\n' \ + + '"The hostname of virtual server instance: 100 was updated."\n' \ + + '"The domain of virtual server instance: 100 was updated."\n' \ + + '"The tags of virtual server instance: 100 was updated."\n' \ + + '"The public speed of virtual server instance: 100 was updated."\n' \ + + '"The private speed of virtual server instance: 100 was updated."\n' + self.assertEqual(result.output, expected_output) self.assert_called_with( 'SoftLayer_Virtual_Guest', 'editObject', From 53acb2aefe2605a700a5a341f394921d81b85c2a Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 20 Apr 2022 16:28:11 -0400 Subject: [PATCH 0612/1385] Improved successful response to command - slcli vlan cancel --- SoftLayer/CLI/vlan/cancel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py index 35f5aa0f9..3470c0599 100644 --- a/SoftLayer/CLI/vlan/cancel.py +++ b/SoftLayer/CLI/vlan/cancel.py @@ -25,10 +25,11 @@ def cli(env, identifier): raise exceptions.CLIAbort(reasons) item = mgr.get_vlan(identifier).get('billingItem') if item: - mgr.cancel_item(item.get('id'), - True, - 'Cancel by cli command', - 'Cancel by cli command') + if mgr.cancel_item(item.get('id'), + True, + 'Cancel by cli command', + 'Cancel by cli command'): + env.fout("VLAN {} was cancelled.".format(identifier)) else: raise exceptions.CLIAbort( "VLAN is an automatically assigned and free of charge VLAN," From 955eacb7ebb57ccc889e01333ee20b2fe3591d97 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 20 Apr 2022 16:52:39 -0500 Subject: [PATCH 0613/1385] got filters working, need to upload changes to zeep --- SoftLayer/transports/soap.py | 52 +++++++++++++++++++++++++++------- tests/transports/soap_tests.py | 9 ++++-- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 1d2ab4284..76fa33a8c 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -12,6 +12,8 @@ from zeep.helpers import serialize_object from zeep.cache import SqliteCache from zeep.plugins import HistoryPlugin +from zeep.wsdl.messages.multiref import process_multiref + import requests @@ -53,6 +55,8 @@ def __call__(self, request): client = Client(f"{self.endpoint_url}/{request.service}?wsdl", settings=zeep_settings, transport=zeep_transport, plugins=[self.history]) + # print(client.wsdl.dump()) + # print("=============== WSDL ==============") # MUST define headers like this because otherwise the objectMask header doesn't work # because it isn't sent in with a namespace. xsdUserAuth = xsd.Element( @@ -65,7 +69,7 @@ def __call__(self, request): factory = client.type_factory(f"{self.soapNS}") theMask = client.get_type(f"{{{self.soapNS}}}SoftLayer_ObjectMask") xsdMask = xsd.Element( - '{http://api.service.softlayer.com/soap/v3.1/}SoftLayer_ObjectMask', + f"{{{self.soapNS}}}SoftLayer_ObjectMask", factory['SoftLayer_ObjectMask'] ) @@ -84,22 +88,48 @@ def __call__(self, request): ]) ) - test = {"type":{"keyName":{"operation":"BARE_METAL_CPU"}} } + # Might one day want to support unauthenticated requests, but for now assume user auth. headers = [ - xsdMask(mask=request.mask or ''), xsdUserAuth(username=request.transport_user, apiKey=request.transport_password), - xsdResultLimit(limit=2, offset=0), - xsdFilter(**request.filter or '') # The ** here forces python to treat this dict as properties ] - pp(headers) - print("HEADERS ^^^^^") - method = getattr(client.service, request.method) + if request.limit: + headers.append(xsdResultLimit(limit=request.limit, offset=request.offset)) + if request.mask: + headers.append(xsdMask(mask=request.mask)) + if request.filter: + # The ** here forces python to treat this dict as properties + headers.append(xsdFilter(**request.filter)) + + + try: + method = getattr(client.service, request.method) + except AttributeError as ex: + message = f"{request.service}::{request.method}() does not exist in {self.soapNS}{request.service}?wsdl" + raise exceptions.TransportError(404, message) from ex - # result = client.service.getObject(_soapheaders=headers) result = method(_soapheaders=headers) - return serialize_object(result['body']['getAllObjectsReturn']) - # result = transport.post(f"{self.endpoint_url}/{request.service}") + # result = client.service.getObject(_soapheaders=headers) + + # process_multiref(result['body']['getAllObjectsReturn']) + + # print("^^^ RESULT ^^^^^^^") + + # TODO GET A WAY TO FIND TOTAL ITEMS + # print(result['header']['totalItems']['amount']) + # print(" ^^ ITEMS ^^^ ") + + try: + methodReturn = f"{request.method}Return" + serialize = serialize_object(result) + if serialize.get('body'): + return serialize['body'][methodReturn] + else: + # Some responses (like SoftLayer_Account::getObject) don't have a body? + return serialize + except KeyError as e: + message = f"Error serializeing response\n{result}\n" + raise exceptions.TransportError(500, message) def print_reproduceable(self, request): diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index df86321c2..bd98733ad 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -83,10 +83,13 @@ def test_objectFilter(self): self.request.method = "getAllObjects" self.request.mask = "mask[id,description,keyName,type[id,keyName],name]" self.request.filter = {'type': {'keyName': {'operation': 'BARE_METAL_CPU'}}} + self.request.limit = 5 + self.request.offset = 0 data = self.transport(self.request) # pp(data) # print("^^^ DATA **** ") for package in data: - pp(package) - print("^^^ PACKAGE **** ") - self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") \ No newline at end of file + + self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") + + ## TODO MORE COMPLEX OBJECT FILTERS! \ No newline at end of file From 34f6dafb5584ba068a86a26c0bb8df433ad26359 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 21 Apr 2022 16:21:45 -0500 Subject: [PATCH 0614/1385] #1602 initParams working --- SoftLayer/transports/soap.py | 11 +++++++++++ tests/transports/soap_tests.py | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 76fa33a8c..7f985fc02 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -102,6 +102,17 @@ def __call__(self, request): headers.append(xsdFilter(**request.filter)) + + if request.identifier: + initParam = f"{request.service}InitParameters" + initParamType = client.get_type(f"{{{self.soapNS}}}{initParam}") + xsdInitParam = xsd.Element( + f"{{{self.soapNS}}}{initParam}", initParamType + ) + # Might want to check if its an id or globalIdentifier at some point, for now only id. + headers.append(xsdInitParam(id=request.identifier)) + + # TODO Add params... maybe try: method = getattr(client.service, request.method) except AttributeError as ex: diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index bd98733ad..51849dc5c 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -92,4 +92,27 @@ def test_objectFilter(self): self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") + def test_virtualGuest(self): + accountRequest = Request() + accountRequest.service = "SoftLayer_Account" + accountRequest.method = "getVirtualGuests" + accountRequest.limit = 5 + accountRequest.offset = 0 + accountRequest.mask = "mask[id,hostname,domain]" + accountRequest.transport_user = self.user + accountRequest.transport_password = self.password + + vsis = self.transport(accountRequest) + for vsi in vsis: + self.assertGreater(vsi.get('id'), 1) + vsiRequest = Request() + vsiRequest.service = "SoftLayer_Virtual_Guest" + vsiRequest.method = "getObject" + vsiRequest.identifier = vsi.get('id') + vsiRequest.mask = "mask[id,hostname,domain]" + vsiRequest.transport_user = self.user + vsiRequest.transport_password = self.password + thisVsi = self.transport(vsiRequest) + self.assertEqual(thisVsi.get('id'), vsi.get('id')) + ## TODO MORE COMPLEX OBJECT FILTERS! \ No newline at end of file From 6da51a38588ebeb0eb99a53799016a5ae3700cb8 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 22 Apr 2022 10:58:07 -0400 Subject: [PATCH 0615/1385] solved parameter domain to domainName and change result from None to emtpy --- SoftLayer/CLI/account/item_detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py index ddc2d31ed..7aac9a963 100644 --- a/SoftLayer/CLI/account/item_detail.py +++ b/SoftLayer/CLI/account/item_detail.py @@ -28,7 +28,7 @@ def item_table(item): table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) table.add_row(['description', item.get('description')]) table.align = 'l' - fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) + fqdn = "{}.{}".format(item.get('hostName', ''), item.get('domainName', '')) if fqdn != ".": table.add_row(['FQDN', fqdn]) From d6ef5619f1bf589f152912513908abc9e7c2ecc7 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 22 Apr 2022 23:19:49 -0400 Subject: [PATCH 0616/1385] slcli autoscale create --- SoftLayer/CLI/autoscale/create.py | 137 ++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Scale_Group.py | 76 +++++++++++ SoftLayer/managers/autoscale.py | 11 ++ SoftLayer/managers/network.py | 12 ++ docs/cli/autoscale.rst | 4 + tests/CLI/modules/autoscale_tests.py | 24 +++- tests/managers/autoscale_tests.py | 51 ++++++++ tests/managers/network_tests.py | 4 + 9 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/autoscale/create.py diff --git a/SoftLayer/CLI/autoscale/create.py b/SoftLayer/CLI/autoscale/create.py new file mode 100644 index 000000000..86f17ddb8 --- /dev/null +++ b/SoftLayer/CLI/autoscale/create.py @@ -0,0 +1,137 @@ +"""Order/create a dedicated server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.managers.autoscale import AutoScaleManager + + +@click.command() +@click.option('--name', help="Scale group's name.") +@click.option('--cooldown', type=click.INT, + help="The number of seconds this group will wait after lastActionDate before performing another action.") +@click.option('--min', 'minimum', type=click.INT, help="Set the minimum number of guests") +@click.option('--max', 'maximum', type=click.INT, help="Set the maximum number of guests") +@click.option('--regional', type=click.INT, + help="The identifier of the regional group this scaling group is assigned to.") +@click.option('--postinstall', '-i', help="Post-install script to download") +@click.option('--os', '-o', help="OS install code. Tip: you can specify _LATEST") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN") +@click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN") +@click.option('--cpu', type=click.INT, help="Number of CPUs for new guests (existing not effected") +@click.option('--memory', type=click.INT, help="RAM in MB or GB for new guests (existing not effected") +@click.option('--policy-relative', help="The type of scale to perform(ABSOLUTE, PERCENT, RELATIVE).") +@click.option('--termination-policy', + help="The termination policy for the group(CLOSEST_TO_NEXT_CHARGE=1, NEWEST=2, OLDEST=3).") +@click.option('--policy-name', help="Collection of policies for this group. This can be empty.") +@click.option('--policy-amount', help="The number to scale by. This number has different meanings based on type.") +@click.option('--userdata', help="User defined metadata string") +@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") +@helpers.multi_option('--disk', help="Disk sizes") +@environment.pass_env +def cli(env, **args): + """Order/create autoscale.""" + scale = AutoScaleManager(env.client) + network = SoftLayer.NetworkManager(env.client) + + pods = network.get_closed_pods() + closure = [] + + datacenter = network.get_datacenter(args.get('datacenter')) + + ssh_keys = [] + for key in args.get('key'): + resolver = SoftLayer.SshKeyManager(env.client).resolve_ids + key_id = helpers.resolve_id(resolver, key, 'SshKey') + ssh_keys.append(key_id) + scale_actions = [ + { + "amount": args['policy_amount'], + "scaleType": args['policy_relative'] + } + ] + policy_template = { + 'name': args['policy_name'], + 'policies': scale_actions + + } + policies = [] + + block = [] + number_disk = 0 + for guest_disk in args['disk']: + disks = {'diskImage': {'capacity': guest_disk}, 'device': number_disk} + block.append(disks) + number_disk += 1 + + virt_template = { + 'localDiskFlag': False, + 'domain': args['domain'], + 'hostname': args['hostname'], + 'sshKeys': ssh_keys, + 'postInstallScriptUri': args.get('postinstall'), + 'operatingSystemReferenceCode': args['os'], + 'maxMemory': args.get('memory'), + 'datacenter': {'id': datacenter[0]['id']}, + 'startCpus': args.get('cpu'), + 'blockDevices': block, + 'hourlyBillingFlag': True, + 'privateNetworkOnlyFlag': False, + 'networkComponents': [{'maxSpeed': 100}], + 'typeId': 1, + 'userData': [{ + 'value': args.get('userdata') + }], + 'networkVlans': [], + + } + + order = { + 'name': args['name'], + 'cooldown': args['cooldown'], + 'maximumMemberCount': args['maximum'], + 'minimumMemberCount': args['minimum'], + 'regionalGroupId': args['regional'], + 'suspendedFlag': False, + 'balancedTerminationFlag': False, + 'virtualGuestMemberTemplate': virt_template, + 'virtualGuestMemberCount': 0, + 'policies': policies.append(clean_dict(policy_template)), + 'terminationPolicyId': args['termination_policy'] + } + + # print(virt_template) + + for pod in pods: + if args.get('datacenter') in str(pod['name']): + closure.append(pod['name']) + click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) + if not (env.skip_confirmations or formatting.confirm( + "This action will incur charges on your account. Continue?")): + raise exceptions.CLIAbort('Aborting scale group order.') + else: + result = scale.create(order) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['Id', result['id']]) + table.add_row(['Created', result['createDate']]) + table.add_row(['Name', result['name']]) + table.add_row(['Virtual Guest Id', result['virtualGuestMembers'][0]['virtualGuest']['id']]) + table.add_row(['Virtual Guest domain', result['virtualGuestMembers'][0]['virtualGuest']['domain']]) + table.add_row(['Virtual Guest hostname', result['virtualGuestMembers'][0]['virtualGuest']['hostname']]) + output = table + + env.fout(output) + + +def clean_dict(dictionary): + """Removes any `None` entires from the dictionary""" + return {k: v for k, v in dictionary.items() if v} diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2d5bb9d65..705ac8b10 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -372,6 +372,7 @@ ('autoscale', 'SoftLayer.CLI.autoscale'), ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), + ('autoscale:create', 'SoftLayer.CLI.autoscale.create:cli'), ('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'), ('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'), ('autoscale:logs', 'SoftLayer.CLI.autoscale.logs:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index f04d8f56e..6b0ce3db6 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -455,3 +455,79 @@ ] editObject = True + +createObject = { + "accountId": 307608, + "cooldown": 3600, + "createDate": "2022-04-22T13:45:24-06:00", + "id": 5446140, + "lastActionDate": "2022-04-22T13:45:29-06:00", + "maximumMemberCount": 5, + "minimumMemberCount": 1, + "name": "test22042022", + "regionalGroupId": 4568, + "suspendedFlag": False, + "terminationPolicyId": 2, + "virtualGuestMemberTemplate": { + "accountId": 307608, + "domain": "test.com", + "hostname": "testvs", + "maxMemory": 2048, + "startCpus": 2, + "blockDevices": [ + { + "diskImage": { + "capacity": 100, + } + } + ], + "hourlyBillingFlag": True, + "localDiskFlag": True, + "networkComponents": [ + { + "maxSpeed": 100, + } + ], + "operatingSystemReferenceCode": "CENTOS_7_64", + "userData": [ + { + "value": "the userData" + } + ] + }, + "virtualGuestMemberCount": 0, + "networkVlans": [], + "policies": [], + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [ + { + "createDate": "2022-04-22T13:45:29-06:00", + "id": 123456, + "scaleGroupId": 5446140, + "virtualGuest": { + "createDate": "2022-04-22T13:45:28-06:00", + "deviceStatusId": 3, + "domain": "test.com", + "fullyQualifiedDomainName": "testvs-97e7.test.com", + "hostname": "testvs-97e7", + "id": 129911702, + "maxCpu": 2, + "maxCpuUnits": "CORE", + "maxMemory": 2048, + "startCpus": 2, + "statusId": 1001, + "typeId": 1, + "uuid": "46e55f99-b412-4287-95b5-b8182b2fc924", + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + } + } + ] +} diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 78fa18e31..e32eb9f35 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -116,3 +116,14 @@ def edit(self, identifier, template): .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ """ return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier) + + def create(self, template): + """Calls `SoftLayer_Scale_Group::createObject()`_ + + :param template: `SoftLayer_Scale_Group`_ + + .. _SoftLayer_Scale_Group::createObject(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/createObject/ + .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ + """ + return self.client.call('SoftLayer_Scale_Group', 'createObject', template) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 0f550ec3d..0128abb51 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -824,3 +824,15 @@ def get_closed_pods(self): mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) + + def get_datacenter(self, _filter=None, datacenter=None): + """Calls SoftLayer_Location::getDatacenters() + + returns datacenter list. + """ + _filter = None + + if datacenter: + _filter = {"name": {"operation": datacenter}} + + return self.client.call('SoftLayer_Location', 'getDatacenters', filter=_filter, limit=1) diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst index a3aa31462..2e2292ffd 100644 --- a/docs/cli/autoscale.rst +++ b/docs/cli/autoscale.rst @@ -34,5 +34,9 @@ For making changes to the triggers or the autoscale group itself, see the `Autos :prog: autoscale edit :show-nested: +.. click:: SoftLayer.CLI.autoscale.create:cli + :prog: autoscale create + :show-nested: + .. _Autoscale Portal: https://cloud.ibm.com/classic/autoscale \ No newline at end of file diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index cb3cdfdb9..cc9d3b9f4 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -77,7 +77,7 @@ def test_autoscale_edit_userdata(self, manager): @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit') def test_autoscale_edit_userfile(self, manager): # On windows, python cannot edit a NamedTemporaryFile. - if(sys.platform.startswith("win")): + if (sys.platform.startswith("win")): self.skipTest("Test doesn't work in Windows") group = fixtures.SoftLayer_Scale_Group.getObject template = { @@ -89,3 +89,25 @@ def test_autoscale_edit_userfile(self, manager): result = self.run_command(['autoscale', 'edit', '12345', '--userfile', userfile.name]) self.assert_no_fail(result) manager.assert_called_with('12345', template) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_autoscale_create(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['autoscale', 'create', + '--name=test', + '--cooldown=3600', + '--min=1', + '--max=3', + '-o=CENTOS_7_64', + '--datacenter=ams01', + '--termination-policy=2', + '-H=testvs', + '-D=test.com', + '--cpu=2', + '--memory=1024', + '--policy-relative=absolute', + '--policy-amount=3', + '--regional=102', + '--disk=25']) + self.assert_no_fail(result) + self.assertEqual(result.exit_code, 0) diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py index 6da505409..febd62606 100644 --- a/tests/managers/autoscale_tests.py +++ b/tests/managers/autoscale_tests.py @@ -123,3 +123,54 @@ def test_edit_object(self): 'editObject', args=(template,), identifier=12345) + + def test_create_object(self): + template = { + 'name': 'test', + 'cooldown': 3600, + 'maximumMemberCount': 5, + 'minimumMemberCount': 1, + 'regionalGroupId': 4568, + 'suspendedFlag': False, + 'balancedTerminationFlag': False, + 'virtualGuestMemberTemplate': { + 'domain': 'test.com', + 'hostname': 'testvs', + 'operatingSystemReferenceCode': 'CENTOS_7_64', + 'maxMemory': 2048, + 'datacenter': { + 'id': 265592 + }, + 'startCpus': 2, + 'blockDevices': [ + { + 'diskImage': { + 'capacity': '100' + }, + 'device': 0 + } + ], + 'hourlyBillingFlag': True, + 'networkComponents': [ + { + 'maxSpeed': 100 + } + ], + 'localDiskFlag': True, + 'typeId': 1, + 'userData': [ + { + 'value': 'the userData' + } + ] + }, + 'virtualGuestMemberCount': 0, + + 'terminationPolicyId': '2', + } + + self.autoscale.create(template) + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'createObject', + args=(template,)) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a578fd604..df145ed67 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -628,3 +628,7 @@ def test_vlan_edit(self): def test_get_all_pods(self): self.network.get_pods() self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') + + def test_get_all_datacenter(self): + self.network.get_datacenter() + self.assert_called_with('SoftLayer_Location', 'getDatacenters') From 7da2e4dd1ae1a219d4903fd8840dc532a192f56c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 25 Apr 2022 14:58:15 -0500 Subject: [PATCH 0617/1385] AutoPep8 fixes --- SoftLayer/fixtures/SoftLayer_Account.py | 2 +- SoftLayer/fixtures/SoftLayer_Billing_Order.py | 2 +- .../fixtures/SoftLayer_Network_Storage.py | 2 +- SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 14 ++--- .../fixtures/SoftLayer_Product_Package.py | 4 +- .../SoftLayer_Software_AccountLicense.py | 52 +++++++++---------- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 2 +- SoftLayer/managers/vs.py | 4 +- SoftLayer/transports/__init__.py | 10 ---- SoftLayer/transports/debug.py | 2 +- SoftLayer/transports/fixture.py | 3 +- SoftLayer/transports/rest.py | 2 +- SoftLayer/transports/soap.py | 9 ++-- SoftLayer/transports/transport.py | 2 + SoftLayer/transports/xmlrpc.py | 1 + tests/CLI/modules/server_tests.py | 36 ++++++------- tests/CLI/modules/vs/vs_tests.py | 36 ++++++------- tests/managers/hardware_tests.py | 6 +-- tests/transport_tests.py | 1 - tests/transports/rest_tests.py | 3 +- tests/transports/soap_tests.py | 8 +-- tests/transports/xmlrpc_tests.py | 5 -- 22 files changed, 98 insertions(+), 108 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index fb5aedb67..ec5457a72 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1173,7 +1173,7 @@ "virtualizationPlatform": 0, "requiredUser": "administrator@vsphere.local" } - } +} ] getActiveVirtualLicenses = [{ diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order.py b/SoftLayer/fixtures/SoftLayer_Billing_Order.py index ae35280ea..6136ece3e 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order.py @@ -44,7 +44,7 @@ ], 'orderApprovalDate': '2019-09-15T13:13:13-06:00', 'orderTotalAmount': '0' - }] +}] getObject = { 'accountId': 1234, diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index bf1f7adc4..acf369d16 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -237,7 +237,7 @@ } refreshDuplicate = { - 'dependentDuplicate': 1 + 'dependentDuplicate': 1 } convertCloneDependentToIndependent = { diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 960c98995..087d854af 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -7,13 +7,13 @@ 'vlanNumber': 4444, 'firewallInterfaces': None, 'billingItem': { - 'allowCancellationFlag': 1, - 'categoryCode': 'network_vlan', - 'description': 'Private Network Vlan', - 'id': 235689, - 'notes': 'test cli', - 'orderItemId': 147258, - } + 'allowCancellationFlag': 1, + 'categoryCode': 'network_vlan', + 'description': 'Private Network Vlan', + 'id': 235689, + 'notes': 'test cli', + 'orderItemId': 147258, + } } editObject = True diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index c4c45985d..d2a93d89c 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2137,7 +2137,7 @@ "recurringFee": "0", "setupFee": "0", "sort": 10, - }] + }] }, { "description": "Public Network Vlan", "id": 1071, @@ -2168,6 +2168,6 @@ "recurringFee": "0", "setupFee": "0", "sort": 10, - }] + }] } ] diff --git a/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py index b4433d104..893a6921a 100644 --- a/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py +++ b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py @@ -1,30 +1,30 @@ getAllObjects = [{ - "capacity": "4", - "key": "ABCDE-6CJ8L-J8R9H-000R0-CDR70", - "units": "CPU", - "billingItem": { - "allowCancellationFlag": 1, - "categoryCode": "software_license", - "createDate": "2018-10-22T11:16:48-06:00", - "cycleStartDate": "2021-06-03T23:11:22-06:00", - "description": "vCenter Server Appliance 6.0", - "id": 123654789, - "lastBillDate": "2021-06-03T23:11:22-06:00", - "modifyDate": "2021-06-03T23:11:22-06:00", - "nextBillDate": "2021-07-03T23:00:00-06:00", - "orderItemId": 385054741, - "recurringMonths": 1, - "serviceProviderId": 1, - }, - "softwareDescription": { - "id": 1529, - "longDescription": "VMware vCenter 6.0", - "manufacturer": "VMware", - "name": "vCenter", - "version": "6.0", - "requiredUser": "administrator@vsphere.local" - } + "capacity": "4", + "key": "ABCDE-6CJ8L-J8R9H-000R0-CDR70", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "createDate": "2018-10-22T11:16:48-06:00", + "cycleStartDate": "2021-06-03T23:11:22-06:00", + "description": "vCenter Server Appliance 6.0", + "id": 123654789, + "lastBillDate": "2021-06-03T23:11:22-06:00", + "modifyDate": "2021-06-03T23:11:22-06:00", + "nextBillDate": "2021-07-03T23:00:00-06:00", + "orderItemId": 385054741, + "recurringMonths": 1, + "serviceProviderId": 1, }, + "softwareDescription": { + "id": 1529, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "version": "6.0", + "requiredUser": "administrator@vsphere.local" + } +}, { "capacity": "1", "key": "CBERT-4RL92-K8999-031K4-AJF5J", @@ -48,4 +48,4 @@ "name": "Virtual SAN Advanced Tier III", "version": "6.2", } - }] +}] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index f7e422d22..3c472b8d2 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -317,7 +317,7 @@ 'item': {'description': '2 GB'}, 'hourlyRecurringFee': '.06', 'recurringFee': '42' - }, + }, 'template': {'maxMemory': 2048} }, { diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 2fa698dce..403cf4dcb 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1067,8 +1067,8 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr category = {'categories': [{ 'categoryCode': 'guest_disk' + str(disk_number), 'complexType': "SoftLayer_Product_Item_Category"}], - 'complexType': 'SoftLayer_Product_Item_Price', - 'id': price_id} + 'complexType': 'SoftLayer_Product_Item_Price', + 'id': price_id} prices.append(category) order['prices'] = prices diff --git a/SoftLayer/transports/__init__.py b/SoftLayer/transports/__init__.py index bbecea227..454f32997 100644 --- a/SoftLayer/transports/__init__.py +++ b/SoftLayer/transports/__init__.py @@ -33,13 +33,3 @@ 'FixtureTransport', 'SoftLayerListResult' ] - - - - - - - - - - diff --git a/SoftLayer/transports/debug.py b/SoftLayer/transports/debug.py index 31b93b847..28ad67310 100644 --- a/SoftLayer/transports/debug.py +++ b/SoftLayer/transports/debug.py @@ -58,4 +58,4 @@ def get_last_calls(self): def print_reproduceable(self, call): """Prints a reproduceable debugging output""" - return self.transport.print_reproduceable(call) \ No newline at end of file + return self.transport.print_reproduceable(call) diff --git a/SoftLayer/transports/fixture.py b/SoftLayer/transports/fixture.py index 3eece28fc..2fa016665 100644 --- a/SoftLayer/transports/fixture.py +++ b/SoftLayer/transports/fixture.py @@ -8,6 +8,7 @@ import importlib + class FixtureTransport(object): """Implements a transport which returns fixtures.""" @@ -27,4 +28,4 @@ def __call__(self, call): def print_reproduceable(self, call): """Not Implemented""" - return call.service \ No newline at end of file + return call.service diff --git a/SoftLayer/transports/rest.py b/SoftLayer/transports/rest.py index e80d5bb35..3af9c1e40 100644 --- a/SoftLayer/transports/rest.py +++ b/SoftLayer/transports/rest.py @@ -179,4 +179,4 @@ def print_reproduceable(self, request): headers = ['"{0}: {1}"'.format(k, v) for k, v in request.transport_headers.items()] headers = " -H ".join(headers) - return command.format(method=method, headers=headers, data=data, uri=request.url) \ No newline at end of file + return command.format(method=method, headers=headers, data=data, uri=request.url) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 7f985fc02..585e4e12d 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -27,6 +27,8 @@ from .transport import SoftLayerListResult from pprint import pprint as pp + + class SoapTransport(object): """XML-RPC transport.""" @@ -101,8 +103,6 @@ def __call__(self, request): # The ** here forces python to treat this dict as properties headers.append(xsdFilter(**request.filter)) - - if request.identifier: initParam = f"{request.service}InitParameters" initParamType = client.get_type(f"{{{self.soapNS}}}{initParam}") @@ -121,7 +121,7 @@ def __call__(self, request): result = method(_soapheaders=headers) # result = client.service.getObject(_soapheaders=headers) - + # process_multiref(result['body']['getAllObjectsReturn']) # print("^^^ RESULT ^^^^^^^") @@ -142,7 +142,6 @@ def __call__(self, request): message = f"Error serializeing response\n{result}\n" raise exceptions.TransportError(500, message) - def print_reproduceable(self, request): """Prints out the minimal python code to reproduce a specific request @@ -150,5 +149,5 @@ def print_reproduceable(self, request): :param request request: Request object """ - + return self.history.last_sent diff --git a/SoftLayer/transports/transport.py b/SoftLayer/transports/transport.py index 40a8e872b..e795b3ecc 100644 --- a/SoftLayer/transports/transport.py +++ b/SoftLayer/transports/transport.py @@ -105,6 +105,7 @@ def __repr__(self): return "{service}::{method}({params})".format( service=self.service, method=self.method, params=param_string) + class SoftLayerListResult(list): """A SoftLayer API list result.""" @@ -115,6 +116,7 @@ def __init__(self, items=None, total_count=0): self.total_count = total_count super().__init__(items) + def _proxies_dict(proxy): """Makes a proxy dict appropriate to pass to requests.""" if not proxy: diff --git a/SoftLayer/transports/xmlrpc.py b/SoftLayer/transports/xmlrpc.py index 31afaf868..c10481812 100644 --- a/SoftLayer/transports/xmlrpc.py +++ b/SoftLayer/transports/xmlrpc.py @@ -21,6 +21,7 @@ from .transport import get_session from .transport import SoftLayerListResult + class XmlRpcTransport(object): """XML-RPC transport.""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index b026fa8cf..8e7382e16 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -693,19 +693,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -748,12 +748,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 4ae31fd6d..35dd2e4ac 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -345,19 +345,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -400,12 +400,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index a9eada76c..58eac87d7 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,9 +557,9 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', },), identifier=100) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 5ae0a448c..0c175df5c 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -18,7 +18,6 @@ from SoftLayer import transports - class TestFixtureTransport(testing.TestCase): def set_up(self): diff --git a/tests/transports/rest_tests.py b/tests/transports/rest_tests.py index ec634ec6c..f0c69cea0 100644 --- a/tests/transports/rest_tests.py +++ b/tests/transports/rest_tests.py @@ -17,6 +17,7 @@ from SoftLayer import testing from SoftLayer import transports + class TestRestAPICall(testing.TestCase): def set_up(self): @@ -362,4 +363,4 @@ def test_complex_encoder_bytes(self): result = json.dumps(to_encode, cls=transports.transport.ComplexEncoder) # result = '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}' # encode doesn't always encode in the same order, so testing exact match SOMETIMES breaks. - self.assertIn("QVNEQVNEQVNE", result) \ No newline at end of file + self.assertIn("QVNEQVNEQVNE", result) diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index 51849dc5c..d4b0bf335 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -20,7 +20,9 @@ from SoftLayer.transports import Request -from pprint import pprint as pp +from pprint import pprint as pp + + def get_soap_response(): response = requests.Response() list_body = b''' @@ -64,7 +66,7 @@ def test_call(self): # self.request.mask = "mask[id,accountName,companyName]" # data = self.transport(self.request) - + # self.assertEqual(data.get('id'), 307608) # debug_data = self.transport.print_reproduceable(self.request) # print(debug_data['envelope']) @@ -115,4 +117,4 @@ def test_virtualGuest(self): thisVsi = self.transport(vsiRequest) self.assertEqual(thisVsi.get('id'), vsi.get('id')) - ## TODO MORE COMPLEX OBJECT FILTERS! \ No newline at end of file + # TODO MORE COMPLEX OBJECT FILTERS! diff --git a/tests/transports/xmlrpc_tests.py b/tests/transports/xmlrpc_tests.py index c59eded0c..8852a327d 100644 --- a/tests/transports/xmlrpc_tests.py +++ b/tests/transports/xmlrpc_tests.py @@ -460,8 +460,3 @@ def test_verify(request, timeout=mock.ANY, verify=expected, auth=None) - - - - - From 8616d3ebf6fb7bbc965a15a3b98f5c33b524c905 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 25 Apr 2022 16:19:05 -0500 Subject: [PATCH 0618/1385] fixed tox style issues --- SoftLayer/transports/__init__.py | 13 ++-- SoftLayer/transports/fixture.py | 3 +- SoftLayer/transports/rest.py | 3 +- SoftLayer/transports/soap.py | 99 ++++++++++++----------------- SoftLayer/transports/timing.py | 3 +- SoftLayer/transports/xmlrpc.py | 2 - tests/transport_tests.py | 8 +-- tests/transports/debug_tests.py | 8 +-- tests/transports/rest_tests.py | 6 +- tests/transports/soap_tests.py | 19 +----- tests/transports/transport_tests.py | 10 --- tests/transports/xmlrpc_tests.py | 1 - tools/test-requirements.txt | 2 +- 13 files changed, 58 insertions(+), 119 deletions(-) diff --git a/SoftLayer/transports/__init__.py b/SoftLayer/transports/__init__.py index 454f32997..c254a780c 100644 --- a/SoftLayer/transports/__init__.py +++ b/SoftLayer/transports/__init__.py @@ -5,21 +5,16 @@ :license: MIT, see LICENSE for more details. """ +# Required imports to not break existing code. -import requests - - -# Required imports to not break existing code. -from .rest import RestTransport -from .xmlrpc import XmlRpcTransport +from .debug import DebugTransport from .fixture import FixtureTransport +from .rest import RestTransport from .timing import TimingTransport -from .debug import DebugTransport - from .transport import Request from .transport import SoftLayerListResult as SoftLayerListResult - +from .xmlrpc import XmlRpcTransport # transports.Request does have a lot of instance attributes. :( # pylint: disable=too-many-instance-attributes, no-self-use diff --git a/SoftLayer/transports/fixture.py b/SoftLayer/transports/fixture.py index 2fa016665..d94fdabe9 100644 --- a/SoftLayer/transports/fixture.py +++ b/SoftLayer/transports/fixture.py @@ -26,6 +26,7 @@ def __call__(self, call): message = '{}::{} fixture is not implemented'.format(call.service, call.method) raise NotImplementedError(message) from ex - def print_reproduceable(self, call): + @staticmethod + def print_reproduceable(call): """Not Implemented""" return call.service diff --git a/SoftLayer/transports/rest.py b/SoftLayer/transports/rest.py index 3af9c1e40..53d360b1e 100644 --- a/SoftLayer/transports/rest.py +++ b/SoftLayer/transports/rest.py @@ -157,7 +157,8 @@ def __call__(self, request): except requests.RequestException as ex: raise exceptions.TransportError(0, str(ex)) - def print_reproduceable(self, request): + @staticmethod + def print_reproduceable(request): """Prints out the minimal python code to reproduce a specific request The will also automatically replace the API key so its not accidently exposed. diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 585e4e12d..6422b671e 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -6,31 +6,22 @@ :license: MIT, see LICENSE for more details. """ import logging -import re -from string import Template -from zeep import Client, Settings, Transport, xsd -from zeep.helpers import serialize_object +from zeep import Client +from zeep import Settings +from zeep import Transport +from zeep import xsd + from zeep.cache import SqliteCache +from zeep.helpers import serialize_object from zeep.plugins import HistoryPlugin -from zeep.wsdl.messages.multiref import process_multiref - - -import requests from SoftLayer import consts from SoftLayer import exceptions -from .transport import _format_object_mask -from .transport import _proxies_dict -from .transport import ComplexEncoder -from .transport import get_session -from .transport import SoftLayerListResult - -from pprint import pprint as pp - +# pylint: disable=too-many-instance-attributes class SoapTransport(object): - """XML-RPC transport.""" + """SoapTransport.""" def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): @@ -44,7 +35,7 @@ def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, self.verify = verify self._client = None self.history = HistoryPlugin() - self.soapNS = "http://api.service.softlayer.com/soap/v3.1/" + self.soapns = "http://api.service.softlayer.com/soap/v3.1/" def __call__(self, request): """Makes a SoftLayer API call against the SOAP endpoint. @@ -59,31 +50,31 @@ def __call__(self, request): # print(client.wsdl.dump()) # print("=============== WSDL ==============") - # MUST define headers like this because otherwise the objectMask header doesn't work + + # Must define headers like this because otherwise the objectMask header doesn't work # because it isn't sent in with a namespace. - xsdUserAuth = xsd.Element( - f"{{{self.soapNS}}}authenticate", + xsd_userauth = xsd.Element( + f"{{{self.soapns}}}authenticate", xsd.ComplexType([ - xsd.Element(f'{{{self.soapNS}}}username', xsd.String()), - xsd.Element(f'{{{self.soapNS}}}apiKey', xsd.String()) + xsd.Element(f'{{{self.soapns}}}username', xsd.String()), + xsd.Element(f'{{{self.soapns}}}apiKey', xsd.String()) ]) ) - factory = client.type_factory(f"{self.soapNS}") - theMask = client.get_type(f"{{{self.soapNS}}}SoftLayer_ObjectMask") - xsdMask = xsd.Element( - f"{{{self.soapNS}}}SoftLayer_ObjectMask", - factory['SoftLayer_ObjectMask'] + # factory = client.type_factory(f"{self.soapns}") + the_mask = client.get_type(f"{{{self.soapns}}}SoftLayer_ObjectMask") + xsd_mask = xsd.Element( + f"{{{self.soapns}}}SoftLayer_ObjectMask", the_mask ) # Object Filter - filterType = client.get_type(f"{{{self.soapNS}}}{request.service}ObjectFilter") - xsdFilter = xsd.Element( - f"{{{self.soapNS}}}{request.service}ObjectFilter", filterType + filter_type = client.get_type(f"{{{self.soapns}}}{request.service}ObjectFilter") + xsd_filter = xsd.Element( + f"{{{self.soapns}}}{request.service}ObjectFilter", filter_type ) # Result Limit - xsdResultLimit = xsd.Element( - f"{{{self.soapNS}}}resultLimit", + xsd_resultlimit = xsd.Element( + f"{{{self.soapns}}}resultLimit", xsd.ComplexType([ xsd.Element('limit', xsd.String()), xsd.Element('offset', xsd.String()), @@ -92,54 +83,47 @@ def __call__(self, request): # Might one day want to support unauthenticated requests, but for now assume user auth. headers = [ - xsdUserAuth(username=request.transport_user, apiKey=request.transport_password), + xsd_userauth(username=request.transport_user, apiKey=request.transport_password), ] if request.limit: - headers.append(xsdResultLimit(limit=request.limit, offset=request.offset)) + headers.append(xsd_resultlimit(limit=request.limit, offset=request.offset)) if request.mask: - headers.append(xsdMask(mask=request.mask)) + headers.append(xsd_mask(mask=request.mask)) if request.filter: # The ** here forces python to treat this dict as properties - headers.append(xsdFilter(**request.filter)) + headers.append(xsd_filter(**request.filter)) if request.identifier: - initParam = f"{request.service}InitParameters" - initParamType = client.get_type(f"{{{self.soapNS}}}{initParam}") - xsdInitParam = xsd.Element( - f"{{{self.soapNS}}}{initParam}", initParamType + init_param = f"{request.service}init_parameters" + init_paramtype = client.get_type(f"{{{self.soapns}}}{init_param}") + xsdinit_param = xsd.Element( + f"{{{self.soapns}}}{init_param}", init_paramtype ) # Might want to check if its an id or globalIdentifier at some point, for now only id. - headers.append(xsdInitParam(id=request.identifier)) + headers.append(xsdinit_param(id=request.identifier)) - # TODO Add params... maybe + # NEXT Add params... maybe try: method = getattr(client.service, request.method) except AttributeError as ex: - message = f"{request.service}::{request.method}() does not exist in {self.soapNS}{request.service}?wsdl" + message = f"{request.service}::{request.method}() does not exist in {self.soapns}{request.service}?wsdl" raise exceptions.TransportError(404, message) from ex result = method(_soapheaders=headers) - # result = client.service.getObject(_soapheaders=headers) - - # process_multiref(result['body']['getAllObjectsReturn']) - # print("^^^ RESULT ^^^^^^^") - - # TODO GET A WAY TO FIND TOTAL ITEMS - # print(result['header']['totalItems']['amount']) - # print(" ^^ ITEMS ^^^ ") + # NEXT GET A WAY TO FIND TOTAL ITEMS try: - methodReturn = f"{request.method}Return" + method_return = f"{request.method}Return" serialize = serialize_object(result) if serialize.get('body'): - return serialize['body'][methodReturn] + return serialize['body'][method_return] else: # Some responses (like SoftLayer_Account::getObject) don't have a body? return serialize - except KeyError as e: - message = f"Error serializeing response\n{result}\n" + except KeyError as ex: + message = f"Error serializeing response\n{result}\n{ex}" raise exceptions.TransportError(500, message) def print_reproduceable(self, request): @@ -149,5 +133,6 @@ def print_reproduceable(self, request): :param request request: Request object """ - + log = logging.getLogger(__name__) + log.DEBUG(f"{request.service}::{request.method}()") return self.history.last_sent diff --git a/SoftLayer/transports/timing.py b/SoftLayer/transports/timing.py index 5b9345276..d32020dfc 100644 --- a/SoftLayer/transports/timing.py +++ b/SoftLayer/transports/timing.py @@ -35,6 +35,7 @@ def get_last_calls(self): self.last_calls = [] return last_calls - def print_reproduceable(self, call): + @staticmethod + def print_reproduceable(call): """Not Implemented""" return call.service diff --git a/SoftLayer/transports/xmlrpc.py b/SoftLayer/transports/xmlrpc.py index c10481812..4830e376b 100644 --- a/SoftLayer/transports/xmlrpc.py +++ b/SoftLayer/transports/xmlrpc.py @@ -5,7 +5,6 @@ :license: MIT, see LICENSE for more details. """ -import logging import re from string import Template import xmlrpc.client @@ -17,7 +16,6 @@ from .transport import _format_object_mask from .transport import _proxies_dict -from .transport import ComplexEncoder from .transport import get_session from .transport import SoftLayerListResult diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 0c175df5c..2c4a6bfb6 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -4,16 +4,10 @@ :license: MIT, see LICENSE for more details. """ -import io -import json -from unittest import mock as mock -import warnings - -import pytest import requests +from unittest import mock as mock import SoftLayer -from SoftLayer import consts from SoftLayer import testing from SoftLayer import transports diff --git a/tests/transports/debug_tests.py b/tests/transports/debug_tests.py index 2527cb302..7fe9621c0 100644 --- a/tests/transports/debug_tests.py +++ b/tests/transports/debug_tests.py @@ -4,16 +4,10 @@ :license: MIT, see LICENSE for more details. """ -import io -import json -from unittest import mock as mock -import warnings - -import pytest import requests +from unittest import mock as mock import SoftLayer -from SoftLayer import consts from SoftLayer import testing from SoftLayer import transports diff --git a/tests/transports/rest_tests.py b/tests/transports/rest_tests.py index f0c69cea0..e8825c3f3 100644 --- a/tests/transports/rest_tests.py +++ b/tests/transports/rest_tests.py @@ -4,16 +4,12 @@ :license: MIT, see LICENSE for more details. """ -import io import json +import requests from unittest import mock as mock import warnings -import pytest -import requests - import SoftLayer -from SoftLayer import consts from SoftLayer import testing from SoftLayer import transports diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index d4b0bf335..50a8c7b37 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -5,22 +5,12 @@ :license: MIT, see LICENSE for more details. """ import io -import json -from unittest import mock as mock import os -import warnings - -import pytest import requests -import SoftLayer -from SoftLayer import consts from SoftLayer import testing -from SoftLayer.transports.soap import SoapTransport from SoftLayer.transports import Request - - -from pprint import pprint as pp +from SoftLayer.transports.soap import SoapTransport def get_soap_response(): @@ -58,7 +48,6 @@ def set_up(self): def test_call(self): data = self.transport(self.request) - pp(data) self.assertEqual(data.get('id'), 307608) self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") @@ -75,7 +64,6 @@ def test_call(self): def test_objectMask(self): self.request.mask = "mask[id,companyName]" data = self.transport(self.request) - pp(data) self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") self.assertIsNone(data.get('address1')) self.assertEqual(data.get('id'), 307608) @@ -88,10 +76,7 @@ def test_objectFilter(self): self.request.limit = 5 self.request.offset = 0 data = self.transport(self.request) - # pp(data) - # print("^^^ DATA **** ") for package in data: - self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") def test_virtualGuest(self): @@ -117,4 +102,4 @@ def test_virtualGuest(self): thisVsi = self.transport(vsiRequest) self.assertEqual(thisVsi.get('id'), vsi.get('id')) - # TODO MORE COMPLEX OBJECT FILTERS! + # NEXT MORE COMPLEX OBJECT FILTERS! diff --git a/tests/transports/transport_tests.py b/tests/transports/transport_tests.py index 32b1eaad9..c22d11b9d 100644 --- a/tests/transports/transport_tests.py +++ b/tests/transports/transport_tests.py @@ -4,16 +4,6 @@ :license: MIT, see LICENSE for more details. """ -import io -import json -from unittest import mock as mock -import warnings - -import pytest -import requests - -import SoftLayer -from SoftLayer import consts from SoftLayer import testing from SoftLayer import transports diff --git a/tests/transports/xmlrpc_tests.py b/tests/transports/xmlrpc_tests.py index 8852a327d..4797c3dc8 100644 --- a/tests/transports/xmlrpc_tests.py +++ b/tests/transports/xmlrpc_tests.py @@ -5,7 +5,6 @@ :license: MIT, see LICENSE for more details. """ import io -import json from unittest import mock as mock import warnings diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index ab52a13fa..b3c013b51 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -10,4 +10,4 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 -zeep \ No newline at end of file +zeep == 4.1.0 \ No newline at end of file From 7813c7939bd326a8cce2a1c74477028920cda9ed Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 26 Apr 2022 17:23:20 -0400 Subject: [PATCH 0619/1385] Unable to get VSI details when last TXN is "Software install is finished" --- SoftLayer/CLI/virt/detail.py | 8 +++++--- tests/CLI/modules/vs/vs_tests.py | 10 ++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index ac9453ddd..30ec44f52 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,11 +69,13 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) - last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), - utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) + last_transaction = '' + if result.get('lastTransaction') != []: + last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), + utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) table.add_row(['last_transaction', last_transaction]) - table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else 'Monthly']) table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 99fafc0bf..f56212561 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -947,3 +947,13 @@ def test_authorize_volume_and_portable_storage_vs(self): def test_monitoring_vs(self): result = self.run_command(['vs', 'monitoring', '1234']) self.assert_no_fail(result) + + def test_last_transaction_empty(self): + vg_return = SoftLayer_Virtual_Guest.getObject + transaction = [] + + vg_return['lastTransaction'] = transaction + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') + mock.return_value = vg_return + result = self.run_command(['vs', 'detail', '100']) + self.assert_no_fail(result) From 1ccbb072adf55f54664f638c5f419b1178d20281 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 27 Apr 2022 12:15:37 -0400 Subject: [PATCH 0620/1385] fix the team code review comments --- SoftLayer/CLI/autoscale/create.py | 21 ++++----------------- SoftLayer/managers/network.py | 2 -- SoftLayer/utils.py | 5 +++++ 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/SoftLayer/CLI/autoscale/create.py b/SoftLayer/CLI/autoscale/create.py index 86f17ddb8..1a0dde07a 100644 --- a/SoftLayer/CLI/autoscale/create.py +++ b/SoftLayer/CLI/autoscale/create.py @@ -1,7 +1,8 @@ -"""Order/create a dedicated server.""" +"""Order/Create a scale group.""" # :license: MIT, see LICENSE for more details. import click +from SoftLayer import utils import SoftLayer from SoftLayer.CLI import environment @@ -36,13 +37,10 @@ @helpers.multi_option('--disk', help="Disk sizes") @environment.pass_env def cli(env, **args): - """Order/create autoscale.""" + """Order/Create a scale group.""" scale = AutoScaleManager(env.client) network = SoftLayer.NetworkManager(env.client) - pods = network.get_closed_pods() - closure = [] - datacenter = network.get_datacenter(args.get('datacenter')) ssh_keys = [] @@ -102,16 +100,10 @@ def cli(env, **args): 'balancedTerminationFlag': False, 'virtualGuestMemberTemplate': virt_template, 'virtualGuestMemberCount': 0, - 'policies': policies.append(clean_dict(policy_template)), + 'policies': policies.append(utils.clean_dict(policy_template)), 'terminationPolicyId': args['termination_policy'] } - # print(virt_template) - - for pod in pods: - if args.get('datacenter') in str(pod['name']): - closure.append(pod['name']) - click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting scale group order.') @@ -130,8 +122,3 @@ def cli(env, **args): output = table env.fout(output) - - -def clean_dict(dictionary): - """Removes any `None` entires from the dictionary""" - return {k: v for k, v in dictionary.items() if v} diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 0128abb51..d37f778e3 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -830,8 +830,6 @@ def get_datacenter(self, _filter=None, datacenter=None): returns datacenter list. """ - _filter = None - if datacenter: _filter = {"name": {"operation": datacenter}} diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 05ef50471..d34befb8a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -427,3 +427,8 @@ def format_comment(comment, max_line_length=60): comment_length = len(word) + 1 return formatted_comment + + +def clean_dict(dictionary): + """Removes any `None` entires from the dictionary""" + return {k: v for k, v in dictionary.items() if v} From 87f84d0e3892bb6e670ac25991f81b3506518ad9 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 27 Apr 2022 16:13:30 -0500 Subject: [PATCH 0621/1385] improved soap unit tests --- .../soap/SoftLayer_Account_getObject.soap | 11 ++++ .../SoftLayer_Account_getVirtualGuests.soap | 19 +++++++ .../SoftLayer_Virtual_Guest_getObject.soap | 12 ++++ .../fixtures/soap/test_objectFilter.soap | 2 + SoftLayer/fixtures/soap/test_objectMask.soap | 2 + SoftLayer/transports/soap.py | 2 +- tests/transports/soap_tests.py | 55 +++++++++++-------- tests/transports/xmlrpc_tests.py | 30 +++++----- tools/requirements.txt | 3 +- tools/test-requirements.txt | 2 +- 10 files changed, 98 insertions(+), 40 deletions(-) create mode 100644 SoftLayer/fixtures/soap/SoftLayer_Account_getObject.soap create mode 100644 SoftLayer/fixtures/soap/SoftLayer_Account_getVirtualGuests.soap create mode 100644 SoftLayer/fixtures/soap/SoftLayer_Virtual_Guest_getObject.soap create mode 100644 SoftLayer/fixtures/soap/test_objectFilter.soap create mode 100644 SoftLayer/fixtures/soap/test_objectMask.soap diff --git a/SoftLayer/fixtures/soap/SoftLayer_Account_getObject.soap b/SoftLayer/fixtures/soap/SoftLayer_Account_getObject.soap new file mode 100644 index 000000000..3cec2abfc --- /dev/null +++ b/SoftLayer/fixtures/soap/SoftLayer_Account_getObject.soap @@ -0,0 +1,11 @@ + + + + + + SoftLayer Internal - Development Community + 307608 + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/soap/SoftLayer_Account_getVirtualGuests.soap b/SoftLayer/fixtures/soap/SoftLayer_Account_getVirtualGuests.soap new file mode 100644 index 000000000..7648b1aac --- /dev/null +++ b/SoftLayer/fixtures/soap/SoftLayer_Account_getVirtualGuests.soap @@ -0,0 +1,19 @@ + + + + + 1 + + + + + + + cgallo.com + KVM-Test + 121401696 + + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/soap/SoftLayer_Virtual_Guest_getObject.soap b/SoftLayer/fixtures/soap/SoftLayer_Virtual_Guest_getObject.soap new file mode 100644 index 000000000..ad3668c63 --- /dev/null +++ b/SoftLayer/fixtures/soap/SoftLayer_Virtual_Guest_getObject.soap @@ -0,0 +1,12 @@ + + + + + + ibm.com + KVM-Test + 121401696 + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/soap/test_objectFilter.soap b/SoftLayer/fixtures/soap/test_objectFilter.soap new file mode 100644 index 000000000..70847b8af --- /dev/null +++ b/SoftLayer/fixtures/soap/test_objectFilter.soap @@ -0,0 +1,2 @@ + +109562U_QUAD_PROCESSOR_MULTI_CORE_NEHALEM_EXQuad Processor Multi Core Nehalem EX2BARE_METAL_CPU126SINGLE_XEON_1200_SANDY_BRIDGE_HASWELLSingle Xeon 1200 Series (Sandy Bridge / Haswell)142SINGLE_XEON_2000_SANDY_BRIDGESingle Xeon 2000 Series (Sandy Bridge)143DUAL_XEON_2000_SANDY_BRIDGEDual Xeon 2000 Series (Sandy Bridge)1443U_GPUSpecialty Server: GPU \ No newline at end of file diff --git a/SoftLayer/fixtures/soap/test_objectMask.soap b/SoftLayer/fixtures/soap/test_objectMask.soap new file mode 100644 index 000000000..85ea89eb5 --- /dev/null +++ b/SoftLayer/fixtures/soap/test_objectMask.soap @@ -0,0 +1,2 @@ + +SoftLayer Internal - Development Community307608 \ No newline at end of file diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 6422b671e..19afab052 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -95,7 +95,7 @@ def __call__(self, request): headers.append(xsd_filter(**request.filter)) if request.identifier: - init_param = f"{request.service}init_parameters" + init_param = f"{request.service}InitParameters" init_paramtype = client.get_type(f"{{{self.soapns}}}{init_param}") xsdinit_param = xsd.Element( f"{{{self.soapns}}}{init_param}", init_paramtype diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index 50a8c7b37..e7ac4f611 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -1,5 +1,5 @@ """ - SoftLayer.tests.transports.xmlrc + SoftLayer.tests.transports.soap ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: MIT, see LICENSE for more details. @@ -7,27 +7,24 @@ import io import os import requests +from unittest import mock as mock from SoftLayer import testing from SoftLayer.transports import Request from SoftLayer.transports.soap import SoapTransport -def get_soap_response(): +def setup_response(filename, status_code=200, total_items=1): + basepath = os.path.dirname(__file__) + body = b'' + print(f"Base Path: {basepath}") + with open(f"{basepath}/../../SoftLayer/fixtures/soap/{filename}.soap", 'rb') as fixture: + body = fixture.read() response = requests.Response() - list_body = b''' - - - - - - - - -''' + list_body = body response.raw = io.BytesIO(list_body) - response.headers['SoftLayer-Total-Items'] = 10 - response.status_code = 200 + response.headers['SoftLayer-Total-Items'] = total_items + response.status_code = status_code return response @@ -35,9 +32,11 @@ class TestSoapAPICall(testing.TestCase): def set_up(self): self.transport = SoapTransport(endpoint_url='https://api.softlayer.com/soap/v3.1/') - self.response = get_soap_response() - self.user = os.getenv('SL_USER') - self.password = os.environ.get('SL_APIKEY') + + self.user = "testUser" + self.password = "testPassword" + # self.user = os.getenv('SL_USER') + # self.password = os.environ.get('SL_APIKEY') request = Request() request.service = 'SoftLayer_Account' request.method = 'getObject' @@ -45,8 +44,10 @@ def set_up(self): request.transport_password = self.password self.request = request - def test_call(self): - + @mock.patch('requests.Session.post') + def test_call(self, zeep_post): + zeep_post.return_value = setup_response('SoftLayer_Account_getObject') + self.request.mask = "mask[id,companyName]" data = self.transport(self.request) self.assertEqual(data.get('id'), 307608) self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") @@ -61,14 +62,18 @@ def test_call(self): # print(debug_data['envelope']) # self.assertEqual(":sdfsdf", debug_data) - def test_objectMask(self): + @mock.patch('requests.Session.post') + def test_objectMask(self, zeep_post): + zeep_post.return_value = setup_response('test_objectMask') self.request.mask = "mask[id,companyName]" data = self.transport(self.request) self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") self.assertIsNone(data.get('address1')) self.assertEqual(data.get('id'), 307608) - def test_objectFilter(self): + @mock.patch('requests.Session.post') + def test_objectFilter(self, zeep_post): + zeep_post.return_value = setup_response('test_objectFilter') self.request.service = "SoftLayer_Product_Package" self.request.method = "getAllObjects" self.request.mask = "mask[id,description,keyName,type[id,keyName],name]" @@ -79,7 +84,12 @@ def test_objectFilter(self): for package in data: self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") - def test_virtualGuest(self): + @mock.patch('requests.Session.post') + def test_virtualGuest(self, zeep_post): + zeep_post.side_effect = [ + setup_response('SoftLayer_Account_getVirtualGuests'), + setup_response('SoftLayer_Virtual_Guest_getObject') + ] accountRequest = Request() accountRequest.service = "SoftLayer_Account" accountRequest.method = "getVirtualGuests" @@ -90,6 +100,7 @@ def test_virtualGuest(self): accountRequest.transport_password = self.password vsis = self.transport(accountRequest) + self.assertEqual(1, len(vsis)) for vsi in vsis: self.assertGreater(vsi.get('id'), 1) vsiRequest = Request() diff --git a/tests/transports/xmlrpc_tests.py b/tests/transports/xmlrpc_tests.py index 4797c3dc8..6e669279e 100644 --- a/tests/transports/xmlrpc_tests.py +++ b/tests/transports/xmlrpc_tests.py @@ -43,7 +43,7 @@ def set_up(self): ) self.response = get_xmlrpc_response() - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_call(self, request): request.return_value = self.response @@ -95,7 +95,7 @@ def test_proxy_without_protocol(self): warnings.warn("Incorrect Exception raised. Expected a " "SoftLayer.TransportError error") - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_valid_proxy(self, request): request.return_value = self.response self.transport.proxy = 'http://localhost:3128' @@ -117,7 +117,7 @@ def test_valid_proxy(self, request): verify=True, auth=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_identifier(self, request): request.return_value = self.response @@ -135,7 +135,7 @@ def test_identifier(self, request): 1234 """.encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_filter(self, request): request.return_value = self.response @@ -153,7 +153,7 @@ def test_filter(self, request): ^= prefix """.encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_limit_offset(self, request): request.return_value = self.response @@ -173,7 +173,7 @@ def test_limit_offset(self, request): 10 """.encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_old_mask(self, request): request.return_value = self.response @@ -195,7 +195,7 @@ def test_old_mask(self, request): """.encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_mask_call_no_mask_prefix(self, request): request.return_value = self.response @@ -211,7 +211,7 @@ def test_mask_call_no_mask_prefix(self, request): "mask[something.nested]".encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_mask_call_v2(self, request): request.return_value = self.response @@ -227,7 +227,7 @@ def test_mask_call_v2(self, request): "mask[something[nested]]".encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_mask_call_filteredMask(self, request): request.return_value = self.response @@ -243,7 +243,7 @@ def test_mask_call_filteredMask(self, request): "filteredMask[something[nested]]".encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_mask_call_v2_dot(self, request): request.return_value = self.response @@ -258,7 +258,7 @@ def test_mask_call_v2_dot(self, request): self.assertIn("mask.something.nested".encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_request_exception(self, request): # Test Text Error e = requests.HTTPError('error') @@ -281,7 +281,7 @@ def test_print_reproduceable(self): output_text = self.transport.print_reproduceable(req) self.assertIn("https://test.com", output_text) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') @mock.patch('requests.auth.HTTPBasicAuth') def test_ibm_id_call(self, auth, request): request.return_value = self.response @@ -325,7 +325,7 @@ def test_ibm_id_call(self, auth, request): self.assertIsInstance(resp, transports.SoftLayerListResult) self.assertEqual(resp.total_count, 10) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_call_large_number_response(self, request): response = requests.Response() body = b''' @@ -359,7 +359,7 @@ def test_call_large_number_response(self, request): resp = self.transport(req) self.assertEqual(resp[0]['bytesUsed'], 2666148982056) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_nonascii_characters(self, request): request.return_value = self.response hostname = 'testé' @@ -411,7 +411,7 @@ def test_nonascii_characters(self, request): self.assertEqual(resp.total_count, 10) -@mock.patch('SoftLayer.transports.requests.Session.request') +@mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') @pytest.mark.parametrize( "transport_verify,request_verify,expected", [ diff --git a/tools/requirements.txt b/tools/requirements.txt index 880148ffd..dc49002d7 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -4,4 +4,5 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 -zeep +# only used for soap transport +# softlayer-zeep >= 5.0.0 diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index b3c013b51..4c79f53ac 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -10,4 +10,4 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 -zeep == 4.1.0 \ No newline at end of file +softlayer-zeep >= 5.0.0 \ No newline at end of file From d885ce5b71a62961d9cc216ba58069b17dbab535 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 27 Apr 2022 18:58:23 -0400 Subject: [PATCH 0622/1385] fix the tox tool --- SoftLayer/CLI/autoscale/create.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/autoscale/create.py b/SoftLayer/CLI/autoscale/create.py index 1a0dde07a..74ba9ba65 100644 --- a/SoftLayer/CLI/autoscale/create.py +++ b/SoftLayer/CLI/autoscale/create.py @@ -107,18 +107,18 @@ def cli(env, **args): if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting scale group order.') - else: - result = scale.create(order) - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' - table.add_row(['Id', result['id']]) - table.add_row(['Created', result['createDate']]) - table.add_row(['Name', result['name']]) - table.add_row(['Virtual Guest Id', result['virtualGuestMembers'][0]['virtualGuest']['id']]) - table.add_row(['Virtual Guest domain', result['virtualGuestMembers'][0]['virtualGuest']['domain']]) - table.add_row(['Virtual Guest hostname', result['virtualGuestMembers'][0]['virtualGuest']['hostname']]) - output = table + result = scale.create(order) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['Id', result['id']]) + table.add_row(['Created', result['createDate']]) + table.add_row(['Name', result['name']]) + table.add_row(['Virtual Guest Id', result['virtualGuestMembers'][0]['virtualGuest']['id']]) + table.add_row(['Virtual Guest domain', result['virtualGuestMembers'][0]['virtualGuest']['domain']]) + table.add_row(['Virtual Guest hostname', result['virtualGuestMembers'][0]['virtualGuest']['hostname']]) + output = table env.fout(output) From fbed4c2f0970cbe20c039b3a4a53f91e81a9d9ce Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 28 Apr 2022 08:32:28 -0400 Subject: [PATCH 0623/1385] fix team code review comments --- SoftLayer/CLI/virt/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 30ec44f52..94b37f742 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -70,7 +70,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) last_transaction = '' - if result.get('lastTransaction') != []: + if result.get('lastTransaction'): last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) From e0f7fc3d6f6ceb9ebb252c30ec35884e0cea5383 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 28 Apr 2022 09:34:56 -0500 Subject: [PATCH 0624/1385] fixed unit tests for SOAP transport --- .../fixtures/soap/test_objectFilter.soap | 47 ++++++- SoftLayer/transports/soap.py | 2 + setup.py | 3 +- tests/transport_tests.py | 133 ------------------ tests/transports/debug_tests.py | 2 +- tests/transports/rest_tests.py | 28 ++-- 6 files changed, 64 insertions(+), 151 deletions(-) delete mode 100644 tests/transport_tests.py diff --git a/SoftLayer/fixtures/soap/test_objectFilter.soap b/SoftLayer/fixtures/soap/test_objectFilter.soap index 70847b8af..70da7e976 100644 --- a/SoftLayer/fixtures/soap/test_objectFilter.soap +++ b/SoftLayer/fixtures/soap/test_objectFilter.soap @@ -1,2 +1,47 @@ -109562U_QUAD_PROCESSOR_MULTI_CORE_NEHALEM_EXQuad Processor Multi Core Nehalem EX2BARE_METAL_CPU126SINGLE_XEON_1200_SANDY_BRIDGE_HASWELLSingle Xeon 1200 Series (Sandy Bridge / Haswell)142SINGLE_XEON_2000_SANDY_BRIDGESingle Xeon 2000 Series (Sandy Bridge)143DUAL_XEON_2000_SANDY_BRIDGEDual Xeon 2000 Series (Sandy Bridge)1443U_GPUSpecialty Server: GPU \ No newline at end of file + + + + 109 + + + + + + + 56 + 2U_QUAD_PROCESSOR_MULTI_CORE_NEHALEM_EX + Quad Processor Multi Core Nehalem EX + + 2 + BARE_METAL_CPU + + + + 126 + SINGLE_XEON_1200_SANDY_BRIDGE_HASWELL + Single Xeon 1200 Series (Sandy Bridge / Haswell) + + + + 142 + SINGLE_XEON_2000_SANDY_BRIDGE + Single Xeon 2000 Series (Sandy Bridge) + + + + 143 + DUAL_XEON_2000_SANDY_BRIDGE + Dual Xeon 2000 Series (Sandy Bridge) + + + + 144 + 3U_GPU + Specialty Server: GPU + + + + + + \ No newline at end of file diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 19afab052..d604ec705 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -6,6 +6,8 @@ :license: MIT, see LICENSE for more details. """ import logging + +# Try to import zeep, make sure its softlayer_zeep, error otherwise from zeep import Client from zeep import Settings from zeep import Transport diff --git a/setup.py b/setup.py index 97514d838..09921feaf 100644 --- a/setup.py +++ b/setup.py @@ -39,8 +39,7 @@ 'requests >= 2.20.0', 'prompt_toolkit >= 2', 'pygments >= 2.0.0', - 'urllib3 >= 1.24', - 'zeep' + 'urllib3 >= 1.24' ], keywords=['softlayer', 'cloud', 'slcli'], classifiers=[ diff --git a/tests/transport_tests.py b/tests/transport_tests.py deleted file mode 100644 index 2c4a6bfb6..000000000 --- a/tests/transport_tests.py +++ /dev/null @@ -1,133 +0,0 @@ -""" - SoftLayer.tests.transport_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -import requests -from unittest import mock as mock - -import SoftLayer -from SoftLayer import testing -from SoftLayer import transports - - -class TestFixtureTransport(testing.TestCase): - - def set_up(self): - self.transport = transports.FixtureTransport() - - def test_basic(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - resp = self.transport(req) - self.assertEqual(resp['accountId'], 1234) - - def test_no_module(self): - req = transports.Request() - req.service = 'Doesnt_Exist' - req.method = 'getObject' - self.assertRaises(NotImplementedError, self.transport, req) - - def test_no_method(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObjectzzzz' - self.assertRaises(NotImplementedError, self.transport, req) - - -class TestTimingTransport(testing.TestCase): - - def set_up(self): - fixture_transport = transports.FixtureTransport() - self.transport = transports.TimingTransport(fixture_transport) - - def test_call(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - resp = self.transport(req) - self.assertEqual(resp['accountId'], 1234) - - def test_get_last_calls(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - resp = self.transport(req) - self.assertEqual(resp['accountId'], 1234) - calls = self.transport.get_last_calls() - self.assertEqual(calls[0][0].service, 'SoftLayer_Account') - - def test_print_reproduceable(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - output_text = self.transport.print_reproduceable(req) - self.assertEqual('SoftLayer_Account', output_text) - - -class TestDebugTransport(testing.TestCase): - - def set_up(self): - fixture_transport = transports.FixtureTransport() - self.transport = transports.DebugTransport(fixture_transport) - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - self.req = req - - def test_call(self): - - resp = self.transport(self.req) - self.assertEqual(resp['accountId'], 1234) - - def test_get_last_calls(self): - - resp = self.transport(self.req) - self.assertEqual(resp['accountId'], 1234) - calls = self.transport.get_last_calls() - self.assertEqual(calls[0].service, 'SoftLayer_Account') - - def test_print_reproduceable(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - output_text = self.transport.print_reproduceable(self.req) - self.assertEqual('SoftLayer_Account', output_text) - - def test_print_reproduceable_post(self): - req = transports.Request() - req.url = "https://test.com" - req.payload = "testing" - req.transport_headers = {"test-headers": 'aaaa'} - req.args = 'createObject' - - rest_transport = transports.RestTransport() - transport = transports.DebugTransport(rest_transport) - - output_text = transport.print_reproduceable(req) - - self.assertIn("https://test.com", output_text) - self.assertIn("-X POST", output_text) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_error(self, request): - # Test JSON Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.text = '''{ - "error": "description", - "code": "Error Code" - }''' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - rest_transport = transports.RestTransport() - transport = transports.DebugTransport(rest_transport) - self.assertRaises(SoftLayer.SoftLayerAPIError, transport, req) - calls = transport.get_last_calls() - self.assertEqual(404, calls[0].exception.faultCode) diff --git a/tests/transports/debug_tests.py b/tests/transports/debug_tests.py index 7fe9621c0..a95f32edd 100644 --- a/tests/transports/debug_tests.py +++ b/tests/transports/debug_tests.py @@ -56,7 +56,7 @@ def test_print_reproduceable_post(self): self.assertIn("https://test.com", output_text) self.assertIn("-X POST", output_text) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_error(self, request): # Test JSON Error e = requests.HTTPError('error') diff --git a/tests/transports/rest_tests.py b/tests/transports/rest_tests.py index e8825c3f3..2c3d5f68f 100644 --- a/tests/transports/rest_tests.py +++ b/tests/transports/rest_tests.py @@ -21,7 +21,7 @@ def set_up(self): endpoint_url='http://something9999999999999999999999.com', ) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_basic(self, request): request().content = '[]' request().text = '[]' @@ -48,7 +48,7 @@ def test_basic(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_http_and_json_error(self, request): # Test JSON Error e = requests.HTTPError('error') @@ -65,7 +65,7 @@ def test_http_and_json_error(self, request): req.method = 'Resource' self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_http_and_empty_error(self, request): # Test JSON Error e = requests.HTTPError('error') @@ -79,7 +79,7 @@ def test_http_and_empty_error(self, request): req.method = 'Resource' self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_empty_error(self, request): # Test empty response error. request().text = '' @@ -89,7 +89,7 @@ def test_empty_error(self, request): req.method = 'Resource' self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_json_error(self, request): # Test non-json response error. request().text = 'Not JSON' @@ -109,7 +109,7 @@ def test_proxy_without_protocol(self): except AssertionError: warnings.warn("AssertionError raised instead of a SoftLayer.TransportError error") - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_valid_proxy(self, request): request().text = '{}' self.transport.proxy = 'http://localhost:3128' @@ -132,7 +132,7 @@ def test_valid_proxy(self, request): timeout=mock.ANY, headers=mock.ANY) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_id(self, request): request().text = '{}' @@ -156,7 +156,7 @@ def test_with_id(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_args(self, request): request().text = '{}' @@ -180,7 +180,7 @@ def test_with_args(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_args_bytes(self, request): request().text = '{}' @@ -204,7 +204,7 @@ def test_with_args_bytes(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_filter(self, request): request().text = '{}' @@ -229,7 +229,7 @@ def test_with_filter(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_mask(self, request): request().text = '{}' @@ -274,7 +274,7 @@ def test_with_mask(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_limit_offset(self, request): request().text = '{}' @@ -300,7 +300,7 @@ def test_with_limit_offset(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_unknown_error(self, request): e = requests.RequestException('error') e.response = mock.MagicMock() @@ -314,7 +314,7 @@ def test_unknown_error(self, request): self.assertRaises(SoftLayer.TransportError, self.transport, req) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') @mock.patch('requests.auth.HTTPBasicAuth') def test_with_special_auth(self, auth, request): request().text = '{}' From 2bb1d332bd4d7ce8d880239bc97b002c141bca83 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 28 Apr 2022 18:20:42 -0400 Subject: [PATCH 0625/1385] new command on autoscale delete --- SoftLayer/CLI/autoscale/delete.py | 28 +++++++++++++++++++++ SoftLayer/CLI/routes.py | 3 ++- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 1 + SoftLayer/managers/autoscale.py | 10 ++++++++ docs/cli/autoscale.rst | 4 +++ tests/CLI/modules/autoscale_tests.py | 4 +++ tests/managers/autoscale_tests.py | 4 +++ 7 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/autoscale/delete.py diff --git a/SoftLayer/CLI/autoscale/delete.py b/SoftLayer/CLI/autoscale/delete.py new file mode 100644 index 000000000..6b790758f --- /dev/null +++ b/SoftLayer/CLI/autoscale/delete.py @@ -0,0 +1,28 @@ +"""Delete autoscale.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.autoscale import AutoScaleManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Sets a user's status to CANCEL_PENDING, which will immediately disable the account, + + and will eventually be fully removed from the account by an automated internal process. + + Example: slcli user delete userId + + """ + + autoscale = AutoScaleManager(env.client) + result = autoscale.delete(identifier) + + if result: + click.secho("%s deleted successfully" % identifier, fg='green') + else: + click.secho("Failed to delete %s" % identifier, fg='red') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index ac2d5084d..a4cdff5cd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -377,7 +377,8 @@ ('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'), ('autoscale:logs', 'SoftLayer.CLI.autoscale.logs:cli'), ('autoscale:tag', 'SoftLayer.CLI.autoscale.tag:cli'), - ('autoscale:edit', 'SoftLayer.CLI.autoscale.edit:cli') + ('autoscale:edit', 'SoftLayer.CLI.autoscale.edit:cli'), + ('autoscale:delete', 'SoftLayer.CLI.autoscale.delete:cli'), ] ALL_ALIASES = { diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index f04d8f56e..909ed88af 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -455,3 +455,4 @@ ] editObject = True +forceDeleteObject = True diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 78fa18e31..772aa40fa 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -116,3 +116,13 @@ def edit(self, identifier, template): .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ """ return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier) + + def delete(self, identifier): + """Calls `SoftLayer_Scale_Group::forceDeleteObject()`_ + + :param identifier: SoftLayer_Scale_Group id + + .. _SoftLayer_Scale_Group::forceDeleteObject(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/forceDeleteObject/ + """ + return self.client.call('SoftLayer_Scale_Group', 'forceDeleteObject', id=identifier) diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst index a3aa31462..f1ebf1462 100644 --- a/docs/cli/autoscale.rst +++ b/docs/cli/autoscale.rst @@ -34,5 +34,9 @@ For making changes to the triggers or the autoscale group itself, see the `Autos :prog: autoscale edit :show-nested: +.. click:: SoftLayer.CLI.autoscale.delete:cli + :prog: autoscale delte + :show-nested: + .. _Autoscale Portal: https://cloud.ibm.com/classic/autoscale \ No newline at end of file diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index cb3cdfdb9..5d09209c8 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -89,3 +89,7 @@ def test_autoscale_edit_userfile(self, manager): result = self.run_command(['autoscale', 'edit', '12345', '--userfile', userfile.name]) self.assert_no_fail(result) manager.assert_called_with('12345', template) + + def test_autoscale_delete(self): + result = self.run_command(['autoscale', 'delete', '12345']) + self.assert_no_fail(result) diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py index 6da505409..3d41e6219 100644 --- a/tests/managers/autoscale_tests.py +++ b/tests/managers/autoscale_tests.py @@ -123,3 +123,7 @@ def test_edit_object(self): 'editObject', args=(template,), identifier=12345) + + def test_delete_object(self): + self.autoscale.delete(12345) + self.assert_called_with('SoftLayer_Scale_Group', 'forceDeleteObject') From 2a753cd5f4a32bf2c701ddefcb7aee74ed67986e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 29 Apr 2022 08:49:09 -0400 Subject: [PATCH 0626/1385] fix the tox analysis --- docs/cli/autoscale.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst index f1ebf1462..5e05c4630 100644 --- a/docs/cli/autoscale.rst +++ b/docs/cli/autoscale.rst @@ -35,7 +35,7 @@ For making changes to the triggers or the autoscale group itself, see the `Autos :show-nested: .. click:: SoftLayer.CLI.autoscale.delete:cli - :prog: autoscale delte + :prog: autoscale delete :show-nested: From 1bd74f8a597eb2e11280eb008c7da8a8d429f2f0 Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Fri, 29 Apr 2022 16:07:00 -0400 Subject: [PATCH 0627/1385] Fix response table title --- SoftLayer/CLI/autoscale/scale.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/autoscale/scale.py b/SoftLayer/CLI/autoscale/scale.py index 69fe1305a..8bc22c279 100644 --- a/SoftLayer/CLI/autoscale/scale.py +++ b/SoftLayer/CLI/autoscale/scale.py @@ -37,14 +37,14 @@ def cli(env, identifier, scale_up, scale_by, amount): try: # Check if the first guest has a cancellation date, assume we are removing guests if it is. - cancel_date = result[0]['virtualGuest']['billingItem']['cancellationDate'] or False + status = result[0]['virtualGuest']['status']['keyName'] or False except (IndexError, KeyError, TypeError): - cancel_date = False + status = False - if cancel_date: - member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Cancelled Guests") - else: + if status == 'ACTIVE': member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Added Guests") + else: + member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Cancelled Guests") for guest in result: real_guest = guest.get('virtualGuest') From 2d30c20cf278a5322be7a4fe26ba3f87b1584318 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 29 Apr 2022 18:12:44 -0400 Subject: [PATCH 0628/1385] fix the team code review comments --- SoftLayer/CLI/autoscale/delete.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/autoscale/delete.py b/SoftLayer/CLI/autoscale/delete.py index 6b790758f..3591a3e33 100644 --- a/SoftLayer/CLI/autoscale/delete.py +++ b/SoftLayer/CLI/autoscale/delete.py @@ -11,11 +11,9 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Sets a user's status to CANCEL_PENDING, which will immediately disable the account, + """Delete this group and destroy all members of it. - and will eventually be fully removed from the account by an automated internal process. - - Example: slcli user delete userId + Example: slcli autoscale delete autoscaleId """ @@ -24,5 +22,3 @@ def cli(env, identifier): if result: click.secho("%s deleted successfully" % identifier, fg='green') - else: - click.secho("Failed to delete %s" % identifier, fg='red') From fc09359c0f701f9f89efe90eec57089b12f440fe Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 29 Apr 2022 18:33:13 -0400 Subject: [PATCH 0629/1385] fix the team code review comments --- SoftLayer/CLI/autoscale/delete.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/autoscale/delete.py b/SoftLayer/CLI/autoscale/delete.py index 3591a3e33..34f41ddc3 100644 --- a/SoftLayer/CLI/autoscale/delete.py +++ b/SoftLayer/CLI/autoscale/delete.py @@ -1,7 +1,7 @@ """Delete autoscale.""" # :license: MIT, see LICENSE for more details. - import click +import SoftLayer from SoftLayer.CLI import environment from SoftLayer.managers.autoscale import AutoScaleManager @@ -18,7 +18,9 @@ def cli(env, identifier): """ autoscale = AutoScaleManager(env.client) - result = autoscale.delete(identifier) - if result: + try: + autoscale.delete(identifier) click.secho("%s deleted successfully" % identifier, fg='green') + except SoftLayer.SoftLayerAPIError as ex: + click.secho("Failed to delete %s\n%s" % (identifier, ex), fg='red') From a72aac4c2f46d817afbfd1f48c0ea3b43b6b3d69 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 3 May 2022 16:35:56 -0500 Subject: [PATCH 0630/1385] some basic textualize support #1630 --- SoftLayer/CLI/core.py | 120 ++++++++++++++++++++++++++++++++++-- setup.py | 7 ++- tools/requirements.txt | 5 +- tools/test-requirements.txt | 5 +- 4 files changed, 124 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 0f7c12f8c..2c303195c 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +import inspect import logging import os import sys @@ -15,6 +16,15 @@ import click import requests +from rich.console import Console, RenderableType +from rich.markup import escape +from rich.text import Text +from rich.highlighter import RegexHighlighter +from rich.panel import Panel +from rich.table import Table +from rich.theme import Theme + + import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions @@ -40,6 +50,24 @@ DEFAULT_FORMAT = 'table' +class OptionHighlighter(RegexHighlighter): + highlights = [ + r"(?P\-\w)", # single options like -v + r"(?P