compute: Allow adding, removing multiple SGs

We also ensure we call neutron rather than the deprecated nova proxy API
in the event that neutron is available.

Change-Id: I8315ea164fd3fa6c1d759f16677bfd6c24c4ef63
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2024-07-18 11:48:43 +01:00
parent 45ac2b62fb
commit ece30e8f70
4 changed files with 108 additions and 37 deletions
openstackclient
compute/v2
tests
functional/common
unit/compute/v2
releasenotes/notes

@ -670,7 +670,7 @@ class AddNetwork(command.Command):
class AddServerSecurityGroup(command.Command): class AddServerSecurityGroup(command.Command):
_description = _("Add security group to server") _description = _("Add security group(s) to server")
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super().get_parser(prog_name) parser = super().get_parser(prog_name)
@ -680,9 +680,13 @@ class AddServerSecurityGroup(command.Command):
help=_('Server (name or ID)'), help=_('Server (name or ID)'),
) )
parser.add_argument( parser.add_argument(
'group', 'security_groups',
metavar='<group>', metavar='<security-group>',
help=_('Security group to add (name or ID)'), nargs='+',
help=_(
'Security group(s) to add to the server (name or ID) '
'(repeat option to add multiple groups)'
),
) )
return parser return parser
@ -694,14 +698,43 @@ class AddServerSecurityGroup(command.Command):
) )
if self.app.client_manager.is_network_endpoint_enabled(): if self.app.client_manager.is_network_endpoint_enabled():
# the server handles both names and IDs for neutron SGs, so just # the server handles both names and IDs for neutron SGs, so just
# pass things through # pass things through if using neutron
security_group = parsed_args.group security_groups = parsed_args.security_groups
else: else:
# however, if using nova-network then it needs a name, not an ID # however, if using nova-network then it needs names, not IDs
security_group = compute_v2.find_security_group( security_groups = []
compute_client, parsed_args.group for security_group in parsed_args.security_groups:
)['name'] security_groups.append(
compute_client.add_security_group_to_server(server, security_group) compute_v2.find_security_group(
compute_client, security_group
)['name']
)
errors = 0
for security_group in security_groups:
try:
compute_client.add_security_group_to_server(
server, security_group
)
except sdk_exceptions.HttpException as e:
errors += 1
LOG.error(
_(
"Failed to add security group with name or ID "
"'%(security_group)s' to server '%(server)s': %(e)s"
),
{
'security_group': security_group,
'server': server.id,
'e': e,
},
)
if errors > 0:
msg = _(
"%(errors)d of %(total)d security groups were not added."
) % {'errors': errors, 'total': len(security_groups)}
raise exceptions.CommandError(msg)
class AddServerVolume(command.ShowOne): class AddServerVolume(command.ShowOne):
@ -1328,6 +1361,7 @@ class CreateServer(command.ShowOne):
metavar='<security-group>', metavar='<security-group>',
action='append', action='append',
default=[], default=[],
dest='security_groups',
help=_( help=_(
'Security group to assign to this server (name or ID) ' 'Security group to assign to this server (name or ID) '
'(repeat option to set multiple groups)' '(repeat option to set multiple groups)'
@ -1948,21 +1982,22 @@ class CreateServer(command.ShowOne):
# 'auto' to maintain legacy behavior if a nic wasn't specified. # 'auto' to maintain legacy behavior if a nic wasn't specified.
networks = 'auto' networks = 'auto'
# Check security group exist and convert ID to name # Check security group(s) exist and convert ID to name
security_groups = [] security_groups = []
if self.app.client_manager.is_network_endpoint_enabled(): if self.app.client_manager.is_network_endpoint_enabled():
network_client = self.app.client_manager.network network_client = self.app.client_manager.network
for each_sg in parsed_args.security_group: for security_group in parsed_args.security_groups:
sg = network_client.find_security_group( sg = network_client.find_security_group(
each_sg, ignore_missing=False security_group, ignore_missing=False
) )
# Use security group ID to avoid multiple security group have # Use security group ID to avoid multiple security group have
# same name in neutron networking backend # same name in neutron networking backend
security_groups.append({'name': sg.id}) security_groups.append({'name': sg.id})
else: else: # nova-network
# Handle nova-network case for security_group in parsed_args.security_groups:
for each_sg in parsed_args.security_group: sg = compute_v2.find_security_group(
sg = compute_v2.find_security_group(compute_client, each_sg) compute_client, security_group
)
security_groups.append({'name': sg['name']}) security_groups.append({'name': sg['name']})
hints = {} hints = {}
@ -4016,9 +4051,13 @@ class RemoveServerSecurityGroup(command.Command):
help=_('Server (name or ID)'), help=_('Server (name or ID)'),
) )
parser.add_argument( parser.add_argument(
'group', 'security_groups',
metavar='<group>', metavar='<security-group>',
help=_('Security group to remove (name or ID)'), nargs='+',
help=_(
'Security group(s) to remove from server (name or ID) '
'(repeat option to remove multiple groups)'
),
) )
return parser return parser
@ -4031,15 +4070,42 @@ class RemoveServerSecurityGroup(command.Command):
if self.app.client_manager.is_network_endpoint_enabled(): if self.app.client_manager.is_network_endpoint_enabled():
# the server handles both names and IDs for neutron SGs, so just # the server handles both names and IDs for neutron SGs, so just
# pass things through # pass things through
security_group = parsed_args.group security_groups = parsed_args.security_groups
else: else:
# however, if using nova-network then it needs a name, not an ID # however, if using nova-network then it needs names, not IDs
security_group = compute_v2.find_security_group( security_groups = []
compute_client, parsed_args.group for security_group in parsed_args.security_groups:
)['name'] security_groups.append(
compute_client.remove_security_group_from_server( compute_v2.find_security_group(
server, security_group compute_client, security_group
) )['name']
)
errors = 0
for security_group in security_groups:
try:
compute_client.remove_security_group_from_server(
server, security_group
)
except sdk_exceptions.HttpException as e:
errors += 1
LOG.error(
_(
"Failed to remove security group with name or ID "
"'%(security_group)s' from server '%(server)s': %(e)s"
),
{
'security_group': security_group,
'server': server.id,
'e': e,
},
)
if errors > 0:
msg = _(
"%(errors)d of %(total)d security groups were not removed."
) % {'errors': errors, 'total': len(security_groups)}
raise exceptions.CommandError(msg)
class RemoveServerVolume(command.Command): class RemoveServerVolume(command.Command):

