From 8f3e475ec99c4c6bcf929f4781d3f639df66833e Mon Sep 17 00:00:00 2001 From: root Date: Thu, 13 Mar 2014 01:59:49 -0500 Subject: [PATCH 1/2] First commit --- SoftLayer/CLI/modules/iscsi.py | 147 +++++++++++++++++++++++++++++++-- SoftLayer/managers/__init__.py | 4 +- SoftLayer/managers/iscsi.py | 108 ++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 7 deletions(-) create mode 100644 SoftLayer/managers/iscsi.py diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index c4f8ceb45..849e12e61 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -4,17 +4,27 @@ Manage iSCSI targets The available commands are: - list List iSCSI targets + list List iSCSI targets + create Create iSCSI target + detail Output details about iSCSI + cancel cancel iSCSI target """ # :license: MIT, see LICENSE for more details. - -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, 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 """ @@ -48,3 +58,130 @@ def execute(self, args): n.get('serviceResourceBackendIpAddress', blank())]) return t + + +class CreateiSCSI(CLIRunnable): + + """ + usage: sl iscsi create [--size=SIZE...] [--d=DC...] [options] + +Order/create an iSCSI storage. + +Required: +-s, --size = SIZE Size +-D, --dc = DC Datacenter +""" + action = 'create' + required_params = ['--size', '--dc'] + + def execute(self, args): + import pdb + pdb.set_trace() + iscsi = iSCSIManager(self.client) + size, location = self._parse_create_args(args) + location = self._validate_create_args(location) + items = iscsi.find_items(int(size)) + iscsi.order_iscsi(items, size, location) + + def _parse_create_args(self, args): + + size = int(args['--size'][0]) + location = str(args['--d'][0]) + return size, location + + def _validate_create_args(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 + err_msg = ('Invalid datacenter name' + 'Valid location must be specified.') + raise exception.InvalidInput(reason=err_msg) + + +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): + import pdb + pdb.set_trace() + 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] [--price] [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 condition if snapshots then show this space + #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']]) + + t.add_row(['schedules', result['schedules']]) + 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 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..2a7147362 --- /dev/null +++ b/SoftLayer/managers/iscsi.py @@ -0,0 +1,108 @@ +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') + # if self.configuration.sl_vol_order_ceil: + # _filter['itemPrices']['item'][ + # 'capacity'] = query_filter('>=%s' % size) + # else: + _filter['itemPrices']['item']['capacity'] = query_filter('%s' % size) + iscsi_item_prices = self.client['Product_Package'].getItemPrices( + id=0, + mask='mask[id,recurringFee,item[capacity]]', + 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 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): + + 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: + LOG.debug(_("Cannot place order: %s" % 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', + 'schedules', + '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): + + 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) From 051dd70ee16c190d22fbcef6986870ba28f7fcac Mon Sep 17 00:00:00 2001 From: root Date: Fri, 14 Mar 2014 04:47:44 -0500 Subject: [PATCH 2/2] FINAL COMMIT --- SoftLayer/CLI/core.py | 2 +- SoftLayer/CLI/exceptions.py | 5 + SoftLayer/CLI/helpers.py | 4 +- SoftLayer/CLI/modules/iscsi.py | 200 ++++++++++++++++++++++++++++----- SoftLayer/managers/iscsi.py | 81 +++++++++---- 5 files changed, 240 insertions(+), 52 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 48f5f01a6..57f66d356 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, format_output, KeyValueTable +from .helpers import CLIAbort, ArgumentError, InvalidInput, format_output, KeyValueTable from .environment import Environment, InvalidCommand, InvalidModule diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index 903485466..6caace3de 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -23,3 +23,8 @@ 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 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 849e12e61..c7c9d5204 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -4,18 +4,23 @@ Manage iSCSI targets The available commands are: - list List iSCSI targets - create Create iSCSI target - detail Output details about iSCSI - cancel cancel iSCSI target + 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 + """ -# :license: MIT, see LICENSE for more details. 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, NestedDict, blank, resolve_id, KeyValueTable, + 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 @@ -63,42 +68,50 @@ def execute(self, args): class CreateiSCSI(CLIRunnable): """ - usage: sl iscsi create [--size=SIZE...] [--d=DC...] [options] + usage: sl iscsi create --size=SIZE --dc=DC [options] Order/create an iSCSI storage. Required: --s, --size = SIZE Size --D, --dc = DC Datacenter + --size=SIZE Size + --dc=DC Datacenter """ action = 'create' + options = ['confirm'] required_params = ['--size', '--dc'] def execute(self, args): - import pdb - pdb.set_trace() iscsi = iSCSIManager(self.client) - size, location = self._parse_create_args(args) - location = self._validate_create_args(location) + + 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'][0]) - location = str(args['--d'][0]) + size = int(args['--size']) + location = str(args['--dc']) return size, location - - def _validate_create_args(self, 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 - err_msg = ('Invalid datacenter name' - 'Valid location must be specified.') - raise exception.InvalidInput(reason=err_msg) + raise InvalidInput('Inavlid datacenter name: %s' + % location) class CanceliSCSI(CLIRunnable): @@ -120,8 +133,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, @@ -136,10 +147,10 @@ def execute(self, args): CLIAbort('Aborted') -class iSCSIDetails(CLIRunnable): +class IscsiDetails(CLIRunnable): """ -usage: sl iscsi detail [--passwords] [--price] [options] +usage: sl iscsi detail [--passwords] [options] Get details for a iSCSI @@ -168,20 +179,149 @@ def execute(self, args): t.add_row(['createDate', result['createDate']]) t.add_row(['nasType', result['nasType']]) t.add_row(['capacityGb', result['capacityGb']]) - # if condition if snapshots then show this space - #t.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) - #t.add_row(['mountableFlag', result['mountableFlag']]) - t.add_row( - ['serviceResourceBackendIpAddress', result['serviceResourceBackendIpAddress']]) + 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']]) - t.add_row(['schedules', result['schedules']]) 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 + + +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/iscsi.py b/SoftLayer/managers/iscsi.py index 2a7147362..c8295ec60 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -25,14 +25,9 @@ def find_items(self, size): 'item'][ 'description'] = query_filter( '~GB iSCSI SAN Storage') - # if self.configuration.sl_vol_order_ceil: - # _filter['itemPrices']['item'][ - # 'capacity'] = query_filter('>=%s' % size) - # else: _filter['itemPrices']['item']['capacity'] = query_filter('%s' % size) iscsi_item_prices = self.client['Product_Package'].getItemPrices( id=0, - mask='mask[id,recurringFee,item[capacity]]', filter=_filter.to_dict()) iscsi_item_prices = sorted( iscsi_item_prices, @@ -44,6 +39,25 @@ def find_items(self, size): 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': @@ -56,16 +70,17 @@ def build_order(self, item, location): return order def order_iscsi(self, items, size, location): - - 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: - LOG.debug(_("Cannot place order: %s" % e)) - continue - return + """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 @@ -83,12 +98,11 @@ def get_iscsi(self, volume_id, **kwargs): 'createDate', 'nasType', 'capacityGb', - #'snapshotCapacityGb' - #'mountableFlag', + 'snapshotCapacityGb', + 'mountableFlag', 'serviceResourceBackendIpAddress', 'billingItem', 'notes', - 'schedules', 'username', 'password' ]) @@ -96,7 +110,8 @@ def get_iscsi(self, volume_id, **kwargs): 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]]') @@ -106,3 +121,31 @@ def cancel_iscsi(self, volume_id, reason='unNeeded', immediate=False): 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)