diff --git a/SoftLayer/CLI/object_storage/credential/__init__.py b/SoftLayer/CLI/object_storage/credential/__init__.py new file mode 100644 index 000000000..1f71754bb --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/__init__.py @@ -0,0 +1,42 @@ +"""Manages Object Storage S3 Credentials.""" +# :license: MIT, see LICENSE for more details. + +import importlib +import os + +import click + +CONTEXT = {'help_option_names': ['-h', '--help'], + 'max_content_width': 999} + + +class CapacityCommands(click.MultiCommand): + """Loads module for object storage S3 credentials related commands.""" + + def __init__(self, **attrs): + click.MultiCommand.__init__(self, **attrs) + self.path = os.path.dirname(__file__) + + def list_commands(self, ctx): + """List all sub-commands.""" + commands = [] + for filename in os.listdir(self.path): + if filename == '__init__.py': + continue + if filename.endswith('.py'): + commands.append(filename[:-3].replace("_", "-")) + commands.sort() + return commands + + def get_command(self, ctx, cmd_name): + """Get command for click.""" + path = "%s.%s" % (__name__, cmd_name) + path = path.replace("-", "_") + module = importlib.import_module(path) + return getattr(module, 'cli') + + +# Required to get the sub-sub-sub command to work. +@click.group(cls=CapacityCommands, context_settings=CONTEXT) +def cli(): + """Base command for all object storage credentials S3 related concerns""" diff --git a/SoftLayer/CLI/object_storage/credential/create.py b/SoftLayer/CLI/object_storage/credential/create.py new file mode 100644 index 000000000..934ac7651 --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/create.py @@ -0,0 +1,28 @@ +"""Create credentials for an IBM Cloud Object Storage Account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Create credentials for an IBM Cloud Object Storage Account""" + + mgr = SoftLayer.ObjectStorageManager(env.client) + credential = mgr.create_credential(identifier) + table = formatting.Table(['id', 'password', 'username', 'type_name']) + table.sortby = 'id' + table.add_row([ + credential['id'], + credential['password'], + credential['username'], + credential['type']['name'] + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/object_storage/credential/delete.py b/SoftLayer/CLI/object_storage/credential/delete.py new file mode 100644 index 000000000..10d7dc655 --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/delete.py @@ -0,0 +1,22 @@ +"""Delete the credential of an Object Storage Account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('identifier') +@click.option('--credential_id', '-id', type=click.INT, + help="This is the credential id associated with the volume") +@environment.pass_env +def cli(env, identifier, credential_id): + """Delete the credential of an Object Storage Account.""" + + mgr = SoftLayer.ObjectStorageManager(env.client) + credential = mgr.delete_credential(identifier, credential_id=credential_id) + + if credential: + env.fout("The credential was deleted successful") diff --git a/SoftLayer/CLI/object_storage/credential/limit.py b/SoftLayer/CLI/object_storage/credential/limit.py new file mode 100644 index 000000000..96d293eae --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/limit.py @@ -0,0 +1,24 @@ +""" Credential limits for this IBM Cloud Object Storage account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """ Credential limits for this IBM Cloud Object Storage account.""" + + mgr = SoftLayer.ObjectStorageManager(env.client) + limit = mgr.limit_credential(identifier) + table = formatting.Table(['limit']) + table.add_row([ + limit, + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/object_storage/credential/list.py b/SoftLayer/CLI/object_storage/credential/list.py new file mode 100644 index 000000000..2cf81ca3c --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/list.py @@ -0,0 +1,29 @@ +"""Retrieve credentials used for generating an AWS signature. Max of 2.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Retrieve credentials used for generating an AWS signature. Max of 2.""" + + mgr = SoftLayer.ObjectStorageManager(env.client) + list = mgr.list_credential(identifier) + table = formatting.Table(['id', 'password', 'username', 'type_name']) + + for credential in list: + table.add_row([ + credential['id'], + credential['password'], + credential['username'], + credential['type']['name'] + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c04233c52..e7fbc022d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -36,6 +36,7 @@ ('virtual:reboot', 'SoftLayer.CLI.virt.power:reboot'), ('virtual:reload', 'SoftLayer.CLI.virt.reload:cli'), ('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'), + ('virtual:usage', 'SoftLayer.CLI.virt.usage:cli'), ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), @@ -199,6 +200,8 @@ 'SoftLayer.CLI.object_storage.list_accounts:cli'), ('object-storage:endpoints', 'SoftLayer.CLI.object_storage.list_endpoints:cli'), + ('object-storage:credential', + 'SoftLayer.CLI.object_storage.credential:cli'), ('order', 'SoftLayer.CLI.order'), ('order:category-list', 'SoftLayer.CLI.order.category_list:cli'), diff --git a/SoftLayer/CLI/virt/usage.py b/SoftLayer/CLI/virt/usage.py new file mode 100644 index 000000000..ce6407b14 --- /dev/null +++ b/SoftLayer/CLI/virt/usage.py @@ -0,0 +1,44 @@ +"""Usage information of a virtual server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment, helpers +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + + +@click.command(short_help="Usage information of a virtual server.") +@click.argument('identifier') +@click.option('--start_date', '-s', type=click.STRING, required=True, help="Start Date e.g. 2019-3-4") +@click.option('--end_date', '-e', type=click.STRING, required=True, help="End Date e.g. 2019-4-2") +@click.option('--valid_type', '-t', type=click.STRING, required=True, + help="Metric_Data_Type keyName e.g. CPU0, CPU1, MEMORY_USAGE, etc.") +@click.option('--summary_period', '-summary', type=click.INT, required=True, + help="300, 600, 1800, 3600, 43200 or 86400 seconds") +@environment.pass_env +def cli(env, identifier, start_date, end_date, valid_type, summary_period): + """Usage information of a virtual server.""" + + vsi = SoftLayer.VSManager(env.client) + table = formatting.Table(['counter', 'dateTime', 'type']) + + if not any([start_date, end_date, valid_type, summary_period]): + raise exceptions.ArgumentError( + "Must provide [--start_date], [--end_date], [--valid_type], or [--summary_period] to retrieve the usage " + "information") + + vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') + + result = vsi.get_summary_data_usage(instance_id=vs_id, start_date=start_date, end_date=end_date, + valid_type=valid_type, summary_period=summary_period) + + for data in result: + table.add_row([ + data['counter'], + data['dateTime'], + data['type'], + ]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py b/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py new file mode 100644 index 000000000..6a0a031a2 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py @@ -0,0 +1,12 @@ +getSummaryData = [ + { + "counter": 1.44, + "dateTime": "2019-03-04T00:00:00-06:00", + "type": "cpu0" + }, + { + "counter": 1.53, + "dateTime": "2019-03-04T00:05:00-06:00", + "type": "cpu0" + }, +] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py new file mode 100644 index 000000000..4bc3f4fc7 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py @@ -0,0 +1,39 @@ +credentialCreate = { + "accountId": "12345", + "createDate": "2019-04-05T13:25:25-06:00", + "id": 11111, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAFhDOjHqYJva", + "username": "XfHhBNBPlPdlWyaPPJAI", + "type": { + "description": "A credential for generating S3 Compatible Signatures.", + "keyName": "S3_COMPATIBLE_SIGNATURE", + "name": "S3 Compatible Signature" + } +} + +getCredentials = [ + { + "accountId": "12345", + "createDate": "2019-04-05T13:25:25-06:00", + "id": 11111, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAFhDOjHqYJva", + "username": "XfHhBNBPlPdlWyaPPJAI", + "type": { + "description": "A credential for generating S3 Compatible Signatures.", + "keyName": "S3_COMPATIBLE_SIGNATURE", + "name": "S3 Compatible Signature" + } + }, + { + "accountId": "12345", + "createDate": "2019-04-05T13:25:25-06:00", + "id": 11111, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAFhDOjHqYJva", + "username": "XfHhBNBPlPdlWyaPPJAI", + "type": { + "description": "A credential for generating S3 Compatible Signatures.", + "keyName": "S3_COMPATIBLE_SIGNATURE", + "name": "S3 Compatible Signature" + } + } +] diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index b25a457e1..0214c2416 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -52,3 +52,39 @@ def list_endpoints(self): }) return endpoints + + def create_credential(self, identifier): + """Create object storage credential. + :param int identifier: The object storage account identifier. + """ + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate', + id=identifier) + + def delete_credential(self, identifier, credential_id=None): + """Delete the object storage credential. + :param int id: The object storage account identifier. + :param int credential_id: The credential id to be deleted. + """ + credential = { + 'id': credential_id + } + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete', + credential, id=identifier) + + def limit_credential(self, identifier): + """Limit object storage credentials. + :param int identifier: The object storage account identifier. + """ + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit', + id=identifier) + + def list_credential(self, identifier): + """List the object storage credentials. + :param int identifier: The object storage account identifier. + """ + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials', + id=identifier) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 00b738d0c..ab1bcb43e 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -51,6 +51,7 @@ def __init__(self, client, ordering_manager=None): self.account = client['Account'] self.guest = client['Virtual_Guest'] self.package_svc = client['Product_Package'] + self.metric_tracking_object = client['Metric_Tracking_Object'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) @@ -1002,6 +1003,25 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public else: return price.get('id') + def get_summary_data_usage(self, instance_id, start_date=None, end_date=None, valid_type=None, summary_period=None): + """Retrieve the usage information of a virtual server. + + :param string instance_id: a string identifier used to resolve ids + :param string start_date: the start data to retrieve the vs usage information + :param string end_date: the start data to retrieve the vs usage information + :param string string valid_type: the Metric_Data_Type keyName. + :param int summary_period: summary period. + """ + valid_types = [ + { + "keyName": valid_type, + "summaryType": "max" + } + ] + + return self.metric_tracking_object.getSummaryData(start_date, end_date, valid_types, summary_period, + id=instance_id) + # pylint: disable=inconsistent-return-statements def _get_price_id_for_upgrade(self, package_items, option, value, public=True): """Find the price id for the option and value to upgrade. diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 7c8e7ec96..28856a964 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -37,3 +37,73 @@ def test_list_endpoints(self): [{'datacenter': 'dal05', 'private': 'https://dal05/auth/v1.0/', 'public': 'https://dal05/auth/v1.0/'}]) + + def test_create_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate') + accounts.return_value = { + "accountId": "12345", + "createDate": "2019-04-05T13:25:25-06:00", + "id": 11111, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCy", + "username": "XfHhBNBPlPdl", + "type": { + "description": "A credential for generating S3 Compatible Signatures.", + "keyName": "S3_COMPATIBLE_SIGNATURE", + "name": "S3 Compatible Signature" + } + } + + result = self.run_command(['object-storage', 'credential', 'create', '100']) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + [{'id': 11111, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCy', + 'type_name': 'S3 Compatible Signature', + 'username': 'XfHhBNBPlPdl'}] + ) + + def test_delete_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') + accounts.return_value = True + + result = self.run_command(['object-storage', 'credential', 'delete', '-id=100', '100']) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + 'The credential was deleted successful' + ) + + def test_limit_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') + accounts.return_value = 2 + + result = self.run_command(['object-storage', 'credential', 'limit', '100']) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), [{'limit': 2}]) + + def test_list_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials') + accounts.return_value = [{'id': 1103123, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', + 'type': {'name': 'S3 Compatible Signature'}, + 'username': 'XfHhBNBPlPdlWya'}, + {'id': 1103333, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9', + 'type': {'name': 'S3 Compatible Signature'}, + 'username': 'XfHhBNBPlPd'}] + + result = self.run_command(['object-storage', 'credential', 'list', '100']) + + self.assert_no_fail(result) + print(json.loads(result.output)) + self.assertEqual(json.loads(result.output), + [{'id': 1103123, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', + 'type_name': 'S3 Compatible Signature', + 'username': 'XfHhBNBPlPdlWya'}, + {'id': 1103333, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9', + 'type_name': 'S3 Compatible Signature', + 'username': 'XfHhBNBPlPd'}]) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index ce1bb9d73..66f09ca4c 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -660,3 +660,27 @@ def test_vs_capture(self): result = self.run_command(['vs', 'capture', '100', '--name', 'TestName']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Virtual_Guest', 'createArchiveTransaction', identifier=100) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_usage_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['vs', 'usage', '100']) + self.assertEqual(result.exit_code, 2) + + def test_usage_startdate_enddate_type_no_summary(self): + result = self.run_command( + ['vs', 'usage', '100']) + self.assertEqual(result.exit_code, 2) + + def test_usage_vs(self): + + result = self.run_command( + ['vs', 'usage', '100', '--start_date=2019-3-4', '--end_date=2019-4-2', '--valid_type=CPU0', + '--summary_period=300']) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + [{'counter': 1.44, 'dateTime': '2019-03-04T00:00:00-06:00', 'type': 'cpu0'}, + {'counter': 1.53, 'dateTime': '2019-03-04T00:05:00-06:00', 'type': 'cpu0'}] + ) diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index 478e5158b..c10524466 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -42,3 +42,80 @@ def test_list_endpoints_no_results(self): endpoints = self.object_storage.list_endpoints() self.assertEqual(endpoints, []) + + def test_create_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate') + accounts.return_value = { + "id": 1103123, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", + "username": "XfHhBNBPlPdlWyaP", + "type": { + "name": "S3 Compatible Signature" + } + } + credential = self.object_storage.create_credential(100) + self.assertEqual(credential, + { + "id": 1103123, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", + "username": "XfHhBNBPlPdlWyaP", + "type": { + "name": "S3 Compatible Signature" + } + }) + + def test_delete_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') + accounts.return_value = 'The credential was deleted successful' + + credential = self.object_storage.delete_credential(100) + self.assertEqual(credential, 'The credential was deleted successful') + + def test_limit_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') + accounts.return_value = 2 + + credential = self.object_storage.limit_credential(100) + self.assertEqual(credential, 2) + + def test_list_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials') + accounts.return_value = [ + { + "id": 1103123, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXsf4sf", + "username": "XfHhBNBPlPdlWyaP3fsd", + "type": { + "name": "S3 Compatible Signature" + } + }, + { + "id": 1102341, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", + "username": "XfHhBNBPlPdlWyaP", + "type": { + "name": "S3 Compatible Signature" + } + } + ] + credential = self.object_storage.list_credential(100) + self.assertEqual(credential, + [ + { + "id": 1103123, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXsf4sf", + "username": "XfHhBNBPlPdlWyaP3fsd", + "type": { + "name": "S3 Compatible Signature" + } + }, + { + "id": 1102341, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", + "username": "XfHhBNBPlPdlWyaP", + "type": { + "name": "S3 Compatible Signature" + } + } + ] + ) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index f13c3e7b9..c07855c1a 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -830,3 +830,13 @@ def test_capture_additional_disks(self): 'createArchiveTransaction', args=args, identifier=1) + + def test_usage_vs(self): + result = self.vs.get_summary_data_usage('100', + start_date='2019-3-4', + end_date='2019-4-2', + valid_type='CPU0', + summary_period=300) + + self.assertEqual(result, [{'counter': 1.44, 'dateTime': '2019-03-04T00:00:00-06:00', 'type': 'cpu0'}, + {'counter': 1.53, 'dateTime': '2019-03-04T00:05:00-06:00', 'type': 'cpu0'}])