@ -21,7 +21,7 @@ class HelpTests(base.TestCase):
"""Functional tests for openstackclient help output.""" """Functional tests for openstackclient help output."""
SERVER_COMMANDS = [ SERVER_COMMANDS = [
('server add security group', 'Add security group to server'), ('server add security group', 'Add security group(s) to server'),
('server add volume', 'Add volume to server'), ('server add volume', 'Add volume to server'),
('server backup create', 'Create a server backup image'), ('server backup create', 'Create a server backup image'),
('server create', 'Create a new server'), ('server create', 'Create a new server'),

@ -1166,7 +1166,7 @@ class TestServerAddSecurityGroup(compute_fakes.TestComputev2):
arglist = [self.server.id, 'fake_sg'] arglist = [self.server.id, 'fake_sg']
verifylist = [ verifylist = [
('server', self.server.id), ('server', self.server.id),
('group', 'fake_sg'), ('security_groups', ['fake_sg']),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -1197,7 +1197,7 @@ class TestServerAddSecurityGroup(compute_fakes.TestComputev2):
arglist = [self.server.id, 'fake_sg'] arglist = [self.server.id, 'fake_sg']
verifylist = [ verifylist = [
('server', self.server.id), ('server', self.server.id),
('group', 'fake_sg'), ('security_groups', ['fake_sg']),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -1429,7 +1429,7 @@ class TestServerCreate(TestServer):
('flavor', self.flavor.id), ('flavor', self.flavor.id),
('key_name', 'keyname'), ('key_name', 'keyname'),
('properties', {'Beta': 'b'}), ('properties', {'Beta': 'b'}),
('security_group', [security_group.id]), ('security_groups', [security_group.id]),
('hints', {'a': ['b', 'c']}), ('hints', {'a': ['b', 'c']}),
('server_group', server_group.id), ('server_group', server_group.id),
('config_drive', True), ('config_drive', True),
@ -1499,7 +1499,7 @@ class TestServerCreate(TestServer):
('image', self.image.id), ('image', self.image.id),
('flavor', self.flavor.id), ('flavor', self.flavor.id),
('key_name', 'keyname'), ('key_name', 'keyname'),
('security_group', ['not_exist_sg']), ('security_groups', ['not_exist_sg']),
('server_name', self.server.name), ('server_name', self.server.name),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -1525,7 +1525,7 @@ class TestServerCreate(TestServer):
verifylist = [ verifylist = [
('image', self.image.id), ('image', self.image.id),
('flavor', self.flavor.id), ('flavor', self.flavor.id),
('security_group', [sg_name]), ('security_groups', [sg_name]),
('server_name', self.server.name), ('server_name', self.server.name),
] ]
@ -7416,7 +7416,7 @@ class TestServerRemoveSecurityGroup(TestServer):
arglist = [self.server.id, 'fake_sg'] arglist = [self.server.id, 'fake_sg']
verifylist = [ verifylist = [
('server', self.server.id), ('server', self.server.id),
('group', 'fake_sg'), ('security_groups', ['fake_sg']),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -7447,7 +7447,7 @@ class TestServerRemoveSecurityGroup(TestServer):
arglist = [self.server.id, 'fake_sg'] arglist = [self.server.id, 'fake_sg']
verifylist = [ verifylist = [
('server', self.server.id), ('server', self.server.id),
('group', 'fake_sg'), ('security_groups', ['fake_sg']),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)

@ -0,0 +1,5 @@
---
features:
- |
The ``server add security group`` and ``server remove security group``
commands now accept multiple security groups.