From b21f4c1e8b9a2dc6c29710f33fc360e5accdb035 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 3 Oct 2023 17:08:29 -0500 Subject: [PATCH 1/8] #2104 added vlan trunks on the server detail page, improved performance of 'hw detail' --- .secrets.baseline | 4 +- SoftLayer/CLI/hardware/detail.py | 29 +++- .../fixtures/SoftLayer_Hardware_Server.py | 63 +++++++- SoftLayer/managers/hardware.py | 92 +++++++++++ tests/CLI/modules/server_tests.py | 151 ++++++------------ tests/managers/hardware_tests.py | 28 +++- 6 files changed, 249 insertions(+), 118 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index cafb8b61a..cb9626bd5 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$", "lines": null }, - "generated_at": "2023-09-27T14:21:34Z", + "generated_at": "2023-10-03T22:08:00Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -238,7 +238,7 @@ "hashed_secret": "fb5f2f1b65d1f2bc130ce9d5729b38d12f2b444e", "is_secret": false, "is_verified": false, - "line_number": 259, + "line_number": 260, "type": "Secret Keyword", "verified_result": null } diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index ba4c8f3f4..49feb866f 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -29,7 +29,7 @@ def cli(env, identifier, passwords, price, components): table.align['value'] = 'l' hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') - result = hardware.get_hardware(hardware_id) + result = hardware.get_hardware_fast(hardware_id) result = utils.NestedDict(result) hard_drives = hardware.get_hard_drives(hardware_id) @@ -72,11 +72,30 @@ def cli(env, identifier, passwords, price, components): table.add_row(['last_transaction', last_transaction]) table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else 'Monthly']) - vlan_table = formatting.Table(['type', 'number', 'id', 'name', 'netmask']) + vlan_table = formatting.Table(['Network', 'Number', 'Id', 'Name', 'Type']) for vlan in result['networkVlans']: - vlan_table.add_row([vlan['networkSpace'], vlan['vlanNumber'], - vlan['id'], vlan['fullyQualifiedName'], - vlan['primarySubnets'][0]['netmask']]) + vlan_table.add_row([ + vlan.get('networkSpace'), + vlan.get('vlanNumber'), + vlan['id'], + vlan['fullyQualifiedName'], + 'Primary' + ]) + + # Shows any VLANS trunked/tagged on this server + for component in result.get('networkComponents', []): + # These are the Primary network components + if component.get('primaryIpAddress', False): + uplink = component.get('uplinkComponent') + for trunk in uplink.get('networkVlanTrunks'): + trunk_vlan = trunk.get('networkVlan') + vlan_table.add_row([ + trunk_vlan.get('networkSpace'), + trunk_vlan.get('vlanNumber'), + trunk_vlan.get('id'), + trunk_vlan.get('fullyQualifiedName'), + 'Trunked' + ]) table.add_row(['vlans', vlan_table]) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 8b1f3c90d..35552957c 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -161,6 +161,7 @@ } } ] +getNetworkComponents = getFrontendNetworkComponents getBandwidthAllotmentDetail = { 'allocationId': 25465663, @@ -410,4 +411,64 @@ { "createDate": "2019-10-14T16:51:12-06:00", "version": "5.10" - }]}}] + } + ] + } +}] +getActiveComponents = getComponents +getActiveTransaction = getObject['activeTransaction'] +getOperatingSystem = getObject['operatingSystem'] +getSoftwareComponents = [ + { + "hardwareId": 1907356, + "id": 59003868, + "manufacturerLicenseInstance": "", + "softwareLicense": { + "id": 20658, + "softwareDescriptionId": 2888, + "softwareDescription": { + "controlPanel": 0, + "id": 2888, + "licenseTermValue": 0, + "longDescription": "Juniper vSRX 1G 19.4R2-S3 Standard 19.4.2.3", + "manufacturer": "Juniper", + "name": "vSRX 1G 19.4R2-S3 Standard", + "operatingSystem": 1, + "referenceCode": "UBUNTU_18_64", + "upgradeSoftwareDescriptionId": None, + "upgradeSwDescId": None, + "version": "19.4.2.3", + "virtualLicense": 0, + "virtualizationPlatform": 0, + "requiredUser": "root" + } + } + }, + { + "hardwareId": 1907356, + "id": 59003870, + "manufacturerLicenseInstance": "", + "softwareLicense": { + "id": 147, + "softwareDescriptionId": 148, + "softwareDescription": { + "controlPanel": 0, + "id": 148, + "licenseTermValue": None, + "longDescription": "Passmark Suite Latest", + "manufacturer": "Passmark", + "name": "Passmark Suite", + "operatingSystem": 0, + "upgradeSoftwareDescriptionId": None, + "upgradeSwDescId": None, + "version": "Latest", + "virtualLicense": 0, + "virtualizationPlatform": 0 + } + } + } +] +getBillingItem = getObject['billingItem'] +getTagReferences = getObject['tagReferences'] +getNetworkVlans = getObject['networkVlans'] +getRemoteManagementAccounts = getObject['remoteManagementAccounts'] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 4cff9675b..6b9922a93 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +import concurrent.futures as cf import datetime import logging import socket @@ -276,6 +277,97 @@ def get_hardware(self, hardware_id, **kwargs): return self.hardware.getObject(id=hardware_id, **kwargs) + @retry(logger=LOGGER) + def get_hardware_fast(self, hardware_id): + """Get details about a hardware device. Similar to get_hardware() but this uses threads + + :param integer id: the hardware ID + :returns: A dictionary containing a large amount of information about the specified server. + """ + + hw_mask = ( + 'id, globalIdentifier, fullyQualifiedDomainName, hostname, domain,' + 'provisionDate, hardwareStatus, bareMetalInstanceFlag, processorPhysicalCoreAmount,' + 'memoryCapacity, notes, privateNetworkOnlyFlag, primaryBackendIpAddress,' + 'primaryIpAddress, networkManagementIpAddress, userData, datacenter, hourlyBillingFlag,' + 'lastTransaction[transactionGroup], hardwareChassis[id,name]' + ) + server = self.client.call('SoftLayer_Hardware_Server', 'getObject', id=hardware_id, mask=hw_mask) + with cf.ThreadPoolExecutor(max_workers=10) as executor: + networkComponentsMask = ( + "id, status, speed, maxSpeed, name, ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress," + "port, primarySubnet[id, netmask, broadcastAddress, networkIdentifier, gateway]," + "uplinkComponent[networkVlanTrunks[networkVlan[networkSpace]]]" + ) + networkComponents = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getNetworkComponents', + id=hardware_id, mask=networkComponentsMask + ) + activeComponentsMask = ( + 'id,hardwareComponentModel[hardwareGenericComponentModel[id,hardwareComponentType[keyName]]]' + ) + activeComponents = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getActiveComponents', + id=hardware_id, mask=activeComponentsMask + ) + + activeTransaction = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getActiveTransaction', + id=hardware_id, mask="id, transactionStatus[friendlyName,name]" + ) + + operatingSystemMask = ( + 'softwareLicense[softwareDescription[manufacturer, name, version, referenceCode]],' + 'passwords[id,username,password]' + ) + operatingSystem = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getOperatingSystem', + id=hardware_id, mask=operatingSystemMask + ) + + # Intentionally reusing the operatingSystemMask here. They are both softwareComponents + softwareComponents = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getSoftwareComponents', + id=hardware_id, mask=operatingSystemMask + ) + + billingItemMask = ( + 'id,nextInvoiceTotalRecurringAmount,' + 'nextInvoiceChildren[nextInvoiceTotalRecurringAmount],' + 'orderItem.order.userRecord[username]' + ) + billingItem = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getBillingItem', + id=hardware_id, mask=billingItemMask + ) + + tagReferences = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getTagReferences', + id=hardware_id, mask="id,tag[name,id]" + ) + + networkVlans = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getNetworkVlans', + id=hardware_id, mask="id,vlanNumber,networkSpace,fullyQualifiedName,primarySubnets[ipAddresses]" + ) + + remoteManagementAccounts = executor.submit( + self.client.call, 'SoftLayer_Hardware_Server', 'getRemoteManagementAccounts', + id=hardware_id, mask="username,password" + ) + + server['networkComponents'] = networkComponents.result() + server['activeComponents'] = activeComponents.result() + server['activeTransaction'] = activeTransaction.result() + server['operatingSystem'] = operatingSystem.result() + server['softwareComponents'] = softwareComponents.result() + server['billingItem'] = billingItem.result() + server['networkVlans'] = networkVlans.result() + server['remoteManagementAccounts'] = remoteManagementAccounts.result() + server['tagReferences'] = tagReferences.result() + + return server + def reload(self, hardware_id, post_uri=None, ssh_keys=None, lvm=False): """Perform an OS reload of a server with its current configuration. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 4b4e6edab..af3f8c87c 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -105,10 +105,7 @@ def test_server_credentials_exception_password_not_found(self): result = self.run_command(['hardware', 'credentials', '12345']) - self.assertEqual( - 'None', - str(result.exception) - ) + self.assertEqual('None', str(result.exception)) def test_server_details(self): result = self.run_command(['server', 'detail', '1234', '--passwords', '--price']) @@ -119,28 +116,17 @@ def test_server_details(self): self.assertEqual(output['prices'][0]['Recurring Price'], 16.08) self.assertEqual(output['remote users'][0]['password'], 'abc123') self.assertEqual(output['users'][0]['username'], 'root') - self.assertEqual(output['vlans'][0]['number'], 1800) + self.assertEqual(output['vlans'][0]['Number'], 1800) self.assertEqual(output['owner'], 'chechu') self.assertEqual(output['Bandwidth'][0]['Allotment'], '250') def test_detail_vs_empty_tag(self): - mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') - mock.return_value = { - 'id': 100, - 'processorPhysicalCoreAmount': 2, - 'memoryCapacity': 2, - 'tagReferences': [ - {'tag': {'name': 'example-tag'}}, - {}, - ], - } + mock = self.set_mock('SoftLayer_Hardware_Server', 'getTagReferences') + mock.return_value = [{'tag': {'name': 'example-tag'}}, {}] result = self.run_command(['server', 'detail', '100']) self.assert_no_fail(result) - self.assertEqual( - json.loads(result.output)['tags'], - ['example-tag'], - ) + self.assertEqual(json.loads(result.output)['tags'], ['example-tag']) def test_detail_empty_allotment(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail') @@ -148,10 +134,7 @@ def test_detail_empty_allotment(self): result = self.run_command(['server', 'detail', '100']) self.assert_no_fail(result) - self.assertEqual( - json.loads(result.output)['Bandwidth'][0]['Allotment'], - '-', - ) + self.assertEqual(json.loads(result.output)['Bandwidth'][0]['Allotment'], '-') def test_detail_drives(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') @@ -255,15 +238,13 @@ def test_cancel_server(self, cancel_mock, ngb_mock): ngb_mock.return_value = False # Check the positive case - result = self.run_command(['--really', 'server', 'cancel', '12345', - '--reason=Test', '--comment=Test']) + result = self.run_command(['--really', 'server', 'cancel', '12345', '--reason=Test', '--comment=Test']) self.assert_no_fail(result) cancel_mock.assert_called_with(12345, "Test", "Test", False) # Test - result = self.run_command(['server', 'cancel', '12345', - '--reason=Test', '--comment=Test']) + result = self.run_command(['server', 'cancel', '12345', '--reason=Test', '--comment=Test']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) @@ -273,8 +254,7 @@ def test_server_power_off(self, confirm_mock): # Check the positive case result = self.run_command(['--really', 'server', 'power-off', '12345']) - self.assert_called_with('SoftLayer_Hardware_Server', 'powerOff', - identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'powerOff', identifier=12345) # Now check to make sure we properly call CLIAbort in the negative case confirm_mock.return_value = False @@ -287,24 +267,19 @@ def test_server_reboot_default(self): result = self.run_command(['--really', 'server', 'reboot', '12345']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Hardware_Server', 'rebootDefault', - identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'rebootDefault', identifier=12345) def test_server_reboot_soft(self): - result = self.run_command(['--really', 'server', 'reboot', '12345', - '--soft']) + result = self.run_command(['--really', 'server', 'reboot', '12345', '--soft']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Hardware_Server', 'rebootSoft', - identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'rebootSoft', identifier=12345) def test_server_reboot_hard(self): - result = self.run_command(['--really', 'server', 'reboot', '12345', - '--hard']) + result = self.run_command(['--really', 'server', 'reboot', '12345', '--hard']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Hardware_Server', 'rebootHard', - identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'rebootHard', identifier=12345) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_server_reboot_negative(self, confirm_mock): @@ -318,8 +293,7 @@ def test_server_power_on(self): result = self.run_command(['--really', 'server', 'power-on', '12345']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Hardware_Server', 'powerOn', - identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'powerOn', identifier=12345) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_harware_power_on_force(self, confirm_mock): @@ -336,12 +310,10 @@ def test_harware_power_off_force(self, confirm_mock): self.assertEqual('Aborted.', result.exception.message) def test_server_power_cycle(self): - result = self.run_command(['--really', 'server', 'power-cycle', - '12345']) + result = self.run_command(['--really', 'server', 'power-cycle', '12345']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Hardware_Server', 'powerCycle', - identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'powerCycle', identifier=12345) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_server_power_cycle_negative(self, confirm_mock): @@ -498,8 +470,7 @@ def test_edit_server_userdata(self): self.assert_no_fail(result) self.assertEqual(result.output, "") self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', - args=({'domain': 'test.sftlyr.ws', - 'hostname': 'hardware-test1'},), + args=({'domain': 'test.sftlyr.ws', 'hostname': 'hardware-test1'},), identifier=1000) @mock.patch('SoftLayer.HardwareManager.edit') @@ -512,10 +483,7 @@ def test_edit_server_failed(self, edit_mock): self.assertEqual(result.exit_code, 2) self.assertEqual(result.output, "") - edit_mock.assert_called_with(1000, - userdata='My data', - domain='test.sftlyr.ws', - hostname='hardware-test1') + edit_mock.assert_called_with(1000, userdata='My data', domain='test.sftlyr.ws', hostname='hardware-test1') def test_edit_server_userfile(self): if (sys.platform.startswith("win")): @@ -523,15 +491,12 @@ def test_edit_server_userfile(self): with tempfile.NamedTemporaryFile() as userfile: userfile.write(b"some data") userfile.flush() - result = self.run_command(['server', 'edit', '1000', - '--userfile=%s' % userfile.name]) + result = self.run_command(['server', 'edit', '1000', '--userfile=%s' % userfile.name]) self.assert_no_fail(result) self.assertEqual(result.output, "") - self.assert_called_with('SoftLayer_Hardware_Server', - 'setUserMetadata', - args=(['some data'],), - identifier=1000) + self.assert_called_with('SoftLayer_Hardware_Server', 'setUserMetadata', + args=(['some data'],), identifier=1000) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_update_firmware(self, confirm_mock): @@ -540,8 +505,7 @@ def test_update_firmware(self, confirm_mock): self.assert_no_fail(result) self.assertEqual(result.output, "") - self.assert_called_with('SoftLayer_Hardware_Server', - 'createFirmwareUpdateTransaction', + self.assert_called_with('SoftLayer_Hardware_Server', 'createFirmwareUpdateTransaction', args=((1, 1, 1, 1)), identifier=1000) @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -551,8 +515,7 @@ def test_reflash_firmware(self, confirm_mock): self.assert_no_fail(result) self.assertEqual(result.output, 'Successfully device firmware reflashed\n') - self.assert_called_with('SoftLayer_Hardware_Server', - 'createFirmwareReflashTransaction', + self.assert_called_with('SoftLayer_Hardware_Server', 'createFirmwareReflashTransaction', args=((1, 1, 1)), identifier=1000) def test_edit(self): @@ -715,8 +678,7 @@ def test_bandwidth_hw_quite(self): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): confirm_mock.return_value = True - getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server', - 'getReverseDomainRecords') + getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server', 'getReverseDomainRecords') getReverseDomainRecords.return_value = [{ 'networkAddress': '172.16.1.100', 'name': '2.240.16.172.in-addr.arpa', @@ -727,8 +689,7 @@ def test_dns_sync_both(self, confirm_mock): 'serial': 1234665663, 'id': 123456, }] - getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', - 'getResourceRecords') + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ 'type': 'a', @@ -749,20 +710,14 @@ def test_dns_sync_both(self, confirm_mock): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Dns_Domain', 'getResourceRecords') - self.assert_called_with('SoftLayer_Hardware_Server', - 'getReverseDomainRecords') - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', - args=createAargs) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', - args=createPTRargs) + self.assert_called_with('SoftLayer_Hardware_Server', 'getReverseDomainRecords') + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=createAargs) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=createPTRargs) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_v6(self, confirm_mock): confirm_mock.return_value = True - getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', - 'getResourceRecords') + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') getResourceRecords.return_value = [] server = self.set_mock('SoftLayer_Hardware_Server', 'getObject') test_server = { @@ -795,9 +750,7 @@ def test_dns_sync_v6(self, confirm_mock): server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', - args=createV6args) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=createV6args) v6Record = { 'id': 1, @@ -807,18 +760,14 @@ def test_dns_sync_v6(self, confirm_mock): 'type': 'aaaa' } - getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', - 'getResourceRecords') + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') getResourceRecords.return_value = [v6Record] editArgs = (v6Record,) result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'editObject', - args=editArgs) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'editObject', args=editArgs) - getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', - 'getResourceRecords') + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') getResourceRecords.return_value = [v6Record, v6Record] result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assertEqual(result.exit_code, 1) @@ -827,8 +776,7 @@ def test_dns_sync_v6(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_a(self, confirm_mock): confirm_mock.return_value = True - getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', - 'getResourceRecords') + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') getResourceRecords.return_value = [ {'id': 1, 'ttl': 7200, 'data': '1.1.1.1', 'host': 'hardware-test1', 'type': 'a'} @@ -839,12 +787,9 @@ def test_dns_sync_edit_a(self, confirm_mock): ) result = self.run_command(['hw', 'dns-sync', '-a', '1000']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'editObject', - args=editArgs) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'editObject', args=editArgs) - getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', - 'getResourceRecords') + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') getResourceRecords.return_value = [ {'id': 1, 'ttl': 7200, 'data': '1.1.1.1', 'host': 'hardware-test1', 'type': 'a'}, @@ -858,8 +803,7 @@ def test_dns_sync_edit_a(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_ptr(self, confirm_mock): confirm_mock.return_value = True - getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server', - 'getReverseDomainRecords') + getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server', 'getReverseDomainRecords') getReverseDomainRecords.return_value = [{ 'networkAddress': '172.16.1.100', 'name': '100.1.16.172.in-addr.arpa', @@ -870,13 +814,10 @@ def test_dns_sync_edit_ptr(self, confirm_mock): 'serial': 1234665663, 'id': 123456, }] - editArgs = ({'host': '100', 'data': 'hardware-test1.test.sftlyr.ws', - 'id': 123, 'ttl': 7200},) + editArgs = ({'host': '100', 'data': 'hardware-test1.test.sftlyr.ws', 'id': 123, 'ttl': 7200},) result = self.run_command(['hw', 'dns-sync', '--ptr', '1000']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'editObject', - args=editArgs) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'editObject', args=editArgs) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_misc_exception(self, confirm_mock): @@ -900,9 +841,7 @@ def test_dns_sync_misc_exception(self, confirm_mock): self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_hardware_storage(self): - result = self.run_command( - ['hw', 'storage', '100']) - + result = self.run_command(['hw', 'storage', '100']) self.assert_no_fail(result) def test_billing(self): @@ -1083,3 +1022,11 @@ def test_list_hw_search_noargs_domain(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware domain: *test*',)) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_hardware_cancel_no_force(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hardware', 'cancel', '102']) + + self.assertEqual(2, result.exit_code) + self.assertEqual('Aborted', result.exception.message) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 05790469a..59e7950b6 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -953,6 +953,26 @@ def test_create_credential(self): self.hardware.create_credential(template) self.assert_called_with('SoftLayer_Software_Component_Password', 'createObject') + def test_get_hardware_fast(self): + result = self.hardware.get_hardware_fast(1234) + self.assertIn('networkComponents', result) + self.assertIn('activeComponents', result) + self.assertIn('activeTransaction', result) + self.assertIn('operatingSystem', result) + self.assertIn('softwareComponents', result) + self.assertIn('billingItem', result) + self.assertIn('networkVlans', result) + self.assertIn('remoteManagementAccounts', result) + self.assertIn('tagReferences', result) + self.assert_called_with('SoftLayer_Hardware_Server', 'getNetworkComponents') + self.assert_called_with('SoftLayer_Hardware_Server', 'getActiveComponents') + self.assert_called_with('SoftLayer_Hardware_Server', 'getOperatingSystem') + self.assert_called_with('SoftLayer_Hardware_Server', 'getSoftwareComponents') + self.assert_called_with('SoftLayer_Hardware_Server', 'getBillingItem') + self.assert_called_with('SoftLayer_Hardware_Server', 'getTagReferences') + self.assert_called_with('SoftLayer_Hardware_Server', 'getNetworkVlans') + self.assert_called_with('SoftLayer_Hardware_Server', 'getRemoteManagementAccounts') + class HardwareHelperTests(testing.TestCase): @@ -1052,11 +1072,3 @@ def test_is_private(self): 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)) - - @mock.patch('SoftLayer.CLI.formatting.confirm') - def test_hardware_cancel_no_force(self, confirm_mock): - confirm_mock.return_value = False - result = self.run_command(['hardware', 'cancel', '102']) - - self.assertEqual(2, result.exit_code) - self.assertEqual('Aborted', result.exception.message) From c0ab2cc47190f3a50321aa99b95e04ea82519533 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 5 Oct 2023 15:01:26 -0500 Subject: [PATCH 2/8] Added 'slcli hw vlan-add' and 'slcli hw vlan-list' --- SoftLayer/CLI/hardware/vlan_add.py | 58 +++++++++++++++++++++++++++++ SoftLayer/CLI/hardware/vlan_list.py | 34 +++++++++++++++++ SoftLayer/CLI/routes.py | 2 + SoftLayer/managers/hardware.py | 44 ++++++++++++++++------ SoftLayer/managers/network.py | 15 ++++++++ docs/cli/hardware.rst | 8 ++++ 6 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 SoftLayer/CLI/hardware/vlan_add.py create mode 100644 SoftLayer/CLI/hardware/vlan_list.py diff --git a/SoftLayer/CLI/hardware/vlan_add.py b/SoftLayer/CLI/hardware/vlan_add.py new file mode 100644 index 000000000..c0846c21e --- /dev/null +++ b/SoftLayer/CLI/hardware/vlan_add.py @@ -0,0 +1,58 @@ +"""Trunk a VLAN to this 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(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('hardware', nargs=1) +@click.argument('vlans', nargs=-1) +@environment.pass_env +def cli(env, hardware, vlans): + """Trunk a VLAN to this server. + + HARDWARE is the id of the server + VLANS is the ID, name, or number of the VLANs you want to add. Multiple vlans can be added at the same time. + It is recommended to use the vlan ID, especially if you have multiple vlans with the same name/number. + """ + + h_mgr = SoftLayer.HardwareManager(env.client) + n_mgr = SoftLayer.NetworkManager(env.client) + hw_id = helpers.resolve_id(h_mgr.resolve_ids, hardware, 'hardware') + # Enclosing in quotes is required for any input that has a space in it. + # "Public DAL10" for example needs to be sent to search as \"Public DAL10\" + sl_vlans = n_mgr.search_for_vlan(" ".join(f"\"{v}\"" for v in vlans)) + if not sl_vlans: + raise exceptions.ArgumentError(f"No vlans found matching {' '.join(vlans)}") + add_vlans = parse_vlans(sl_vlans) + component_mask = "mask[id, name, port, macAddress, primaryIpAddress]" + # TODO: Add nice output / exception handling + if len(add_vlans['public']) > 0: + components = h_mgr.get_network_components(hw_id, mask=component_mask, space='public') + for c in components: + if c.get('primaryIpAddress'): + h_mgr.trunk_vlan(c.get('id'), add_vlans['public']) + if len(add_vlans['private']) > 0: + components = h_mgr.get_network_components(hw_id, mask=component_mask, space='private') + for c in components: + if c.get('primaryIpAddress'): + h_mgr.trunk_vlan(c.get('id'), add_vlans['private']) + + +def parse_vlans(vlans): + """returns a dictionary mapping for public / private vlans""" + + pub_vlan = [] + pri_vlan = [] + for vlan in vlans: + print(f"{vlan.get('networkSpace')} | {vlan.get('id')} -> {vlan.get('vlanNumber')}") + if vlan.get('networkSpace') == "PUBLIC": + pub_vlan.append(vlan) + else: + pri_vlan.append(vlan) + return {"public": pub_vlan, "private": pri_vlan} diff --git a/SoftLayer/CLI/hardware/vlan_list.py b/SoftLayer/CLI/hardware/vlan_list.py new file mode 100644 index 000000000..25347d160 --- /dev/null +++ b/SoftLayer/CLI/hardware/vlan_list.py @@ -0,0 +1,34 @@ +"""List VLANs this server can be attached to.""" +# :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(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('hardware') +@environment.pass_env +def cli(env, hardware): + """List VLANs this server can be attached to.""" + + mgr = SoftLayer.HardwareManager(env.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, hardware, 'hardware') + mask = ( + "mask[id,primaryIpAddress," + "networkVlansTrunkable[id,name,vlanNumber,fullyQualifiedName,networkSpace]]" + ) + table = formatting.Table([ + "ID", "VLAN", "Name", "Space" + ]) + hw_components = env.client.call('SoftLayer_Hardware_Server', 'getNetworkComponents', id=hw_id, mask=mask) + for component in hw_components: + if component.get('primaryIpAddress'): + for vlan in component.get('networkVlansTrunkable', []): + table.add_row([ + vlan.get('id'), vlan.get('fullyQualifiedName'), vlan.get('name'), vlan.get('networkSpace') + ]) + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index dbaf1cfe0..5bbf5fa0b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -315,6 +315,8 @@ ('hardware:notification-add', 'SoftLayer.CLI.hardware.notification_add:cli'), ('hardware:notification-delete', 'SoftLayer.CLI.hardware.notification_delete:cli'), ('hardware:create-credential', 'SoftLayer.CLI.hardware.create_credential:cli'), + ('hardware:vlan-list', 'SoftLayer.CLI.hardware.vlan_list:cli'), + ('hardware:vlan-add', 'SoftLayer.CLI.hardware.vlan_add:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 6b9922a93..82f8a7d03 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1164,22 +1164,42 @@ def get_components(self, hardware_id, mask=None, filter_component=None): "createDate": { "operation": "orderBy", "options": [ - { - "name": "sort", - "value": [ - "DESC" - ] - }, - { - "name": "sortOrder", - "value": [ - 1 - ]}]} - }}}} + {"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_network_components(self, hardware_id, mask=None, space=None): + """Calls SoftLayer_Hardware_Server::getNetworkComponents() + + :param int hardware_id: SoftLayer_Hardware_Server id + :param string mask: The object mask to use if you do not want the default + :param string space: 'public', 'private', or None for both. + :returns: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_Component/ + """ + + if mask is None: + mask = "mask[uplinkComponent, router, redundancyEnabledFlag, redundancyCapableFlag]" + method = "getNetworkComponents" + if space == "public": + method = "getFrontendNetworkComponents" + elif space == "private": + method = "getBackendNetworkComponents" + return self.client.call("SoftLayer_Hardware_Server", method, id=hardware_id, mask=mask) + + def trunk_vlan(self, component_id, vlans): + """Calls SoftLayer_Network_Component::addNetworkVlanTrunks() + + :param int component_id: SoftLayer_Network_Component id + :param list vlans: list of SoftLayer_Network_Vlan objects to add. Each object needs at least id or vlanNumber + """ + return self.client.call('SoftLayer_Network_Component', 'addNetworkVlanTrunks', vlans, id=component_id) + def get_sensors(self, hardware_id): """Returns Hardware sensor data""" return self.client.call('Hardware', 'getSensorData', id=hardware_id) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index ac720109b..11554beee 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -81,6 +81,7 @@ def __init__(self, client): self.subnet = client['Network_Subnet'] self.network_storage = self.client['Network_Storage'] self.security_group = self.client['Network_SecurityGroup'] + self.resolvers = [self.search_for_vlan] def add_global_ip(self, version=4, test_order=False): """Adds a global IP address to the account. @@ -887,3 +888,17 @@ def clear_route(self, identifier): returns true or false. """ return self.client.call('SoftLayer_Network_Subnet', 'clearRoute', id=identifier) + + def search_for_vlan(self, vlan): + """Returns a list of matching VLAN objects. + + :param string vlan: Could be either vlan name, number, id, or fully qualified name + :return list: List of SoftLayer_Network_Vlan objects + """ + + query = f"_objectType:SoftLayer_Network_Vlan {vlan}" + mask = "mask[resource(SoftLayer_Network_Vlan)[id,name,vlanNumber,fullyQualifiedName,networkSpace]]" + + results = self.client.call('SoftLayer_Search', 'advancedSearch', query, mask=mask) + # This returns JUST the Network_Vlan information, none of the search information. + return [result.get('resource') for result in results] diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index ce55f2cc9..569f5c991 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -147,3 +147,11 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.create_credential:cli :prog: hardware create-credential :show-nested: + +.. click:: SoftLayer.CLI.hardware.vlan_add:cli + :prog: hardware vlan_add + :show-nested: + +.. click:: SoftLayer.CLI.hardware.vlan_list:cli + :prog: hardware vlan_list + :show-nested: \ No newline at end of file From 18567ee6803afabb79f4c8dce4644076c7e4f59c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 11 Oct 2023 15:01:47 -0500 Subject: [PATCH 3/8] #2104 added 'hardware vlan-remove' command --- SoftLayer/CLI/formatting.py | 21 ++++++ SoftLayer/CLI/hardware/vlan_add.py | 4 +- SoftLayer/CLI/hardware/vlan_remove.py | 65 +++++++++++++++++ .../{vlan_list.py => vlan_trunkable.py} | 9 +-- SoftLayer/CLI/routes.py | 3 +- SoftLayer/managers/hardware.py | 28 ++++++++ docs/cli/hardware.rst | 10 ++- tests/CLI/formatting_table_tests.py | 72 +++++++++++++++++++ tests/CLI/helper_tests.py | 6 -- 9 files changed, 203 insertions(+), 15 deletions(-) create mode 100644 SoftLayer/CLI/hardware/vlan_remove.py rename SoftLayer/CLI/hardware/{vlan_list.py => vlan_trunkable.py} (88%) create mode 100644 tests/CLI/formatting_table_tests.py diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 17731fe25..de531d95b 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -318,6 +318,23 @@ def __init__(self, columns, title=None, align=None): self.align = align or {} self.sortby = None self.title = title + # Used to print a message if the table is empty + self.empty_message = None + + def __bool__(self): + """Useful for seeing if the table has any rows""" + return len(self.rows) > 0 + + def set_empty_message(self, message): + """Sets the empty message for this table for env.fout + + Set this message if you want to print a message instead of a table to the user + but still want the json output to print an empty list `[]` + + :param message str: Message to print if the table has no rows + """ + self.empty_message = message + def add_row(self, row): """Add a row to the table. @@ -337,6 +354,10 @@ def to_python(self): def prettytable(self, fmt='table', theme=None): """Returns a RICH table instance.""" + + # Used to print a message instead of a bad looking empty table + if not self and self.empty_message: + return self.empty_message box_style = box.SQUARE if fmt == 'raw': box_style = None diff --git a/SoftLayer/CLI/hardware/vlan_add.py b/SoftLayer/CLI/hardware/vlan_add.py index c0846c21e..3109d2078 100644 --- a/SoftLayer/CLI/hardware/vlan_add.py +++ b/SoftLayer/CLI/hardware/vlan_add.py @@ -21,6 +21,8 @@ def cli(env, hardware, vlans): It is recommended to use the vlan ID, especially if you have multiple vlans with the same name/number. """ + if not vlans: + raise exceptions.ArgumentError("Error: Missing argument 'VLANS'.") h_mgr = SoftLayer.HardwareManager(env.client) n_mgr = SoftLayer.NetworkManager(env.client) hw_id = helpers.resolve_id(h_mgr.resolve_ids, hardware, 'hardware') @@ -31,7 +33,7 @@ def cli(env, hardware, vlans): raise exceptions.ArgumentError(f"No vlans found matching {' '.join(vlans)}") add_vlans = parse_vlans(sl_vlans) component_mask = "mask[id, name, port, macAddress, primaryIpAddress]" - # TODO: Add nice output / exception handling + # NEXT: Add nice output / exception handling if len(add_vlans['public']) > 0: components = h_mgr.get_network_components(hw_id, mask=component_mask, space='public') for c in components: diff --git a/SoftLayer/CLI/hardware/vlan_remove.py b/SoftLayer/CLI/hardware/vlan_remove.py new file mode 100644 index 000000000..36f3bd1f8 --- /dev/null +++ b/SoftLayer/CLI/hardware/vlan_remove.py @@ -0,0 +1,65 @@ +"""Remove VLANs trunked to this 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(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('hardware', nargs=1) +@click.argument('vlans', nargs=-1) +@click.option('--all', 'all_vlans', is_flag=True, default=False, help="Remove ALL trunked vlans from this server.") +@environment.pass_env +def cli(env, hardware, vlans, all_vlans): + """Remove VLANs trunked to this server. + + HARDWARE is the id of the server + VLANS is the ID, name, or number of the VLANs you want to remove. Multiple vlans can be removed at the same time. + It is recommended to use the vlan ID, especially if you have multiple vlans with the same name/number. + """ + if not vlans and not all_vlans: + raise exceptions.ArgumentError("Error: Missing argument 'VLANS'.") + h_mgr = SoftLayer.HardwareManager(env.client) + n_mgr = SoftLayer.NetworkManager(env.client) + hw_id = helpers.resolve_id(h_mgr.resolve_ids, hardware, 'hardware') + + if all_vlans: + h_mgr.clear_vlan(hw_id) + env.fout("Done.") + return + + # Enclosing in quotes is required for any input that has a space in it. + # "Public DAL10" for example needs to be sent to search as \"Public DAL10\" + sl_vlans = n_mgr.search_for_vlan(" ".join(f"\"{v}\"" for v in vlans)) + if not sl_vlans: + raise exceptions.ArgumentError(f"No vlans found matching {' '.join(vlans)}") + del_vlans = parse_vlans(sl_vlans) + component_mask = "mask[id, name, port, macAddress, primaryIpAddress]" + # NEXT: Add nice output / exception handling + if len(del_vlans['public']) > 0: + components = h_mgr.get_network_components(hw_id, mask=component_mask, space='public') + for c in components: + if c.get('primaryIpAddress'): + h_mgr.remove_vlan(c.get('id'), del_vlans['public']) + if len(del_vlans['private']) > 0: + components = h_mgr.get_network_components(hw_id, mask=component_mask, space='private') + for c in components: + if c.get('primaryIpAddress'): + h_mgr.remove_vlan(c.get('id'), del_vlans['private']) + + +def parse_vlans(vlans): + """returns a dictionary mapping for public / private vlans""" + + pub_vlan = [] + pri_vlan = [] + for vlan in vlans: + if vlan.get('networkSpace') == "PUBLIC": + pub_vlan.append(vlan) + else: + pri_vlan.append(vlan) + return {"public": pub_vlan, "private": pri_vlan} diff --git a/SoftLayer/CLI/hardware/vlan_list.py b/SoftLayer/CLI/hardware/vlan_trunkable.py similarity index 88% rename from SoftLayer/CLI/hardware/vlan_list.py rename to SoftLayer/CLI/hardware/vlan_trunkable.py index 25347d160..9477ca016 100644 --- a/SoftLayer/CLI/hardware/vlan_list.py +++ b/SoftLayer/CLI/hardware/vlan_trunkable.py @@ -8,7 +8,7 @@ from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers - +from pprint import pprint as pp @click.command(cls=SoftLayer.CLI.command.SLCommand, ) @click.argument('hardware') @environment.pass_env @@ -21,14 +21,15 @@ def cli(env, hardware): "mask[id,primaryIpAddress," "networkVlansTrunkable[id,name,vlanNumber,fullyQualifiedName,networkSpace]]" ) - table = formatting.Table([ - "ID", "VLAN", "Name", "Space" - ]) + table = formatting.Table(["ID", "VLAN", "Name", "Space"]) + table.set_empty_message("No trunkable vlans found.") hw_components = env.client.call('SoftLayer_Hardware_Server', 'getNetworkComponents', id=hw_id, mask=mask) + for component in hw_components: if component.get('primaryIpAddress'): for vlan in component.get('networkVlansTrunkable', []): table.add_row([ vlan.get('id'), vlan.get('fullyQualifiedName'), vlan.get('name'), vlan.get('networkSpace') ]) + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5bbf5fa0b..f6ba2a8cb 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -315,8 +315,9 @@ ('hardware:notification-add', 'SoftLayer.CLI.hardware.notification_add:cli'), ('hardware:notification-delete', 'SoftLayer.CLI.hardware.notification_delete:cli'), ('hardware:create-credential', 'SoftLayer.CLI.hardware.create_credential:cli'), - ('hardware:vlan-list', 'SoftLayer.CLI.hardware.vlan_list:cli'), + ('hardware:vlan-trunkable', 'SoftLayer.CLI.hardware.vlan_trunkable:cli'), ('hardware:vlan-add', 'SoftLayer.CLI.hardware.vlan_add:cli'), + ('hardware:vlan-remove', 'SoftLayer.CLI.hardware.vlan_remove:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 82f8a7d03..c59ba99db 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1200,6 +1200,34 @@ def trunk_vlan(self, component_id, vlans): """ return self.client.call('SoftLayer_Network_Component', 'addNetworkVlanTrunks', vlans, id=component_id) + def remove_vlan(self, component_id, vlans): + """Calls SoftLayer_Network_Component::removeNetworkVlanTrunks() + + :param int component_id: SoftLayer_Network_Component id + :param list vlans: list of SoftLayer_Network_Vlan objects to remove. Each object needs at least id or vlanNumber + """ + return self.client.call('SoftLayer_Network_Component', 'removeNetworkVlanTrunks', vlans, id=component_id) + def clear_vlan(self, hardware_id): + """Clears all vlan trunks from a hardware_id + + :param int hardware_id: server to clear vlans from + """ + component_mask = ( + "mask[id, " + "backendNetworkComponents[id,networkVlanTrunks[networkVlanId]], " + "frontendNetworkComponents[id,networkVlanTrunks[networkVlanId]]" + "]" + ) + components = self.client.call('SoftLayer_Hardware_Server', 'getObject', id=hardware_id, mask=component_mask) + back_component_id = utils.lookup(components, 'backendNetworkComponent', 'id') + front_component_id = utils.lookup(components, 'frontendNetworkComponent', 'id') + for c in components.get('backendNetworkComponent', []): + if len(c.get('networkVlanTrunks')): + self.client.call('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', id=c.get('id')) + for c in components.get('frontendNetworkComponent', []): + if len(c.get('networkVlanTrunks')): + self.client.call('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', id=c.get('id')) + def get_sensors(self, hardware_id): """Returns Hardware sensor data""" return self.client.call('Hardware', 'getSensorData', id=hardware_id) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 569f5c991..535dd2d38 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -149,9 +149,13 @@ This function updates the firmware of a server. If already at the latest version :show-nested: .. click:: SoftLayer.CLI.hardware.vlan_add:cli - :prog: hardware vlan_add + :prog: hardware vlan-add :show-nested: -.. click:: SoftLayer.CLI.hardware.vlan_list:cli - :prog: hardware vlan_list +.. click:: SoftLayer.CLI.hardware.vlan_remove:cli + :prog: hardware vlan-remove + :show-nested: + +.. click:: SoftLayer.CLI.hardware.vlan_trunkable:cli + :prog: hardware vlan-trunkable :show-nested: \ No newline at end of file diff --git a/tests/CLI/formatting_table_tests.py b/tests/CLI/formatting_table_tests.py new file mode 100644 index 000000000..e7ea392e9 --- /dev/null +++ b/tests/CLI/formatting_table_tests.py @@ -0,0 +1,72 @@ +""" + SoftLayer.tests.CLI.formatting_table_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import json +import os +import sys +import tempfile + +import click +from rich.table import Table +from unittest import mock as mock + +from SoftLayer.CLI import core +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.CLI import template +from SoftLayer import testing + +class TestTable(testing.TestCase): + + def test_table_with_duplicated_columns(self): + self.assertRaises(exceptions.CLIHalt, formatting.Table, ['col', 'col']) + + def test_boolean_table(self): + table = formatting.Table(["column1"], title="Test Title") + self.assertFalse(table) + table.add_row(["entry1"]) + self.assertTrue(table) + + +class IterToTableTests(testing.TestCase): + + def test_format_api_dict(self): + result = formatting._format_dict({'key': 'value'}) + + self.assertIsInstance(result, formatting.Table) + self.assertEqual(result.columns, ['name', 'value']) + self.assertEqual(result.rows, [['key', 'value']]) + + def test_format_api_list(self): + result = formatting._format_list([{'key': 'value'}]) + + self.assertIsInstance(result, formatting.Table) + self.assertEqual(result.columns, ['key']) + self.assertEqual(result.rows, [['value']]) + + def test_format_api_list_non_objects(self): + result = formatting._format_list(['a', 'b', 'c']) + + 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']) + + def test_format_api_list_with_empty_array(self): + result = formatting.iter_to_table([{'id': 130224450, 'activeTickets': []}]) + self.assertIsInstance(result, formatting.Table) + self.assertIn('id', result.columns) + self.assertIn('activeTickets', result.columns) + formatted = formatting.format_output(result, "table") + # No good ways to test whats actually in a Rich.Table without going through the hassel of + # printing it out. As long as this didn't throw and exception it should be fine. + self.assertEqual(formatted.row_count, 1) \ No newline at end of file diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index 1c8c2d91d..262e98d00 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -255,12 +255,6 @@ def test_resolve_id_multiple(self): exceptions.CLIAbort, helpers.resolve_id, lambda r: [12345, 54321], 'test') -class TestTable(testing.TestCase): - - def test_table_with_duplicated_columns(self): - self.assertRaises(exceptions.CLIHalt, formatting.Table, ['col', 'col']) - - class TestFormatOutput(testing.TestCase): def test_format_output_string(self): From 4b3519b98802a8cc41ddadb97fe962fd57210ff4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 11 Oct 2023 15:17:03 -0500 Subject: [PATCH 4/8] tox fixes --- SoftLayer/CLI/formatting.py | 1 - SoftLayer/CLI/hardware/vlan_trunkable.py | 2 +- SoftLayer/managers/hardware.py | 9 +++++---- tests/CLI/formatting_table_tests.py | 14 ++------------ 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index de531d95b..9c32318df 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -335,7 +335,6 @@ def set_empty_message(self, message): """ self.empty_message = message - def add_row(self, row): """Add a row to the table. diff --git a/SoftLayer/CLI/hardware/vlan_trunkable.py b/SoftLayer/CLI/hardware/vlan_trunkable.py index 9477ca016..670a8df03 100644 --- a/SoftLayer/CLI/hardware/vlan_trunkable.py +++ b/SoftLayer/CLI/hardware/vlan_trunkable.py @@ -8,7 +8,7 @@ from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers -from pprint import pprint as pp + @click.command(cls=SoftLayer.CLI.command.SLCommand, ) @click.argument('hardware') @environment.pass_env diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c59ba99db..c68482ecf 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -21,7 +21,7 @@ LOGGER = logging.getLogger(__name__) # Invalid names are ignored due to long method names and short argument names -# pylint: disable=invalid-name, too-many-lines +# pylint: disable=invalid-name, too-many-lines, too-many-public-methods EXTRA_CATEGORIES = ['pri_ipv6_addresses', 'static_ipv6_addresses', @@ -1207,6 +1207,7 @@ def remove_vlan(self, component_id, vlans): :param list vlans: list of SoftLayer_Network_Vlan objects to remove. Each object needs at least id or vlanNumber """ return self.client.call('SoftLayer_Network_Component', 'removeNetworkVlanTrunks', vlans, id=component_id) + def clear_vlan(self, hardware_id): """Clears all vlan trunks from a hardware_id @@ -1219,14 +1220,14 @@ def clear_vlan(self, hardware_id): "]" ) components = self.client.call('SoftLayer_Hardware_Server', 'getObject', id=hardware_id, mask=component_mask) - back_component_id = utils.lookup(components, 'backendNetworkComponent', 'id') - front_component_id = utils.lookup(components, 'frontendNetworkComponent', 'id') + # We only want to call this API on components with actual trunks. + # Calling this on the primary and redundant components might cause exceptions. for c in components.get('backendNetworkComponent', []): if len(c.get('networkVlanTrunks')): self.client.call('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', id=c.get('id')) for c in components.get('frontendNetworkComponent', []): if len(c.get('networkVlanTrunks')): - self.client.call('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', id=c.get('id')) + self.client.call('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', id=c.get('id')) def get_sensors(self, hardware_id): """Returns Hardware sensor data""" diff --git a/tests/CLI/formatting_table_tests.py b/tests/CLI/formatting_table_tests.py index e7ea392e9..117667072 100644 --- a/tests/CLI/formatting_table_tests.py +++ b/tests/CLI/formatting_table_tests.py @@ -4,22 +4,12 @@ :license: MIT, see LICENSE for more details. """ -import json -import os -import sys -import tempfile -import click -from rich.table import Table -from unittest import mock as mock - -from SoftLayer.CLI import core from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer.CLI import template from SoftLayer import testing + class TestTable(testing.TestCase): def test_table_with_duplicated_columns(self): @@ -69,4 +59,4 @@ def test_format_api_list_with_empty_array(self): formatted = formatting.format_output(result, "table") # No good ways to test whats actually in a Rich.Table without going through the hassel of # printing it out. As long as this didn't throw and exception it should be fine. - self.assertEqual(formatted.row_count, 1) \ No newline at end of file + self.assertEqual(formatted.row_count, 1) From b2847f408cb9da325015ce79e89803a24a983fcd Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 12 Oct 2023 15:20:09 -0500 Subject: [PATCH 5/8] added unit tests for vlan-add --- .secrets.baseline | 6 ++-- SoftLayer/CLI/hardware/detail.py | 4 +-- .../fixtures/SoftLayer_Hardware_Server.py | 25 ++++++++++---- .../fixtures/SoftLayer_Network_Component.py | 3 ++ SoftLayer/fixtures/SoftLayer_Search.py | 34 +++++++++++++++++++ tests/CLI/modules/server_tests.py | 13 +++++++ 6 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Component.py diff --git a/.secrets.baseline b/.secrets.baseline index cb9626bd5..6e9b30e9c 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$", "lines": null }, - "generated_at": "2023-10-03T22:08:00Z", + "generated_at": "2023-10-12T20:19:44Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -238,7 +238,7 @@ "hashed_secret": "fb5f2f1b65d1f2bc130ce9d5729b38d12f2b444e", "is_secret": false, "is_verified": false, - "line_number": 260, + "line_number": 273, "type": "Secret Keyword", "verified_result": null } @@ -544,7 +544,7 @@ "hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee", "is_secret": false, "is_verified": false, - "line_number": 57, + "line_number": 58, "type": "Secret Keyword", "verified_result": null } diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index 49feb866f..b25c4ad60 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -86,8 +86,8 @@ def cli(env, identifier, passwords, price, components): for component in result.get('networkComponents', []): # These are the Primary network components if component.get('primaryIpAddress', False): - uplink = component.get('uplinkComponent') - for trunk in uplink.get('networkVlanTrunks'): + uplink = component.get('uplinkComponent', {}) + for trunk in uplink.get('networkVlanTrunks', []): trunk_vlan = trunk.get('networkVlan') vlan_table.add_row([ trunk_vlan.get('networkSpace'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 35552957c..0e6a1746c 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -130,38 +130,51 @@ getReverseDomainRecords = [ {'resourceRecords': [{'data': '2.0.1.10.in-addr.arpa'}]}] bootToRescueLayer = True -getFrontendNetworkComponents = [ + +getNetworkComponents = [ {'maxSpeed': 100}, { 'maxSpeed': 1000, 'networkComponentGroup': { 'groupTypeId': 2, 'networkComponents': [{'maxSpeed': 1000}, {'maxSpeed': 1000}] - } + }, + 'primaryIpAddress': '192.168.1.1', + 'id': 998877, + 'uplinkComponent': {} }, { 'maxSpeed': 1000, 'networkComponentGroup': { 'groupTypeId': 2, 'networkComponents': [{'maxSpeed': 1000}, {'maxSpeed': 1000}] - } + }, + 'id': 665544, + 'uplinkComponent': {} }, { 'maxSpeed': 1000, 'networkComponentGroup': { 'groupTypeId': 2, 'networkComponents': [{'maxSpeed': 1000}, {'maxSpeed': 1000}] - } + }, + 'id': 112233, + 'uplinkComponent': {} }, { 'maxSpeed': 1000, 'networkComponentGroup': { 'groupTypeId': 2, 'networkComponents': [{'maxSpeed': 1000}, {'maxSpeed': 1000}] - } + }, + 'primaryIpAddress': '10.0.0.1', + 'id': 123456, + 'uplinkComponent': {} } ] -getNetworkComponents = getFrontendNetworkComponents +# This splits out the network components into 2 sections so they are different enough for tests +getFrontendNetworkComponents = getNetworkComponents[0::1] +getBackendNetworkComponents = getNetworkComponents[2::3] getBandwidthAllotmentDetail = { 'allocationId': 25465663, diff --git a/SoftLayer/fixtures/SoftLayer_Network_Component.py b/SoftLayer/fixtures/SoftLayer_Network_Component.py new file mode 100644 index 000000000..5927a323f --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Component.py @@ -0,0 +1,3 @@ +addNetworkVlanTrunks = True +clearNetworkVlanTrunks = True +removeNetworkVlanTrunks = True diff --git a/SoftLayer/fixtures/SoftLayer_Search.py b/SoftLayer/fixtures/SoftLayer_Search.py index c7ebab1dd..383c15c9f 100644 --- a/SoftLayer/fixtures/SoftLayer_Search.py +++ b/SoftLayer/fixtures/SoftLayer_Search.py @@ -49,3 +49,37 @@ "type": "string" }] }] + + +advancedSearchVlan = [ + { + "matchedTerms": [ + "name:|IBMPrivate|", + "name.sort:|IBMPrivate|" + ], + "relevanceScore": "9.05", + "resource": { + "fullyQualifiedName": "dal10.bcr03.0000", + "id": 11111, + "name": "IBMPrivate", + "vlanNumber": 0000, + "networkSpace": "PRIVATE" + }, + "resourceType": "SoftLayer_Network_Vlan" + }, + { + "matchedTerms": [ + "name:|IBMPublic|", + "name.sort:|IBMPublic|" + ], + "relevanceScore": "9.01", + "resource": { + "fullyQualifiedName": "dal10.bcr03.11111", + "id": 999999, + "name": "IBMPublic", + "vlanNumber": 11111, + "networkSpace": "PUBLIC" + }, + "resourceType": "SoftLayer_Network_Vlan" + } +] \ No newline at end of file diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index af3f8c87c..afa0bf7cf 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -15,6 +15,7 @@ from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Search from SoftLayer import SoftLayerError from SoftLayer import testing from SoftLayer import utils @@ -1030,3 +1031,15 @@ def test_hardware_cancel_no_force(self, confirm_mock): self.assertEqual(2, result.exit_code) self.assertEqual('Aborted', result.exception.message) + + def test_hardware_vlan_add(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = SoftLayer_Search.advancedSearchVlan + result = self.run_command(['hardware', 'vlan-add', '12345', '5555']) + self.assert_no_fail(result) + search_args = '_objectType:SoftLayer_Network_Vlan "5555"' + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=(search_args,)) + self.assert_called_with('SoftLayer_Hardware_Server', 'getFrontendNetworkComponents', identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'getBackendNetworkComponents', identifier=12345) + self.assert_called_with('SoftLayer_Network_Component', 'addNetworkVlanTrunks', identifier=998877) + self.assert_called_with('SoftLayer_Network_Component', 'addNetworkVlanTrunks', identifier=123456) \ No newline at end of file From 2fe5c71b042b58f520d5b22641bff40ead99f355 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 12 Oct 2023 17:47:25 -0500 Subject: [PATCH 6/8] More vlan-trunking related unit tests --- .secrets.baseline | 19 ++- .../fixtures/SoftLayer_Hardware_Server.py | 4 +- SoftLayer/managers/hardware.py | 4 +- tests/CLI/helper_tests.py | 37 ----- .../hardware_basic_tests.py} | 13 +- .../modules/hardware/hardware_vlan_tests.py | 142 ++++++++++++++++++ 6 files changed, 156 insertions(+), 63 deletions(-) rename tests/CLI/modules/{server_tests.py => hardware/hardware_basic_tests.py} (97%) create mode 100644 tests/CLI/modules/hardware/hardware_vlan_tests.py diff --git a/.secrets.baseline b/.secrets.baseline index 6e9b30e9c..2818245de 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$", "lines": null }, - "generated_at": "2023-10-12T20:19:44Z", + "generated_at": "2023-10-12T22:45:35Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -529,23 +529,22 @@ "verified_result": null } ], - "tests/CLI/modules/securitygroup_tests.py": [ + "tests/CLI/modules/hardware/hardware_basic_tests.py": [ { - "hashed_secret": "bc553d847e40dd6f3f63638f16f57b28ce1425cc", - "is_secret": false, + "hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee", "is_verified": false, - "line_number": 339, - "type": "Hex High Entropy String", + "line_number": 58, + "type": "Secret Keyword", "verified_result": null } ], - "tests/CLI/modules/server_tests.py": [ + "tests/CLI/modules/securitygroup_tests.py": [ { - "hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee", + "hashed_secret": "bc553d847e40dd6f3f63638f16f57b28ce1425cc", "is_secret": false, "is_verified": false, - "line_number": 58, - "type": "Secret Keyword", + "line_number": 339, + "type": "Hex High Entropy String", "verified_result": null } ], diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 0e6a1746c..510f537c5 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -173,8 +173,8 @@ } ] # This splits out the network components into 2 sections so they are different enough for tests -getFrontendNetworkComponents = getNetworkComponents[0::1] -getBackendNetworkComponents = getNetworkComponents[2::3] +getFrontendNetworkComponents = getNetworkComponents[:2] +getBackendNetworkComponents = getNetworkComponents[3:] getBandwidthAllotmentDetail = { 'allocationId': 25465663, diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c68482ecf..a59e7244d 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1223,10 +1223,10 @@ def clear_vlan(self, hardware_id): # We only want to call this API on components with actual trunks. # Calling this on the primary and redundant components might cause exceptions. for c in components.get('backendNetworkComponent', []): - if len(c.get('networkVlanTrunks')): + if len(c.get('networkVlanTrunks', [])): self.client.call('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', id=c.get('id')) for c in components.get('frontendNetworkComponent', []): - if len(c.get('networkVlanTrunks')): + if len(c.get('networkVlanTrunks', [])): self.client.call('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', id=c.get('id')) def get_sensors(self, hardware_id): diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index 262e98d00..69b2924f2 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -450,41 +450,4 @@ def test_export_to_template(self): self.assertIn('disk=disk1,disk2\n', data) -class IterToTableTests(testing.TestCase): - def test_format_api_dict(self): - result = formatting._format_dict({'key': 'value'}) - - self.assertIsInstance(result, formatting.Table) - self.assertEqual(result.columns, ['name', 'value']) - self.assertEqual(result.rows, [['key', 'value']]) - - def test_format_api_list(self): - result = formatting._format_list([{'key': 'value'}]) - - self.assertIsInstance(result, formatting.Table) - self.assertEqual(result.columns, ['key']) - self.assertEqual(result.rows, [['value']]) - - def test_format_api_list_non_objects(self): - result = formatting._format_list(['a', 'b', 'c']) - - 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']) - - def test_format_api_list_with_empty_array(self): - result = formatting.iter_to_table([{'id': 130224450, 'activeTickets': []}]) - self.assertIsInstance(result, formatting.Table) - self.assertIn('id', result.columns) - self.assertIn('activeTickets', result.columns) - formatted = formatting.format_output(result, "table") - # No good ways to test whats actually in a Rich.Table without going through the hassel of - # printing it out. As long as this didn't throw and exception it should be fine. - self.assertEqual(formatted.row_count, 1) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/hardware/hardware_basic_tests.py similarity index 97% rename from tests/CLI/modules/server_tests.py rename to tests/CLI/modules/hardware/hardware_basic_tests.py index afa0bf7cf..acc152b7b 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/hardware/hardware_basic_tests.py @@ -21,7 +21,7 @@ from SoftLayer import utils -class ServerCLITests(testing.TestCase): +class HardwareCLITests(testing.TestCase): def test_server_cancel_reasons(self): result = self.run_command(['server', 'cancel-reasons']) @@ -1032,14 +1032,3 @@ def test_hardware_cancel_no_force(self, confirm_mock): self.assertEqual(2, result.exit_code) self.assertEqual('Aborted', result.exception.message) - def test_hardware_vlan_add(self): - mock = self.set_mock('SoftLayer_Search', 'advancedSearch') - mock.return_value = SoftLayer_Search.advancedSearchVlan - result = self.run_command(['hardware', 'vlan-add', '12345', '5555']) - self.assert_no_fail(result) - search_args = '_objectType:SoftLayer_Network_Vlan "5555"' - self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=(search_args,)) - self.assert_called_with('SoftLayer_Hardware_Server', 'getFrontendNetworkComponents', identifier=12345) - self.assert_called_with('SoftLayer_Hardware_Server', 'getBackendNetworkComponents', identifier=12345) - self.assert_called_with('SoftLayer_Network_Component', 'addNetworkVlanTrunks', identifier=998877) - self.assert_called_with('SoftLayer_Network_Component', 'addNetworkVlanTrunks', identifier=123456) \ No newline at end of file diff --git a/tests/CLI/modules/hardware/hardware_vlan_tests.py b/tests/CLI/modules/hardware/hardware_vlan_tests.py new file mode 100644 index 000000000..be2e3090c --- /dev/null +++ b/tests/CLI/modules/hardware/hardware_vlan_tests.py @@ -0,0 +1,142 @@ +""" + SoftLayer.tests.CLI.modules.hardware.hardware_vlan_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + These tests are for any commands that work with the vlans on hardware objects. + + :license: MIT, see LICENSE for more details. +""" + +import json +import sys +import tempfile +from unittest import mock as mock + +from SoftLayer.CLI import exceptions +from SoftLayer.fixtures import SoftLayer_Hardware_Server +from SoftLayer.fixtures import SoftLayer_Search +from SoftLayer import SoftLayerError +from SoftLayer import testing +from SoftLayer import utils + + +class HardwareVlanCLITests(testing.TestCase): + + # slcli hardware vlan-trunkable + def test_hardware_vlan_trunkable_no_hardware(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getNetworkComponents') + mock.return_value = [] + result = self.run_command(['hardware', 'vlan-trunkable']) + self.assertEqual(2, result.exit_code) + self.assertIn("Missing argument 'HARDWARE'.", result.output) + + def test_hardware_vlan_trunkable_happypath(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getNetworkComponents') + mock.return_value = [ + { + 'maxSpeed': 1000, + 'networkVlansTrunkable': [{ + 'id':5555, + 'fullyQualifiedName': 'test01.ibm99.1234', + 'name': 'IBMTEst', + 'networkSpace': 'PUBLIC' + }], + 'primaryIpAddress': '192.168.1.1', + 'id': 998877, + } + ] + result = self.run_command(['hardware', 'vlan-trunkable', '12345']) + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(len(output), 1) + self.assertEqual(output[0]['ID'], 5555) + + def test_hardware_vlan_trunkable_no_vlans(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getNetworkComponents') + mock.return_value = [] + result = self.run_command(['--format=table', 'hardware', 'vlan-trunkable', '12345']) + print(result.output) + self.assert_no_fail(result) + self.assertIn("No trunkable vlans found.", result.output) + + def test_hardware_vlan_trunkable_no_vlans_json(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getNetworkComponents') + mock.return_value = [] + result = self.run_command(['hardware', 'vlan-trunkable', '12345']) + output = json.loads(result.output) + self.assert_no_fail(result) + self.assertEqual([], output) + + + # slcli hardware vlan-remove + def test_hardware_vlan_remove(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = SoftLayer_Search.advancedSearchVlan + result = self.run_command(['hardware', 'vlan-remove', '12345', '5555']) + self.assert_no_fail(result) + search_args = '_objectType:SoftLayer_Network_Vlan "5555"' + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=(search_args,)) + self.assert_called_with('SoftLayer_Hardware_Server', 'getFrontendNetworkComponents', identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'getBackendNetworkComponents', identifier=12345) + self.assert_called_with('SoftLayer_Network_Component', 'removeNetworkVlanTrunks', identifier=998877) + self.assert_called_with('SoftLayer_Network_Component', 'removeNetworkVlanTrunks', identifier=123456) + + def test_hardware_vlan_remove_two_vlans(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = SoftLayer_Search.advancedSearchVlan + result = self.run_command(['hardware', 'vlan-remove', '12345', '5555', 'testVlan']) + self.assert_no_fail(result) + search_args = '_objectType:SoftLayer_Network_Vlan "5555" "testVlan"' + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=(search_args,)) + + def test_hardware_vlan_remove_no_vlans(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = SoftLayer_Search.advancedSearchVlan + result = self.run_command(['hardware', 'vlan-remove', '12345']) + self.assertEqual(2, result.exit_code) + self.assertEqual("Argument Error: Error: Missing argument 'VLANS'.", result.exception.message) + + def test_hardware_vlan_remove_all_vlans(self): + from pprint import pprint as pp + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = SoftLayer_Search.advancedSearchVlan + hardware_mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') + hardware_return = { + 'backendNetworkComponent': SoftLayer_Hardware_Server.getBackendNetworkComponents, + 'frontendNetworkComponent': SoftLayer_Hardware_Server.getFrontendNetworkComponents + } + hardware_return['backendNetworkComponent'][1]['networkVlanTrunks'] = [{'id': 99}] + hardware_return['frontendNetworkComponent'][1]['networkVlanTrunks'] = [{'id': 11}] + hardware_mock.return_value = hardware_return + result = self.run_command(['hardware', 'vlan-remove', '12345', '--all']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', identifier=998877) + self.assert_called_with('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', identifier=123456) + + # slcli hardware vlan-add + def test_hardware_vlan_add(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = SoftLayer_Search.advancedSearchVlan + result = self.run_command(['hardware', 'vlan-add', '12345', '5555']) + self.assert_no_fail(result) + search_args = '_objectType:SoftLayer_Network_Vlan "5555"' + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=(search_args,)) + self.assert_called_with('SoftLayer_Hardware_Server', 'getFrontendNetworkComponents', identifier=12345) + self.assert_called_with('SoftLayer_Hardware_Server', 'getBackendNetworkComponents', identifier=12345) + self.assert_called_with('SoftLayer_Network_Component', 'addNetworkVlanTrunks', identifier=998877) + self.assert_called_with('SoftLayer_Network_Component', 'addNetworkVlanTrunks', identifier=123456) + + def test_hardware_vlan_add_two_vlans(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = SoftLayer_Search.advancedSearchVlan + result = self.run_command(['hardware', 'vlan-add', '12345', '5555', 'testVlan']) + self.assert_no_fail(result) + search_args = '_objectType:SoftLayer_Network_Vlan "5555" "testVlan"' + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=(search_args,)) + + def test_hardware_vlan_add_no_vlans(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = SoftLayer_Search.advancedSearchVlan + result = self.run_command(['hardware', 'vlan-add', '12345']) + self.assertEqual(2, result.exit_code) + self.assertEqual("Argument Error: Error: Missing argument 'VLANS'.", result.exception.message) \ No newline at end of file From 4d2d288f808f237d0348feda945c5cfe0f641a2e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 12 Oct 2023 18:00:07 -0500 Subject: [PATCH 7/8] tox fixes --- .secrets.baseline | 4 ++-- SoftLayer/fixtures/SoftLayer_Search.py | 2 +- tests/CLI/helper_tests.py | 3 --- tests/CLI/modules/hardware/hardware_basic_tests.py | 2 -- tests/CLI/modules/hardware/hardware_vlan_tests.py | 12 ++---------- 5 files changed, 5 insertions(+), 18 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 2818245de..fad7f7e5a 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$", "lines": null }, - "generated_at": "2023-10-12T22:45:35Z", + "generated_at": "2023-10-12T23:00:01Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -533,7 +533,7 @@ { "hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee", "is_verified": false, - "line_number": 58, + "line_number": 57, "type": "Secret Keyword", "verified_result": null } diff --git a/SoftLayer/fixtures/SoftLayer_Search.py b/SoftLayer/fixtures/SoftLayer_Search.py index 383c15c9f..181bd2ed5 100644 --- a/SoftLayer/fixtures/SoftLayer_Search.py +++ b/SoftLayer/fixtures/SoftLayer_Search.py @@ -82,4 +82,4 @@ }, "resourceType": "SoftLayer_Network_Vlan" } -] \ No newline at end of file +] diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index 69b2924f2..73b727be6 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -448,6 +448,3 @@ def test_export_to_template(self): self.assertEqual(len(data.splitlines()), 2) self.assertIn('datacenter=ams01\n', data) self.assertIn('disk=disk1,disk2\n', data) - - - diff --git a/tests/CLI/modules/hardware/hardware_basic_tests.py b/tests/CLI/modules/hardware/hardware_basic_tests.py index acc152b7b..61135fa4f 100644 --- a/tests/CLI/modules/hardware/hardware_basic_tests.py +++ b/tests/CLI/modules/hardware/hardware_basic_tests.py @@ -15,7 +15,6 @@ from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Product_Order -from SoftLayer.fixtures import SoftLayer_Search from SoftLayer import SoftLayerError from SoftLayer import testing from SoftLayer import utils @@ -1031,4 +1030,3 @@ def test_hardware_cancel_no_force(self, confirm_mock): self.assertEqual(2, result.exit_code) self.assertEqual('Aborted', result.exception.message) - diff --git a/tests/CLI/modules/hardware/hardware_vlan_tests.py b/tests/CLI/modules/hardware/hardware_vlan_tests.py index be2e3090c..bccbe112a 100644 --- a/tests/CLI/modules/hardware/hardware_vlan_tests.py +++ b/tests/CLI/modules/hardware/hardware_vlan_tests.py @@ -8,16 +8,10 @@ """ import json -import sys -import tempfile -from unittest import mock as mock -from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Hardware_Server from SoftLayer.fixtures import SoftLayer_Search -from SoftLayer import SoftLayerError from SoftLayer import testing -from SoftLayer import utils class HardwareVlanCLITests(testing.TestCase): @@ -36,7 +30,7 @@ def test_hardware_vlan_trunkable_happypath(self): { 'maxSpeed': 1000, 'networkVlansTrunkable': [{ - 'id':5555, + 'id': 5555, 'fullyQualifiedName': 'test01.ibm99.1234', 'name': 'IBMTEst', 'networkSpace': 'PUBLIC' @@ -67,7 +61,6 @@ def test_hardware_vlan_trunkable_no_vlans_json(self): self.assert_no_fail(result) self.assertEqual([], output) - # slcli hardware vlan-remove def test_hardware_vlan_remove(self): mock = self.set_mock('SoftLayer_Search', 'advancedSearch') @@ -97,7 +90,6 @@ def test_hardware_vlan_remove_no_vlans(self): self.assertEqual("Argument Error: Error: Missing argument 'VLANS'.", result.exception.message) def test_hardware_vlan_remove_all_vlans(self): - from pprint import pprint as pp mock = self.set_mock('SoftLayer_Search', 'advancedSearch') mock.return_value = SoftLayer_Search.advancedSearchVlan hardware_mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') @@ -139,4 +131,4 @@ def test_hardware_vlan_add_no_vlans(self): mock.return_value = SoftLayer_Search.advancedSearchVlan result = self.run_command(['hardware', 'vlan-add', '12345']) self.assertEqual(2, result.exit_code) - self.assertEqual("Argument Error: Error: Missing argument 'VLANS'.", result.exception.message) \ No newline at end of file + self.assertEqual("Argument Error: Error: Missing argument 'VLANS'.", result.exception.message) From 96b36f9fa1ebcf5bfe6184ce27f8c2ef1254d666 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 13 Oct 2023 15:28:22 -0500 Subject: [PATCH 8/8] unit tests for vlan trunk commands --- .secrets.baseline | 6 +- .../fixtures/SoftLayer_Hardware_Server.py | 10 + .../modules/hardware/hardware_vlan_tests.py | 8 +- tests/managers/hardware_tests.py | 189 ++++++------------ 4 files changed, 75 insertions(+), 138 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index fad7f7e5a..c30275090 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$", "lines": null }, - "generated_at": "2023-10-12T23:00:01Z", + "generated_at": "2023-10-13T20:28:05Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -238,7 +238,7 @@ "hashed_secret": "fb5f2f1b65d1f2bc130ce9d5729b38d12f2b444e", "is_secret": false, "is_verified": false, - "line_number": 273, + "line_number": 274, "type": "Secret Keyword", "verified_result": null } @@ -593,7 +593,7 @@ "hashed_secret": "fb5f2f1b65d1f2bc130ce9d5729b38d12f2b444e", "is_secret": false, "is_verified": false, - "line_number": 737, + "line_number": 673, "type": "Secret Keyword", "verified_result": null } diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 510f537c5..150b8d8bf 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -112,6 +112,7 @@ } ] } + editObject = True setTags = True setPrivateNetworkInterfaceSpeed = True @@ -485,3 +486,12 @@ getTagReferences = getObject['tagReferences'] getNetworkVlans = getObject['networkVlans'] getRemoteManagementAccounts = getObject['remoteManagementAccounts'] + + +# Setup for hardwareManager.clear_vlan related tests +getObjectVlanClear = { + 'backendNetworkComponent': getBackendNetworkComponents, + 'frontendNetworkComponent': getFrontendNetworkComponents +} +getObjectVlanClear['backendNetworkComponent'][1]['networkVlanTrunks'] = [{'id': 99}] +getObjectVlanClear['frontendNetworkComponent'][1]['networkVlanTrunks'] = [{'id': 11}] diff --git a/tests/CLI/modules/hardware/hardware_vlan_tests.py b/tests/CLI/modules/hardware/hardware_vlan_tests.py index bccbe112a..59a192079 100644 --- a/tests/CLI/modules/hardware/hardware_vlan_tests.py +++ b/tests/CLI/modules/hardware/hardware_vlan_tests.py @@ -93,13 +93,7 @@ def test_hardware_vlan_remove_all_vlans(self): mock = self.set_mock('SoftLayer_Search', 'advancedSearch') mock.return_value = SoftLayer_Search.advancedSearchVlan hardware_mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') - hardware_return = { - 'backendNetworkComponent': SoftLayer_Hardware_Server.getBackendNetworkComponents, - 'frontendNetworkComponent': SoftLayer_Hardware_Server.getFrontendNetworkComponents - } - hardware_return['backendNetworkComponent'][1]['networkVlanTrunks'] = [{'id': 99}] - hardware_return['frontendNetworkComponent'][1]['networkVlanTrunks'] = [{'id': 11}] - hardware_mock.return_value = hardware_return + hardware_mock.return_value = SoftLayer_Hardware_Server.getObjectVlanClear result = self.run_command(['hardware', 'vlan-remove', '12345', '--all']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', identifier=998877) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 59e7950b6..c6175d073 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -275,10 +275,7 @@ def test_get_hardware_item_prices(self): "longName": "Montreal 1", "name": "mon01", "regions": [ - { - "description": "MON01 - Montreal", - "keyname": "MONTREAL", - } + {"description": "MON01 - Montreal", "keyname": "MONTREAL"} ] } ] @@ -303,8 +300,7 @@ def test_generate_create_dict_no_regions(self): packages_copy[0]['regions'] = [] packages.return_value = packages_copy - ex = self.assertRaises(SoftLayer.SoftLayerError, - self.hardware._generate_create_dict, + ex = self.assertRaises(SoftLayer.SoftLayerError, self.hardware._generate_create_dict, **MINIMAL_TEST_CREATE_ARGS) self.assertIn("Could not find valid location for: 'wdc01'", str(ex)) @@ -372,16 +368,8 @@ def test_generate_create_dict_by_router_network_component(self): 'hardware': [{ 'domain': 'giggles.woo', 'hostname': 'unicorn', - 'primaryNetworkComponent': { - "router": { - "id": 1111 - } - }, - 'primaryBackendNetworkComponent': { - "router": { - "id": 1234 - } - } + 'primaryNetworkComponent': {"router": {"id": 1111}}, + 'primaryBackendNetworkComponent': {"router": {"id": 1234}} }] } @@ -429,8 +417,7 @@ def test_place_order(self, create_dict, place_order): def test_cancel_hardware_without_reason(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') - mock.return_value = {'id': 987, 'billingItem': {'id': 1234}, - 'openCancellationTicket': {'id': 1234}} + mock.return_value = {'id': 987, 'billingItem': {'id': 1234}, 'openCancellationTicket': {'id': 1234}} result = self.hardware.cancel_hardware(987) @@ -441,8 +428,7 @@ def test_cancel_hardware_without_reason(self): def test_cancel_hardware_with_reason_and_comment(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') - mock.return_value = {'id': 987, 'billingItem': {'id': 1234}, - 'openCancellationTicket': {'id': 1234}} + mock.return_value = {'id': 987, 'billingItem': {'id': 1234}, 'openCancellationTicket': {'id': 1234}} result = self.hardware.cancel_hardware(6327, reason='sales', comment='Test Comment') @@ -465,18 +451,14 @@ def test_cancel_hardware_no_billing_item(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') mock.return_value = {'id': 987, 'openCancellationTicket': {'id': 1234}} - ex = self.assertRaises(SoftLayer.SoftLayerError, - self.hardware.cancel_hardware, - 6327) + ex = self.assertRaises(SoftLayer.SoftLayerError, self.hardware.cancel_hardware, 6327) self.assertEqual("Ticket #1234 already exists for this server", str(ex)) def test_cancel_hardwareno_billing_item_or_ticket(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') mock.return_value = {'id': 987} - ex = self.assertRaises(SoftLayer.SoftLayerError, - self.hardware.cancel_hardware, - 6327) + ex = self.assertRaises(SoftLayer.SoftLayerError, self.hardware.cancel_hardware, 6327) self.assertEqual("Cannot locate billing for the server. The server may already be cancelled.", str(ex)) def test_cancel_hardware_monthly_now(self): @@ -513,34 +495,25 @@ def test_cancel_running_transaction(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') mock.return_value = {'id': 987, 'billingItem': {'id': 6327}, 'activeTransaction': {'id': 4567}} - self.assertRaises(SoftLayer.SoftLayerError, - self.hardware.cancel_hardware, - 12345) + self.assertRaises(SoftLayer.SoftLayerError, self.hardware.cancel_hardware, 12345) def test_change_port_speed_public(self): self.hardware.change_port_speed(2, True, 100, 'degraded') - self.assert_called_with('SoftLayer_Hardware_Server', - 'setPublicNetworkInterfaceSpeed', - identifier=2, - args=([100, 'degraded'],)) + self.assert_called_with('SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', + identifier=2, args=([100, 'degraded'],)) def test_change_port_speed_private(self): self.hardware.change_port_speed(2, False, 10, 'redundant') - self.assert_called_with('SoftLayer_Hardware_Server', - 'setPrivateNetworkInterfaceSpeed', - identifier=2, - args=([10, 'redundant'],)) + self.assert_called_with('SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', + identifier=2, args=([10, 'redundant'],)) def test_edit_meta(self): # Test editing user data self.hardware.edit(100, userdata='my data') - self.assert_called_with('SoftLayer_Hardware_Server', - 'setUserMetadata', - args=(['my data'],), - identifier=100) + self.assert_called_with('SoftLayer_Hardware_Server', 'setUserMetadata', args=(['my data'],), identifier=100) def test_edit_blank(self): # Now test a blank edit @@ -549,13 +522,9 @@ def test_edit_blank(self): def test_edit(self): # Finally, test a full edit - self.hardware.edit(100, - hostname='new-host', - domain='new.sftlyr.ws', - notes='random notes') + self.hardware.edit(100, hostname='new-host', domain='new.sftlyr.ws', notes='random notes') - self.assert_called_with('SoftLayer_Hardware_Server', - 'editObject', + self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ 'hostname': 'new-host', 'domain': 'new.sftlyr.ws', @@ -567,44 +536,34 @@ def test_rescue(self): result = self.hardware.rescue(1234) self.assertEqual(result, True) - self.assert_called_with('SoftLayer_Hardware_Server', - 'bootToRescueLayer', - identifier=1234) + self.assert_called_with('SoftLayer_Hardware_Server', 'bootToRescueLayer', identifier=1234) def test_update_firmware(self): result = self.hardware.update_firmware(100) self.assertEqual(result, True) - self.assert_called_with('SoftLayer_Hardware_Server', - 'createFirmwareUpdateTransaction', + self.assert_called_with('SoftLayer_Hardware_Server', 'createFirmwareUpdateTransaction', identifier=100, args=(1, 1, 1, 1)) def test_update_firmware_selective(self): - result = self.hardware.update_firmware(100, - ipmi=False, - hard_drive=False) + result = self.hardware.update_firmware(100, ipmi=False, hard_drive=False) self.assertEqual(result, True) - self.assert_called_with('SoftLayer_Hardware_Server', - 'createFirmwareUpdateTransaction', + self.assert_called_with('SoftLayer_Hardware_Server', 'createFirmwareUpdateTransaction', identifier=100, args=(0, 1, 1, 0)) def test_reflash_firmware(self): result = self.hardware.reflash_firmware(100) self.assertEqual(result, True) - self.assert_called_with('SoftLayer_Hardware_Server', - 'createFirmwareReflashTransaction', + self.assert_called_with('SoftLayer_Hardware_Server', 'createFirmwareReflashTransaction', identifier=100, args=(1, 1, 1)) def test_reflash_firmware_selective(self): - result = self.hardware.reflash_firmware(100, - raid_controller=False, - bios=False) + result = self.hardware.reflash_firmware(100, raid_controller=False, bios=False) self.assertEqual(result, True) - self.assert_called_with('SoftLayer_Hardware_Server', - 'createFirmwareReflashTransaction', + self.assert_called_with('SoftLayer_Hardware_Server', 'createFirmwareReflashTransaction', identifier=100, args=(1, 0, 0)) def test_get_tracking_id(self): @@ -614,10 +573,8 @@ def test_get_tracking_id(self): def test_get_bandwidth_data(self): result = self.hardware.get_bandwidth_data(1234, '2019-01-01', '2019-02-01', 'public', 1000) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getBandwidthData', - args=('2019-01-01', '2019-02-01', 'public', 1000), - identifier=1000) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getBandwidthData', + args=('2019-01-01', '2019-02-01', 'public', 1000), identifier=1000) self.assertEqual(result[0]['type'], 'cpu0') def test_get_bandwidth_allocation(self): @@ -681,7 +638,7 @@ def test_get_storage_iscsi_empty_details(self): def test_get_storage_nas_details(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') - mock.return_value = [ + getAttachedNetworkStorages = [ { "accountId": 11111, "capacityGb": 12000, @@ -691,15 +648,9 @@ def test_get_storage_nas_details(self): } ] + mock.return_value = getAttachedNetworkStorages result = self.hardware.get_storage_details(1234, 'NAS') - - self.assertEqual([{ - "accountId": 11111, - "capacityGb": 12000, - "id": 3777111, - "nasType": "NAS", - "username": "SL02SEL32222-9", - }], result) + self.assertEqual(getAttachedNetworkStorages, result) def test_get_storage_nas_empty_details(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') @@ -711,7 +662,7 @@ def test_get_storage_nas_empty_details(self): def test_get_storage_credentials(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getAllowedHost') - mock.return_value = { + getAllowedHost = { "accountId": 11111, "id": 33333, "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", @@ -723,33 +674,19 @@ def test_get_storage_credentials(self): "username": "SL02SU11111-V62941551" } } - + mock.return_value = getAllowedHost 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) + self.assertEqual(getAllowedHost, 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) def test_get_hard_drives(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') - mock.return_value = [ + hd_return = [ { "id": 11111, "serialNumber": "z1w4sdf", @@ -773,33 +710,9 @@ def test_get_hard_drives(self): } } ] - + mock.return_value = hd_return 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) + self.assertEqual(hd_return, result) def test_get_hard_drive_empty(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') @@ -817,9 +730,7 @@ def test_authorize_storage(self): 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, "#") + self.assertRaises(SoftLayer.exceptions.SoftLayerError, self.hardware.authorize_storage, 1234, "#") def test_get_price_id_memory_capacity(self): upgrade_prices = [ @@ -945,11 +856,10 @@ def test_create_credential(self): "software": { "hardwareId": 123456, "softwareLicense": { - "softwareDescription": { - "name": 'system' - } + "softwareDescription": {"name": 'system'} } - }} + } + } self.hardware.create_credential(template) self.assert_called_with('SoftLayer_Software_Component_Password', 'createObject') @@ -973,6 +883,29 @@ def test_get_hardware_fast(self): self.assert_called_with('SoftLayer_Hardware_Server', 'getNetworkVlans') self.assert_called_with('SoftLayer_Hardware_Server', 'getRemoteManagementAccounts') + def test_trunk_vlan(self): + vlans = [{'id': 9999}] + vlan_id = 1234 + self.hardware.trunk_vlan(vlan_id, vlans) + self.assert_called_with('SoftLayer_Network_Component', 'addNetworkVlanTrunks', + identifier=vlan_id, args=(vlans,)) + + def test_remove_vlan(self): + vlans = [{'id': 9999}] + vlan_id = 1234 + self.hardware.remove_vlan(vlan_id, vlans) + self.assert_called_with('SoftLayer_Network_Component', 'removeNetworkVlanTrunks', + identifier=vlan_id, args=(vlans,)) + + def test_clear_vlan(self): + hardware_mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') + hardware_mock.return_value = fixtures.SoftLayer_Hardware_Server.getObjectVlanClear + server_id = 1234 + self.hardware.clear_vlan(server_id) + self.assert_called_with('SoftLayer_Hardware_Server', 'getObject', identifier=server_id) + self.assert_called_with('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', identifier=998877) + self.assert_called_with('SoftLayer_Network_Component', 'clearNetworkVlanTrunks', identifier=123456) + class HardwareHelperTests(testing.TestCase):