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:
@@ -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).
|
@@ -595,6 +595,9 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
def get_instances_1234_root(self, **kw):
|
def get_instances_1234_root(self, **kw):
|
||||||
return (200, {}, {"rootEnabled": 'True'})
|
return (200, {}, {"rootEnabled": 'True'})
|
||||||
|
|
||||||
|
def get_instances_master_1(self, **kw):
|
||||||
|
return (200, {}, {"instance": {"id": 'myid'}})
|
||||||
|
|
||||||
def get_clusters_cls_1234_root(self, **kw):
|
def get_clusters_cls_1234_root(self, **kw):
|
||||||
return (200, {}, {"rootEnabled": 'True'})
|
return (200, {}, {"rootEnabled": 'True'})
|
||||||
|
|
||||||
|
@@ -100,7 +100,9 @@ class InstancesTest(testtools.TestCase):
|
|||||||
datastore="datastore",
|
datastore="datastore",
|
||||||
datastore_version="datastore-version",
|
datastore_version="datastore-version",
|
||||||
nics=nics, slave_of='test',
|
nics=nics, slave_of='test',
|
||||||
modules=['mod_id'])
|
replica_count=4,
|
||||||
|
modules=['mod_id'],
|
||||||
|
locality='affinity')
|
||||||
self.assertEqual("/instances", p)
|
self.assertEqual("/instances", p)
|
||||||
self.assertEqual("instance", i)
|
self.assertEqual("instance", i)
|
||||||
self.assertEqual(['db1', 'db2'], b["instance"]["databases"])
|
self.assertEqual(['db1', 'db2'], b["instance"]["databases"])
|
||||||
@@ -118,6 +120,8 @@ class InstancesTest(testtools.TestCase):
|
|||||||
self.assertNotIn('slave_of', b['instance'])
|
self.assertNotIn('slave_of', b['instance'])
|
||||||
self.assertTrue(mock_warn.called)
|
self.assertTrue(mock_warn.called)
|
||||||
self.assertEqual([{'id': 'mod_id'}], b["instance"]["modules"])
|
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):
|
def test_list(self):
|
||||||
page_mock = mock.Mock()
|
page_mock = mock.Mock()
|
||||||
|
@@ -261,8 +261,7 @@ class ShellTest(utils.TestCase):
|
|||||||
{'instance': {
|
{'instance': {
|
||||||
'volume': {'size': 1, 'type': 'lvm'},
|
'volume': {'size': 1, 'type': 'lvm'},
|
||||||
'flavorRef': 1,
|
'flavorRef': 1,
|
||||||
'name': 'test-member-1',
|
'name': 'test-member-1'
|
||||||
'replica_count': 1
|
|
||||||
}})
|
}})
|
||||||
|
|
||||||
def test_boot_with_modules(self):
|
def test_boot_with_modules(self):
|
||||||
@@ -274,7 +273,6 @@ class ShellTest(utils.TestCase):
|
|||||||
'volume': {'size': 1, 'type': 'lvm'},
|
'volume': {'size': 1, 'type': 'lvm'},
|
||||||
'flavorRef': 1,
|
'flavorRef': 1,
|
||||||
'name': 'test-member-1',
|
'name': 'test-member-1',
|
||||||
'replica_count': 1,
|
|
||||||
'modules': [{'id': '4321'}, {'id': '8765'}]
|
'modules': [{'id': '4321'}, {'id': '8765'}]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
@@ -286,10 +284,67 @@ class ShellTest(utils.TestCase):
|
|||||||
{'instance': {
|
{'instance': {
|
||||||
'volume': {'size': 1, 'type': 'lvm'},
|
'volume': {'size': 1, 'type': 'lvm'},
|
||||||
'flavorRef': 1,
|
'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
|
'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):
|
def test_boot_nic_error(self):
|
||||||
cmd = ('create test-member-1 1 --size 1 --volume_type lvm '
|
cmd = ('create test-member-1 1 --size 1 --volume_type lvm '
|
||||||
'--nic net-id=some-id,port-id=some-id')
|
'--nic net-id=some-id,port-id=some-id')
|
||||||
@@ -307,7 +362,6 @@ class ShellTest(utils.TestCase):
|
|||||||
'flavorRef': 1,
|
'flavorRef': 1,
|
||||||
'name': 'test-restore-1',
|
'name': 'test-restore-1',
|
||||||
'restorePoint': {'backupRef': 'bk-1234'},
|
'restorePoint': {'backupRef': 'bk-1234'},
|
||||||
'replica_count': 1
|
|
||||||
}})
|
}})
|
||||||
|
|
||||||
def test_boot_restore_by_name(self):
|
def test_boot_restore_by_name(self):
|
||||||
@@ -319,7 +373,6 @@ class ShellTest(utils.TestCase):
|
|||||||
'flavorRef': 1,
|
'flavorRef': 1,
|
||||||
'name': 'test-restore-1',
|
'name': 'test-restore-1',
|
||||||
'restorePoint': {'backupRef': 'bk-1234'},
|
'restorePoint': {'backupRef': 'bk-1234'},
|
||||||
'replica_count': 1
|
|
||||||
}})
|
}})
|
||||||
|
|
||||||
def test_cluster_create(self):
|
def test_cluster_create(self):
|
||||||
|
@@ -89,7 +89,7 @@ class Instances(base.ManagerWithFind):
|
|||||||
restorePoint=None, availability_zone=None, datastore=None,
|
restorePoint=None, availability_zone=None, datastore=None,
|
||||||
datastore_version=None, nics=None, configuration=None,
|
datastore_version=None, nics=None, configuration=None,
|
||||||
replica_of=None, slave_of=None, replica_count=None,
|
replica_of=None, slave_of=None, replica_count=None,
|
||||||
modules=None):
|
modules=None, locality=None):
|
||||||
"""Create (boot) a new instance."""
|
"""Create (boot) a new instance."""
|
||||||
|
|
||||||
body = {"instance": {
|
body = {"instance": {
|
||||||
@@ -129,6 +129,8 @@ class Instances(base.ManagerWithFind):
|
|||||||
body["instance"]["replica_count"] = replica_count
|
body["instance"]["replica_count"] = replica_count
|
||||||
if modules:
|
if modules:
|
||||||
body["instance"]["modules"] = self._get_module_list(modules)
|
body["instance"]["modules"] = self._get_module_list(modules)
|
||||||
|
if locality:
|
||||||
|
body["instance"]["locality"] = locality
|
||||||
|
|
||||||
return self._create("/instances", body, "instance")
|
return self._create("/instances", body, "instance")
|
||||||
|
|
||||||
|
@@ -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 "
|
NIC_ERROR = ("Invalid NIC argument: %s. Must specify either net-id or port-id "
|
||||||
"but not both. Please refer to help.")
|
"but not both. Please refer to help.")
|
||||||
NO_LOG_FOUND_ERROR = "ERROR: No published '%s' log was found for %s."
|
NO_LOG_FOUND_ERROR = "ERROR: No published '%s' log was found for %s."
|
||||||
|
LOCALITY_DOMAIN = ['affinity', 'anti-affinity']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
@@ -470,26 +471,42 @@ def do_update(cs, args):
|
|||||||
@utils.arg('--replica_count',
|
@utils.arg('--replica_count',
|
||||||
metavar='<count>',
|
metavar='<count>',
|
||||||
type=int,
|
type=int,
|
||||||
default=1,
|
default=None,
|
||||||
help='Number of replicas to create (defaults to %(default)s).')
|
help='Number of replicas to create (defaults to 1 if replica_of '
|
||||||
|
'specified).')
|
||||||
@utils.arg('--module', metavar='<module>',
|
@utils.arg('--module', metavar='<module>',
|
||||||
type=str, dest='modules', action='append', default=[],
|
type=str, dest='modules', action='append', default=[],
|
||||||
help='ID or name of the module to apply. Specify multiple '
|
help='ID or name of the module to apply. Specify multiple '
|
||||||
'times to apply multiple modules.')
|
'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')
|
@utils.service_type('database')
|
||||||
def do_create(cs, args):
|
def do_create(cs, args):
|
||||||
"""Creates a new instance."""
|
"""Creates a new instance."""
|
||||||
volume = None
|
|
||||||
replica_of_instance = None
|
|
||||||
flavor_id = _find_flavor(cs, args.flavor).id
|
flavor_id = _find_flavor(cs, args.flavor).id
|
||||||
|
volume = None
|
||||||
if args.size:
|
if args.size:
|
||||||
volume = {"size": args.size,
|
volume = {"size": args.size,
|
||||||
"type": args.volume_type}
|
"type": args.volume_type}
|
||||||
restore_point = None
|
restore_point = None
|
||||||
if args.backup:
|
if args.backup:
|
||||||
restore_point = {"backupRef": _find_backup(cs, args.backup).id}
|
restore_point = {"backupRef": _find_backup(cs, args.backup).id}
|
||||||
|
replica_of = None
|
||||||
|
replica_count = args.replica_count
|
||||||
if args.replica_of:
|
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]
|
databases = [{'name': value} for value in args.databases]
|
||||||
users = [{'name': n, 'password': p, 'databases': databases} for (n, p) in
|
users = [{'name': n, 'password': p, 'databases': databases} for (n, p) in
|
||||||
[z.split(':')[:2] for z in args.users]]
|
[z.split(':')[:2] for z in args.users]]
|
||||||
@@ -514,9 +531,9 @@ def do_create(cs, args):
|
|||||||
datastore_version=args.datastore_version,
|
datastore_version=args.datastore_version,
|
||||||
nics=nics,
|
nics=nics,
|
||||||
configuration=args.configuration,
|
configuration=args.configuration,
|
||||||
replica_of=replica_of_instance,
|
replica_of=replica_of,
|
||||||
replica_count=args.replica_count,
|
replica_count=replica_count,
|
||||||
modules=modules)
|
modules=modules, locality=locality)
|
||||||
_print_instance(instance)
|
_print_instance(instance)
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user