Locality support for replication

In order to allow replication sets to be all on the same hypervisor
(affinity) or all on different hypervisors (anti-affinity) a new
argument (locality) needed to be added to the Trove create API.

This changeset addresses the Trove client part of this feature.
A --locality flag is now available on the 'create' command and
is passed to the server for processing.

The --replica_count argument was also cleaned up in that it's not
passed on unless it is set. If --replica_of is specified, the
default is set to '1.'

DocImpact: New functionality

Partially implements: blueprint replication-cluster-locality
Change-Id: I18f242983775526a7f1e2644302ebdc0dac025cf
This commit is contained in:
Peter Stachowski 2016-04-01 21:01:24 +00:00
parent 260bb64c70
commit 358dbf3c80
6 changed files with 101 additions and 16 deletions

View File

@ -0,0 +1,6 @@
---
features:
- A --locality flag was added to the trove create command
to allow a user to specify whether new replicas should
be on the same hypervisor (affinity) or on different
hypervisors (anti-affinity).

View File

@ -595,6 +595,9 @@ class FakeHTTPClient(base_client.HTTPClient):
def get_instances_1234_root(self, **kw):
return (200, {}, {"rootEnabled": 'True'})
def get_instances_master_1(self, **kw):
return (200, {}, {"instance": {"id": 'myid'}})
def get_clusters_cls_1234_root(self, **kw):
return (200, {}, {"rootEnabled": 'True'})

View File

@ -100,7 +100,9 @@ class InstancesTest(testtools.TestCase):
datastore="datastore",
datastore_version="datastore-version",
nics=nics, slave_of='test',
modules=['mod_id'])
replica_count=4,
modules=['mod_id'],
locality='affinity')
self.assertEqual("/instances", p)
self.assertEqual("instance", i)
self.assertEqual(['db1', 'db2'], b["instance"]["databases"])
@ -118,6 +120,8 @@ class InstancesTest(testtools.TestCase):
self.assertNotIn('slave_of', b['instance'])
self.assertTrue(mock_warn.called)
self.assertEqual([{'id': 'mod_id'}], b["instance"]["modules"])
self.assertEqual(4, b["instance"]["replica_count"])
self.assertEqual('affinity', b["instance"]["locality"])
def test_list(self):
page_mock = mock.Mock()

View File

@ -261,8 +261,7 @@ class ShellTest(utils.TestCase):
{'instance': {
'volume': {'size': 1, 'type': 'lvm'},
'flavorRef': 1,
'name': 'test-member-1',
'replica_count': 1
'name': 'test-member-1'
}})
def test_boot_with_modules(self):
@ -274,7 +273,6 @@ class ShellTest(utils.TestCase):
'volume': {'size': 1, 'type': 'lvm'},
'flavorRef': 1,
'name': 'test-member-1',
'replica_count': 1,
'modules': [{'id': '4321'}, {'id': '8765'}]
}})
@ -286,10 +284,67 @@ class ShellTest(utils.TestCase):
{'instance': {
'volume': {'size': 1, 'type': 'lvm'},
'flavorRef': 1,
'name': 'test-member-1',
'name': 'test-member-1'
}})
def test_boot_repl_set(self):
self.run_command('create repl-1 1 --size 1 --locality=anti-affinity '
'--replica_count=4')
self.assert_called_anytime(
'POST', '/instances',
{'instance': {
'volume': {'size': 1, 'type': None},
'flavorRef': 1,
'name': 'repl-1',
'replica_count': 4,
'locality': 'anti-affinity'
}})
def test_boot_replica(self):
self.run_command('create slave-1 1 --size 1 --replica_of=master_1')
self.assert_called_anytime(
'POST', '/instances',
{'instance': {
'volume': {'size': 1, 'type': None},
'flavorRef': 1,
'name': 'slave-1',
'replica_of': 'myid',
'replica_count': 1
}})
def test_boot_replica_count(self):
self.run_command('create slave-1 1 --size 1 --replica_of=master_1 '
'--replica_count=3')
self.assert_called_anytime(
'POST', '/instances',
{'instance': {
'volume': {'size': 1, 'type': None},
'flavorRef': 1,
'name': 'slave-1',
'replica_of': 'myid',
'replica_count': 3
}})
def test_boot_locality(self):
self.run_command('create master-1 1 --size 1 --locality=affinity')
self.assert_called_anytime(
'POST', '/instances',
{'instance': {
'volume': {'size': 1, 'type': None},
'flavorRef': 1,
'name': 'master-1',
'locality': 'affinity'
}})
def test_boot_locality_error(self):
cmd = ('create slave-1 1 --size 1 --locality=affinity '
'--replica_of=master_1')
self.assertRaisesRegexp(
exceptions.ValidationError,
'Cannot specify locality when adding replicas to existing '
'master.',
self.run_command, cmd)
def test_boot_nic_error(self):
cmd = ('create test-member-1 1 --size 1 --volume_type lvm '
'--nic net-id=some-id,port-id=some-id')
@ -307,7 +362,6 @@ class ShellTest(utils.TestCase):
'flavorRef': 1,
'name': 'test-restore-1',
'restorePoint': {'backupRef': 'bk-1234'},
'replica_count': 1
}})
def test_boot_restore_by_name(self):
@ -319,7 +373,6 @@ class ShellTest(utils.TestCase):
'flavorRef': 1,
'name': 'test-restore-1',
'restorePoint': {'backupRef': 'bk-1234'},
'replica_count': 1
}})
def test_cluster_create(self):

