From 780b3a38c691d44b2eec628e94f0f853e05a31eb Mon Sep 17 00:00:00 2001 From: Saurabh Surana Date: Mon, 18 May 2015 14:23:00 -0700 Subject: [PATCH] Accepting network and availability zone for instances in cluster Standalone instance create call in trove already accepts neutron networks and availability zone information. In the similar manner, instances which are part of the cluster can now be supplied with a specific neutron network information and availability zone. Instance parameter now includes flavor_id, volume, network and availability_zone. We are now spliting the cluster create arguments by trove cluster arguments. Then dealing with each sub-parameters individually. Co-Authored-By: Saurabh Surana Co-Authored-By: Sharika Pongubala Change-Id: I25af22d3a48b728e59d93f959cf8646d54973d2e Partial-Bug: #1447350 --- troveclient/tests/test_v1_shell.py | 49 +++++++++- troveclient/v1/shell.py | 145 ++++++++++++++++++++++++----- 2 files changed, 168 insertions(+), 26 deletions(-) diff --git a/troveclient/tests/test_v1_shell.py b/troveclient/tests/test_v1_shell.py index ff9b7f41..a3f991f4 100644 --- a/troveclient/tests/test_v1_shell.py +++ b/troveclient/tests/test_v1_shell.py @@ -199,7 +199,8 @@ class ShellTest(utils.TestCase): cmd = ('create test-member-1 1 --size 1 ' '--nic net-id=some-id,port-id=some-id') self.assertRaisesRegexp( - exceptions.ValidationError, 'Invalid nic argument', + exceptions.ValidationError, + 'Invalid NIC argument net-id=some-id,port-id=some-id.', self.run_command, cmd) def test_cluster_create(self): @@ -261,6 +262,52 @@ class ShellTest(utils.TestCase): self.run_command(cmd) self.assert_called('POST', '/clusters/cls-1234') + def test_cluster_create_with_nic_az(self): + cmd = ('cluster-create test-clstr1 vertica 7.1 ' + '--instance flavor=2,volume=2,nic=net-id=some-id,' + 'availability_zone=2 ' + '--instance flavor=2,volume=2,nic=net-id=some-id,' + 'availability_zone=2') + self.run_command(cmd) + self.assert_called_anytime( + 'POST', '/clusters', + {'cluster': { + 'instances': [ + { + 'flavorRef': '2', + 'volume': {'size': '2'}, + 'nics': [{'net-id': 'some-id'}], + 'availability-zone': '2' + }, + { + 'flavorRef': '2', + 'volume': {'size': '2'}, + 'nics': [{'net-id': 'some-id'}], + 'availability-zone': '2' + }], + 'datastore': {'version': '7.1', 'type': 'vertica'}, + 'name': 'test-clstr1'}}) + + def test_cluster_create_with_nic_az_error(self): + cmd = ('cluster-create test-clstr vertica 7.1 ' + '--instance flavor=2,volume=2,nic=net-id=some-id,' + 'port-id=some-port-id,availability_zone=2 ' + '--instance flavor=2,volume=1,nic=net-id=some-id,' + 'port-id=some-port-id,availability_zone=2') + self.assertRaisesRegexp( + exceptions.ValidationError, 'Invalid NIC argument', + self.run_command, cmd) + + def test_cluster_create_with_nic_az_error_again(self): + cmd = ('cluster-create test-clstr vertica 7.1 ' + '--instance flavor=2,volume=2,nic=\'v4-fixed-ip=10.0.0.1\',' + 'availability_zone=2 ' + '--instance flavor=2,volume=1,nic=\'v4-fixed-ip=10.0.0.1\',' + 'availability_zone=2') + self.assertRaisesRegexp( + exceptions.ValidationError, 'Invalid NIC argument', + self.run_command, cmd) + def test_datastore_list(self): self.run_command('datastore-list') self.assert_called('GET', '/datastores') diff --git a/troveclient/v1/shell.py b/troveclient/v1/shell.py index 46d83dd1..7d0c7b09 100644 --- a/troveclient/v1/shell.py +++ b/troveclient/v1/shell.py @@ -20,7 +20,9 @@ import sys import time INSTANCE_ERROR = ("Instance argument(s) must be of the form --instance " - "") + " - see help for details.") +NIC_ERROR = ("Invalid NIC argument %s. Must specify either net-id or port-id " + "but not both. Please refer to help.") try: import simplejson as json @@ -449,12 +451,7 @@ def do_create(cs, args): for nic_str in args.nics: nic_info = dict([(k, v) for (k, v) in [z.split("=", 1)[:2] for z in nic_str.split(",")]]) - if bool(nic_info.get('net-id')) == bool(nic_info.get('port-id')): - err_msg = ("Invalid nic argument '%s'. Nic arguments must be of " - "the form --nic , with at minimum net-id or port-id " - "(but not both) specified." % nic_str) - raise exceptions.ValidationError(err_msg) + _validate_nic_info(nic_info, nic_str) nics.append(nic_info) instance = cs.instances.create(args.name, @@ -473,41 +470,139 @@ def do_create(cs, args): _print_instance(instance) +def _validate_nic_info(nic_info, nic_str): + if bool(nic_info.get('net-id')) or bool(nic_info.get('port-id')): + if bool(nic_info.get('net-id')) and bool(nic_info.get('port-id')): + raise exceptions.ValidationError(NIC_ERROR % nic_str) + else: + raise exceptions.ValidationError(NIC_ERROR % nic_str) + + +def _get_flavors(cs, instance_str): + if 'flavor' in instance_str: + try: + flavor_arg = instance_str.split("flavor")[1] + flavor_value = flavor_arg.split(',')[0] + flavor_num = flavor_value.split('=')[1] + flavor_id = _find_flavor(cs, flavor_num).id + except IndexError: + err_msg = ("Invalid flavor parameter. %s." % INSTANCE_ERROR) + raise exceptions.ValidationError(err_msg) + + return str(flavor_id) + else: + err_msg = ("flavor is required. %s." % INSTANCE_ERROR) + raise exceptions.ValidationError(err_msg) + + +def _get_networks(instance_str): + nic_info = {} + nic_arg_list = ['net-id', 'port-id', 'v4-fixed-ip'] + + nics_present = False + nic_arg = '' + for arg in nic_arg_list: + if arg in instance_str: + nics_present = True + nic_arg = arg + instance_str.split(arg)[1] + break + + if 'nic' in instance_str: + nic_arg = instance_str.split("nic")[1] + if nic_arg.split('=')[0]: + err_msg = (NIC_ERROR % ("nic%s" % nic_arg)) + raise exceptions.ValidationError(err_msg) + try: + for arg in nic_arg_list: + if arg in nic_arg: + single_arg = nic_arg.split(arg)[1] + single_value = single_arg.split('=')[1] + single_num = single_value.split(',')[0] + nic_info[str(arg)] = str(single_num) + except IndexError: + err_msg = (NIC_ERROR % ("nic%s" % nic_arg)) + raise exceptions.ValidationError(err_msg) + + _validate_nic_info(nic_info, "nic%s" % nic_arg) + + return [nic_info] + elif 'nic' not in instance_str and nics_present: + err_msg = ("Invalid NIC argument. Must specify nic='%s'." + " Please refer to help" % (nic_arg)) + raise exceptions.ValidationError(err_msg) + + else: + return None + + +def _get_volumes(instance_str): + try: + volume_arg = instance_str.split("volume")[1] + volume_value = volume_arg.split(',')[0] + volume_num = volume_value.split('=')[1] + except IndexError: + err_msg = ("Invalid volume parameter. %s." % INSTANCE_ERROR) + raise exceptions.ValidationError(err_msg) + + return {"size": volume_num} + + +def _get_availability_zones(instance_str): + if 'availability_zone' in instance_str: + try: + az_arg = instance_str.split("availability_zone")[1] + az_value = az_arg.split(',')[0] + az_num = az_value.split('=')[1] + except IndexError: + err_msg = ("Invalid availability zone parameter. %s." + % INSTANCE_ERROR) + raise exceptions.ValidationError(err_msg) + + return str(az_num) + else: + return None + + @utils.arg('name', metavar='', type=str, help='Name of the cluster.') @utils.arg('datastore', metavar='', - help='A datastore name or UUID.') + help='A datastore name or ID.') @utils.arg('datastore_version', metavar='', - help='A datastore version name or UUID.') + help='A datastore version name or ID.') @utils.arg('--instance', - metavar="", + metavar="", + help="Create an instance for the cluster. Specify multiple " + "times to create multiple instances. " + "Valid options are: flavor=flavor_name_or_id, " + "volume=disk_size_in_GB, " + "nic='net-id=net-uuid,v4-fixed-ip=ip-addr,port-id=port-uuid' " + "(where net-id=network_id, v4-fixed-ip=IPv4r_fixed_address, " + "port-id=port_id), availability_zone=AZ_hint_for_Nova.", action='append', dest='instances', - default=[], - help="Create an instance for the cluster. Specify multiple " - "times to create multiple instances.") + default=[]) @utils.service_type('database') def do_cluster_create(cs, args): """Creates a new cluster.""" instances = [] for instance_str in args.instances: instance_info = {} - for z in instance_str.split(","): - for (k, v) in [z.split("=", 1)[:2]]: - if k == "flavor": - flavor_id = _find_flavor(cs, v).id - instance_info["flavorRef"] = str(flavor_id) - elif k == "volume": - instance_info["volume"] = {"size": v} - else: - instance_info[k] = v - if not instance_info.get('flavorRef'): - err_msg = ("flavor is required. %s." % INSTANCE_ERROR) - raise exceptions.ValidationError(err_msg) + + instance_info["flavorRef"] = _get_flavors(cs, instance_str) + instance_info["volume"] = _get_volumes(instance_str) + + nics = _get_networks(instance_str) + if nics: + instance_info["nics"] = nics + + availability_zones = _get_availability_zones(instance_str) + if availability_zones: + instance_info["availability-zone"] = availability_zones + instances.append(instance_info) if len(instances) == 0: