Skip to content

Commit 16cd760

Browse files
author
caberos
committed
fixcode
2 parents 319121e + c707a08 commit 16cd760

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+380
-129
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
python-version: [3.5,3.6,3.7,3.8,3.9]
13+
python-version: [3.6,3.7,3.8,3.9]
1414

1515
steps:
1616
- uses: actions/checkout@v2

SoftLayer/CLI/environment.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,23 @@ def input(self, prompt, default=None, show_default=True):
6767

6868
def getpass(self, prompt, default=None):
6969
"""Provide a password prompt."""
70-
return click.prompt(prompt, hide_input=True, default=default)
70+
password = click.prompt(prompt, hide_input=True, default=default)
71+
72+
# https://github.com/softlayer/softlayer-python/issues/1436
73+
# click.prompt uses python's getpass() in the background
74+
# https://github.com/python/cpython/blob/3.9/Lib/getpass.py#L97
75+
# In windows, shift+insert actually inputs the below 2 characters
76+
# If we detect those 2 characters, need to manually read from the clipbaord instead
77+
# https://stackoverflow.com/questions/101128/how-do-i-read-text-from-the-clipboard
78+
if password == 'àR':
79+
# tkinter is a built in python gui, but it has clipboard reading functions.
80+
# pylint: disable=import-outside-toplevel
81+
from tkinter import Tk
82+
tk_manager = Tk()
83+
password = tk_manager.clipboard_get()
84+
# keep the window from showing
85+
tk_manager.withdraw()
86+
return password
7187

7288
# Command loading methods
7389
def list_commands(self, *path):

SoftLayer/CLI/hardware/upgrade.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,40 @@
2424
default=None,
2525
type=click.Choice(['Non-RAID', 'RAID']))
2626
@click.option('--public-bandwidth', type=click.INT, help="Public Bandwidth in GB")
27+
@click.option('--add-disk', nargs=2, multiple=True, type=(int, int),
28+
help="Add a Hard disk in GB to a specific channel, e.g 1000 GB in disk2, it will be "
29+
"--add-disk 1000 2")
30+
@click.option('--resize-disk', nargs=2, multiple=True, type=(int, int),
31+
help="Upgrade a specific disk size in GB, e.g --resize-disk 2000 2")
2732
@click.option('--test', is_flag=True, default=False, help="Do not actually upgrade the hardware server")
2833
@environment.pass_env
29-
def cli(env, identifier, memory, network, drive_controller, public_bandwidth, test):
34+
def cli(env, identifier, memory, network, drive_controller, public_bandwidth, add_disk, resize_disk, test):
3035
"""Upgrade a Hardware Server."""
3136

3237
mgr = SoftLayer.HardwareManager(env.client)
3338

34-
if not any([memory, network, drive_controller, public_bandwidth]):
39+
if not any([memory, network, drive_controller, public_bandwidth, add_disk, resize_disk]):
3540
raise exceptions.ArgumentError("Must provide "
36-
" [--memory], [--network], [--drive-controller], or [--public-bandwidth]")
41+
" [--memory], [--network], [--drive-controller], [--public-bandwidth],"
42+
"[--add-disk] or [--resize-disk]")
3743

3844
hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Hardware')
3945
if not test:
4046
if not (env.skip_confirmations or formatting.confirm(
4147
"This action will incur charges on your account. Continue?")):
4248
raise exceptions.CLIAbort('Aborted')
4349

50+
disk_list = list()
51+
if add_disk:
52+
for guest_disk in add_disk:
53+
disks = {'description': 'add_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]}
54+
disk_list.append(disks)
55+
if resize_disk:
56+
for guest_disk in resize_disk:
57+
disks = {'description': 'resize_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]}
58+
disk_list.append(disks)
59+
4460
if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller,
45-
public_bandwidth=public_bandwidth, test=test):
61+
public_bandwidth=public_bandwidth, disk=disk_list, test=test):
4662
raise exceptions.CLIAbort('Hardware Server Upgrade Failed')
4763
env.fout('Successfully Upgraded.')

