Skip to content

Commit 1a6df70

Browse files
committed
compute: Add 'server * --all-projects' option
Add an '--all-projects' option to a number of commands: - server delete - server start - server stop This is in addition to 'server list', which already supports this option. This option allows users to request the corresponding action on one or more servers using the server names when that server exists in another project. This is admin-only by default. As part of this work, we also introduce a 'boolenv' helper function that allows us to parse the environment variable as a boolean using 'bool_from_string' helper provided by oslo.utils. This could probably be clever and it has the unfortunate side effect of modifying the help text in environments where this is configured, but it's good enough for now. It also appears to add a new dependency, in the form of oslo.utils, but that dependency was already required by osc-lib and probably more. Change-Id: I4811f8f66dcb14ed99cc1cfb80b00e2d77afe45f Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
1 parent bfa032c commit 1a6df70

4 files changed

Lines changed: 136 additions & 8 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from osc_lib.command import command
3232
from osc_lib import exceptions
3333
from osc_lib import utils
34+
from oslo_utils import strutils
3435

3536
from openstackclient.i18n import _
3637
from openstackclient.identity import common as identity_common
@@ -193,6 +194,24 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
193194
return info
194195

195196

197+
def boolenv(*vars, default=False):
198+
"""Search for the first defined of possibly many bool-like env vars.
199+
200+
Returns the first environment variable defined in vars, or returns the
201+
default.
202+
203+
:param vars: Arbitrary strings to search for. Case sensitive.
204+
:param default: The default to return if no value found.
205+
:returns: A boolean corresponding to the value found, else the default if
206+
no value found.
207+
"""
208+
for v in vars:
209+
value = os.environ.get(v, None)
210+
if value:
211+
return strutils.bool_from_string(value)
212+
return default
213+
214+
196215
class AddFixedIP(command.Command):
197216
_description = _("Add fixed IP address to server")
198217

@@ -1322,6 +1341,15 @@ def get_parser(self, prog_name):
13221341
action='store_true',
13231342
help=_('Force delete server(s)'),
13241343
)
1344+
parser.add_argument(
1345+
'--all-projects',
1346+
action='store_true',
1347+
default=boolenv('ALL_PROJECTS'),
1348+
help=_(
1349+
'Delete server(s) in another project by name (admin only)'
1350+
'(can be specified using the ALL_PROJECTS envvar)'
1351+
),
1352+
)
13251353
parser.add_argument(
13261354
'--wait',
13271355
action='store_true',
@@ -1339,19 +1367,22 @@ def _show_progress(progress):
13391367
compute_client = self.app.client_manager.compute
13401368
for server in parsed_args.server:
13411369
server_obj = utils.find_resource(
1342-
compute_client.servers, server)
1370+
compute_client.servers, server,
1371+
all_tenants=parsed_args.all_projects)
13431372

13441373
if parsed_args.force:
13451374
compute_client.servers.force_delete(server_obj.id)
13461375
else:
13471376
compute_client.servers.delete(server_obj.id)
13481377

13491378
if parsed_args.wait:
1350-
if not utils.wait_for_delete(compute_client.servers,
1351-
server_obj.id,
1352-
callback=_show_progress):
1353-
LOG.error(_('Error deleting server: %s'),
1354-
server_obj.id)
1379+
if not utils.wait_for_delete(
1380+
compute_client.servers,
1381+
server_obj.id,
1382+
callback=_show_progress,
1383+
):
1384+
msg = _('Error deleting server: %s')
1385+
LOG.error(msg, server_obj.id)
13551386
self.app.stdout.write(_('Error deleting server\n'))
13561387
raise SystemExit
13571388

@@ -1446,8 +1477,11 @@ def get_parser(self, prog_name):
14461477
parser.add_argument(
14471478
'--all-projects',
14481479
action='store_true',
1449-
default=bool(int(os.environ.get("ALL_PROJECTS", 0))),
1450-
help=_('Include all projects (admin only)'),
1480+
default=boolenv('ALL_PROJECTS'),
1481+
help=_(
1482+
'Include all projects (admin only) '
1483+
'(can be specified using the ALL_PROJECTS envvar)'
1484+
),
14511485
)
14521486
parser.add_argument(
14531487
'--project',
@@ -3939,6 +3973,15 @@ def get_parser(self, prog_name):
39393973
nargs="+",
39403974
help=_('Server(s) to start (name or ID)'),
39413975
)
3976+
parser.add_argument(
3977+
'--all-projects',
3978+
action='store_true',
3979+
default=boolenv('ALL_PROJECTS'),
3980+
help=_(
3981+
'Start server(s) in another project by name (admin only)'
3982+
'(can be specified using the ALL_PROJECTS envvar)'
3983+
),
3984+
)
39423985
return parser
39433986

