diff --git a/fuelclient/commands/environment.py b/fuelclient/commands/environment.py index 531686a..89c26bd 100644 --- a/fuelclient/commands/environment.py +++ b/fuelclient/commands/environment.py @@ -193,6 +193,43 @@ class EnvAddNodes(EnvMixIn, base.BaseCommand): r=parsed_args.roles)) +class EnvRemoveNodes(EnvMixIn, base.BaseCommand): + """Removes nodes from an environment.""" + + def get_parser(self, prog_name): + + parser = super(EnvRemoveNodes, self).get_parser(prog_name) + + parser.add_argument('-e', + '--env', + type=int, + required=True, + help='Id of the environment to remove nodes from') + + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-n', + '--nodes', + type=int, + nargs='+', + help='Ids of the nodes to remove.') + + group.add_argument('--nodes-all', + action='store_true', + help='Remove all nodes from environment') + + return parser + + def take_action(self, parsed_args): + nodes = None if parsed_args.nodes_all else parsed_args.nodes + self.client.remove_nodes(environment_id=parsed_args.env, + nodes=nodes) + + msg = 'Nodes were removed from the environment with id={e}\n'.format( + e=parsed_args.env) + + self.app.stdout.write(msg) + + class EnvDeploy(EnvMixIn, base.BaseCommand): """Deploys changes on the specified environment.""" diff --git a/fuelclient/objects/environment.py b/fuelclient/objects/environment.py index c2fe58f..85dad8e 100644 --- a/fuelclient/objects/environment.py +++ b/fuelclient/objects/environment.py @@ -79,7 +79,7 @@ class Environment(BaseObject): self.connection.get_request( "nodes/?cluster_id={0}".format(self.id) ) - ), key=attrgetter) + ), key=attrgetter('id')) def unassign_all(self): nodes = self.get_all_nodes() diff --git a/fuelclient/tests/unit/v2/cli/test_env.py b/fuelclient/tests/unit/v2/cli/test_env.py index ec5d321..9db0e46 100644 --- a/fuelclient/tests/unit/v2/cli/test_env.py +++ b/fuelclient/tests/unit/v2/cli/test_env.py @@ -158,6 +158,22 @@ class TestEnvCommand(test_engine.BaseCLITest): roles=['compute', 'cinder']) + def test_env_remove_nodes_by_id(self): + args = 'env remove nodes -e 42 -n 24 25' + self.exec_command(args) + + self.m_get_client.assert_called_once_with('environment', mock.ANY) + self.m_client.remove_nodes.assert_called_once_with(environment_id=42, + nodes=[24, 25]) + + def test_env_remove_nodes_all(self): + args = 'env remove nodes -e 42 --nodes-all' + self.exec_command(args) + + self.m_get_client.assert_called_once_with('environment', mock.ANY) + self.m_client.remove_nodes.assert_called_once_with(environment_id=42, + nodes=None) + def test_env_update(self): self.m_client._updatable_attributes = \ environment.EnvironmentClient._updatable_attributes diff --git a/fuelclient/tests/unit/v2/lib/test_environment.py b/fuelclient/tests/unit/v2/lib/test_environment.py index 2d7b44b..ef4d007 100644 --- a/fuelclient/tests/unit/v2/lib/test_environment.py +++ b/fuelclient/tests/unit/v2/lib/test_environment.py @@ -212,3 +212,51 @@ class TestEnvFacade(test_api.BaseLibTest): self.client.spawn_vms(env_id) self.assertTrue(matcher.called) + + def test_env_remove_nodes_by_id(self): + nodes = [25, 26] + env_id = 42 + + expected_body = [] + for n in nodes: + expected_body.append({'id': n}) + + expected_uri = self.get_object_uri(self.res_uri, + env_id, '/unassignment/') + + matcher = self.m_request.post(expected_uri, json={}) + + self.client.remove_nodes(env_id, nodes=nodes) + + self.assertTrue(matcher.called) + + for unassignment in matcher.last_request.json(): + # Check whether all unassignments are expected + self.assertIn(unassignment, expected_body) + + def test_env_remove_nodes_all(self): + nodes = [24, 25, 26] + env_id = 42 + + expected_body = [] + for n in nodes: + expected_body.append({'id': n}) + + fake_nodes = [utils.get_fake_node(node_name='node_' + str(n), + node_id=n, + cluster=env_id) for n in nodes] + + expected_uri = self.get_object_uri(self.res_uri, + env_id, '/unassignment/') + matcher_get = self.m_request.get( + '/api/v1/nodes/?cluster_id={}'.format(env_id), + json=fake_nodes + ) + matcher_post = self.m_request.post(expected_uri, json={}) + self.client.remove_nodes(env_id) + self.assertTrue(matcher_get.called) + self.assertTrue(matcher_post.called) + + for unassignment in matcher_post.last_request.json(): + # Check whether all unassignments are expected + self.assertIn(unassignment, expected_body) diff --git a/fuelclient/v1/environment.py b/fuelclient/v1/environment.py index fbf800c..649f0d2 100644 --- a/fuelclient/v1/environment.py +++ b/fuelclient/v1/environment.py @@ -65,6 +65,21 @@ class EnvironmentClient(base_v1.BaseV1Client): env.assign(nodes, roles) + def remove_nodes(self, environment_id, nodes=None): + """Remove nodes from environment. If nodes are empty list then + all nodes will be removed + + :param environment_id: Id of specific environment (cluster) + :type environment_id: int + :param nodes: List of node ids that should be removed + :type nodes: list + """ + env = self._entity_wrapper(obj_id=environment_id) + if nodes is not None: + env.unassign(nodes) + else: + env.unassign_all() + def deploy_changes(self, environment_id, dry_run=False): env = self._entity_wrapper(obj_id=environment_id) diff --git a/setup.cfg b/setup.cfg index b902da1..e825bf2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,6 +35,7 @@ fuelclient = env_deploy=fuelclient.commands.environment:EnvDeploy env_list=fuelclient.commands.environment:EnvList env_redeploy=fuelclient.commands.environment:EnvRedeploy + env_remove_nodes=fuelclient.commands.environment:EnvRemoveNodes env_show=fuelclient.commands.environment:EnvShow env_spawn-vms=fuelclient.commands.environment:EnvSpawnVms env_update=fuelclient.commands.environment:EnvUpdate