SoftLayer/CLI/virt/detail.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ def cli(env, identifier, passwords=False, price=False):
6969
table.add_row(['transient', result.get('transientGuestFlag', False)])
7070
table.add_row(['created', result['createDate']])
7171
table.add_row(['modified', result['modifyDate']])
72+
last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'),
73+
utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate')))
74+
75+
table.add_row(['last_transaction', last_transaction])
76+
table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly'])
7277
table.add_row(['preset', utils.lookup(result, 'billingItem',
7378
'orderItem',
7479
'preset',

SoftLayer/fixtures/SoftLayer_Hardware_Server.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
'children': [
1414
{'description': 'test', 'nextInvoiceTotalRecurringAmount': 1},
1515
],
16+
'nextInvoiceChildren': [
17+
{'description': 'test', 'nextInvoiceTotalRecurringAmount': 1, 'categoryCode': 'disk1'},
18+
{'description': 'test2', 'nextInvoiceTotalRecurringAmount': 2, 'categoryCode': 'disk3'}
19+
],
1620
'orderItem': {
1721
'order': {
1822
'userRecord': {
@@ -336,5 +340,40 @@
336340
"id": 6177,
337341
"keyName": "BANDWIDTH_500_GB"
338342
}
343+
},
344+
{
345+
"hourlyRecurringFee": ".023",
346+
"id": 49759,
347+
"recurringFee": "15",
348+
"categories": [
349+
{
350+
"categoryCode": "disk2",
351+
"id": 6,
352+
"name": "Third Hard Drive"
353+
}
354+
],
355+
"item": {
356+
"capacity": "1000",
357+
"description": "1.00 TB SATA",
358+
"id": 6159,
359+
"keyName": "HARD_DRIVE_1_00_TB_SATA_2",
360+
}
361+
},
362+
{
363+
"id": 49759,
364+
"recurringFee": "0",
365+
"categories": [
366+
{
367+
"categoryCode": "disk1",
368+
"id": 5,
369+
"name": "Second Hard Drive"
370+
}
371+
],
372+
"item": {
373+
"capacity": "1000",
374+
"description": "1.00 TB SATA",
375+
"id": 6159,
376+
"keyName": "HARD_DRIVE_1_00_TB_SATA_2"
377+
}
339378
}
340379
]

SoftLayer/managers/hardware.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -821,14 +821,15 @@ def authorize_storage(self, hardware_id, username_storage):
821821

822822
def upgrade(self, instance_id, memory=None,
823823
nic_speed=None, drive_controller=None,
824-
public_bandwidth=None, test=False):
824+
public_bandwidth=None, disk=None, test=False):
825825
"""Upgrades a hardware server instance.
826826
827827
:param int instance_id: Instance id of the hardware server to be upgraded.
828828
:param int memory: Memory size.
829829
:param string nic_speed: Network Port Speed data.
830830
:param string drive_controller: Drive Controller data.
831831
:param int public_bandwidth: Public keyName data.
832+
:param list disk: List of disks to add or upgrade Hardware Server.
832833
:param bool test: Test option to verify the request.
833834
834835
:returns: bool
@@ -860,6 +861,10 @@ def upgrade(self, instance_id, memory=None,
860861
'packageId': package_id
861862
}
862863

864+
if disk:
865+
prices = self._get_disk_price_list(instance_id, disk)
866+
order['prices'] = prices
867+
863868
for option, value in data.items():
864869
price_id = self._get_prices_for_upgrade_option(upgrade_prices, option, value)
865870
if not price_id:
@@ -888,13 +893,13 @@ def get_instance(self, instance_id):
888893
the specified instance.
889894
"""
890895
mask = [
891-
'billingItem[id,package[id,keyName]]'
896+
'billingItem[id,package[id,keyName],nextInvoiceChildren]'
892897
]
893898
mask = "mask[%s]" % ','.join(mask)
894899

895900
return self.hardware.getObject(id=instance_id, mask=mask)
896901

897-
def _get_upgrade_prices(self, instance_id, include_downgrade_options=True):
902+
def _get_upgrade_prices(self, instance_id):
898903
"""Following Method gets all the price ids related to upgrading a Hardware Server.
899904
900905
:param int instance_id: Instance id of the Hardware Server to be upgraded.
@@ -908,7 +913,7 @@ def _get_upgrade_prices(self, instance_id, include_downgrade_options=True):
908913
'item[keyName,description,capacity,units]'
909914
]
910915
mask = "mask[%s]" % ','.join(mask)
911-
return self.hardware.getUpgradeItemPrices(include_downgrade_options, id=instance_id, mask=mask)
916+
return self.hardware.getUpgradeItemPrices(id=instance_id, mask=mask)
912917

913918
@staticmethod
914919
def _get_prices_for_upgrade_option(upgrade_prices, option, value):
@@ -927,7 +932,10 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value):
927932
'disk_controller': 'disk_controller',
928933
'bandwidth': 'bandwidth'
929934
}
930-
category_code = option_category.get(option)
935+
if 'disk' in option:
936+
category_code = option
937+
else:
938+
category_code = option_category.get(option)
931939

932940
for price in upgrade_prices:
933941
if price.get('categories') is None or price.get('item') is None:
@@ -953,12 +961,75 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value):
953961
elif option == 'bandwidth':
954962
if str(product.get('capacity')) == str(value):
955963
price_id = price.get('id')
964+
elif 'disk' in option:
965+
if str(product.get('capacity')) == str(value):
966+
price_id = price
956967
else:
957968
if str(product.get('capacity')) == str(value):
958969
price_id = price.get('id')
959970

960971
return price_id
961972

973+
def _get_disk_price_list(self, instance_id, disk):
974+
"""Get the disks prices to be added or upgraded.
975+
976+
:param int instance_id: Hardware Server instance id.
977+
:param list disk: List of disks to be added o upgraded to the HW.
978+
979+
:return list.
980+
"""
981+
prices = []
982+
disk_exist = False
983+
upgrade_prices = self._get_upgrade_prices(instance_id)
984+
server_response = self.get_instance(instance_id)
985+
for disk_data in disk:
986+
disk_channel = 'disk' + str(disk_data.get('number'))
987+
for item in utils.lookup(server_response, 'billingItem', 'nextInvoiceChildren'):
988+
if disk_channel == item['categoryCode']:
989+
disk_exist = True
990+
break
991+
if disk_exist:
992+
disk_price_detail = self._get_disk_price_detail(disk_data, upgrade_prices, disk_channel, 'add_disk')
993+
prices.append(disk_price_detail)
994+
else:
995+
disk_price_detail = self._get_disk_price_detail(disk_data, upgrade_prices, disk_channel, 'resize_disk')
996+
prices.append(disk_price_detail)
997+
998+
return prices
999+
1000+
def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_type):
1001+
"""Get the disk price detail.
1002+
1003+
:param disk_data: List of disks to be added or upgraded.
1004+
:param list upgrade_prices: List of item prices.
1005+
:param String disk_channel: Disk position.
1006+
:param String disk_type: Disk type.
1007+
1008+
"""
1009+
if disk_data.get('description') == disk_type:
1010+
if "add" in disk_type:
1011+
raise SoftLayerError("Unable to add the disk because this already exists.")
1012+
if "resize" in disk_type:
1013+
raise SoftLayerError("Unable to resize the disk because this does not exists.")
1014+
else:
1015+
price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel,
1016+
disk_data.get('capacity'))
1017+
if not price_id:
1018+
raise SoftLayerError("The item price was not found for %s with 'capacity:' %i" %
1019+
(disk_channel, disk_data.get('capacity')))
1020+
1021+
disk_price = {
1022+
"id": price_id.get('id'),
1023+
"categories": [
1024+
{
1025+
"categoryCode": price_id['categories'][0]['categoryCode'],
1026+
"id": price_id['categories'][0]['id']
1027+
}
1028+
]
1029+
}
1030+
1031+
return disk_price
1032+
9621033

9631034
def _get_bandwidth_key(items, hourly=True, no_public=False, location=None):
9641035
"""Picks a valid Bandwidth Item, returns the KeyName"""

SoftLayer/managers/vs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def get_instance(self, instance_id, **kwargs):
227227
'maxMemory,'
228228
'datacenter,'
229229
'activeTransaction[id, transactionStatus[friendlyName,name]],'
230-
'lastTransaction[transactionStatus],'
230+
'lastTransaction[transactionStatus,modifyDate,transactionGroup[name]],'
231231
'lastOperatingSystemReload.id,'
232232
'blockDevices,'
233233
'blockDeviceTemplateGroup[id, name, globalIdentifier],'

SoftLayer/testing/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
import logging
1010
import os.path
1111
import unittest
12+
from unittest import mock as mock
1213

1314
from click import testing
14-
import mock
1515

1616
import SoftLayer
1717
from SoftLayer.CLI import core

SoftLayer/utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
:license: MIT, see LICENSE for more details.
77
"""
8+
import collections
89
import datetime
910
import re
1011
import time
@@ -57,6 +58,23 @@ def to_dict(self):
5758
for key, val in self.items()}
5859

5960

61+
def dict_merge(dct1, dct2):
62+
"""Recursively merges dct2 and dct1, ideal for merging objectFilter together.
63+
64+
:param dct1: A dictionary
65+
:param dct2: A dictionary
66+
:return: dct1 + dct2
67+
"""
68+
69+
dct = dct1.copy()
70+
for k, _ in dct2.items():
71+
if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)):
72+
dct[k] = dict_merge(dct1[k], dct2[k])
73+
else:
74+
dct[k] = dct2[k]
75+
return dct
76+
77+
6078
def query_filter(query):
6179
"""Translate a query-style string to a 'filter'.
6280

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
'Operating System :: OS Independent',
5151
'Topic :: Software Development :: Libraries :: Python Modules',
5252
'Programming Language :: Python :: 3',
53-
'Programming Language :: Python :: 3.5',
5453
'Programming Language :: Python :: 3.6',
5554
'Programming Language :: Python :: 3.7',
5655
'Programming Language :: Python :: 3.8',

0 commit comments

Comments
 (0)