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
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- The command ``trove create`` is now available to use in
|
||||||
|
the python-openstackclient CLI as ``openstack database instance create``
|
@@ -44,6 +44,7 @@ openstack.database.v1 =
|
|||||||
database_db_list = troveclient.osc.v1.databases:ListDatabases
|
database_db_list = troveclient.osc.v1.databases:ListDatabases
|
||||||
database_flavor_list = troveclient.osc.v1.database_flavors:ListDatabaseFlavors
|
database_flavor_list = troveclient.osc.v1.database_flavors:ListDatabaseFlavors
|
||||||
database_flavor_show = troveclient.osc.v1.database_flavors:ShowDatabaseFlavor
|
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_delete = troveclient.osc.v1.database_instances:DeleteDatabaseInstance
|
||||||
database_instance_list = troveclient.osc.v1.database_instances:ListDatabaseInstances
|
database_instance_list = troveclient.osc.v1.database_instances:ListDatabaseInstances
|
||||||
database_instance_show = troveclient.osc.v1.database_instances:ShowDatabaseInstance
|
database_instance_show = troveclient.osc.v1.database_instances:ShowDatabaseInstance
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
"""Database v1 Instances action implementations"""
|
"""Database v1 Instances action implementations"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
from osc_lib.command import command
|
from osc_lib.command import command
|
||||||
from osc_lib import exceptions
|
from osc_lib import exceptions
|
||||||
from osc_lib import utils as osc_utils
|
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")
|
msg = (_("Failed to delete instance %(instance)s: %(e)s")
|
||||||
% {'instance': parsed_args.instance, 'e': e})
|
% {'instance': parsed_args.instance, 'e': e})
|
||||||
raise exceptions.CommandError(msg)
|
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='<name>',
|
||||||
|
help=_("Name of the instance."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'flavor',
|
||||||
|
metavar='<flavor>',
|
||||||
|
type=str,
|
||||||
|
help=_("A flavor name or ID."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--size',
|
||||||
|
metavar='<size>',
|
||||||
|
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='<volume_type>',
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help=_("Volume type. Optional when volume support is enabled."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--databases',
|
||||||
|
metavar='<database>',
|
||||||
|
nargs="+",
|
||||||
|
default=[],
|
||||||
|
help=_("Optional list of databases."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--users',
|
||||||
|
metavar='<user:password>',
|
||||||
|
nargs="+",
|
||||||
|
default=[],
|
||||||
|
help=_("Optional list of users."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--backup',
|
||||||
|
metavar='<backup>',
|
||||||
|
default=None,
|
||||||
|
help=_("A backup name or ID."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--availability_zone',
|
||||||
|
metavar='<availability_zone>',
|
||||||
|
default=None,
|
||||||
|
help=_("The Zone hint to give to Nova."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--datastore',
|
||||||
|
metavar='<datastore>',
|
||||||
|
default=None,
|
||||||
|
help=_("A datastore name or ID."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--datastore_version',
|
||||||
|
metavar='<datastore_version>',
|
||||||
|
default=None,
|
||||||
|
help=_("A datastore version name or ID."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--nic',
|
||||||
|
metavar='<net-id=<net-uuid>,v4-fixed-ip=<ip-addr>,'
|
||||||
|
'port-id=<port-uuid>>',
|
||||||
|
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='<configuration>',
|
||||||
|
default=None,
|
||||||
|
help=_("ID of the configuration group to attach to the instance."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--replica_of',
|
||||||
|
metavar='<source_instance>',
|
||||||
|
default=None,
|
||||||
|
help=_("ID or name of an existing instance to replicate from."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--replica_count',
|
||||||
|
metavar='<count>',
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help=_("Number of replicas to create (defaults to 1 if "
|
||||||
|
"replica_of specified)."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--module',
|
||||||
|
metavar='<module>',
|
||||||
|
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='<policy>',
|
||||||
|
default=None,
|
||||||
|
choices=['affinity', 'anti-affinity'],
|
||||||
|
help=_("Locality policy to use when creating replicas. Choose "
|
||||||
|
"one of %(choices)s."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--region',
|
||||||
|
metavar='<region>',
|
||||||
|
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)))
|
||||||
|
@@ -193,6 +193,28 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
r = {'instance': self.get_instances()[2]['instances'][0]}
|
r = {'instance': self.get_instances()[2]['instances'][0]}
|
||||||
return (200, {}, r)
|
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):
|
def post_instances(self, body, **kw):
|
||||||
assert_has_keys(
|
assert_has_keys(
|
||||||
body['instance'],
|
body['instance'],
|
||||||
|
@@ -94,6 +94,7 @@ class FakeUsers(object):
|
|||||||
|
|
||||||
class FakeInstances(object):
|
class FakeInstances(object):
|
||||||
fake_instances = (fakes.FakeHTTPClient().get_instances()[2]['instances'])
|
fake_instances = (fakes.FakeHTTPClient().get_instances()[2]['instances'])
|
||||||
|
fake_instance = fakes.FakeHTTPClient().get_instance_create()[2]
|
||||||
|
|
||||||
def get_instances_1234(self):
|
def get_instances_1234(self):
|
||||||
return instances.Instance(None, self.fake_instances[0])
|
return instances.Instance(None, self.fake_instances[0])
|
||||||
@@ -102,6 +103,9 @@ class FakeInstances(object):
|
|||||||
return [instances.Instance(None, fake_instance)
|
return [instances.Instance(None, fake_instance)
|
||||||
for fake_instance in self.fake_instances]
|
for fake_instance in self.fake_instances]
|
||||||
|
|
||||||
|
def get_instance_create(self):
|
||||||
|
return instances.Instance(None, self.fake_instance['instance'])
|
||||||
|
|
||||||
|
|
||||||
class FakeDatabases(object):
|
class FakeDatabases(object):
|
||||||
fake_databases = [{'name': 'fakedb1'}]
|
fake_databases = [{'name': 'fakedb1'}]
|
||||||
|
@@ -109,3 +109,60 @@ class TestDatabaseInstanceDelete(TestInstances):
|
|||||||
self.assertRaises(exceptions.CommandError,
|
self.assertRaises(exceptions.CommandError,
|
||||||
self.cmd.take_action,
|
self.cmd.take_action,
|
||||||
parsed_args)
|
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)
|
||||||
|
Reference in New Issue
Block a user