From 268854a1cbf55cb8523612eea45964c1801926fc Mon Sep 17 00:00:00 2001 From: Aparna Patil Date: Tue, 25 Mar 2014 18:21:28 +0530 Subject: [PATCH 1/7] Created new iSCSI Manager iSCSI manager provides an easy interface to work with iSCSI volumes on SL. --- SoftLayer/CLI/core.py | 90 ++++------ SoftLayer/CLI/exceptions.py | 8 +- SoftLayer/CLI/helpers.py | 4 +- SoftLayer/CLI/modules/iscsi.py | 315 +++++++++++++++++++++++++++++++-- SoftLayer/managers/__init__.py | 4 +- SoftLayer/managers/iscsi.py | 151 ++++++++++++++++ 6 files changed, 492 insertions(+), 80 deletions(-) create mode 100644 SoftLayer/managers/iscsi.py diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 481f69feb..57f66d356 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -17,7 +17,6 @@ sshkey Manage SSH keys on your account Networking: - cdn Content Delivery Network service management dns Domain Name System firewall Firewall rule and security management globalip Global IP address management @@ -33,7 +32,6 @@ General: config View and edit configuration for this tool - ticket Manage account tickets summary Display an overall summary of your account help Show help @@ -51,7 +49,7 @@ from SoftLayer import Client, TimedClient, SoftLayerError, SoftLayerAPIError from SoftLayer.consts import VERSION -from .helpers import CLIAbort, ArgumentError, format_output, KeyValueTable +from .helpers import CLIAbort, ArgumentError, InvalidInput, format_output, KeyValueTable from .environment import Environment, InvalidCommand, InvalidModule @@ -66,25 +64,18 @@ class CommandParser(object): - """ Helper class to parse commands - - :param env: Environment instance - """ def __init__(self, env): self.env = env def get_main_help(self): - """ Get main help text """ return __doc__.strip() def get_module_help(self, module_name): - """ Get help text for a module """ module = self.env.load_module(module_name) arg_doc = module.__doc__ return arg_doc.strip() def get_command_help(self, module_name, command_name): - """ Get help text for a specific command """ command = self.env.get_command(module_name, command_name) default_format = 'raw' @@ -102,18 +93,16 @@ def get_command_help(self, module_name, command_name): if '[options]' in arg_doc: arg_doc += """ Standard Options: - --format=ARG Output format. [Options: table, raw] [Default: %s] - -C FILE --config=FILE Config file location. [Default: ~/.softlayer] - --debug=LEVEL Specifies the debug noise level + --format=ARG Output format. [Options: table, raw] [Default: %s] + -C FILE --config=FILE Config file location. [Default: ~/.softlayer] + --debug=LEVEL Specifies the debug noise level 1=warn, 2=info, 3=debug - --timings Time each API call and display after results - --proxy=PROTO:PROXY_URL HTTP[s] proxy to be use to make API calls - -h --help Show this screen + --timings Time each API call and display after results + -h --help Show this screen """ % default_format return arg_doc.strip() def parse_main_args(self, args): - """ Parse root arguments """ main_help = self.get_main_help() arguments = docopt( main_help, @@ -124,7 +113,6 @@ def parse_main_args(self, args): return arguments def parse_module_args(self, module_name, args): - """ Parse module arguments """ arg_doc = self.get_module_help(module_name) arguments = docopt( arg_doc, @@ -134,14 +122,12 @@ def parse_module_args(self, module_name, args): return arguments def parse_command_args(self, module_name, command_name, args): - """ Parse command arguments """ command = self.env.get_command(module_name, command_name) arg_doc = self.get_command_help(module_name, command_name) arguments = docopt(arg_doc, version=VERSION, argv=[module_name] + args) return command, arguments def parse(self, args): - """ Parse entire tree of arguments """ # handle `sl ...` main_args = self.parse_main_args(args) module_name = main_args[''] @@ -173,18 +159,14 @@ def main(args=sys.argv[1:], env=Environment()): debug_level = command_args.get('--debug') if debug_level: logger = logging.getLogger() - handler = logging.StreamHandler() - logger.addHandler(handler) + h = logging.StreamHandler() + logger.addHandler(h) logger.setLevel(DEBUG_LOGGING_MAP.get(debug_level, logging.DEBUG)) - kwargs = { - 'proxy': command_args.get('--proxy'), - 'config_file': command_args.get('--config') - } if command_args.get('--timings'): - client = TimedClient(**kwargs) + client = TimedClient(config_file=command_args.get('--config')) else: - client = Client(**kwargs) + client = Client(config_file=command_args.get('--config')) # Do the thing runnable = command(client=client, env=env) @@ -193,56 +175,56 @@ def main(args=sys.argv[1:], env=Environment()): out_format = command_args.get('--format', 'table') if out_format not in VALID_FORMATS: raise ArgumentError('Invalid format "%s"' % out_format) - output = format_output(data, fmt=out_format) - if output: - env.out(output) + s = format_output(data, fmt=out_format) + if s: + env.out(s) if command_args.get('--timings'): out_format = command_args.get('--format', 'table') api_calls = client.get_last_calls() - timing_table = KeyValueTable(['call', 'time']) + t = KeyValueTable(['call', 'time']) for call, _, duration in api_calls: - timing_table.add_row([call, duration]) + t.add_row([call, duration]) - env.err(format_output(timing_table, fmt=out_format)) + env.err(format_output(t, fmt=out_format)) - except InvalidCommand as ex: - env.err(resolver.get_module_help(ex.module_name)) - if ex.command_name: + except InvalidCommand as e: + env.err(resolver.get_module_help(e.module_name)) + if e.command_name: env.err('') - env.err(str(ex)) + env.err(str(e)) exit_status = 1 - except InvalidModule as ex: + except InvalidModule as e: env.err(resolver.get_main_help()) - if ex.module_name: + if e.module_name: env.err('') - env.err(str(ex)) + env.err(str(e)) exit_status = 1 - except DocoptExit as ex: - env.err(ex.usage) + except DocoptExit as e: + env.err(e.usage) env.err( '\nUnknown argument(s), use -h or --help for available options') exit_status = 127 except KeyboardInterrupt: env.out('') exit_status = 1 - except CLIAbort as ex: - env.err(str(ex.message)) - exit_status = ex.code - except SystemExit as ex: - exit_status = ex.code - except SoftLayerAPIError as ex: - if 'invalid api token' in ex.faultString.lower(): + except CLIAbort as e: + env.err(str(e.message)) + exit_status = e.code + except SystemExit as e: + exit_status = e.code + except SoftLayerAPIError as e: + if 'invalid api token' in e.faultString.lower(): env.out("Authentication Failed: To update your credentials, use " "'sl config setup'") else: - env.err(str(ex)) + env.err(str(e)) exit_status = 1 - except SoftLayerError as ex: - env.err(str(ex)) + except SoftLayerError as e: + env.err(str(e)) exit_status = 1 - except Exception: + except Exception as e: import traceback env.err(traceback.format_exc()) exit_status = 1 diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index 100966945..6caace3de 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -8,21 +8,23 @@ class CLIHalt(SystemExit): - """ Smoothly halt the execution of the command. No error """ def __init__(self, code=0, *args): super(CLIHalt, self).__init__(*args) self.code = code class CLIAbort(CLIHalt): - """ Halt the execution of the command. Gives an exit code of 2 """ def __init__(self, msg, *args): super(CLIAbort, self).__init__(code=2, *args) self.message = msg class ArgumentError(CLIAbort): - """ Halt the execution of the command because of invalid arguments. """ def __init__(self, msg, *args): super(ArgumentError, self).__init__(msg, *args) self.message = "Argument Error: %s" % msg + +class InvalidInput(CLIAbort): + def _init_(self, msg, *args): + super(InvalidInput, self).__init__(msg, *args) + self.message = "InvalidInput: %s" % msg diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index 6c9e928ad..9fbf21da9 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -8,7 +8,7 @@ from SoftLayer.utils import NestedDict from SoftLayer.CLI.environment import CLIRunnable -from .exceptions import CLIHalt, CLIAbort, ArgumentError +from .exceptions import CLIHalt, CLIAbort, ArgumentError, InvalidInput from .formatting import ( Table, KeyValueTable, FormattedItem, SequentialOutput, confirm, no_going_back, mb_to_gb, gb, listing, blank, format_output, @@ -19,7 +19,7 @@ # Core/Misc 'CLIRunnable', 'NestedDict', 'FALSE_VALUES', 'resolve_id', # Exceptions - 'CLIAbort', 'CLIHalt', 'ArgumentError', + 'CLIAbort', 'CLIHalt', 'ArgumentError', 'InvalidInput', # Formatting 'Table', 'KeyValueTable', 'FormattedItem', 'SequentialOutput', 'valid_response', 'confirm', 'no_going_back', 'mb_to_gb', 'gb', diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index e8df62968..c7c9d5204 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -4,17 +4,32 @@ Manage iSCSI targets The available commands are: - list List iSCSI targets -""" -# :license: MIT, see LICENSE for more details. + list List iSCSI targets + create Create iSCSI target + detail Output details about iSCSI + cancel cancel iSCSI target + order_snapshot_space Orders space for snapshots + create_snapshot Create snapshot of iSCSI + delete_snapshot Delete iSCSI snapshot + restore_volume Restores volume from existing snapshot + list_snapshots List Snapshots of given iscsi -from SoftLayer.CLI import CLIRunnable, Table, FormattedItem -from SoftLayer.CLI.helpers import NestedDict, blank +""" +from SoftLayer.utils import lookup +from SoftLayer.CLI import ( + CLIRunnable, Table, no_going_back, confirm, mb_to_gb, listing, + FormattedItem) +from SoftLayer.CLI.helpers import ( + CLIAbort, ArgumentError, InvalidInput, NestedDict, blank, resolve_id, KeyValueTable, + update_with_template_args, FALSE_VALUES, export_to_template, + active_txn, transaction_status) +from SoftLayer import iSCSIManager class ListISCSI(CLIRunnable): + """ -usage: sl iscsi list [options] + usage: sl iscsi list [options] List iSCSI accounts """ @@ -23,11 +38,11 @@ class ListISCSI(CLIRunnable): def execute(self, args): account = self.client['Account'] - iscsi_list = account.getIscsiNetworkStorage( + iscsi = account.getIscsiNetworkStorage( mask='eventCount,serviceResource[datacenter.name]') - iscsi_list = [NestedDict(iscsi) for iscsi in iscsi_list] + iscsi = [NestedDict(n) for n in iscsi] - table = Table([ + t = Table([ 'id', 'datacenter', 'size', @@ -36,15 +51,277 @@ def execute(self, args): 'server' ]) - for iscsi in iscsi_list: - table.add_row([ - iscsi['id'], - iscsi['serviceResource']['datacenter'].get('name', blank()), + for n in iscsi: + t.add_row([ + n['id'], + n['serviceResource']['datacenter'].get('name', blank()), FormattedItem( - iscsi.get('capacityGb', blank()), - "%dGB" % iscsi.get('capacityGb', 0)), - iscsi.get('username', blank()), - iscsi.get('password', blank()), - iscsi.get('serviceResourceBackendIpAddress', blank())]) + n.get('capacityGb', blank()), + "%dGB" % n.get('capacityGb', 0)), + n.get('username', blank()), + n.get('password', blank()), + n.get('serviceResourceBackendIpAddress', blank())]) + + return t + + +class CreateiSCSI(CLIRunnable): + + """ + usage: sl iscsi create --size=SIZE --dc=DC [options] + +Order/create an iSCSI storage. + +Required: + --size=SIZE Size + --dc=DC Datacenter +""" + action = 'create' + options = ['confirm'] + required_params = ['--size', '--dc'] + + def execute(self, args): + iscsi = iSCSIManager(self.client) + + self._validate_create_args(args) + + size, location = self._parse_create_args(args) + location = self._get_location_id(location) + items = iscsi.find_items(int(size)) + + iscsi.order_iscsi(items, size, location) + + def _validate_create_args(self, args): + invalid_args = [k for k in self.required_params if args.get(k) is None] + if invalid_args: + raise ArgumentError('Missing required options: %s' + % ','.join(invalid_args)) + + def _parse_create_args(self, args): + + size = int(args['--size']) + location = str(args['--dc']) + return size, location + + def _get_location_id(self, location): + datacenters = self.client['Location_Datacenter'].getDatacenters( + mask='mask[longName,id,name]') + for dc in datacenters: + if dc['name'] == location: + self.location = dc['id'] + return self.location + raise InvalidInput('Inavlid datacenter name: %s' + % location) + + +class CanceliSCSI(CLIRunnable): + + """ +usage: sl iscsi [--immediate] [--reason] cancel [options] + +Cancel iSCSI Storage + +options : +--immediate Cancels the iSCSI immediately (instead of on the billing + anniversary) +--reason Reason for cancellation + +Prompt Options: +-y, --really Confirm all prompt actions +""" + action = 'cancel' + options = ['confirm'] + + def execute(self, args): + iscsi = iSCSIManager(self.client) + iscsi_id = resolve_id( + iscsi.resolve_ids, + args.get(''), + 'iSCSI') + immediate = args.get('--immediate', False) + reason = args.get('--reason', str('No longer needed')) + + if args['--really'] or no_going_back(iscsi_id): + iscsi.cancel_iscsi(iscsi_id, reason, immediate) + else: + CLIAbort('Aborted') + + +class IscsiDetails(CLIRunnable): + + """ +usage: sl iscsi detail [--passwords] [options] + +Get details for a iSCSI + +Options: + --passwords Show passwords + + +""" + action = 'detail' + + def execute(self, args): + iscsi = iSCSIManager(self.client) + t = KeyValueTable(['Name', 'Value']) + t.align['Name'] = 'r' + t.align['Value'] = 'l' + + iscsi_id = resolve_id( + iscsi.resolve_ids, + args.get(''), + 'iSCSI') + result = iscsi.get_iscsi(iscsi_id) + result = NestedDict(result) + + t.add_row(['id', result['id']]) + t.add_row(['serviceResourceName', result['serviceResourceName']]) + t.add_row(['createDate', result['createDate']]) + t.add_row(['nasType', result['nasType']]) + t.add_row(['capacityGb', result['capacityGb']]) + if result['snapshotCapacityGb']: + t.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) + t.add_row(['mountableFlag', result['mountableFlag']]) + t.add_row(['serviceResourceBackendIpAddress', result['serviceResourceBackendIpAddress']]) + t.add_row(['price', result['billingItem']['recurringFee']]) + t.add_row(['BillingItemId', result['billingItem']['id']]) + if result.get('notes'): + t.add_row(['notes', result['notes']]) + + if args.get('--passwords'): + pass_table = Table(['username', 'password']) + pass_table.add_row([result['username'], result['password']]) + t.add_row(['users', pass_table]) + + return t - return table + +class IscsiCreateSnapshot(CLIRunnable): + + """ +usage: sl iscsi create_snapshot [options] + +create an iSCSI snapshot. + +""" + action = 'create_snapshot' + + def execute(self, args): + iscsi = iSCSIManager(self.client) + iscsi_id = resolve_id(iscsi.resolve_ids, + args.get(''), + 'iSCSI') + iscsi.create_snapshot(iscsi_id) + + +class OrderIscsiSpace(CLIRunnable): + + """ +usage: sl iscsi order_snapshot_space [--capacity=Capacity...] [options] + +Order iSCSI snapshot space. + +Required : +--capacity = Snapshot Capacity +""" + + action = 'order_snapshot_space' + required_params = ['--capacity'] + + def execute(self, args): + iscsi = iSCSIManager(self.client) + invalid_args = [k for k in self.required_params if args.get(k) is None] + if invalid_args: + raise ArgumentError('Missing required options: %s' + % ','.join(invalid_args)) + iscsi_id = resolve_id( + iscsi.resolve_ids, + args.get(''), + 'iSCSI') + item_price = iscsi.find_space(int(args['--capacity'][0])) + result = iscsi.get_iscsi( + iscsi_id, + mask='mask[id,capacityGb,serviceResource[datacenter]]') + snapshotSpaceOrder = { + 'complexType': + 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', + 'location': result['serviceResource']['datacenter']['id'], + 'packageId': 0, + 'prices': [{'id': item_price}], + 'quantity': 1, + 'volumeId': iscsi_id} + iscsi.Order_snapshot_space(**snapshotSpaceOrder) + + +class IscsiDeleteSnapshot(CLIRunnable): + + """ +usage: sl iscsi delete_snapshot [options] + +Delete iSCSI snapshot. + +""" + action = 'delete_snapshot' + + def execute(self, args): + iscsi = iSCSIManager(self.client) + snapshot_id = resolve_id( + iscsi.resolve_ids, + args.get(''), + 'Snapshot') + iscsi.delete_snapshot(snapshot_id) + + +class RestoreVolumefromSnapshot(CLIRunnable): + + """ +usage: sl iscsi restore_volume + +restores volume from existing snapshot. + +""" + action = 'restore_volume' + + def execute(self, args): + iscsi = iSCSIManager(self.client) + snapshot_id = resolve_id( + iscsi.resolve_ids, + args.get(''), + 'Snapshot') + result = iscsi.get_iscsi(snapshot_id,mask='mask[parentPartnerships.volumeId]') + volume_id = result['parentPartnerships'][0]['volumeId'] + iscsi.restore_from_snapshot(volume_id, snapshot_id) + +class ListISCSISnapshots(CLIRunnable): + + """ + usage: sl iscsi list_snapshots + +List iSCSI Snapshots +""" + action = 'list_snapshots' + + def execute(self, args): + mgr = iSCSIManager(self.client) + iscsi_id = resolve_id(mgr.resolve_ids,args.get(''),'iSCSI') + iscsi = self.client['Network_Storage_Iscsi'] + snapshots = iscsi.getPartnerships(mask='volumeId,partnerVolumeId,createDate,type', id = iscsi_id) + print snapshots + snapshots = [NestedDict(n) for n in snapshots] + + t = Table([ + 'id', + 'createDate', + 'name', + 'description', + ]) + + for n in snapshots: + t.add_row([ + n['partnerVolumeId'], + n['createDate'], + n['type']['name'], + n['type']['description'], + ]) + return t + diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 9db06a1d3..2f3f8260c 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -18,7 +18,7 @@ from SoftLayer.managers.sshkey import SshKeyManager from SoftLayer.managers.ssl import SSLManager from SoftLayer.managers.ticket import TicketManager - +from SoftLayer.managers.iscsi import iSCSIManager __all__ = ['CCIManager', 'DNSManager', 'FirewallManager', 'HardwareManager', 'ImageManager', 'MessagingManager', 'MetadataManager', - 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager'] + 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager', 'iSCSIManager'] diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py new file mode 100644 index 000000000..c8295ec60 --- /dev/null +++ b/SoftLayer/managers/iscsi.py @@ -0,0 +1,151 @@ +import socket +from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin + + +class iSCSIManager(IdentifierMixin, object): + + """ + Manages iSCSI storages. + + :param SoftLayer.API.Client client: an API client instance + """ + + def __init__(self, client): + self.configuration = {} + self.client = client + self.iscsi = self.client['Network_Storage_Iscsi'] + self.product_order = self.client['SoftLayer_Product_Order'] + self.account = self.client['Account'] + + def find_items(self, size): + items = [] + _filter = NestedDict({}) + _filter[ + 'itemPrices'][ + 'item'][ + 'description'] = query_filter( + '~GB iSCSI SAN Storage') + _filter['itemPrices']['item']['capacity'] = query_filter('%s' % size) + iscsi_item_prices = self.client['Product_Package'].getItemPrices( + id=0, + filter=_filter.to_dict()) + iscsi_item_prices = sorted( + iscsi_item_prices, + key=lambda x: float(x.get('recurringFee', 0))) + iscsi_item_prices = sorted( + iscsi_item_prices, + key=lambda x: float(x['item']['capacity'])) + for price in iscsi_item_prices: + items.append(price['id']) + return items + + def find_space(self, size): + _filter = NestedDict({}) + _filter[ + 'itemPrices'][ + 'item'][ + 'description'] = query_filter( + '~iSCSI SAN Snapshot Space') + _filter['itemPrices']['item']['capacity'] = query_filter('>=%s' % size) + item_prices = self.client['Product_Package'].getItemPrices( + id=0, + mask='mask[id,item[capacity]]', + filter=_filter.to_dict()) + item_prices = sorted( + item_prices, + key=lambda x: int(x['item']['capacity'])) + if len(item_prices) == 0: + return None + return item_prices[0]['id'] + + def build_order(self, item, location): + order = { + 'complexType': + 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi', + 'location': location, + 'packageId': 0, # storage package + 'prices': [{'id': item}], + 'quantity': 1 + } + return order + + def order_iscsi(self, items, size, location): + """Places an order for iSCSI volume + """ + + for item in items: + iscsi_order = self.build_order(item, location) + try: + self.product_order.verifyOrder(iscsi_order) + order = self.product_order.placeOrder(iscsi_order) + except Exception as e: + continue + return + + def get_iscsi(self, volume_id, **kwargs): + """ Get details about a iSCSI storage + + :param integer volume_id: the volume ID + :returns: A dictionary containing a large amount of information about + the specified storage. + + """ + + if 'mask' not in kwargs: + items = set([ + 'id', + 'serviceResourceName', + 'createDate', + 'nasType', + 'capacityGb', + 'snapshotCapacityGb', + 'mountableFlag', + 'serviceResourceBackendIpAddress', + 'billingItem', + 'notes', + 'username', + 'password' + ]) + kwargs['mask'] = "mask[%s]" % ','.join(items) + return self.iscsi.getObject(id=volume_id, **kwargs) + + def cancel_iscsi(self, volume_id, reason='unNeeded', immediate=False): + """Cancels the given iSCSI volume. + """ + iscsi = self.get_iscsi( + volume_id, + mask='mask[id,capacityGb,username,password,billingItem[id]]') + billingItemId = iscsi['billingItem']['id'] + self.client['SoftLayer_Billing_Item'].cancelItem( + immediate, + True, + reason, + id=billingItemId) + + def create_snapshot(self, volume_id): + """ Orders a snapshot for given volume + """ + + self.iscsi.createSnapshot('', id=volume_id) + + def Order_snapshot_space(self, **snapshotSpaceOrder): + """ Orders a snapshot space for given volume + """ + + self.product_order.verifyOrder(snapshotSpaceOrder) + order = self.product_order.placeOrder(snapshotSpaceOrder) + + def delete_snapshot(self, snapshot_id): + """ Deletes the snapshot + + :params: integer snapshot_id: the snapshot ID + """ + + self.iscsi.deleteObject(id=snapshot_id) + + def restore_from_snapshot(self, volume_id, snapshot_id): + """ Restore the volume to snapshot's contents + + :params: integer snapshot_id: the snapshot ID + """ + self.iscsi.restoreFromSnapshot(snapshot_id, id = volume_id) From a53243a97421b19b00e2275abea68b84e2e8075d Mon Sep 17 00:00:00 2001 From: Aparna Patil Date: Wed, 26 Mar 2014 18:25:22 +0530 Subject: [PATCH 2/7] Added 50% Unit tests --- SoftLayer/CLI/core.py | 2 +- SoftLayer/CLI/exceptions.py | 5 -- SoftLayer/CLI/helpers.py | 4 +- SoftLayer/CLI/modules/iscsi.py | 48 ++++++++--------- SoftLayer/managers/__init__.py | 4 +- SoftLayer/managers/iscsi.py | 38 ++++++------- SoftLayer/tests/fixtures/Billing_Item.py | 1 + .../tests/fixtures/Network_Storage_Iscsi.py | 18 +++++++ SoftLayer/tests/managers/iscsi_tests.py | 53 +++++++++++++++++++ 9 files changed, 118 insertions(+), 55 deletions(-) create mode 100644 SoftLayer/tests/fixtures/Network_Storage_Iscsi.py create mode 100644 SoftLayer/tests/managers/iscsi_tests.py diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 57f66d356..48f5f01a6 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -49,7 +49,7 @@ from SoftLayer import Client, TimedClient, SoftLayerError, SoftLayerAPIError from SoftLayer.consts import VERSION -from .helpers import CLIAbort, ArgumentError, InvalidInput, format_output, KeyValueTable +from .helpers import CLIAbort, ArgumentError, format_output, KeyValueTable from .environment import Environment, InvalidCommand, InvalidModule diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index 6caace3de..903485466 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -23,8 +23,3 @@ class ArgumentError(CLIAbort): def __init__(self, msg, *args): super(ArgumentError, self).__init__(msg, *args) self.message = "Argument Error: %s" % msg - -class InvalidInput(CLIAbort): - def _init_(self, msg, *args): - super(InvalidInput, self).__init__(msg, *args) - self.message = "InvalidInput: %s" % msg diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index 9fbf21da9..6c9e928ad 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -8,7 +8,7 @@ from SoftLayer.utils import NestedDict from SoftLayer.CLI.environment import CLIRunnable -from .exceptions import CLIHalt, CLIAbort, ArgumentError, InvalidInput +from .exceptions import CLIHalt, CLIAbort, ArgumentError from .formatting import ( Table, KeyValueTable, FormattedItem, SequentialOutput, confirm, no_going_back, mb_to_gb, gb, listing, blank, format_output, @@ -19,7 +19,7 @@ # Core/Misc 'CLIRunnable', 'NestedDict', 'FALSE_VALUES', 'resolve_id', # Exceptions - 'CLIAbort', 'CLIHalt', 'ArgumentError', 'InvalidInput', + 'CLIAbort', 'CLIHalt', 'ArgumentError', # Formatting 'Table', 'KeyValueTable', 'FormattedItem', 'SequentialOutput', 'valid_response', 'confirm', 'no_going_back', 'mb_to_gb', 'gb', diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index c7c9d5204..a7e9ae3a2 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -20,10 +20,10 @@ CLIRunnable, Table, no_going_back, confirm, mb_to_gb, listing, FormattedItem) from SoftLayer.CLI.helpers import ( - CLIAbort, ArgumentError, InvalidInput, NestedDict, blank, resolve_id, KeyValueTable, + CLIAbort, ArgumentError, NestedDict, blank, resolve_id, KeyValueTable, update_with_template_args, FALSE_VALUES, export_to_template, active_txn, transaction_status) -from SoftLayer import iSCSIManager +from SoftLayer import ISCSIManager class ListISCSI(CLIRunnable): @@ -81,15 +81,15 @@ class CreateiSCSI(CLIRunnable): required_params = ['--size', '--dc'] def execute(self, args): - iscsi = iSCSIManager(self.client) + iscsi = ISCSIManager(self.client) self._validate_create_args(args) - - size, location = self._parse_create_args(args) - location = self._get_location_id(location) - items = iscsi.find_items(int(size)) - - iscsi.order_iscsi(items, size, location) + order = { + 'size':int(args['--size']), + } + location = self._get_location_id(args['--dc']) + order['dc'] = location + iscsi.order_iscsi(**order) def _validate_create_args(self, args): invalid_args = [k for k in self.required_params if args.get(k) is None] @@ -110,37 +110,34 @@ def _get_location_id(self, location): if dc['name'] == location: self.location = dc['id'] return self.location - raise InvalidInput('Inavlid datacenter name: %s' - % location) - + raise ArgumentError('Invalid datacenter name: %s'%location) class CanceliSCSI(CLIRunnable): """ -usage: sl iscsi [--immediate] [--reason] cancel [options] +usage: sl iscsi cancel [options] Cancel iSCSI Storage options : ---immediate Cancels the iSCSI immediately (instead of on the billing +--immediate Cancels the iSCSI immediately (instead of on the billing anniversary) ---reason Reason for cancellation +--reason=REASON An optional cancellation reason. -Prompt Options: --y, --really Confirm all prompt actions """ action = 'cancel' options = ['confirm'] def execute(self, args): - iscsi = iSCSIManager(self.client) + iscsi = ISCSIManager(self.client) iscsi_id = resolve_id( iscsi.resolve_ids, args.get(''), 'iSCSI') + immediate = args.get('--immediate', False) - reason = args.get('--reason', str('No longer needed')) + reason = args.get('--reason') if args['--really'] or no_going_back(iscsi_id): iscsi.cancel_iscsi(iscsi_id, reason, immediate) else: @@ -162,7 +159,7 @@ class IscsiDetails(CLIRunnable): action = 'detail' def execute(self, args): - iscsi = iSCSIManager(self.client) + iscsi = ISCSIManager(self.client) t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' @@ -207,7 +204,7 @@ class IscsiCreateSnapshot(CLIRunnable): action = 'create_snapshot' def execute(self, args): - iscsi = iSCSIManager(self.client) + iscsi = ISCSIManager(self.client) iscsi_id = resolve_id(iscsi.resolve_ids, args.get(''), 'iSCSI') @@ -229,7 +226,7 @@ class OrderIscsiSpace(CLIRunnable): required_params = ['--capacity'] def execute(self, args): - iscsi = iSCSIManager(self.client) + iscsi = ISCSIManager(self.client) invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: raise ArgumentError('Missing required options: %s' @@ -264,7 +261,7 @@ class IscsiDeleteSnapshot(CLIRunnable): action = 'delete_snapshot' def execute(self, args): - iscsi = iSCSIManager(self.client) + iscsi = ISCSIManager(self.client) snapshot_id = resolve_id( iscsi.resolve_ids, args.get(''), @@ -283,7 +280,7 @@ class RestoreVolumefromSnapshot(CLIRunnable): action = 'restore_volume' def execute(self, args): - iscsi = iSCSIManager(self.client) + iscsi = ISCSIManager(self.client) snapshot_id = resolve_id( iscsi.resolve_ids, args.get(''), @@ -302,11 +299,10 @@ class ListISCSISnapshots(CLIRunnable): action = 'list_snapshots' def execute(self, args): - mgr = iSCSIManager(self.client) + mgr = ISCSIManager(self.client) iscsi_id = resolve_id(mgr.resolve_ids,args.get(''),'iSCSI') iscsi = self.client['Network_Storage_Iscsi'] snapshots = iscsi.getPartnerships(mask='volumeId,partnerVolumeId,createDate,type', id = iscsi_id) - print snapshots snapshots = [NestedDict(n) for n in snapshots] t = Table([ diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 2f3f8260c..1670530f9 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -18,7 +18,7 @@ from SoftLayer.managers.sshkey import SshKeyManager from SoftLayer.managers.ssl import SSLManager from SoftLayer.managers.ticket import TicketManager -from SoftLayer.managers.iscsi import iSCSIManager +from SoftLayer.managers.iscsi import ISCSIManager __all__ = ['CCIManager', 'DNSManager', 'FirewallManager', 'HardwareManager', 'ImageManager', 'MessagingManager', 'MetadataManager', - 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager', 'iSCSIManager'] + 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager', 'ISCSIManager'] diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index c8295ec60..f93c9080a 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -2,7 +2,7 @@ from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin -class iSCSIManager(IdentifierMixin, object): +class ISCSIManager(IdentifierMixin, object): """ Manages iSCSI storages. @@ -14,11 +14,11 @@ def __init__(self, client): self.configuration = {} self.client = client self.iscsi = self.client['Network_Storage_Iscsi'] - self.product_order = self.client['SoftLayer_Product_Order'] + self.product_order = self.client['Product_Order'] self.account = self.client['Account'] - def find_items(self, size): - items = [] + def _find_item_prices(self, size): + item_prices = [] _filter = NestedDict({}) _filter[ 'itemPrices'][ @@ -31,13 +31,10 @@ def find_items(self, size): filter=_filter.to_dict()) iscsi_item_prices = sorted( iscsi_item_prices, - key=lambda x: float(x.get('recurringFee', 0))) - iscsi_item_prices = sorted( - iscsi_item_prices, - key=lambda x: float(x['item']['capacity'])) + key=lambda x: (float(x['item']['capacity']),float(x.get('recurringFee', 0)))) for price in iscsi_item_prices: - items.append(price['id']) - return items + item_prices.append(price['id']) + return item_prices def find_space(self, size): _filter = NestedDict({}) @@ -58,28 +55,31 @@ def find_space(self, size): return None return item_prices[0]['id'] - def build_order(self, item, location): + def build_order(self, price, dc): order = { 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi', - 'location': location, + 'location': dc, 'packageId': 0, # storage package - 'prices': [{'id': item}], + 'prices': [{'id': price}], 'quantity': 1 } return order + - def order_iscsi(self, items, size, location): + def order_iscsi(self, **kwargs): """Places an order for iSCSI volume """ - - for item in items: - iscsi_order = self.build_order(item, location) + size = kwargs.get('size') + dc = kwargs.get('dc') + item_prices = self._find_item_prices(size) + for price in item_prices: + iscsi_order = self.build_order(price, dc) try: self.product_order.verifyOrder(iscsi_order) order = self.product_order.placeOrder(iscsi_order) except Exception as e: - continue + continue # if verifyOrder() fails for perticular item-price-id return def get_iscsi(self, volume_id, **kwargs): @@ -116,7 +116,7 @@ def cancel_iscsi(self, volume_id, reason='unNeeded', immediate=False): volume_id, mask='mask[id,capacityGb,username,password,billingItem[id]]') billingItemId = iscsi['billingItem']['id'] - self.client['SoftLayer_Billing_Item'].cancelItem( + self.client['Billing_Item'].cancelItem( immediate, True, reason, diff --git a/SoftLayer/tests/fixtures/Billing_Item.py b/SoftLayer/tests/fixtures/Billing_Item.py index 620cf14b5..36a07fbed 100644 --- a/SoftLayer/tests/fixtures/Billing_Item.py +++ b/SoftLayer/tests/fixtures/Billing_Item.py @@ -1,2 +1,3 @@ cancelService = {} cancelServiceOnAnniversaryDate = {} +cancelItem = {} diff --git a/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py b/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py new file mode 100644 index 000000000..beb839f37 --- /dev/null +++ b/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py @@ -0,0 +1,18 @@ +getObject = {'accountId': 278184, + 'billingItem': {'id': 6327}, + 'capacityGb': 20, + 'createDate': '2014-03-14T06:50:15-04:00', + 'guestId': '', + 'hardwareId': '', + 'hostId': '', + 'id': 100, + 'nasType': 'ISCSI', + 'notes': "{'status': 'available', 'name': u'IBMI278184-201', 'id': u'702dc07d-b04b-49eb-9ffc-cb4493470df9'}", + 'password': 'YAQSb9s3FbEz', + 'serviceProviderId': 1, + 'serviceResourceBackendIpAddress': '10.2.37.21', + 'serviceResourceName': 'storagesng0101', + 'username': 'IBMI278184-201' +} +editObject = True +createObject = getObject diff --git a/SoftLayer/tests/managers/iscsi_tests.py b/SoftLayer/tests/managers/iscsi_tests.py new file mode 100644 index 000000000..fee4e85b7 --- /dev/null +++ b/SoftLayer/tests/managers/iscsi_tests.py @@ -0,0 +1,53 @@ +""" + SoftLayer.tests.managers.iscsi_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer import ISCSIManager +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests.fixtures import Network_Storage_Iscsi, Billing_Item, Product_Package + +from mock import MagicMock, ANY, call, patch + + +class ISCSITests(unittest.TestCase): + + def setUp(self): + self.client = FixtureClient() + self.iscsi = ISCSIManager(self.client) + + def test_get_iscsi(self): + result = self.iscsi.get_iscsi(100) + self.client['Network_Storage_Iscsi'].getObject.assert_called_once_with(id=100, mask=ANY) + self.assertEqual(Network_Storage_Iscsi.getObject, result) + + def test_cancel_iscsi_immediately(self): + iscsi_id=6327 + result = self.iscsi.cancel_iscsi(iscsi_id,immediate=True) + f = self.client['Billing_Item'].cancelItem + f.assert_called_once_with(True,True,'unNeeded',id=iscsi_id) + + def test_cancel_iscsi_without_reason(self): + iscsi_id=6327 + result = self.iscsi.cancel_iscsi(iscsi_id) + f = self.client['Billing_Item'].cancelItem + f.assert_called_once_with(False,True,'unNeeded',id=iscsi_id) + #self.assertEqual(result,Billing_Item.cancelItem) + + def test_cancel_iscsi_with_reason(self): + iscsi_id=6327 + reason='Network Performance' + result = self.iscsi.cancel_iscsi(iscsi_id,reason) + f = self.client['Billing_Item'].cancelItem + f.assert_called_once_with(False,True,reason,id=iscsi_id) + #self.assertEqual(result,Billing_Item.cancelItem) + + #@patch('SoftLayer.managers.iscsi.ISCSIManager.build_order') + def test_order_iscsi(self): + #create_dict.return_value = {'test': 1, 'verify': 1} + self.iscsi.order_iscsi(test=1, verify=1) + #create_dict.assert_called_once_with(test=1, verify=1) + f = self.client['Product_Order'].placeOrder + f.assert_called_once_with({'test': 1, 'verify': 1}) + From 808af0d5df9e6914ba365b45c0623b400ec740be Mon Sep 17 00:00:00 2001 From: Aparna Patil Date: Thu, 27 Mar 2014 16:29:58 +0530 Subject: [PATCH 3/7] Unit Tests Added Unit tests for ISCSI and snapshots --- SoftLayer/CLI/modules/iscsi.py | 44 ++++++------- SoftLayer/managers/iscsi.py | 62 +++++++------------ .../tests/fixtures/Network_Storage_Iscsi.py | 48 ++++++++++++++ SoftLayer/tests/fixtures/Product_Package.py | 51 +++++++++++++++ SoftLayer/tests/managers/iscsi_tests.py | 51 +++++++++++++-- 5 files changed, 187 insertions(+), 69 deletions(-) diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index a7e9ae3a2..e2b68d317 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -81,6 +81,8 @@ class CreateiSCSI(CLIRunnable): required_params = ['--size', '--dc'] def execute(self, args): + import pdb + pdb.set_trace() iscsi = ISCSIManager(self.client) self._validate_create_args(args) @@ -129,6 +131,8 @@ class CanceliSCSI(CLIRunnable): options = ['confirm'] def execute(self, args): + import pdb + pdb.set_trace() iscsi = ISCSIManager(self.client) iscsi_id = resolve_id( iscsi.resolve_ids, @@ -200,32 +204,40 @@ class IscsiCreateSnapshot(CLIRunnable): create an iSCSI snapshot. +Options: +--notes=NOTE An optional note + """ action = 'create_snapshot' def execute(self, args): + import pdb + pdb.set_trace() iscsi = ISCSIManager(self.client) iscsi_id = resolve_id(iscsi.resolve_ids, args.get(''), 'iSCSI') - iscsi.create_snapshot(iscsi_id) + notes = args.get('--notes') + iscsi.create_snapshot(iscsi_id, notes) class OrderIscsiSpace(CLIRunnable): """ -usage: sl iscsi order_snapshot_space [--capacity=Capacity...] [options] +usage: sl iscsi order_snapshot_space [options] Order iSCSI snapshot space. Required : ---capacity = Snapshot Capacity +--capacity=Capacity Snapshot Capacity """ action = 'order_snapshot_space' required_params = ['--capacity'] def execute(self, args): + import pdb + pdb.set_trace() iscsi = ISCSIManager(self.client) invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: @@ -235,20 +247,8 @@ def execute(self, args): iscsi.resolve_ids, args.get(''), 'iSCSI') - item_price = iscsi.find_space(int(args['--capacity'][0])) - result = iscsi.get_iscsi( - iscsi_id, - mask='mask[id,capacityGb,serviceResource[datacenter]]') - snapshotSpaceOrder = { - 'complexType': - 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', - 'location': result['serviceResource']['datacenter']['id'], - 'packageId': 0, - 'prices': [{'id': item_price}], - 'quantity': 1, - 'volumeId': iscsi_id} - iscsi.Order_snapshot_space(**snapshotSpaceOrder) - + capacity = args.get('--capacity') + iscsi.order_snapshot_space(iscsi_id, capacity) class IscsiDeleteSnapshot(CLIRunnable): @@ -272,7 +272,7 @@ def execute(self, args): class RestoreVolumefromSnapshot(CLIRunnable): """ -usage: sl iscsi restore_volume +usage: sl iscsi restore_volume restores volume from existing snapshot. @@ -281,12 +281,8 @@ class RestoreVolumefromSnapshot(CLIRunnable): def execute(self, args): iscsi = ISCSIManager(self.client) - snapshot_id = resolve_id( - iscsi.resolve_ids, - args.get(''), - 'Snapshot') - result = iscsi.get_iscsi(snapshot_id,mask='mask[parentPartnerships.volumeId]') - volume_id = result['parentPartnerships'][0]['volumeId'] + volume_id = resolve_id(iscsi.resolve_ids,args.get(''),'iSCSI') + snapshot_id = resolve_id(iscsi.resolve_ids,args.get(''),'Snapshot') iscsi.restore_from_snapshot(volume_id, snapshot_id) class ListISCSISnapshots(CLIRunnable): diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index f93c9080a..d68d08d67 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -17,14 +17,14 @@ def __init__(self, client): self.product_order = self.client['Product_Order'] self.account = self.client['Account'] - def _find_item_prices(self, size): + def _find_item_prices(self, size, query): item_prices = [] _filter = NestedDict({}) - _filter[ + _filter[ 'itemPrices'][ 'item'][ 'description'] = query_filter( - '~GB iSCSI SAN Storage') + query) _filter['itemPrices']['item']['capacity'] = query_filter('%s' % size) iscsi_item_prices = self.client['Product_Package'].getItemPrices( id=0, @@ -36,32 +36,13 @@ def _find_item_prices(self, size): item_prices.append(price['id']) return item_prices - def find_space(self, size): - _filter = NestedDict({}) - _filter[ - 'itemPrices'][ - 'item'][ - 'description'] = query_filter( - '~iSCSI SAN Snapshot Space') - _filter['itemPrices']['item']['capacity'] = query_filter('>=%s' % size) - item_prices = self.client['Product_Package'].getItemPrices( - id=0, - mask='mask[id,item[capacity]]', - filter=_filter.to_dict()) - item_prices = sorted( - item_prices, - key=lambda x: int(x['item']['capacity'])) - if len(item_prices) == 0: - return None - return item_prices[0]['id'] - - def build_order(self, price, dc): + def build_order(self, item_price, dc): order = { 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi', 'location': dc, 'packageId': 0, # storage package - 'prices': [{'id': price}], + 'prices': [{'id': item_price[-1]}], 'quantity': 1 } return order @@ -72,15 +53,10 @@ def order_iscsi(self, **kwargs): """ size = kwargs.get('size') dc = kwargs.get('dc') - item_prices = self._find_item_prices(size) - for price in item_prices: - iscsi_order = self.build_order(price, dc) - try: - self.product_order.verifyOrder(iscsi_order) - order = self.product_order.placeOrder(iscsi_order) - except Exception as e: - continue # if verifyOrder() fails for perticular item-price-id - return + item_price = self._find_item_prices(size,'~GB iSCSI SAN Storage') + iscsi_order = self.build_order(item_price, dc) + self.product_order.verifyOrder(iscsi_order) + self.product_order.placeOrder(iscsi_order) def get_iscsi(self, volume_id, **kwargs): """ Get details about a iSCSI storage @@ -122,18 +98,26 @@ def cancel_iscsi(self, volume_id, reason='unNeeded', immediate=False): reason, id=billingItemId) - def create_snapshot(self, volume_id): + def create_snapshot(self, volume_id, notes='unNeeded'): """ Orders a snapshot for given volume """ - self.iscsi.createSnapshot('', id=volume_id) + self.iscsi.createSnapshot(notes, id=volume_id) - def Order_snapshot_space(self, **snapshotSpaceOrder): + def order_snapshot_space(self, volume_id, capacity): """ Orders a snapshot space for given volume """ - + item_price = self._find_item_prices(int(capacity),'~iSCSI SAN Snapshot Space') + result = self.get_iscsi(volume_id, mask='mask[id,capacityGb,serviceResource[datacenter]]') + snapshotSpaceOrder = { + 'complexType':'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', + 'location': result['serviceResource']['datacenter']['id'], + 'packageId': 0, + 'prices': [{'id': item_price[0]}], + 'quantity': 1, + 'volumeId': volume_id} self.product_order.verifyOrder(snapshotSpaceOrder) - order = self.product_order.placeOrder(snapshotSpaceOrder) + self.product_order.placeOrder(snapshotSpaceOrder) def delete_snapshot(self, snapshot_id): """ Deletes the snapshot @@ -145,7 +129,7 @@ def delete_snapshot(self, snapshot_id): def restore_from_snapshot(self, volume_id, snapshot_id): """ Restore the volume to snapshot's contents - + :params: imteger volume_id: the volume ID :params: integer snapshot_id: the snapshot ID """ self.iscsi.restoreFromSnapshot(snapshot_id, id = volume_id) diff --git a/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py b/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py index beb839f37..15d0a3763 100644 --- a/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py +++ b/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py @@ -10,9 +10,57 @@ 'notes': "{'status': 'available', 'name': u'IBMI278184-201', 'id': u'702dc07d-b04b-49eb-9ffc-cb4493470df9'}", 'password': 'YAQSb9s3FbEz', 'serviceProviderId': 1, + 'serviceResource': {'datacenter':{'id':138124}}, 'serviceResourceBackendIpAddress': '10.2.37.21', 'serviceResourceName': 'storagesng0101', 'username': 'IBMI278184-201' } + +createSnapshot = { + 'accountId': 278184, + 'capacityGb': 20, + 'createDate': '2014-03-27T03:51:11-04:00', + 'guestId': '', + 'hardwareId': '', + 'hostId': '', + 'id': 101, + 'nasType': 'ISCSI_SNAPSHOT', + 'parentVolume': { + 'accountId': 278184, + 'capacityGb': 20, + 'createDate': '2014-03-27T03:38:47-04:00', + 'guestId': '', + 'hardwareId': '', + 'hostId': '', + 'id': 100, + 'nasType': 'ISCSI', + 'password': 'L8ta7MRXELwg', + 'properties': [ + {'createDate': '2014-03-27T03:40:22-04:00', + 'modifyDate': '', + 'type': { + 'description': 'Percent of reserved snapshot space that is available', + 'keyname': 'SNAPSHOT_RESERVE_AVAILABLE', + 'name': 'Snaphot Reserve Available'}, + + 'value': '100', + 'volumeId': 2678430}], + + 'propertyCount': 0, + 'serviceProviderId': 1, + 'name': 'storagedal0506', + 'snapshotCapacityGb': '40', + 'username': 'IBMI278184-211'}, + 'password': 'L8ta7MRXELwg', + 'serviceProviderId': 1, + 'serviceResource': {'backendIpAddress': '10.1.145.26', + 'name': 'storagedal0506', + 'type': {'type': 'ISCSI'}}, + 'serviceResourceBackendIpAddress': '10.1.145.26', + 'serviceResourceName': 'storagedal0506', + 'username': 'IBMI278184-211'} + +restoreFromSnapshot = True editObject = True createObject = getObject +deleteObject = True diff --git a/SoftLayer/tests/fixtures/Product_Package.py b/SoftLayer/tests/fixtures/Product_Package.py index e2b70a8f7..72d8fa87e 100644 --- a/SoftLayer/tests/fixtures/Product_Package.py +++ b/SoftLayer/tests/fixtures/Product_Package.py @@ -1071,3 +1071,54 @@ def get_bmc_categories_mock(): 'itemCategory': {'categoryCode': 'global_ipv6'}, 'prices': [{'id': 611}], }] +getItemPrices = [{'currentPriceFlag': '', + 'id': 2152, + 'item': {'capacity': '1', + 'description': '1 GB iSCSI SAN Storage', + 'id': 1150, + 'softwareDescriptionId': '', + 'units': 'GB', + 'upgradeItemId': 548}, + 'itemId': 1150, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'packageReferences': [{'id': 46626, 'itemPriceId': 2152, 'packageId': 0}], + 'quantity': '', + 'recurringFee': '.35', + 'setupFee': '0', + 'sort': 0}, + {'currentPriceFlag': '', + 'id': 22501, + 'item': {'capacity': '1', + 'description': '1 GB iSCSI SAN Storage', + 'id': 1150, + 'softwareDescriptionId': '', + 'units': 'GB', + 'upgradeItemId': 548}, + 'itemId': 1150, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'packageReferences': [{'id': 252983, 'itemPriceId': 22501, 'packageId': 0}], + 'quantity': '', + 'recurringFee': '0', + 'setupFee': '0', + 'sort': 0}, + {'currentPriceFlag': '', + 'id': 22441, + 'item': {'capacity': '1', + 'description': '1 GB iSCSI SAN Storage', + 'id': 1150, + 'softwareDescriptionId': '', + 'units': 'GB', + 'upgradeItemId': 548}, + 'itemId': 1150, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'packageReferences': [{'id': 250326, 'itemPriceId': 22441, 'packageId': 0}], + 'quantity': '', + 'recurringFee': '15', + 'setupFee': '0', + 'sort': 0}] diff --git a/SoftLayer/tests/managers/iscsi_tests.py b/SoftLayer/tests/managers/iscsi_tests.py index fee4e85b7..6ca04de21 100644 --- a/SoftLayer/tests/managers/iscsi_tests.py +++ b/SoftLayer/tests/managers/iscsi_tests.py @@ -43,11 +43,50 @@ def test_cancel_iscsi_with_reason(self): f.assert_called_once_with(False,True,reason,id=iscsi_id) #self.assertEqual(result,Billing_Item.cancelItem) - #@patch('SoftLayer.managers.iscsi.ISCSIManager.build_order') - def test_order_iscsi(self): - #create_dict.return_value = {'test': 1, 'verify': 1} + def test_order_iscsi_without_recurringFee(self): self.iscsi.order_iscsi(test=1, verify=1) - #create_dict.assert_called_once_with(test=1, verify=1) f = self.client['Product_Order'].placeOrder - f.assert_called_once_with({'test': 1, 'verify': 1}) - + f.assert_called_once_with({'prices': [{'id': 22441}], + 'quantity': 1, + 'location': None, + 'packageId': 0, + 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi'}) + + """def test_order_iscsi_with_recurringFee(self): + self.iscsi.order_iscsi(test=1, verify=1) + f = self.client['Product_Order'].placeOrder + f.assert_called_once_with({'prices': [{'id': 22441}], + 'quantity': 1, + 'location': None, + 'packageId': 0, + 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi'})""" + + def test_delete_snapshot(self): + self.iscsi.delete_snapshot(1) + self.client['Network_Storage_Iscsi'].deleteObject.assert_called_once_with(id=1) + + def test_create_snapshot(self): + iscsi_id = 100 + result = self.iscsi.create_snapshot(iscsi_id, 'unNeeded') + f = self.client['Network_Storage_Iscsi'].createSnapshot + f.assert_called_once_with('unNeeded', id=iscsi_id) + + def test_order_snapshot_space(self): + iscsi_id = 100 + capacity = 20 + result = self.iscsi.order_snapshot_space(iscsi_id,capacity) + f = self.client['Product_Order'].placeOrder + f.assert_called_once_with({'volumeId': 100, + 'location': 138124, + 'packageId': 0, + 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', + 'prices': [{'id': 22501}], + 'quantity': 1 + }) + + def test_restore_from_snapshot(self): + volume_id = 100 + snapshot_id = 101 + result = self.iscsi.restore_from_snapshot(volume_id,snapshot_id) + f = self.client['Network_Storage_Iscsi'].restoreFromSnapshot + f.assert_called_once_with(101, id=100) From df978c2c8d96d7b15e494be88c78b7c9ffedf228 Mon Sep 17 00:00:00 2001 From: Aparna Patil Date: Thu, 27 Mar 2014 16:38:48 +0530 Subject: [PATCH 4/7] Unit Tests Covered Unit tests for ISCSI and snapshot related operations --- SoftLayer/CLI/modules/iscsi.py | 8 -------- SoftLayer/managers/iscsi.py | 7 +++++++ SoftLayer/tests/managers/iscsi_tests.py | 11 ----------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index e2b68d317..840a58e47 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -81,8 +81,6 @@ class CreateiSCSI(CLIRunnable): required_params = ['--size', '--dc'] def execute(self, args): - import pdb - pdb.set_trace() iscsi = ISCSIManager(self.client) self._validate_create_args(args) @@ -131,8 +129,6 @@ class CanceliSCSI(CLIRunnable): options = ['confirm'] def execute(self, args): - import pdb - pdb.set_trace() iscsi = ISCSIManager(self.client) iscsi_id = resolve_id( iscsi.resolve_ids, @@ -211,8 +207,6 @@ class IscsiCreateSnapshot(CLIRunnable): action = 'create_snapshot' def execute(self, args): - import pdb - pdb.set_trace() iscsi = ISCSIManager(self.client) iscsi_id = resolve_id(iscsi.resolve_ids, args.get(''), @@ -236,8 +230,6 @@ class OrderIscsiSpace(CLIRunnable): required_params = ['--capacity'] def execute(self, args): - import pdb - pdb.set_trace() iscsi = ISCSIManager(self.client) invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index d68d08d67..7ed6ba9ab 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -87,6 +87,8 @@ def get_iscsi(self, volume_id, **kwargs): def cancel_iscsi(self, volume_id, reason='unNeeded', immediate=False): """Cancels the given iSCSI volume. + + :param integer volume_id: the volume ID """ iscsi = self.get_iscsi( volume_id, @@ -100,12 +102,17 @@ def cancel_iscsi(self, volume_id, reason='unNeeded', immediate=False): def create_snapshot(self, volume_id, notes='unNeeded'): """ Orders a snapshot for given volume + + :param integer volume_id: the volume ID """ self.iscsi.createSnapshot(notes, id=volume_id) def order_snapshot_space(self, volume_id, capacity): """ Orders a snapshot space for given volume + + :param integer volume_id: the volume ID + :param integer capacity: capacity in ~GB """ item_price = self._find_item_prices(int(capacity),'~iSCSI SAN Snapshot Space') result = self.get_iscsi(volume_id, mask='mask[id,capacityGb,serviceResource[datacenter]]') diff --git a/SoftLayer/tests/managers/iscsi_tests.py b/SoftLayer/tests/managers/iscsi_tests.py index 6ca04de21..b7de9b820 100644 --- a/SoftLayer/tests/managers/iscsi_tests.py +++ b/SoftLayer/tests/managers/iscsi_tests.py @@ -33,7 +33,6 @@ def test_cancel_iscsi_without_reason(self): result = self.iscsi.cancel_iscsi(iscsi_id) f = self.client['Billing_Item'].cancelItem f.assert_called_once_with(False,True,'unNeeded',id=iscsi_id) - #self.assertEqual(result,Billing_Item.cancelItem) def test_cancel_iscsi_with_reason(self): iscsi_id=6327 @@ -41,7 +40,6 @@ def test_cancel_iscsi_with_reason(self): result = self.iscsi.cancel_iscsi(iscsi_id,reason) f = self.client['Billing_Item'].cancelItem f.assert_called_once_with(False,True,reason,id=iscsi_id) - #self.assertEqual(result,Billing_Item.cancelItem) def test_order_iscsi_without_recurringFee(self): self.iscsi.order_iscsi(test=1, verify=1) @@ -52,15 +50,6 @@ def test_order_iscsi_without_recurringFee(self): 'packageId': 0, 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi'}) - """def test_order_iscsi_with_recurringFee(self): - self.iscsi.order_iscsi(test=1, verify=1) - f = self.client['Product_Order'].placeOrder - f.assert_called_once_with({'prices': [{'id': 22441}], - 'quantity': 1, - 'location': None, - 'packageId': 0, - 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi'})""" - def test_delete_snapshot(self): self.iscsi.delete_snapshot(1) self.client['Network_Storage_Iscsi'].deleteObject.assert_called_once_with(id=1) From bac474c19c89db359f3e7280436016f04daefe40 Mon Sep 17 00:00:00 2001 From: Aparna Patil Date: Thu, 27 Mar 2014 17:49:44 +0530 Subject: [PATCH 5/7] Corrected PEP8 Errors --- SoftLayer/CLI/modules/iscsi.py | 79 ++++++------ SoftLayer/managers/iscsi.py | 37 +++--- .../tests/fixtures/Network_Storage_Iscsi.py | 105 ++++++++-------- SoftLayer/tests/fixtures/Product_Package.py | 117 ++++++++++-------- SoftLayer/tests/managers/iscsi_tests.py | 85 +++++++------ 5 files changed, 226 insertions(+), 197 deletions(-) diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index 840a58e47..887f15540 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -15,14 +15,9 @@ list_snapshots List Snapshots of given iscsi """ -from SoftLayer.utils import lookup -from SoftLayer.CLI import ( - CLIRunnable, Table, no_going_back, confirm, mb_to_gb, listing, - FormattedItem) +from SoftLayer.CLI import (CLIRunnable, Table, no_going_back, FormattedItem) from SoftLayer.CLI.helpers import ( - CLIAbort, ArgumentError, NestedDict, blank, resolve_id, KeyValueTable, - update_with_template_args, FALSE_VALUES, export_to_template, - active_txn, transaction_status) + CLIAbort, ArgumentError, NestedDict, blank, resolve_id, KeyValueTable) from SoftLayer import ISCSIManager @@ -70,12 +65,12 @@ class CreateiSCSI(CLIRunnable): """ usage: sl iscsi create --size=SIZE --dc=DC [options] -Order/create an iSCSI storage. + Order/create an iSCSI storage. -Required: - --size=SIZE Size - --dc=DC Datacenter -""" + Required: + --size=SIZE Size + --dc=DC Datacenter + """ action = 'create' options = ['confirm'] required_params = ['--size', '--dc'] @@ -84,15 +79,15 @@ def execute(self, args): iscsi = ISCSIManager(self.client) self._validate_create_args(args) - order = { - 'size':int(args['--size']), - } - location = self._get_location_id(args['--dc']) - order['dc'] = location - iscsi.order_iscsi(**order) + order = { + 'size': int(args['--size']), + } + location = self._get_location_id(args['--dc']) + order['dc'] = location + iscsi.order_iscsi(**order) def _validate_create_args(self, args): - invalid_args = [k for k in self.required_params if args.get(k) is None] + invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: raise ArgumentError('Missing required options: %s' % ','.join(invalid_args)) @@ -110,7 +105,8 @@ def _get_location_id(self, location): if dc['name'] == location: self.location = dc['id'] return self.location - raise ArgumentError('Invalid datacenter name: %s'%location) + raise ArgumentError('Invalid datacenter name: %s' % location) + class CanceliSCSI(CLIRunnable): @@ -177,9 +173,11 @@ def execute(self, args): t.add_row(['nasType', result['nasType']]) t.add_row(['capacityGb', result['capacityGb']]) if result['snapshotCapacityGb']: - t.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) - t.add_row(['mountableFlag', result['mountableFlag']]) - t.add_row(['serviceResourceBackendIpAddress', result['serviceResourceBackendIpAddress']]) + t.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) + t.add_row(['mountableFlag', result['mountableFlag']]) + t.add_row( + ['serviceResourceBackendIpAddress', + result['serviceResourceBackendIpAddress']]) t.add_row(['price', result['billingItem']['recurringFee']]) t.add_row(['BillingItemId', result['billingItem']['id']]) if result.get('notes'): @@ -211,7 +209,7 @@ def execute(self, args): iscsi_id = resolve_id(iscsi.resolve_ids, args.get(''), 'iSCSI') - notes = args.get('--notes') + notes = args.get('--notes') iscsi.create_snapshot(iscsi_id, notes) @@ -231,7 +229,7 @@ class OrderIscsiSpace(CLIRunnable): def execute(self, args): iscsi = ISCSIManager(self.client) - invalid_args = [k for k in self.required_params if args.get(k) is None] + invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: raise ArgumentError('Missing required options: %s' % ','.join(invalid_args)) @@ -242,6 +240,7 @@ def execute(self, args): capacity = args.get('--capacity') iscsi.order_snapshot_space(iscsi_id, capacity) + class IscsiDeleteSnapshot(CLIRunnable): """ @@ -273,10 +272,13 @@ class RestoreVolumefromSnapshot(CLIRunnable): def execute(self, args): iscsi = ISCSIManager(self.client) - volume_id = resolve_id(iscsi.resolve_ids,args.get(''),'iSCSI') - snapshot_id = resolve_id(iscsi.resolve_ids,args.get(''),'Snapshot') + volume_id = resolve_id( + iscsi.resolve_ids, args.get(''), 'iSCSI') + snapshot_id = resolve_id( + iscsi.resolve_ids, args.get(''), 'Snapshot') iscsi.restore_from_snapshot(volume_id, snapshot_id) + class ListISCSISnapshots(CLIRunnable): """ @@ -288,24 +290,25 @@ class ListISCSISnapshots(CLIRunnable): def execute(self, args): mgr = ISCSIManager(self.client) - iscsi_id = resolve_id(mgr.resolve_ids,args.get(''),'iSCSI') - iscsi = self.client['Network_Storage_Iscsi'] - snapshots = iscsi.getPartnerships(mask='volumeId,partnerVolumeId,createDate,type', id = iscsi_id) + iscsi_id = resolve_id( + mgr.resolve_ids, args.get(''), 'iSCSI') + iscsi = self.client['Network_Storage_Iscsi'] + snapshots = iscsi.getPartnerships( + mask='volumeId,partnerVolumeId,createDate,type', id=iscsi_id) snapshots = [NestedDict(n) for n in snapshots] t = Table([ 'id', 'createDate', 'name', - 'description', + 'description', ]) - + for n in snapshots: t.add_row([ n['partnerVolumeId'], - n['createDate'], - n['type']['name'], - n['type']['description'], - ]) - return t - + n['createDate'], + n['type']['name'], + n['type']['description'], + ]) + return t diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index 7ed6ba9ab..a1339d6cb 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -1,4 +1,3 @@ -import socket from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin @@ -20,7 +19,7 @@ def __init__(self, client): def _find_item_prices(self, size, query): item_prices = [] _filter = NestedDict({}) - _filter[ + _filter[ 'itemPrices'][ 'item'][ 'description'] = query_filter( @@ -31,7 +30,9 @@ def _find_item_prices(self, size, query): filter=_filter.to_dict()) iscsi_item_prices = sorted( iscsi_item_prices, - key=lambda x: (float(x['item']['capacity']),float(x.get('recurringFee', 0)))) + key=lambda x: + (float(x['item']['capacity']), + float(x.get('recurringFee', 0)))) for price in iscsi_item_prices: item_prices.append(price['id']) return item_prices @@ -46,14 +47,13 @@ def build_order(self, item_price, dc): 'quantity': 1 } return order - def order_iscsi(self, **kwargs): """Places an order for iSCSI volume """ - size = kwargs.get('size') - dc = kwargs.get('dc') - item_price = self._find_item_prices(size,'~GB iSCSI SAN Storage') + size = kwargs.get('size') + dc = kwargs.get('dc') + item_price = self._find_item_prices(size, '~GB iSCSI SAN Storage') iscsi_order = self.build_order(item_price, dc) self.product_order.verifyOrder(iscsi_order) self.product_order.placeOrder(iscsi_order) @@ -75,7 +75,7 @@ def get_iscsi(self, volume_id, **kwargs): 'nasType', 'capacityGb', 'snapshotCapacityGb', - 'mountableFlag', + 'mountableFlag', 'serviceResourceBackendIpAddress', 'billingItem', 'notes', @@ -114,15 +114,18 @@ def order_snapshot_space(self, volume_id, capacity): :param integer volume_id: the volume ID :param integer capacity: capacity in ~GB """ - item_price = self._find_item_prices(int(capacity),'~iSCSI SAN Snapshot Space') - result = self.get_iscsi(volume_id, mask='mask[id,capacityGb,serviceResource[datacenter]]') + item_price = self._find_item_prices( + int(capacity), '~iSCSI SAN Snapshot Space') + result = self.get_iscsi( + volume_id, mask='mask[id,capacityGb,serviceResource[datacenter]]') snapshotSpaceOrder = { - 'complexType':'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', - 'location': result['serviceResource']['datacenter']['id'], - 'packageId': 0, - 'prices': [{'id': item_price[0]}], - 'quantity': 1, - 'volumeId': volume_id} + 'complexType': + 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', + 'location': result['serviceResource']['datacenter']['id'], + 'packageId': 0, + 'prices': [{'id': item_price[0]}], + 'quantity': 1, + 'volumeId': volume_id} self.product_order.verifyOrder(snapshotSpaceOrder) self.product_order.placeOrder(snapshotSpaceOrder) @@ -139,4 +142,4 @@ def restore_from_snapshot(self, volume_id, snapshot_id): :params: imteger volume_id: the volume ID :params: integer snapshot_id: the snapshot ID """ - self.iscsi.restoreFromSnapshot(snapshot_id, id = volume_id) + self.iscsi.restoreFromSnapshot(snapshot_id, id=volume_id) diff --git a/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py b/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py index 15d0a3763..da7b76610 100644 --- a/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py +++ b/SoftLayer/tests/fixtures/Network_Storage_Iscsi.py @@ -1,57 +1,60 @@ -getObject = {'accountId': 278184, - 'billingItem': {'id': 6327}, - 'capacityGb': 20, - 'createDate': '2014-03-14T06:50:15-04:00', - 'guestId': '', - 'hardwareId': '', - 'hostId': '', - 'id': 100, - 'nasType': 'ISCSI', - 'notes': "{'status': 'available', 'name': u'IBMI278184-201', 'id': u'702dc07d-b04b-49eb-9ffc-cb4493470df9'}", - 'password': 'YAQSb9s3FbEz', - 'serviceProviderId': 1, - 'serviceResource': {'datacenter':{'id':138124}}, - 'serviceResourceBackendIpAddress': '10.2.37.21', - 'serviceResourceName': 'storagesng0101', - 'username': 'IBMI278184-201' +getObject = { + 'accountId': 278184, + 'billingItem': {'id': 6327}, + 'capacityGb': 20, + 'createDate': '2014-03-14T06:50:15-04:00', + 'guestId': '', + 'hardwareId': '', + 'hostId': '', + 'id': 100, + 'nasType': 'ISCSI', + 'notes': """{'status': 'available', 'name': u'IBMI278184-201', 'id': +u'702dc07d-b04b-49eb-9ffc-cb4493470df9'}""", + 'password': 'YAQSb9s3FbEz', + 'serviceProviderId': 1, + 'serviceResource': {'datacenter': {'id': 138124}}, + 'serviceResourceBackendIpAddress': '10.2.37.21', + 'serviceResourceName': 'storagesng0101', + 'username': 'IBMI278184-201' } createSnapshot = { - 'accountId': 278184, - 'capacityGb': 20, - 'createDate': '2014-03-27T03:51:11-04:00', - 'guestId': '', - 'hardwareId': '', - 'hostId': '', - 'id': 101, - 'nasType': 'ISCSI_SNAPSHOT', - 'parentVolume': { - 'accountId': 278184, - 'capacityGb': 20, - 'createDate': '2014-03-27T03:38:47-04:00', - 'guestId': '', - 'hardwareId': '', - 'hostId': '', - 'id': 100, - 'nasType': 'ISCSI', - 'password': 'L8ta7MRXELwg', - 'properties': [ - {'createDate': '2014-03-27T03:40:22-04:00', - 'modifyDate': '', - 'type': { - 'description': 'Percent of reserved snapshot space that is available', - 'keyname': 'SNAPSHOT_RESERVE_AVAILABLE', - 'name': 'Snaphot Reserve Available'}, - - 'value': '100', - 'volumeId': 2678430}], - - 'propertyCount': 0, - 'serviceProviderId': 1, - 'name': 'storagedal0506', - 'snapshotCapacityGb': '40', - 'username': 'IBMI278184-211'}, - 'password': 'L8ta7MRXELwg', + 'accountId': 278184, + 'capacityGb': 20, + 'createDate': '2014-03-27T03:51:11-04:00', + 'guestId': '', + 'hardwareId': '', + 'hostId': '', + 'id': 101, + 'nasType': 'ISCSI_SNAPSHOT', + 'parentVolume': { + 'accountId': 278184, + 'capacityGb': 20, + 'createDate': '2014-03-27T03:38:47-04:00', + 'guestId': '', + 'hardwareId': '', + 'hostId': '', + 'id': 100, + 'nasType': 'ISCSI', + 'password': 'L8ta7MRXELwg', + 'properties': [ + {'createDate': '2014-03-27T03:40:22-04:00', + 'modifyDate': '', + 'type': { + 'description': + 'Percent of reserved snapshot space that is available', + 'keyname': 'SNAPSHOT_RESERVE_AVAILABLE', + 'name': 'Snaphot Reserve Available'}, + + 'value': '100', + 'volumeId': 2678430}], + + 'propertyCount': 0, + 'serviceProviderId': 1, + 'name': 'storagedal0506', + 'snapshotCapacityGb': '40', + 'username': 'IBMI278184-211'}, + 'password': 'L8ta7MRXELwg', 'serviceProviderId': 1, 'serviceResource': {'backendIpAddress': '10.1.145.26', 'name': 'storagedal0506', diff --git a/SoftLayer/tests/fixtures/Product_Package.py b/SoftLayer/tests/fixtures/Product_Package.py index 72d8fa87e..31e513479 100644 --- a/SoftLayer/tests/fixtures/Product_Package.py +++ b/SoftLayer/tests/fixtures/Product_Package.py @@ -1071,54 +1071,69 @@ def get_bmc_categories_mock(): 'itemCategory': {'categoryCode': 'global_ipv6'}, 'prices': [{'id': 611}], }] -getItemPrices = [{'currentPriceFlag': '', - 'id': 2152, - 'item': {'capacity': '1', - 'description': '1 GB iSCSI SAN Storage', - 'id': 1150, - 'softwareDescriptionId': '', - 'units': 'GB', - 'upgradeItemId': 548}, - 'itemId': 1150, - 'laborFee': '0', - 'onSaleFlag': '', - 'oneTimeFee': '0', - 'packageReferences': [{'id': 46626, 'itemPriceId': 2152, 'packageId': 0}], - 'quantity': '', - 'recurringFee': '.35', - 'setupFee': '0', - 'sort': 0}, - {'currentPriceFlag': '', - 'id': 22501, - 'item': {'capacity': '1', - 'description': '1 GB iSCSI SAN Storage', - 'id': 1150, - 'softwareDescriptionId': '', - 'units': 'GB', - 'upgradeItemId': 548}, - 'itemId': 1150, - 'laborFee': '0', - 'onSaleFlag': '', - 'oneTimeFee': '0', - 'packageReferences': [{'id': 252983, 'itemPriceId': 22501, 'packageId': 0}], - 'quantity': '', - 'recurringFee': '0', - 'setupFee': '0', - 'sort': 0}, - {'currentPriceFlag': '', - 'id': 22441, - 'item': {'capacity': '1', - 'description': '1 GB iSCSI SAN Storage', - 'id': 1150, - 'softwareDescriptionId': '', - 'units': 'GB', - 'upgradeItemId': 548}, - 'itemId': 1150, - 'laborFee': '0', - 'onSaleFlag': '', - 'oneTimeFee': '0', - 'packageReferences': [{'id': 250326, 'itemPriceId': 22441, 'packageId': 0}], - 'quantity': '', - 'recurringFee': '15', - 'setupFee': '0', - 'sort': 0}] +getItemPrices = [ + { + 'currentPriceFlag': '', + 'id': 2152, + 'item': { + 'capacity': '1', + 'description': '1 GB iSCSI SAN Storage', + 'id': 1150, + 'softwareDescriptionId': '', + 'units': 'GB', + 'upgradeItemId': 548}, + 'itemId': 1150, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'packageReferences': [{'id': 46626, + 'itemPriceId': 2152, 'packageId': 0}], + 'quantity': '', + 'recurringFee': '.35', + 'setupFee': '0', + 'sort': 0 + }, + { + 'currentPriceFlag': '', + 'id': 22501, + 'item': {'capacity': '1', + 'description': '1 GB iSCSI SAN Storage', + 'id': 1150, + 'softwareDescriptionId': '', + 'units': 'GB', + 'upgradeItemId': 548}, + 'itemId': 1150, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'packageReferences': [{ + 'id': 252983, + 'itemPriceId': 22501, 'packageId': 0 + }], + 'quantity': '', + 'recurringFee': '0', + 'setupFee': '0', + 'sort': 0 + }, + { + 'currentPriceFlag': '', + 'id': 22441, + 'item': { + 'capacity': '1', + 'description': '1 GB iSCSI SAN Storage', + 'id': 1150, + 'softwareDescriptionId': '', + 'units': 'GB', + 'upgradeItemId': 548 + }, + 'itemId': 1150, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'packageReferences': [{'id': 250326, + 'itemPriceId': 22441, 'packageId': 0}], + 'quantity': '', + 'recurringFee': '15', + 'setupFee': '0', + 'sort': 0 + }] diff --git a/SoftLayer/tests/managers/iscsi_tests.py b/SoftLayer/tests/managers/iscsi_tests.py index b7de9b820..3d234c513 100644 --- a/SoftLayer/tests/managers/iscsi_tests.py +++ b/SoftLayer/tests/managers/iscsi_tests.py @@ -6,76 +6,81 @@ """ from SoftLayer import ISCSIManager from SoftLayer.tests import unittest, FixtureClient -from SoftLayer.tests.fixtures import Network_Storage_Iscsi, Billing_Item, Product_Package +from SoftLayer.tests.fixtures import Network_Storage_Iscsi -from mock import MagicMock, ANY, call, patch +from mock import ANY class ISCSITests(unittest.TestCase): - def setUp(self): self.client = FixtureClient() self.iscsi = ISCSIManager(self.client) def test_get_iscsi(self): - result = self.iscsi.get_iscsi(100) - self.client['Network_Storage_Iscsi'].getObject.assert_called_once_with(id=100, mask=ANY) - self.assertEqual(Network_Storage_Iscsi.getObject, result) + result = self.iscsi.get_iscsi(100) + self.client['Network_Storage_Iscsi'].getObject.assert_called_once_with( + id=100, mask=ANY) + self.assertEqual(Network_Storage_Iscsi.getObject, result) def test_cancel_iscsi_immediately(self): - iscsi_id=6327 - result = self.iscsi.cancel_iscsi(iscsi_id,immediate=True) - f = self.client['Billing_Item'].cancelItem - f.assert_called_once_with(True,True,'unNeeded',id=iscsi_id) - + iscsi_id = 6327 + self.iscsi.cancel_iscsi(iscsi_id, immediate=True) + f = self.client['Billing_Item'].cancelItem + f.assert_called_once_with(True, True, 'unNeeded', id=iscsi_id) + def test_cancel_iscsi_without_reason(self): - iscsi_id=6327 - result = self.iscsi.cancel_iscsi(iscsi_id) - f = self.client['Billing_Item'].cancelItem - f.assert_called_once_with(False,True,'unNeeded',id=iscsi_id) + iscsi_id = 6327 + self.iscsi.cancel_iscsi(iscsi_id) + f = self.client['Billing_Item'].cancelItem + f.assert_called_once_with(False, True, 'unNeeded', id=iscsi_id) def test_cancel_iscsi_with_reason(self): - iscsi_id=6327 - reason='Network Performance' - result = self.iscsi.cancel_iscsi(iscsi_id,reason) - f = self.client['Billing_Item'].cancelItem - f.assert_called_once_with(False,True,reason,id=iscsi_id) + iscsi_id = 6327 + reason = 'Network Performance' + self.iscsi.cancel_iscsi(iscsi_id, reason) + f = self.client['Billing_Item'].cancelItem + f.assert_called_once_with(False, True, reason, id=iscsi_id) def test_order_iscsi_without_recurringFee(self): - self.iscsi.order_iscsi(test=1, verify=1) - f = self.client['Product_Order'].placeOrder - f.assert_called_once_with({'prices': [{'id': 22441}], - 'quantity': 1, - 'location': None, - 'packageId': 0, - 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi'}) + self.iscsi.order_iscsi(test=1, verify=1) + f = self.client['Product_Order'].placeOrder + f.assert_called_once_with( + {'prices': [{'id': 22441}], + 'quantity': 1, + 'location': None, + 'packageId': 0, + 'complexType': + 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi'}) def test_delete_snapshot(self): self.iscsi.delete_snapshot(1) - self.client['Network_Storage_Iscsi'].deleteObject.assert_called_once_with(id=1) + self.client[ + 'Network_Storage_Iscsi'].deleteObject.assert_called_once_with(id=1) def test_create_snapshot(self): iscsi_id = 100 - result = self.iscsi.create_snapshot(iscsi_id, 'unNeeded') - f = self.client['Network_Storage_Iscsi'].createSnapshot - f.assert_called_once_with('unNeeded', id=iscsi_id) + self.iscsi.create_snapshot(iscsi_id, 'unNeeded') + f = self.client['Network_Storage_Iscsi'].createSnapshot + f.assert_called_once_with('unNeeded', id=iscsi_id) def test_order_snapshot_space(self): iscsi_id = 100 capacity = 20 - result = self.iscsi.order_snapshot_space(iscsi_id,capacity) + self.iscsi.order_snapshot_space(iscsi_id, capacity) f = self.client['Product_Order'].placeOrder - f.assert_called_once_with({'volumeId': 100, - 'location': 138124, - 'packageId': 0, - 'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', - 'prices': [{'id': 22501}], - 'quantity': 1 - }) + f.assert_called_once_with( + {'volumeId': 100, + 'location': 138124, + 'packageId': 0, + 'complexType': + 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', + 'prices': [{'id': 22501}], + 'quantity': 1 + }) def test_restore_from_snapshot(self): volume_id = 100 snapshot_id = 101 - result = self.iscsi.restore_from_snapshot(volume_id,snapshot_id) + self.iscsi.restore_from_snapshot(volume_id, snapshot_id) f = self.client['Network_Storage_Iscsi'].restoreFromSnapshot f.assert_called_once_with(101, id=100) From 7744fa979df1d9d4eace76d0c6891ac494b3291b Mon Sep 17 00:00:00 2001 From: Aparna Patil Date: Thu, 27 Mar 2014 17:58:41 +0530 Subject: [PATCH 6/7] Corrected PEP8 Errors --- SoftLayer/managers/__init__.py | 3 ++- SoftLayer/managers/iscsi.py | 3 ++- SoftLayer/tests/managers/iscsi_tests.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 1670530f9..4561639df 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -21,4 +21,5 @@ from SoftLayer.managers.iscsi import ISCSIManager __all__ = ['CCIManager', 'DNSManager', 'FirewallManager', 'HardwareManager', 'ImageManager', 'MessagingManager', 'MetadataManager', - 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager', 'ISCSIManager'] + 'NetworkManager', 'SshKeyManager', 'SSLManager', + 'TicketManager', 'ISCSIManager'] diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index a1339d6cb..26cb13af2 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -120,7 +120,8 @@ def order_snapshot_space(self, volume_id, capacity): volume_id, mask='mask[id,capacityGb,serviceResource[datacenter]]') snapshotSpaceOrder = { 'complexType': - 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', + '''SoftLayer_Container_Product_Order_ +Network_Storage_Iscsi_SnapshotSpace''', 'location': result['serviceResource']['datacenter']['id'], 'packageId': 0, 'prices': [{'id': item_price[0]}], diff --git a/SoftLayer/tests/managers/iscsi_tests.py b/SoftLayer/tests/managers/iscsi_tests.py index 3d234c513..fa7c8204b 100644 --- a/SoftLayer/tests/managers/iscsi_tests.py +++ b/SoftLayer/tests/managers/iscsi_tests.py @@ -73,7 +73,8 @@ def test_order_snapshot_space(self): 'location': 138124, 'packageId': 0, 'complexType': - 'SoftLayer_Container_Product_Order_Network_Storage_Iscsi_SnapshotSpace', + '''SoftLayer_Container_ +Product_Order_Network_Storage_Iscsi_SnapshotSpace''', 'prices': [{'id': 22501}], 'quantity': 1 }) From cc7cf70cad182ec31b5c2c549ffb6b45687fb610 Mon Sep 17 00:00:00 2001 From: Aparna Patil Date: Fri, 28 Mar 2014 10:32:16 +0530 Subject: [PATCH 7/7] Corrected PEP8 Errors --- SoftLayer/managers/iscsi.py | 4 ++-- SoftLayer/tests/managers/iscsi_tests.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index 26cb13af2..b80db863f 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -120,8 +120,8 @@ def order_snapshot_space(self, volume_id, capacity): volume_id, mask='mask[id,capacityGb,serviceResource[datacenter]]') snapshotSpaceOrder = { 'complexType': - '''SoftLayer_Container_Product_Order_ -Network_Storage_Iscsi_SnapshotSpace''', + 'SoftLayer_Container_Product_Order_\ +Network_Storage_Iscsi_SnapshotSpace', 'location': result['serviceResource']['datacenter']['id'], 'packageId': 0, 'prices': [{'id': item_price[0]}], diff --git a/SoftLayer/tests/managers/iscsi_tests.py b/SoftLayer/tests/managers/iscsi_tests.py index fa7c8204b..303a709a6 100644 --- a/SoftLayer/tests/managers/iscsi_tests.py +++ b/SoftLayer/tests/managers/iscsi_tests.py @@ -73,8 +73,8 @@ def test_order_snapshot_space(self): 'location': 138124, 'packageId': 0, 'complexType': - '''SoftLayer_Container_ -Product_Order_Network_Storage_Iscsi_SnapshotSpace''', + 'SoftLayer_Container_\ +Product_Order_Network_Storage_Iscsi_SnapshotSpace', 'prices': [{'id': 22501}], 'quantity': 1 })