Redis Clustering Initial Implementation

Implements the CLI and python-API methods to support the
clustering commands, including the new cluster scaling
commands.

Change-Id: Icc9b4eea8ed7db1455692823d29586088cfc9434
Implements: blueprint redis-cluster
This commit is contained in:
Morgan Jones
2015-07-20 11:33:17 -04:00
parent 7ec45dba07
commit d95cefff2a
4 changed files with 97 additions and 2 deletions

View File

@@ -270,6 +270,9 @@ class FakeHTTPClient(base_client.HTTPClient):
assert_has_keys(instance, required=['volume', 'flavorRef']) assert_has_keys(instance, required=['volume', 'flavorRef'])
return (202, {}, self.get_clusters_cls_1234()[2]) return (202, {}, self.get_clusters_cls_1234()[2])
def post_clusters_cls_1234(self, body, **kw):
return (202, {}, None)
def post_instances_1234_action(self, **kw): def post_instances_1234_action(self, **kw):
return (202, {}, None) return (202, {}, None)

View File

@@ -13,10 +13,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import fixtures
import mock
import six import six
import fixtures
import mock
import troveclient.client import troveclient.client
from troveclient import exceptions from troveclient import exceptions
import troveclient.shell import troveclient.shell
@@ -26,6 +26,7 @@ import troveclient.v1.shell
class ShellFixture(fixtures.Fixture): class ShellFixture(fixtures.Fixture):
def setUp(self): def setUp(self):
super(ShellFixture, self).setUp() super(ShellFixture, self).setUp()
self.shell = troveclient.shell.OpenStackTroveShell() self.shell = troveclient.shell.OpenStackTroveShell()
@@ -232,6 +233,18 @@ class ShellTest(utils.TestCase):
exceptions.CommandError, 'flavor is required', exceptions.CommandError, 'flavor is required',
self.run_command, cmd) self.run_command, cmd)
def test_cluster_grow(self):
cmd = ('cluster-grow cls-1234 '
'--instance flavor=2,volume=2 '
'--instance flavor=2,volume=1')
self.run_command(cmd)
self.assert_called('POST', '/clusters/cls-1234')
def test_cluster_shrink(self):
cmd = ('cluster-shrink cls-1234 1234')
self.run_command(cmd)
self.assert_called('POST', '/clusters/cls-1234')
def test_datastore_list(self): def test_datastore_list(self):
self.run_command('datastore-list') self.run_command('datastore-list')
self.assert_called('GET', '/datastores') self.assert_called('GET', '/datastores')

View File

@@ -70,6 +70,15 @@ class Clusters(base.ManagerWithFind):
resp, body = self.api.client.delete(url) resp, body = self.api.client.delete(url)
common.check_for_exceptions(resp, body, url) common.check_for_exceptions(resp, body, url)
def _action(self, cluster, body):
"""Perform a cluster "action" -- grow/shrink/etc."""
url = "/clusters/%s" % base.getid(cluster)
resp, body = self.api.client.post(url, body=body)
common.check_for_exceptions(resp, body, url)
if body:
return self.resource_class(self, body['cluster'], loaded=True)
return body
def add_shard(self, cluster): def add_shard(self, cluster):
"""Adds a shard to the specified cluster. """Adds a shard to the specified cluster.
@@ -83,6 +92,24 @@ class Clusters(base.ManagerWithFind):
return self.resource_class(self, body, loaded=True) return self.resource_class(self, body, loaded=True)
return body return body
def grow(self, cluster, instances=None):
"""Grow a cluster.
:param cluster: The cluster to grow
:param instances: List of instances to add
"""
body = {"grow": instances}
return self._action(cluster, body)
def shrink(self, cluster, instances=None):
"""Shrink a cluster.
:param cluster: The cluster to shrink
:param instances: List of instances to drop
"""
body = {"shrink": instances}
return self._action(cluster, body)
class ClusterStatus(object): class ClusterStatus(object):

View File

@@ -253,6 +253,58 @@ def do_cluster_instances(cs, args):
obj_is_dict=True) obj_is_dict=True)
@utils.arg('--instance',
metavar="<name=name,flavor=flavor_name_or_id,volume=volume>",
action='append',
dest='instances',
default=[],
help="Add an instance to the cluster. Specify "
"multiple times to create multiple instances.")
@utils.arg('cluster', metavar='<cluster>', help='ID or name of the cluster.')
@utils.service_type('database')
def do_cluster_grow(cs, args):
"""Adds more instances to a cluster."""
cluster = _find_cluster(cs, args.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 == "name":
instance_info[k] = v
elif 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'):
raise exceptions.CommandError(
'flavor is required. '
'Instance arguments must be of the form '
'--instance <flavor=flavor_name_or_id,volume=volume,data=data>'
)
instances.append(instance_info)
cs.clusters.grow(cluster, instances=instances)
@utils.arg('cluster', metavar='<cluster>', help='ID or name of the cluster.')
@utils.arg('instances',
nargs='+',
metavar='<instance>',
default=[],
help="Drop instance(s) from the cluster. Specify "
"multiple ids to drop multiple instances.")
@utils.service_type('database')
def do_cluster_shrink(cs, args):
"""Drops instances from a cluster."""
cluster = _find_cluster(cs, args.cluster)
instances = [{'id': _find_instance(cs, instance).id}
for instance in args.instances]
cs.clusters.shrink(cluster, instances=instances)
@utils.arg('instance', metavar='<instance>', @utils.arg('instance', metavar='<instance>',
help='ID or name of the instance.') help='ID or name of the instance.')
@utils.service_type('database') @utils.service_type('database')