diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 631793475..70430bc8f 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -33,6 +33,7 @@ def _update_with_like_args(ctx, _, value): 'dedicated': like_details['dedicatedAccountHostOnlyFlag'], 'private': like_details['privateNetworkOnlyFlag'], 'placement_id': like_details.get('placementGroupId', None), + 'transient': like_details.get('transientGuestFlag', None), } like_args['flavor'] = utils.lookup(like_details, @@ -83,6 +84,7 @@ def _parse_create_args(client, args): "domain": args.get('domain', None), "host_id": args.get('host_id', None), "private": args.get('private', None), + "transient": args.get('transient', None), "hostname": args.get('hostname', None), "nic_speed": args.get('network', None), "boot_mode": args.get('boot_mode', None), @@ -105,7 +107,8 @@ def _parse_create_args(client, args): if args.get('image'): if args.get('image').isdigit(): image_mgr = SoftLayer.ImageManager(client) - image_details = image_mgr.get_image(args.get('image'), mask="id,globalIdentifier") + image_details = image_mgr.get_image(args.get('image'), + mask="id,globalIdentifier") data['image_id'] = image_details['globalIdentifier'] else: data['image_id'] = args['image'] @@ -198,6 +201,8 @@ def _parse_create_args(client, args): @click.option('--placementgroup', help="Placement Group name or Id to order this guest on. See: slcli vs placementgroup list") @click.option('--ipv6', is_flag=True, help="Adds an IPv6 address to this guest") +@click.option('--transient', is_flag=True, + help="Create a transient virtual server") @environment.pass_env def cli(env, **args): """Order/create virtual servers.""" @@ -281,6 +286,10 @@ def _validate_args(env, args): raise exceptions.ArgumentError( '[-m | --memory] not allowed with [-f | --flavor]') + if all([args['dedicated'], args['transient']]): + raise exceptions.ArgumentError( + '[--dedicated] not allowed with [--transient]') + if all([args['dedicated'], args['flavor']]): raise exceptions.ArgumentError( '[-d | --dedicated] not allowed with [-f | --flavor]') @@ -289,6 +298,10 @@ def _validate_args(env, args): raise exceptions.ArgumentError( '[-h | --host-id] not allowed with [-f | --flavor]') + if args['transient'] and args['billing'] == 'monthly': + raise exceptions.ArgumentError( + '[--transient] not allowed with [--billing monthly]') + if all([args['userdata'], args['userfile']]): raise exceptions.ArgumentError( '[-u | --userdata] not allowed with [-F | --userfile]') diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index b50fde3d2..601c0f3ac 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -12,7 +12,7 @@ from SoftLayer import utils -@click.command() +@click.command(short_help="Get options to use for creating virtual servers.") @environment.pass_env def cli(env): """Virtual server order options.""" @@ -32,27 +32,7 @@ def cli(env): table.add_row(['datacenter', formatting.listing(datacenters, separator='\n')]) - def _add_flavor_rows(flavor_key, flavor_label, flavor_options): - flavors = [] - - for flavor_option in flavor_options: - flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') - if not flavor_key_name.startswith(flavor_key): - continue - - flavors.append(flavor_key_name) - - if len(flavors) > 0: - table.add_row(['flavors (%s)' % flavor_label, - formatting.listing(flavors, separator='\n')]) - - if result.get('flavors', None): - _add_flavor_rows('B1', 'balanced', result['flavors']) - _add_flavor_rows('BL1', 'balanced local - hdd', result['flavors']) - _add_flavor_rows('BL2', 'balanced local - ssd', result['flavors']) - _add_flavor_rows('C1', 'compute', result['flavors']) - _add_flavor_rows('M1', 'memory', result['flavors']) - _add_flavor_rows('AC', 'GPU', result['flavors']) + _add_flavors_to_table(result, table) # CPUs standard_cpus = [int(x['template']['startCpus']) for x in result['processors'] @@ -167,3 +147,36 @@ def add_block_rows(disks, name): formatting.listing(ded_host_speeds, separator=',')]) env.fout(table) + + +def _add_flavors_to_table(result, table): + grouping = { + 'balanced': {'key_starts_with': 'B1', 'flavors': []}, + 'balanced local - hdd': {'key_starts_with': 'BL1', 'flavors': []}, + 'balanced local - ssd': {'key_starts_with': 'BL2', 'flavors': []}, + 'compute': {'key_starts_with': 'C1', 'flavors': []}, + 'memory': {'key_starts_with': 'M1', 'flavors': []}, + 'GPU': {'key_starts_with': 'AC', 'flavors': []}, + 'transient': {'transient': True, 'flavors': []}, + } + + if result.get('flavors', None) is None: + return + + for flavor_option in result['flavors']: + flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') + + for name, group in grouping.items(): + if utils.lookup(flavor_option, 'template', 'transientGuestFlag') is True: + if utils.lookup(group, 'transient') is True: + group['flavors'].append(flavor_key_name) + break + + elif utils.lookup(group, 'key_starts_with') is not None \ + and flavor_key_name.startswith(group['key_starts_with']): + group['flavors'].append(flavor_key_name) + break + + for name, group in grouping.items(): + if len(group['flavors']) > 0: + table.add_row(['flavors (%s)' % name, formatting.listing(group['flavors'], separator='\n')]) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index feb03cf82..53ae7e04d 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -61,6 +61,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['private_ip', result.get('primaryBackendIpAddress', '-')]) table.add_row(['private_only', result['privateNetworkOnlyFlag']]) table.add_row(['private_cpu', result['dedicatedAccountHostOnlyFlag']]) + table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 3975ad333..6bf9e6bb6 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -42,7 +42,7 @@ ] -@click.command() +@click.command(short_help="List virtual servers.") @click.option('--cpu', '-c', help='Number of CPU cores', type=click.INT) @click.option('--domain', '-D', help='Domain portion of the FQDN') @click.option('--datacenter', '-d', help='Datacenter shortname') @@ -51,6 +51,7 @@ @click.option('--network', '-n', help='Network port speed in Mbps') @click.option('--hourly', is_flag=True, help='Show only hourly instances') @click.option('--monthly', is_flag=True, help='Show only monthly instances') +@click.option('--transient', help='Filter by transient instances', type=click.BOOL) @helpers.multi_option('--tag', help='Filter by tags') @click.option('--sortby', help='Column to sort by', @@ -68,7 +69,7 @@ show_default=True) @environment.pass_env def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, - hourly, monthly, tag, columns, limit): + hourly, monthly, tag, columns, limit, transient): """List virtual servers.""" vsi = SoftLayer.VSManager(env.client) @@ -80,6 +81,7 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, memory=memory, datacenter=datacenter, nic_speed=network, + transient=transient, tags=tag, mask=columns.mask(), limit=limit) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index aaae79b73..270ecf2ad 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -49,6 +49,7 @@ 'vlanNumber': 23, 'id': 1}], 'dedicatedHost': {'id': 37401}, + 'transientGuestFlag': False, 'operatingSystem': { 'passwords': [{'username': 'user', 'password': 'pass'}], 'softwareLicense': { @@ -75,6 +76,17 @@ } } }, + { + 'flavor': { + 'keyName': 'B1_1X2X25_TRANSIENT' + }, + 'template': { + 'supplementalCreateObjectOptions': { + 'flavorKeyName': 'B1_1X2X25_TRANSIENT' + }, + 'transientGuestFlag': True + } + }, { 'flavor': { 'keyName': 'B1_1X2X100' diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 03ac6d035..85bbdf9d9 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -61,7 +61,7 @@ def __init__(self, client, ordering_manager=None): def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, memory=None, hostname=None, domain=None, local_disk=None, datacenter=None, nic_speed=None, - public_ip=None, private_ip=None, **kwargs): + public_ip=None, private_ip=None, transient=None, **kwargs): """Retrieve a list of all virtual servers on the account. Example:: @@ -88,6 +88,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, :param integer nic_speed: filter based on network speed (in MBPS) :param string public_ip: filter based on public ip address :param string private_ip: filter based on private ip address + :param boolean transient: filter on transient or non-transient instances :param dict \\*\\*kwargs: response-level options (mask, limit, etc.) :returns: Returns a list of dictionaries representing the matching virtual servers @@ -157,6 +158,11 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, _filter['virtualGuests']['primaryBackendIpAddress'] = ( utils.query_filter(private_ip)) + if transient is not None: + _filter['virtualGuests']['transientGuestFlag'] = ( + utils.query_filter(bool(transient)) + ) + kwargs['filter'] = _filter.to_dict() kwargs['iter'] = True return self.client.call('Account', call, **kwargs) @@ -194,6 +200,7 @@ def get_instance(self, instance_id, **kwargs): 'provisionDate,' 'notes,' 'dedicatedAccountHostOnlyFlag,' + 'transientGuestFlag,' 'privateNetworkOnlyFlag,' 'primaryBackendIpAddress,' 'primaryIpAddress,' @@ -312,7 +319,7 @@ def _generate_create_dict( private_subnet=None, public_subnet=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None, public_security_groups=None, - private_security_groups=None, boot_mode=None, **kwargs): + private_security_groups=None, boot_mode=None, transient=False, **kwargs): """Returns a dict appropriate to pass into Virtual_Guest::createObject See :func:`create_instance` for a list of available options. @@ -362,6 +369,9 @@ def _generate_create_dict( if private: data['privateNetworkOnlyFlag'] = private + if transient: + data['transientGuestFlag'] = transient + if image_id: data["blockDeviceTemplateGroup"] = {"globalIdentifier": image_id} elif os_code: @@ -505,6 +515,7 @@ def verify_create_instance(self, **kwargs): 'flavor': 'BL1_1X2X100' 'dedicated': False, 'private': False, + 'transient': False, 'os_code' : u'UBUNTU_LATEST', 'hourly': True, 'ssh_keys': [1234], @@ -883,6 +894,7 @@ def order_guest(self, guest_object, test=False): 'flavor': 'BL1_1X2X100' 'dedicated': False, 'private': False, + 'transient': False, 'os_code' : u'UBUNTU_LATEST', 'hourly': True, 'ssh_keys': [1234], diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 91946471a..761778db6 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -485,6 +485,48 @@ def test_create_like_flavor(self, confirm_mock): 'networkComponents': [{'maxSpeed': 100}]},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_like_transient(self, confirm_mock): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') + mock.return_value = { + 'hostname': 'vs-test-like', + 'domain': 'test.sftlyr.ws', + 'datacenter': {'name': 'dal05'}, + 'networkComponents': [{'maxSpeed': 100}], + 'dedicatedAccountHostOnlyFlag': False, + 'privateNetworkOnlyFlag': False, + 'billingItem': {'orderItem': {'preset': {'keyName': 'B1_1X2X25'}}}, + 'operatingSystem': {'softwareLicense': { + 'softwareDescription': {'referenceCode': 'UBUNTU_LATEST'} + }}, + 'hourlyBillingFlag': True, + 'localDiskFlag': False, + 'transientGuestFlag': True, + 'userData': {} + } + + confirm_mock.return_value = True + result = self.run_command(['vs', 'create', '--like=123']) + + self.assert_no_fail(result) + self.assertIn('"guid": "1a2b3c-1701"', result.output) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + + args = ({'datacenter': {'name': 'dal05'}, + 'domain': 'test.sftlyr.ws', + 'hourlyBillingFlag': True, + 'hostname': 'vs-test-like', + 'startCpus': None, + 'maxMemory': None, + 'localDiskFlag': None, + 'transientGuestFlag': True, + 'supplementalCreateObjectOptions': { + 'bootMode': None, + 'flavorKeyName': 'B1_1X2X25'}, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'networkComponents': [{'maxSpeed': 100}]},) + self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_vs_test(self, confirm_mock): confirm_mock.return_value = True @@ -511,9 +553,39 @@ def test_create_vs_bad_memory(self): result = self.run_command(['vs', 'create', '--hostname', 'TEST', '--domain', 'TESTING', '--cpu', '1', '--memory', '2034MB', '--flavor', - 'UBUNTU', '--datacenter', 'TEST00']) + 'B1_2X8X25', '--datacenter', 'TEST00']) - self.assertEqual(result.exit_code, 2) + self.assertEqual(2, result.exit_code) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_vs_transient(self, confirm_mock): + confirm_mock.return_value = True + + result = self.run_command(['vs', 'create', '--hostname', 'TEST', + '--domain', 'TESTING', '--flavor', + 'B1_2X8X25', '--datacenter', 'TEST00', + '--transient', '--os', 'UBUNTU_LATEST']) + + self.assert_no_fail(result) + self.assertEqual(0, result.exit_code) + + def test_create_vs_bad_transient_monthly(self): + result = self.run_command(['vs', 'create', '--hostname', 'TEST', + '--domain', 'TESTING', '--flavor', + 'B1_2X8X25', '--datacenter', 'TEST00', + '--transient', '--billing', 'monthly', + '--os', 'UBUNTU_LATEST']) + + self.assertEqual(2, result.exit_code) + + def test_create_vs_bad_transient_dedicated(self): + result = self.run_command(['vs', 'create', '--hostname', 'TEST', + '--domain', 'TESTING', '--flavor', + 'B1_2X8X25', '--datacenter', 'TEST00', + '--transient', '--dedicated', + '--os', 'UBUNTU_LATEST']) + + self.assertEqual(2, result.exit_code) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_with_ipv6(self, confirm_mock): diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 16016d450..203230913 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -268,8 +268,7 @@ def test_create_options(self): result = self.run_command(['vs', 'create-options']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - {'cpus (dedicated host)': [4, 56], + self.assertEqual({'cpus (dedicated host)': [4, 56], 'cpus (dedicated)': [1], 'cpus (standard)': [1, 2, 3, 4], 'datacenter': ['ams01', 'dal05'], @@ -279,6 +278,7 @@ def test_create_options(self): 'flavors (compute)': ['C1_1X2X25'], 'flavors (memory)': ['M1_1X2X100'], 'flavors (GPU)': ['AC1_1X2X100', 'ACL1_1X2X100'], + 'flavors (transient)': ['B1_1X2X25_TRANSIENT'], 'local disk(0)': ['25', '100'], 'memory': [1024, 2048, 3072, 4096], 'memory (dedicated host)': [8192, 65536], @@ -286,7 +286,8 @@ def test_create_options(self): 'nic (dedicated host)': ['1000'], 'os (CENTOS)': 'CENTOS_6_64', 'os (DEBIAN)': 'DEBIAN_7_64', - 'os (UBUNTU)': 'UBUNTU_12_64'}) + 'os (UBUNTU)': 'UBUNTU_12_64'}, + json.loads(result.output)) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 2192e642b..2816f8b06 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -60,6 +60,7 @@ def test_list_instances_with_filters(self): nic_speed=100, public_ip='1.2.3.4', private_ip='4.3.2.1', + transient=False, ) _filter = { @@ -78,7 +79,8 @@ def test_list_instances_with_filters(self): 'hostname': {'operation': '_= hostname'}, 'networkComponents': {'maxSpeed': {'operation': 100}}, 'primaryIpAddress': {'operation': '_= 1.2.3.4'}, - 'primaryBackendIpAddress': {'operation': '_= 4.3.2.1'} + 'primaryBackendIpAddress': {'operation': '_= 4.3.2.1'}, + 'transientGuestFlag': {'operation': False}, } } self.assert_called_with('SoftLayer_Account', 'getVirtualGuests',