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:
@@ -270,6 +270,9 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
assert_has_keys(instance, required=['volume', 'flavorRef'])
|
||||
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):
|
||||
return (202, {}, None)
|
||||
|
||||
|
@@ -13,10 +13,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
import six
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
import troveclient.client
|
||||
from troveclient import exceptions
|
||||
import troveclient.shell
|
||||
@@ -26,6 +26,7 @@ import troveclient.v1.shell
|
||||
|
||||
|
||||
class ShellFixture(fixtures.Fixture):
|
||||
|
||||
def setUp(self):
|
||||
super(ShellFixture, self).setUp()
|
||||
self.shell = troveclient.shell.OpenStackTroveShell()
|
||||
@@ -232,6 +233,18 @@ class ShellTest(utils.TestCase):
|
||||
exceptions.CommandError, 'flavor is required',
|
||||
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):
|
||||
self.run_command('datastore-list')
|
||||
self.assert_called('GET', '/datastores')
|
||||
|
@@ -70,6 +70,15 @@ class Clusters(base.ManagerWithFind):
|
||||
resp, body = self.api.client.delete(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):
|
||||
"""Adds a shard to the specified cluster.
|
||||
|
||||
@@ -83,6 +92,24 @@ class Clusters(base.ManagerWithFind):
|
||||
return self.resource_class(self, body, loaded=True)
|
||||
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):
|
||||
|
||||
|
@@ -253,6 +253,58 @@ def do_cluster_instances(cs, args):
|
||||
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>',
|
||||
help='ID or name of the instance.')
|
||||
@utils.service_type('database')
|
||||
|
Reference in New Issue
Block a user