39443987
def take_action(self, parsed_args):
@@ -3947,6 +3990,7 @@ def take_action(self, parsed_args):
39473990
utils.find_resource(
39483991
compute_client.servers,
39493992
server,
3993+
all_tenants=parsed_args.all_projects,
39503994
).start()
39513995

39523996

@@ -3961,6 +4005,15 @@ def get_parser(self, prog_name):
39614005
nargs="+",
39624006
help=_('Server(s) to stop (name or ID)'),
39634007
)
4008+
parser.add_argument(
4009+
'--all-projects',
4010+
action='store_true',
4011+
default=boolenv('ALL_PROJECTS'),
4012+
help=_(
4013+
'Stop server(s) in another project by name (admin only)'
4014+
'(can be specified using the ALL_PROJECTS envvar)'
4015+
),
4016+
)
39644017
return parser
39654018

39664019
def take_action(self, parsed_args):
@@ -3969,6 +4022,7 @@ def take_action(self, parsed_args):
39694022
utils.find_resource(
39704023
compute_client.servers,
39714024
server,
4025+
all_tenants=parsed_args.all_projects,
39724026
).stop()
39734027

39744028

openstackclient/tests/unit/compute/v2/test_server.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2913,6 +2913,28 @@ def test_server_delete_multi_servers(self):
29132913
self.servers_mock.delete.assert_has_calls(calls)
29142914
self.assertIsNone(result)
29152915

2916+
@mock.patch.object(common_utils, 'find_resource')
2917+
def test_server_delete_with_all_projects(self, mock_find_resource):
2918+
servers = self.setup_servers_mock(count=1)
2919+
mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers(
2920+
servers, 0,
2921+
)
2922+
2923+
arglist = [
2924+
servers[0].id,
2925+
'--all-projects',
2926+
]
2927+
verifylist = [
2928+
('server', [servers[0].id]),
2929+
]
2930+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2931+
2932+
self.cmd.take_action(parsed_args)
2933+
2934+
mock_find_resource.assert_called_once_with(
2935+
mock.ANY, servers[0].id, all_tenants=True,
2936+
)
2937+
29162938
@mock.patch.object(common_utils, 'wait_for_delete', return_value=True)
29172939
def test_server_delete_wait_ok(self, mock_wait_for_delete):
29182940
servers = self.setup_servers_mock(count=1)
@@ -6781,6 +6803,28 @@ def test_server_start_one_server(self):
67816803
def test_server_start_multi_servers(self):
67826804
self.run_method_with_servers('start', 3)
67836805

6806+
@mock.patch.object(common_utils, 'find_resource')
6807+
def test_server_start_with_all_projects(self, mock_find_resource):
6808+
servers = self.setup_servers_mock(count=1)
6809+
mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers(
6810+
servers, 0,
6811+
)
6812+
6813+
arglist = [
6814+
servers[0].id,
6815+
'--all-projects',
6816+
]
6817+
verifylist = [
6818+
('server', [servers[0].id]),
6819+
]
6820+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
6821+
6822+
self.cmd.take_action(parsed_args)
6823+
6824+
mock_find_resource.assert_called_once_with(
6825+
mock.ANY, servers[0].id, all_tenants=True,
6826+
)
6827+
67846828

67856829
class TestServerStop(TestServer):
67866830

@@ -6801,6 +6845,28 @@ def test_server_stop_one_server(self):
68016845
def test_server_stop_multi_servers(self):
68026846
self.run_method_with_servers('stop', 3)
68036847

6848+
@mock.patch.object(common_utils, 'find_resource')
6849+
def test_server_start_with_all_projects(self, mock_find_resource):
6850+
servers = self.setup_servers_mock(count=1)
6851+
mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers(
6852+
servers, 0,
6853+
)
6854+
6855+
arglist = [
6856+
servers[0].id,
6857+
'--all-projects',
6858+
]
6859+
verifylist = [
6860+
('server', [servers[0].id]),
6861+
]
6862+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
6863+
6864+
self.cmd.take_action(parsed_args)
6865+
6866+
mock_find_resource.assert_called_once_with(
6867+
mock.ANY, servers[0].id, all_tenants=True,
6868+
)
6869+
68046870

68056871
class TestServerSuspend(TestServer):
68066872

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
features:
3+
- |
4+
The ``server delete``, ``server start`` and ``server stop`` commands now
5+
support the ``--all-projects`` option. This allows you to perform the
6+
specified action on a server in another project using the server name.
7+
This is an admin-only action by default.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ iso8601>=0.1.11 # MIT
88
openstacksdk>=0.52.0 # Apache-2.0
99
osc-lib>=2.3.0 # Apache-2.0
1010
oslo.i18n>=3.15.3 # Apache-2.0
11+
oslo.utils>=3.33.0 # Apache-2.0
1112
python-keystoneclient>=3.22.0 # Apache-2.0
1213
python-novaclient>=17.0.0 # Apache-2.0
1314
python-cinderclient>=3.3.0 # Apache-2.0

0 commit comments

Comments
 (0)