diff --git a/troveclient/tests/fakes.py b/troveclient/tests/fakes.py index a2791059..90345312 100644 --- a/troveclient/tests/fakes.py +++ b/troveclient/tests/fakes.py @@ -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) diff --git a/troveclient/tests/test_v1_shell.py b/troveclient/tests/test_v1_shell.py index 72a52a0b..9ebcd5bb 100644 --- a/troveclient/tests/test_v1_shell.py +++ b/troveclient/tests/test_v1_shell.py @@ -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') diff --git a/troveclient/v1/clusters.py b/troveclient/v1/clusters.py index dc9c2f05..bf207221 100644 --- a/troveclient/v1/clusters.py +++ b/troveclient/v1/clusters.py @@ -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): diff --git a/troveclient/v1/shell.py b/troveclient/v1/shell.py index 3dfa5f77..38b110d5 100644 --- a/troveclient/v1/shell.py +++ b/troveclient/v1/shell.py @@ -253,6 +253,58 @@ def do_cluster_instances(cs, args): obj_is_dict=True) +@utils.arg('--instance', + metavar="", + action='append', + dest='instances', + default=[], + help="Add an instance to the cluster. Specify " + "multiple times to create multiple instances.") +@utils.arg('cluster', metavar='', 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 ' + ) + instances.append(instance_info) + cs.clusters.grow(cluster, instances=instances) + + +@utils.arg('cluster', metavar='', help='ID or name of the cluster.') +@utils.arg('instances', + nargs='+', + metavar='', + 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='', help='ID or name of the instance.') @utils.service_type('database')