From 377387bceb1cc52a2277b17a824bb70420ec3819 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 28 Feb 2022 17:06:12 -0600 Subject: [PATCH 1/2] #1590 basic structure for the DC closure report --- SoftLayer/CLI/report/dc_closures.py | 125 ++++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + 2 files changed, 126 insertions(+) create mode 100644 SoftLayer/CLI/report/dc_closures.py diff --git a/SoftLayer/CLI/report/dc_closures.py b/SoftLayer/CLI/report/dc_closures.py new file mode 100644 index 000000000..1f4e307c4 --- /dev/null +++ b/SoftLayer/CLI/report/dc_closures.py @@ -0,0 +1,125 @@ +"""Metric Utilities""" +import datetime +import itertools +import sys + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +from pprint import pprint as pp + +@click.command(short_help="""Report on Resources in closing datacenters""") +@environment.pass_env +def cli(env): + """Report on Resources in closing datacenters + + Displays a list of Datacenters soon to be shutdown, and any resources on the account +in those locations + """ + + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, +backendRouterName, frontendRouterName]""" + closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask) + # Find all VLANs in the POD that is going to close. + search = "_objectType:SoftLayer_Network_Vlan primaryRouter.hostname: \"{}\" || primaryRouter.hostname: \"{}\"" + resource_mask = """mask[ + resource(SoftLayer_Network_Vlan)[ + id,fullyQualifiedName,name,note,vlanNumber,networkSpace, + virtualGuests[id,fullyQualifiedDomainName,billingItem[cancellationDate]], + hardware[id,fullyQualifiedDomainName,billingItem[cancellationDate]], + networkVlanFirewall[id,primaryIpAddress,billingItem[cancellationDate]], + privateNetworkGateways[id,name,networkSpace], + publicNetworkGateways[id,name,networkSpace] + ] + ] + """ + table_title = "Resources in closing datacenters" + resource_table = formatting.Table(["Id", "Name", "Public VLAN", "Private VLAN", "Type", "Datacenter", + "POD", "Cancellation Date"], title=table_title) + resource_table.align = 'l' + for pod in closing_pods: + resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} + vlans = env.client.call('SoftLayer_Search', 'advancedSearch', + search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), + iter=True, mask=resource_mask) + for vlan in vlans: + resources = process_vlan(vlan.get('resource', {}), resources) + + for resource_type in resources.keys(): + + for resource_object in resources[resource_type].values(): + resource_table.add_row([ + resource_object['id'], + resource_object['name'], + resource_object['vlan'].get('PUBLIC', '-'), + resource_object['vlan'].get('PRIVATE', '-'), + resource_type, + pod.get('datacenterLongName'), + pod.get('backendRouterName'), + resource_object['cancelDate'] + ]) + + env.fout(resource_table) + + +# returns a Table Row for a given resource +def process_vlan(vlan, resources=None): + if resources is None: + resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} + + type_x = "virtual" + for x in vlan.get('virtualGuests', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + + type_x = 'hardware' + for x in vlan.get('hardware', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + + type_x = 'firewall' + for x in vlan.get('networkVlanFirewall', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('primaryIpAddress', vlan, x, existing) + + type_x = 'gateway' + for x in vlan.get('privateNetworkGateways', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + for x in vlan.get('publicNetworkGateways', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + + return resources + +# name_property is what property to use as the name from resource +# vlan is the vlan object +# resource has the data we want +# entry is for any existing data +def build_resource_object(name_property, vlan, resource, entry): + new_entry = { + 'id': resource.get('id'), + 'name': resource.get(name_property), + 'vlan': {vlan.get('networkSpace'): vlan.get('vlanNumber')}, + 'cancelDate': utils.clean_time(utils.lookup(resource, 'billingItem', 'cancellationDate')) + } + if entry: + entry['vlan'][vlan.get('networkSpace')] = vlan.get('vlanNumber') + else: + entry = new_entry + + return entry \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 02d3420d3..995845419 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -365,6 +365,7 @@ ('report', 'SoftLayer.CLI.report'), ('report:bandwidth', 'SoftLayer.CLI.report.bandwidth:cli'), + ('report:datacenter-closures', 'SoftLayer.CLI.report.dc_closures:cli'), ('autoscale', 'SoftLayer.CLI.autoscale'), ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), From da2273ee50e983868065270f0a3445f108b59526 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 1 Mar 2022 17:49:45 -0600 Subject: [PATCH 2/2] #1590 added docs and unit tests --- SoftLayer/CLI/report/dc_closures.py | 76 ++++++++++----------- docs/cli/reports.rst | 12 +++- tests/CLI/modules/report_tests.py | 102 ++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 39 deletions(-) diff --git a/SoftLayer/CLI/report/dc_closures.py b/SoftLayer/CLI/report/dc_closures.py index 1f4e307c4..563533ab1 100644 --- a/SoftLayer/CLI/report/dc_closures.py +++ b/SoftLayer/CLI/report/dc_closures.py @@ -1,8 +1,4 @@ -"""Metric Utilities""" -import datetime -import itertools -import sys - +"""Report on Resources in closing datacenters""" import click from SoftLayer.CLI import environment @@ -10,14 +6,12 @@ from SoftLayer import utils -from pprint import pprint as pp - @click.command(short_help="""Report on Resources in closing datacenters""") @environment.pass_env def cli(env): """Report on Resources in closing datacenters - Displays a list of Datacenters soon to be shutdown, and any resources on the account + Displays a list of Datacenters soon to be shutdown, and any resources on the account in those locations """ @@ -33,7 +27,7 @@ def cli(env): } mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" - closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask) + closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) # Find all VLANs in the POD that is going to close. search = "_objectType:SoftLayer_Network_Vlan primaryRouter.hostname: \"{}\" || primaryRouter.hostname: \"{}\"" resource_mask = """mask[ @@ -54,16 +48,17 @@ def cli(env): for pod in closing_pods: resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} vlans = env.client.call('SoftLayer_Search', 'advancedSearch', - search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), - iter=True, mask=resource_mask) + search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), + iter=True, mask=resource_mask) + # Go through the vlans and coalate the resources into a data structure that is easy to print out for vlan in vlans: resources = process_vlan(vlan.get('resource', {}), resources) - - for resource_type in resources.keys(): - - for resource_object in resources[resource_type].values(): + + # Go through each resource and add it to the table + for resource_type, resource_values in resources.items(): + for resource_id, resource_object in resource_values.items(): resource_table.add_row([ - resource_object['id'], + resource_id, resource_object['name'], resource_object['vlan'].get('PUBLIC', '-'), resource_object['vlan'].get('PRIVATE', '-'), @@ -72,46 +67,51 @@ def cli(env): pod.get('backendRouterName'), resource_object['cancelDate'] ]) - + env.fout(resource_table) # returns a Table Row for a given resource def process_vlan(vlan, resources=None): + """Takes in a vlan object and pulls out the needed resources""" if resources is None: resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} type_x = "virtual" - for x in vlan.get('virtualGuests', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + for obj_x in vlan.get('virtualGuests', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, obj_x, existing) type_x = 'hardware' - for x in vlan.get('hardware', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + for obj_x in vlan.get('hardware', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, obj_x, existing) type_x = 'firewall' - for x in vlan.get('networkVlanFirewall', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('primaryIpAddress', vlan, x, existing) + for obj_x in vlan.get('networkVlanFirewall', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('primaryIpAddress', vlan, obj_x, existing) type_x = 'gateway' - for x in vlan.get('privateNetworkGateways', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) - for x in vlan.get('publicNetworkGateways', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + for obj_x in vlan.get('privateNetworkGateways', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('name', vlan, obj_x, existing) + for obj_x in vlan.get('publicNetworkGateways', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('name', vlan, obj_x, existing) return resources -# name_property is what property to use as the name from resource -# vlan is the vlan object -# resource has the data we want -# entry is for any existing data + def build_resource_object(name_property, vlan, resource, entry): - new_entry = { + """builds out a resource object and puts the required values in the right place. + + :param: name_property is what property to use as the name from resource + :param: vlan is the vlan object + :param: resource has the data we want + :param: entry is for any existing data + """ + new_entry = { 'id': resource.get('id'), 'name': resource.get(name_property), 'vlan': {vlan.get('networkSpace'): vlan.get('vlanNumber')}, @@ -122,4 +122,4 @@ def build_resource_object(name_property, vlan, resource, entry): else: entry = new_entry - return entry \ No newline at end of file + return entry diff --git a/docs/cli/reports.rst b/docs/cli/reports.rst index f62de5882..39299e99b 100644 --- a/docs/cli/reports.rst +++ b/docs/cli/reports.rst @@ -14,4 +14,14 @@ A list of datacenters, and how many servers, VSI, vlans, subnets and public_ips .. click:: SoftLayer.CLI.report.bandwidth:cli :prog: report bandwidth - :show-nested: \ No newline at end of file + :show-nested: + + +.. click:: SoftLayer.CLI.report.dc_closures:cli + :prog: report datacenter-closures + :show-nested: + +Displays some basic information about the Servers and other resources that are in Datacenters scheduled to be +decommissioned in the near future. +See `IBM Cloud Datacenter Consolidation `_ for +more information \ No newline at end of file diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index f756704c0..f2012ab34 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -7,6 +7,7 @@ from SoftLayer import testing import json +from unittest import mock as mock from pprint import pprint as pp @@ -295,3 +296,104 @@ def test_server_bandwidth_report(self): 300, ) self.assertEqual(expected_args, call.args) + + def test_dc_closure_report(self): + search_mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + search_mock.side_effect = [_advanced_search(), [], [], []] + result = self.run_command(['report', 'datacenter-closures']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects', filter=mock.ANY, mask=mock.ANY) + self.assert_called_with('SoftLayer_Search', 'advancedSearch') + json_output = json.loads(result.output) + pp(json_output) + self.assertEqual(5, len(json_output)) + self.assertEqual('bcr01a.ams01', json_output[0]['POD']) + + +def _advanced_search(): + results = [{'matchedTerms': ['primaryRouter.hostname:|fcr01a.mex01|'], + 'relevanceScore': '5.4415264', + 'resource': {'fullyQualifiedName': 'mex01.fcr01.858', + 'hardware': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testpooling2.ibmtest.com', + 'id': 1676221}, + {'billingItem': {'cancellationDate': '2022-03-03T23:59:59-06:00'}, + 'fullyQualifiedDomainName': 'testpooling.ibmtest.com', + 'id': 1534033}], + 'id': 1133383, + 'name': 'Mex-BM-Public', + 'networkSpace': 'PUBLIC', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 858}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|fcr01a.mex01|'], + 'relevanceScore': '5.4415264', + 'resource': {'fullyQualifiedName': 'mex01.fcr01.1257', + 'hardware': [], + 'id': 2912280, + 'networkSpace': 'PUBLIC', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'imageTest.ibmtest.com', + 'id': 127270182}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'test.deleteme.com', + 'id': 106291032}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testslack.test.com', + 'id': 127889958}], + 'vlanNumber': 1257}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '5.003179', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1472', + 'hardware': [], + 'id': 2912282, + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'imageTest.ibmtest.com', + 'id': 127270182}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'test.deleteme.com', + 'id': 106291032}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testslack.test.com', + 'id': 127889958}], + 'vlanNumber': 1472}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '4.9517627', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1664', + 'hardware': [{'billingItem': {'cancellationDate': '2022-03-03T23:59:59-06:00'}, + 'fullyQualifiedDomainName': 'testpooling.ibmtest.com', + 'id': 1534033}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testpooling2.ibmtest.com', + 'id': 1676221}], + 'id': 3111644, + 'name': 'testmex', + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 1664}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '4.9517627', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1414', + 'hardware': [], + 'id': 2933662, + 'name': 'test-for-trunks', + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 1414}, + 'resourceType': 'SoftLayer_Network_Vlan'}] + return results