View File

@ -89,7 +89,7 @@ class Instances(base.ManagerWithFind):
restorePoint=None, availability_zone=None, datastore=None,
datastore_version=None, nics=None, configuration=None,
replica_of=None, slave_of=None, replica_count=None,
modules=None):
modules=None, locality=None):
"""Create (boot) a new instance."""
body = {"instance": {
@ -129,6 +129,8 @@ class Instances(base.ManagerWithFind):
body["instance"]["replica_count"] = replica_count
if modules:
body["instance"]["modules"] = self._get_module_list(modules)
if locality:
body["instance"]["locality"] = locality
return self._create("/instances", body, "instance")

View File

@ -26,6 +26,7 @@ INSTANCE_ERROR = ("Instance argument(s) must be of the form --instance "
NIC_ERROR = ("Invalid NIC argument: %s. Must specify either net-id or port-id "
"but not both. Please refer to help.")
NO_LOG_FOUND_ERROR = "ERROR: No published '%s' log was found for %s."
LOCALITY_DOMAIN = ['affinity', 'anti-affinity']
try:
import simplejson as json
@ -470,26 +471,42 @@ def do_update(cs, args):
@utils.arg('--replica_count',
metavar='<count>',
type=int,
default=1,
help='Number of replicas to create (defaults to %(default)s).')
default=None,
help='Number of replicas to create (defaults to 1 if replica_of '
'specified).')
@utils.arg('--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.')
@utils.arg('--locality',
metavar='<policy>',
default=None,
choices=LOCALITY_DOMAIN,
help='Locality policy to use when creating replicas. Choose '
'one of %(choices)s.')
@utils.service_type('database')
def do_create(cs, args):
"""Creates a new instance."""
volume = None
replica_of_instance = None
flavor_id = _find_flavor(cs, args.flavor).id
volume = None
if args.size:
volume = {"size": args.size,
"type": args.volume_type}
restore_point = None
if args.backup:
restore_point = {"backupRef": _find_backup(cs, args.backup).id}
replica_of = None
replica_count = args.replica_count
if args.replica_of:
replica_of_instance = _find_instance(cs, args.replica_of)
replica_of = _find_instance(cs, args.replica_of)
replica_count = replica_count or 1
locality = None
if args.locality:
locality = args.locality
if replica_of:
raise exceptions.ValidationError(
'Cannot specify locality when adding replicas to existing '
'master.')
databases = [{'name': value} for value in args.databases]
users = [{'name': n, 'password': p, 'databases': databases} for (n, p) in
[z.split(':')[:2] for z in args.users]]
@ -514,9 +531,9 @@ def do_create(cs, args):
datastore_version=args.datastore_version,
nics=nics,
configuration=args.configuration,
replica_of=replica_of_instance,
replica_count=args.replica_count,
modules=modules)
replica_of=replica_of,
replica_count=replica_count,
modules=modules, locality=locality)
_print_instance(instance)