From 81c38d58245d72081361afadfb72af96a52f9c86 Mon Sep 17 00:00:00 2001 From: wangyao Date: Fri, 22 Dec 2017 00:50:23 +0800 Subject: [PATCH] Add create to OSC This change adds database support to the python-openstackclient project for the create command. The trove command create is now: openstack database instance create Change-Id: Icab8761a35a4cbc2f7b5f845eb1da917675eb98d Partially-Implements: blueprint trove-support-in-python-openstackclient --- ...stance-create-to-osc-77484f1c477aa864.yaml | 4 + setup.cfg | 1 + troveclient/osc/v1/database_instances.py | 205 ++++++++++++++++++ troveclient/tests/fakes.py | 22 ++ troveclient/tests/osc/v1/fakes.py | 4 + .../tests/osc/v1/test_database_instances.py | 57 +++++ 6 files changed, 293 insertions(+) create mode 100644 releasenotes/notes/add-instance-create-to-osc-77484f1c477aa864.yaml diff --git a/releasenotes/notes/add-instance-create-to-osc-77484f1c477aa864.yaml b/releasenotes/notes/add-instance-create-to-osc-77484f1c477aa864.yaml new file mode 100644 index 00000000..831196fe --- /dev/null +++ b/releasenotes/notes/add-instance-create-to-osc-77484f1c477aa864.yaml @@ -0,0 +1,4 @@ +--- +features: + - The command ``trove create`` is now available to use in + the python-openstackclient CLI as ``openstack database instance create`` diff --git a/setup.cfg b/setup.cfg index a5c900cd..10e7962c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,6 +44,7 @@ openstack.database.v1 = database_db_list = troveclient.osc.v1.databases:ListDatabases database_flavor_list = troveclient.osc.v1.database_flavors:ListDatabaseFlavors database_flavor_show = troveclient.osc.v1.database_flavors:ShowDatabaseFlavor + database_instance_create = troveclient.osc.v1.database_instances:CreateDatabaseInstance database_instance_delete = troveclient.osc.v1.database_instances:DeleteDatabaseInstance database_instance_list = troveclient.osc.v1.database_instances:ListDatabaseInstances database_instance_show = troveclient.osc.v1.database_instances:ShowDatabaseInstance diff --git a/troveclient/osc/v1/database_instances.py b/troveclient/osc/v1/database_instances.py index d03c13ee..0c362019 100644 --- a/troveclient/osc/v1/database_instances.py +++ b/troveclient/osc/v1/database_instances.py @@ -12,6 +12,7 @@ """Database v1 Instances action implementations""" +import argparse from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils @@ -159,3 +160,207 @@ class DeleteDatabaseInstance(command.Command): msg = (_("Failed to delete instance %(instance)s: %(e)s") % {'instance': parsed_args.instance, 'e': e}) raise exceptions.CommandError(msg) + + +class CreateDatabaseInstance(command.ShowOne): + + _description = _("Creates a new database instance.") + + def get_parser(self, prog_name): + parser = super(CreateDatabaseInstance, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_("Name of the instance."), + ) + parser.add_argument( + 'flavor', + metavar='', + type=str, + help=_("A flavor name or ID."), + ) + parser.add_argument( + '--size', + metavar='', + type=int, + default=None, + help=_("Size of the instance disk volume in GB. " + "Required when volume support is enabled."), + ) + parser.add_argument( + '--volume_type', + metavar='', + type=str, + default=None, + help=_("Volume type. Optional when volume support is enabled."), + ) + parser.add_argument( + '--databases', + metavar='', + nargs="+", + default=[], + help=_("Optional list of databases."), + ) + parser.add_argument( + '--users', + metavar='', + nargs="+", + default=[], + help=_("Optional list of users."), + ) + parser.add_argument( + '--backup', + metavar='', + default=None, + help=_("A backup name or ID."), + ) + parser.add_argument( + '--availability_zone', + metavar='', + default=None, + help=_("The Zone hint to give to Nova."), + ) + parser.add_argument( + '--datastore', + metavar='', + default=None, + help=_("A datastore name or ID."), + ) + parser.add_argument( + '--datastore_version', + metavar='', + default=None, + help=_("A datastore version name or ID."), + ) + parser.add_argument( + '--nic', + metavar=',v4-fixed-ip=,' + 'port-id=>', + action='append', + dest='nics', + default=[], + help=_("Create a NIC on the instance. Specify option multiple " + "times to create multiple NICs. net-id: attach NIC to " + "network with this ID (either port-id or net-id must be " + "specified), v4-fixed-ip: IPv4 fixed address for NIC " + "(optional), port-id: attach NIC to port with this ID " + "(either port-id or net-id must be specified)."), + ) + parser.add_argument( + '--configuration', + metavar='', + default=None, + help=_("ID of the configuration group to attach to the instance."), + ) + parser.add_argument( + '--replica_of', + metavar='', + default=None, + help=_("ID or name of an existing instance to replicate from."), + ) + parser.add_argument( + '--replica_count', + metavar='', + type=int, + default=None, + help=_("Number of replicas to create (defaults to 1 if " + "replica_of specified)."), + ) + parser.add_argument( + '--module', + metavar='', + type=str, + dest='modules', + action='append', + default=[], + help=_("ID or name of the module to apply. Specify multiple " + "times to apply multiple modules."), + ) + parser.add_argument( + '--locality', + metavar='', + default=None, + choices=['affinity', 'anti-affinity'], + help=_("Locality policy to use when creating replicas. Choose " + "one of %(choices)s."), + ) + parser.add_argument( + '--region', + metavar='', + type=str, + default=None, + help=argparse.SUPPRESS, + ) + return parser + + def take_action(self, parsed_args): + database = self.app.client_manager.database + db_instances = database.instances + flavor_id = osc_utils.find_resource(database.flavors, + parsed_args.flavor).id + volume = None + if parsed_args.size is not None and parsed_args.size <= 0: + raise exceptions.ValidationError( + _("Volume size '%s' must be an integer and greater than 0.") + % parsed_args.size) + elif parsed_args.size: + volume = {"size": parsed_args.size, + "type": parsed_args.volume_type} + restore_point = None + if parsed_args.backup: + restore_point = {"backupRef": osc_utils.find_resource( + database.backups, parsed_args.backup).id} + replica_of = None + replica_count = parsed_args.replica_count + if parsed_args.replica_of: + replica_of = osc_utils.find_resource( + db_instances, parsed_args.replica_of) + replica_count = replica_count or 1 + locality = None + if parsed_args.locality: + locality = parsed_args.locality + if replica_of: + raise exceptions.ValidationError( + _('Cannot specify locality when adding replicas ' + 'to existing master.')) + databases = [{'name': value} for value in parsed_args.databases] + users = [{'name': n, 'password': p, 'databases': databases} for (n, p) + in + [z.split(':')[:2] for z in parsed_args.users]] + nics = [] + for nic_str in parsed_args.nics: + nic_info = dict([(k, v) for (k, v) in [z.split("=", 1)[:2] for z in + nic_str.split(",")]]) + # need one or the other, not both, not none (!= ~ XOR) + if not (bool(nic_info.get('net-id')) != bool( + nic_info.get('port-id'))): + raise exceptions.\ + ValidationError(_("Invalid NIC argument: %s. Must specify " + "either net-id or port-id but not both. " + "Please refer to help.") + % (_("nic='%s'") % nic_str)) + nics.append(nic_info) + modules = [] + for module in parsed_args.modules: + modules.append(osc_utils.find_resource(database.modules, + module).id) + instance = db_instances.create(parsed_args.name, + flavor_id, + volume=volume, + databases=databases, + users=users, + restorePoint=restore_point, + availability_zone=(parsed_args. + availability_zone), + datastore=parsed_args.datastore, + datastore_version=(parsed_args. + datastore_version), + nics=nics, + configuration=parsed_args.configuration, + replica_of=replica_of, + replica_count=replica_count, + modules=modules, + locality=locality, + region_name=parsed_args.region) + instance = set_attributes_for_print_detail(instance) + return zip(*sorted(six.iteritems(instance))) diff --git a/troveclient/tests/fakes.py b/troveclient/tests/fakes.py index d79b0c9c..708dedfd 100644 --- a/troveclient/tests/fakes.py +++ b/troveclient/tests/fakes.py @@ -193,6 +193,28 @@ class FakeHTTPClient(base_client.HTTPClient): r = {'instance': self.get_instances()[2]['instances'][0]} return (200, {}, r) + def get_instance_create(self, **kw): + return (200, {}, {"instance": { + "status": "BUILD", + "updated": "2017-12-22T20:02:32", + "name": "test", + "created": "2017-12-22T20:02:32", + "networks": { + "name": "test-net", + "id": "net-id" + }, + "id": "2468", + "volume": { + "size": 1 + }, + "flavor": { + "id": "310" + }, + "datastore": { + "version": "5.6", + "type": "mysql" + }}}) + def post_instances(self, body, **kw): assert_has_keys( body['instance'], diff --git a/troveclient/tests/osc/v1/fakes.py b/troveclient/tests/osc/v1/fakes.py index 4d866f80..77730864 100644 --- a/troveclient/tests/osc/v1/fakes.py +++ b/troveclient/tests/osc/v1/fakes.py @@ -94,6 +94,7 @@ class FakeUsers(object): class FakeInstances(object): fake_instances = (fakes.FakeHTTPClient().get_instances()[2]['instances']) + fake_instance = fakes.FakeHTTPClient().get_instance_create()[2] def get_instances_1234(self): return instances.Instance(None, self.fake_instances[0]) @@ -102,6 +103,9 @@ class FakeInstances(object): return [instances.Instance(None, fake_instance) for fake_instance in self.fake_instances] + def get_instance_create(self): + return instances.Instance(None, self.fake_instance['instance']) + class FakeDatabases(object): fake_databases = [{'name': 'fakedb1'}] diff --git a/troveclient/tests/osc/v1/test_database_instances.py b/troveclient/tests/osc/v1/test_database_instances.py index 2020212e..9cf09f90 100644 --- a/troveclient/tests/osc/v1/test_database_instances.py +++ b/troveclient/tests/osc/v1/test_database_instances.py @@ -109,3 +109,60 @@ class TestDatabaseInstanceDelete(TestInstances): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestDatabaseInstanceCreate(TestInstances): + + values = ('2017-12-22T20:02:32', 'mysql', '5.6', '310', + '2468', 'test', 'test-net', 'net-id', 'BUILD', + '2017-12-22T20:02:32', 1) + columns = ( + 'created', + 'datastore', + 'datastore_version', + 'flavor', + 'id', + 'name', + 'networks', + 'networks_id', + 'status', + 'updated', + 'volume', + ) + + def setUp(self): + super(TestDatabaseInstanceCreate, self).setUp() + self.cmd = database_instances.CreateDatabaseInstance(self.app, None) + self.data = self.fake_instances.get_instance_create() + self.instance_client.create.return_value = self.data + + @mock.patch.object(utils, 'find_resource') + def test_instance_create(self, mock_find): + mock_find.id.side_effect = ['103', 'test', 'mod_id'] + args = ['test-name', '103', + '--size', '1', + '--databases', 'db1', 'db2', + '--users', 'u1:111', 'u2:111', + '--datastore', "datastore", + '--datastore_version', "datastore_version", + '--nic', 'net-id=net1', + '--replica_of', 'test', + '--replica_count', '4', + '--module', 'mod_id'] + verifylist = [ + ('name', 'test-name'), + ('flavor', '103'), + ('size', 1), + ('databases', ['db1', 'db2']), + ('users', ['u1:111', 'u2:111']), + ('datastore', "datastore"), + ('datastore_version', "datastore_version"), + ('nics', ['net-id=net1']), + ('replica_of', 'test'), + ('replica_count', 4), + ('modules', ['mod_id']), + ] + parsed_args = self.check_parser(self.cmd, args, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.values, data)