-
Notifications
You must be signed in to change notification settings - Fork 194
Reserved Capacity Support #1051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
42ba6f5
#1026 groundwork for capacity commands
allmightyspiff cd3d417
#1026 functions for create-options
allmightyspiff 475b1eb
got capacity create working
allmightyspiff a53a75a
Merge branch '1026' of github.com:allmightyspiff/softlayer-python int…
allmightyspiff af4fd92
list and detail support for capacity groups
allmightyspiff 87b51a9
create-guest base files
allmightyspiff 39f9e1b
support for creating guests, some more features for list and detail
allmightyspiff 9f99ed3
#1026 mostly done with the bits that actually do things. still need u…
allmightyspiff fcd90dd
Merge branch 'master' of github.com:allmightyspiff/softlayer-python i…
allmightyspiff 1046cfe
Merge branch 'master' of github.com:softlayer/softlayer-python into 1026
allmightyspiff 53492ee
#1026 unit tests and fixtures for ReservedCapacityGroup
allmightyspiff 4815cdb
#1026 unit tests
allmightyspiff ac15931
#1026 pylint fixes
allmightyspiff 0ac4de3
Merge remote-tracking branch 'origin/master' into 1026
allmightyspiff 87a8ded
doc updates
allmightyspiff 9d87c90
vs capacity docs
allmightyspiff b2e6784
Fixed an object mask
allmightyspiff 082c1ea
more docs
allmightyspiff 893ff90
fixed whitespace issue
allmightyspiff 3e770a2
Merge branch '1026' of github.com:allmightyspiff/softlayer-python int…
allmightyspiff 03d3c8e
#1026 resolving pull request feedback
allmightyspiff 0d22da9
fixed unit tests
allmightyspiff 4cff83c
some final touches, ended up auto-translating commands so they confor…
allmightyspiff File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"""Manages Reserved Capacity.""" | ||
# :license: MIT, see LICENSE for more details. | ||
|
||
import importlib | ||
import os | ||
|
||
import click | ||
|
||
CONTEXT = {'help_option_names': ['-h', '--help'], | ||
'max_content_width': 999} | ||
|
||
|
||
class CapacityCommands(click.MultiCommand): | ||
"""Loads module for capacity related commands. | ||
|
||
Will automatically replace _ with - where appropriate. | ||
I'm not sure if this is better or worse than using a long list of manual routes, so I'm trying it here. | ||
CLI/virt/capacity/create_guest.py -> slcli vs capacity create-guest | ||
""" | ||
|
||
def __init__(self, **attrs): | ||
click.MultiCommand.__init__(self, **attrs) | ||
self.path = os.path.dirname(__file__) | ||
|
||
def list_commands(self, ctx): | ||
"""List all sub-commands.""" | ||
commands = [] | ||
for filename in os.listdir(self.path): | ||
if filename == '__init__.py': | ||
continue | ||
if filename.endswith('.py'): | ||
commands.append(filename[:-3].replace("_", "-")) | ||
commands.sort() | ||
return commands | ||
|
||
def get_command(self, ctx, cmd_name): | ||
"""Get command for click.""" | ||
path = "%s.%s" % (__name__, cmd_name) | ||
path = path.replace("-", "_") | ||
module = importlib.import_module(path) | ||
return getattr(module, 'cli') | ||
|
||
|
||
# Required to get the sub-sub-sub command to work. | ||
@click.group(cls=CapacityCommands, context_settings=CONTEXT) | ||
def cli(): | ||
"""Base command for all capacity related concerns""" | ||
pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"""Create a Reserved Capacity instance.""" | ||
|
||
import click | ||
|
||
|
||
from SoftLayer.CLI import environment | ||
from SoftLayer.CLI import formatting | ||
from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager | ||
|
||
|
||
@click.command(epilog=click.style("""WARNING: Reserved Capacity is on a yearly contract""" | ||
""" and not cancelable until the contract is expired.""", fg='red')) | ||
@click.option('--name', '-n', required=True, prompt=True, | ||
help="Name for your new reserved capacity") | ||
@click.option('--backend_router_id', '-b', required=True, prompt=True, type=int, | ||
help="backendRouterId, create-options has a list of valid ids to use.") | ||
@click.option('--flavor', '-f', required=True, prompt=True, | ||
help="Capacity keyname (C1_2X2_1_YEAR_TERM for example).") | ||
@click.option('--instances', '-i', required=True, prompt=True, type=int, | ||
help="Number of VSI instances this capacity reservation can support.") | ||
@click.option('--test', is_flag=True, | ||
help="Do not actually create the virtual server") | ||
@environment.pass_env | ||
def cli(env, name, backend_router_id, flavor, instances, test=False): | ||
"""Create a Reserved Capacity instance. | ||
|
||
*WARNING*: Reserved Capacity is on a yearly contract and not cancelable until the contract is expired. | ||
""" | ||
manager = CapacityManager(env.client) | ||
|
||
result = manager.create( | ||
name=name, | ||
backend_router_id=backend_router_id, | ||
flavor=flavor, | ||
instances=instances, | ||
test=test) | ||
if test: | ||
table = formatting.Table(['Name', 'Value'], "Test Order") | ||
container = result['orderContainers'][0] | ||
table.add_row(['Name', container['name']]) | ||
table.add_row(['Location', container['locationObject']['longName']]) | ||
for price in container['prices']: | ||
table.add_row(['Contract', price['item']['description']]) | ||
table.add_row(['Hourly Total', result['postTaxRecurring']]) | ||
else: | ||
table = formatting.Table(['Name', 'Value'], "Reciept") | ||
table.add_row(['Order Date', result['orderDate']]) | ||
table.add_row(['Order ID', result['orderId']]) | ||
table.add_row(['status', result['placedOrder']['status']]) | ||
table.add_row(['Hourly Total', result['orderDetails']['postTaxRecurring']]) | ||
env.fout(table) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
"""List Reserved Capacity""" | ||
# :license: MIT, see LICENSE for more details. | ||
|
||
import click | ||
|
||
from SoftLayer.CLI import environment | ||
from SoftLayer.CLI import formatting | ||
from SoftLayer.CLI import helpers | ||
from SoftLayer.CLI.virt.create import _parse_create_args as _parse_create_args | ||
from SoftLayer.CLI.virt.create import _update_with_like_args as _update_with_like_args | ||
from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager | ||
|
||
|
||
@click.command() | ||
@click.option('--capacity-id', type=click.INT, help="Reserve capacity Id to provision this guest into.") | ||
@click.option('--primary-disk', type=click.Choice(['25', '100']), default='25', help="Size of the main drive.") | ||
@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN.") | ||
@click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN.") | ||
@click.option('--os', '-o', help="OS install code. Tip: you can specify <OS>_LATEST.") | ||
@click.option('--image', help="Image ID. See: 'slcli image list' for reference.") | ||
@click.option('--boot-mode', type=click.STRING, | ||
help="Specify the mode to boot the OS in. Supported modes are HVM and PV.") | ||
@click.option('--postinstall', '-i', help="Post-install script to download.") | ||
@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user.") | ||
@helpers.multi_option('--disk', help="Additional disk sizes.") | ||
@click.option('--private', is_flag=True, help="Forces the VS to only have access the private network.") | ||
@click.option('--like', is_eager=True, callback=_update_with_like_args, | ||
help="Use the configuration from an existing VS.") | ||
@click.option('--network', '-n', help="Network port speed in Mbps.") | ||
@helpers.multi_option('--tag', '-g', help="Tags to add to the instance.") | ||
@click.option('--userdata', '-u', help="User defined metadata string.") | ||
@click.option('--ipv6', is_flag=True, help="Adds an IPv6 address to this guest") | ||
@click.option('--test', is_flag=True, | ||
help="Test order, will return the order container, but not actually order a server.") | ||
@environment.pass_env | ||
def cli(env, **args): | ||
"""Allows for creating a virtual guest in a reserved capacity.""" | ||
create_args = _parse_create_args(env.client, args) | ||
if args.get('ipv6'): | ||
create_args['ipv6'] = True | ||
create_args['primary_disk'] = args.get('primary_disk') | ||
manager = CapacityManager(env.client) | ||
capacity_id = args.get('capacity_id') | ||
test = args.get('test') | ||
|
||
result = manager.create_guest(capacity_id, test, create_args) | ||
|
||
env.fout(_build_receipt(result, test)) | ||
|
||
|
||
def _build_receipt(result, test=False): | ||
title = "OrderId: %s" % (result.get('orderId', 'No order placed')) | ||
table = formatting.Table(['Item Id', 'Description'], title=title) | ||
table.align['Description'] = 'l' | ||
|
||
if test: | ||
prices = result['prices'] | ||
else: | ||
prices = result['orderDetails']['prices'] | ||
|
||
for item in prices: | ||
table.add_row([item['id'], item['item']['description']]) | ||
return table |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
"""List options for creating Reserved Capacity""" | ||
# :license: MIT, see LICENSE for more details. | ||
|
||
import click | ||
|
||
from SoftLayer.CLI import environment | ||
from SoftLayer.CLI import formatting | ||
from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager | ||
|
||
|
||
@click.command() | ||
@environment.pass_env | ||
def cli(env): | ||
"""List options for creating Reserved Capacity""" | ||
manager = CapacityManager(env.client) | ||
items = manager.get_create_options() | ||
|
||
items.sort(key=lambda term: int(term['capacity'])) | ||
table = formatting.Table(["KeyName", "Description", "Term", "Default Hourly Price Per Instance"], | ||
title="Reserved Capacity Options") | ||
table.align["Hourly Price"] = "l" | ||
table.align["Description"] = "l" | ||
table.align["KeyName"] = "l" | ||
for item in items: | ||
table.add_row([ | ||
item['keyName'], item['description'], item['capacity'], get_price(item) | ||
]) | ||
env.fout(table) | ||
|
||
regions = manager.get_available_routers() | ||
location_table = formatting.Table(['Location', 'POD', 'BackendRouterId'], 'Orderable Locations') | ||
for region in regions: | ||
for location in region['locations']: | ||
for pod in location['location']['pods']: | ||
location_table.add_row([region['keyname'], pod['backendRouterName'], pod['backendRouterId']]) | ||
env.fout(location_table) | ||
|
||
|
||
def get_price(item): | ||
"""Finds the price with the default locationGroupId""" | ||
the_price = "No Default Pricing" | ||
for price in item.get('prices', []): | ||
if not price.get('locationGroupId'): | ||
the_price = "%0.4f" % float(price['hourlyRecurringFee']) | ||
return the_price |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
"""Shows the details of a reserved capacity group""" | ||
|
||
import click | ||
|
||
from SoftLayer.CLI import columns as column_helper | ||
from SoftLayer.CLI import environment | ||
from SoftLayer.CLI import formatting | ||
from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager | ||
|
||
COLUMNS = [ | ||
column_helper.Column('Id', ('id',)), | ||
column_helper.Column('hostname', ('hostname',)), | ||
column_helper.Column('domain', ('domain',)), | ||
column_helper.Column('primary_ip', ('primaryIpAddress',)), | ||
column_helper.Column('backend_ip', ('primaryBackendIpAddress',)), | ||
] | ||
|
||
DEFAULT_COLUMNS = [ | ||
'id', | ||
'hostname', | ||
'domain', | ||
'primary_ip', | ||
'backend_ip' | ||
] | ||
|
||
|
||
@click.command(epilog="Once provisioned, virtual guests can be managed with the slcli vs commands") | ||
@click.argument('identifier') | ||
@click.option('--columns', | ||
callback=column_helper.get_formatter(COLUMNS), | ||
help='Columns to display. [options: %s]' | ||
% ', '.join(column.name for column in COLUMNS), | ||
default=','.join(DEFAULT_COLUMNS), | ||
show_default=True) | ||
@environment.pass_env | ||
def cli(env, identifier, columns): | ||
"""Reserved Capacity Group details. Will show which guests are assigned to a reservation.""" | ||
|
||
manager = CapacityManager(env.client) | ||
mask = """mask[instances[id,createDate,guestId,billingItem[id, description, recurringFee, category[name]], | ||
guest[modifyDate,id, primaryBackendIpAddress, primaryIpAddress,domain, hostname]]]""" | ||
result = manager.get_object(identifier, mask) | ||
|
||
try: | ||
flavor = result['instances'][0]['billingItem']['description'] | ||
except KeyError: | ||
flavor = "Pending Approval..." | ||
|
||
table = formatting.Table(columns.columns, title="%s - %s" % (result.get('name'), flavor)) | ||
# RCI = Reserved Capacity Instance | ||
for rci in result['instances']: | ||
guest = rci.get('guest', None) | ||
if guest is not None: | ||
table.add_row([value or formatting.blank() for value in columns.row(guest)]) | ||
else: | ||
table.add_row(['-' for value in columns.columns]) | ||
env.fout(table) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
"""List Reserved Capacity""" | ||
|
||
import click | ||
|
||
from SoftLayer.CLI import environment | ||
from SoftLayer.CLI import formatting | ||
from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager | ||
|
||
|
||
@click.command() | ||
@environment.pass_env | ||
def cli(env): | ||
"""List Reserved Capacity groups.""" | ||
manager = CapacityManager(env.client) | ||
result = manager.list() | ||
table = formatting.Table( | ||
["ID", "Name", "Capacity", "Flavor", "Location", "Created"], | ||
title="Reserved Capacity" | ||
) | ||
for r_c in result: | ||
occupied_string = "#" * int(r_c.get('occupiedInstanceCount', 0)) | ||
available_string = "-" * int(r_c.get('availableInstanceCount', 0)) | ||
|
||
try: | ||
flavor = r_c['instances'][0]['billingItem']['description'] | ||
# cost = float(r_c['instances'][0]['billingItem']['hourlyRecurringFee']) | ||
except KeyError: | ||
flavor = "Unknown Billing Item" | ||
location = r_c['backendRouter']['hostname'] | ||
capacity = "%s%s" % (occupied_string, available_string) | ||
table.add_row([r_c['id'], r_c['name'], capacity, flavor, location, r_c['createDate']]) | ||
env.fout(table) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.