Skip to content

Commit 8b783fd

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "compute: Migrate 'server add/remove security group' to SDK"
2 parents 10b4483 + abef798 commit 8b783fd

4 files changed

Lines changed: 282 additions & 66 deletions

File tree

openstackclient/api/compute_v2.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@
1111
# under the License.
1212
#
1313

14-
"""Compute v2 API Library"""
14+
"""Compute v2 API Library
15+
16+
A collection of wrappers for deprecated Compute v2 APIs that are not
17+
intentionally supported by SDK. Most of these are proxy APIs.
18+
"""
19+
20+
import http
1521

1622
from keystoneauth1 import exceptions as ksa_exceptions
23+
from openstack import exceptions as sdk_exceptions
1724
from osc_lib.api import api
1825
from osc_lib import exceptions
1926
from osc_lib.i18n import _
@@ -577,3 +584,40 @@ def security_group_rule_delete(
577584
return self.delete(f'/{url}/{security_group_rule_id}')
578585

579586
return None
587+
588+
589+
def find_security_group(compute_client, name_or_id):
590+
"""Find the name for a given security group name or ID
591+
592+
https://docs.openstack.org/api-ref/compute/#show-security-group-details
593+
594+
:param compute_client: A compute client
595+
:param name_or_id: The name or ID of the security group to look up
596+
:returns: A security group object
597+
:raises exception.NotFound: If a matching security group could not be
598+
found or more than one match was found
599+
"""
600+
response = compute_client.get(
601+
f'/os-security-groups/{name_or_id}', microversion='2.1'
602+
)
603+
if response.status_code != http.HTTPStatus.NOT_FOUND:
604+
# there might be other, non-404 errors
605+
sdk_exceptions.raise_from_response(response)
606+
return response.json()['security_group']
607+
608+
response = compute_client.get('/os-security-groups', microversion='2.1')
609+
sdk_exceptions.raise_from_response(response)
610+
found = None
611+
security_groups = response.json()['security_groups']
612+
for security_group in security_groups:
613+
if security_group['name'] == name_or_id:
614+
if found:
615+
raise exceptions.NotFound(
616+
f'multiple matches found for {name_or_id}'
617+
)
618+
found = security_group
619+
620+
if not found:
621+
raise exceptions.NotFound(f'{name_or_id} not found')
622+
623+
return found

openstackclient/compute/v2/server.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from osc_lib import exceptions
3333
from osc_lib import utils
3434

35+
from openstackclient.api import compute_v2
3536
from openstackclient.common import pagination
3637
from openstackclient.i18n import _
3738
from openstackclient.identity import common as identity_common
@@ -677,17 +678,21 @@ def get_parser(self, prog_name):
677678
return parser
678679

679680
def take_action(self, parsed_args):
680-
compute_client = self.app.client_manager.compute
681+
compute_client = self.app.client_manager.sdk_connection.compute
681682

682-
server = utils.find_resource(
683-
compute_client.servers,
684-
parsed_args.server,
685-
)
686-
security_group = compute_client.api.security_group_find(
687-
parsed_args.group,
683+
server = compute_client.find_server(
684+
parsed_args.server, ignore_missing=False
688685
)
689-
690-
server.add_security_group(security_group['id'])
686+
if self.app.client_manager.is_network_endpoint_enabled():
687+
# the server handles both names and IDs for neutron SGs, so just
688+
# pass things through
689+
security_group = parsed_args.group
690+
else:
691+
# however, if using nova-network then it needs a name, not an ID
692+
security_group = compute_v2.find_security_group(
693+
compute_client, parsed_args.group
694+
)['name']
695+
compute_client.add_security_group_to_server(server, security_group)
691696

692697

693698
class AddServerVolume(command.ShowOne):
@@ -3960,28 +3965,34 @@ def get_parser(self, prog_name):
39603965
parser.add_argument(
39613966
'server',
39623967
metavar='<server>',
3963-
help=_('Name or ID of server to use'),
3968+
help=_('Server (name or ID)'),
39643969
)
39653970
parser.add_argument(
39663971
'group',
39673972
metavar='<group>',
3968-
help=_('Name or ID of security group to remove from server'),
3973+
help=_('Security group to remove (name or ID)'),
39693974
)
39703975
return parser
39713976

39723977
def take_action(self, parsed_args):
3973-
compute_client = self.app.client_manager.compute
3978+
compute_client = self.app.client_manager.sdk_connection.compute
39743979

3975-
server = utils.find_resource(
3976-
compute_client.servers,
3977-
parsed_args.server,
3980+
server = compute_client.find_server(
3981+
parsed_args.server, ignore_missing=False
39783982
)
3979-
security_group = compute_client.api.security_group_find(
3980-
parsed_args.group,
3983+
if self.app.client_manager.is_network_endpoint_enabled():
3984+
# the server handles both names and IDs for neutron SGs, so just
3985+
# pass things through
3986+
security_group = parsed_args.group
3987+
else:
3988+
# however, if using nova-network then it needs a name, not an ID
3989+
security_group = compute_v2.find_security_group(
3990+
compute_client, parsed_args.group
3991+
)['name']
3992+
compute_client.remove_security_group_from_server(
3993+
server, security_group
39813994
)
39823995

3983-
server.remove_security_group(security_group['id'])
3984-
39853996

39863997
class RemoveServerVolume(command.Command):
39873998
_description = _(

openstackclient/tests/unit/api/test_compute_v2.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@
1313

1414
"""Compute v2 API Library Tests"""
1515

16+
import http
17+
from unittest import mock
18+
import uuid
19+
1620
from keystoneauth1 import session
21+
from openstack.compute.v2 import _proxy
1722
from osc_lib import exceptions as osc_lib_exceptions
1823
from requests_mock.contrib import fixture
1924

2025
from openstackclient.api import compute_v2 as compute
26+
from openstackclient.tests.unit import fakes
2127
from openstackclient.tests.unit import utils
2228

2329

@@ -648,3 +654,112 @@ def test_security_group_rule_delete(self):
648654
ret = self.api.security_group_rule_delete('1')
649655
self.assertEqual(202, ret.status_code)
650656
self.assertEqual("", ret.text)
657+
658+
659+
class TestFindSecurityGroup(utils.TestCase):
660+
661+
def setUp(self):
662+
super().setUp()
663+
664+
self.compute_sdk_client = mock.Mock(_proxy.Proxy)
665+
666+
def test_find_security_group_by_id(self):
667+
sg_id = uuid.uuid4().hex
668+
sg_name = 'name-' + uuid.uuid4().hex
669+
data = {
670+
'security_group': {
671+
'id': sg_id,
672+
'name': sg_name,
673+
'description': 'description-' + uuid.uuid4().hex,
674+
'tenant_id': 'project-id-' + uuid.uuid4().hex,
675+
'rules': [],
676+
}
677+
}
678+
self.compute_sdk_client.get.side_effect = [
679+
fakes.FakeResponse(data=data),
680+
]
681+
682+
result = compute.find_security_group(self.compute_sdk_client, sg_id)
683+
684+
self.compute_sdk_client.get.assert_has_calls(
685+
[
686+
mock.call(f'/os-security-groups/{sg_id}', microversion='2.1'),
687+
]
688+
)
689+
self.assertEqual(data['security_group'], result)
690+
691+
def test_find_security_group_by_name(self):
692+
sg_id = uuid.uuid4().hex
693+
sg_name = 'name-' + uuid.uuid4().hex
694+
data = {
695+
'security_groups': [
696+
{
697+
'id': sg_id,
698+
'name': sg_name,
699+
'description': 'description-' + uuid.uuid4().hex,
700+
'tenant_id': 'project-id-' + uuid.uuid4().hex,
701+
'rules': [],
702+
}
703+
],
704+
}
705+
self.compute_sdk_client.get.side_effect = [
706+
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
707+
fakes.FakeResponse(data=data),
708+
]
709+
710+
result = compute.find_security_group(self.compute_sdk_client, sg_name)
711+
712+
self.compute_sdk_client.get.assert_has_calls(
713+
[
714+
mock.call(
715+
f'/os-security-groups/{sg_name}', microversion='2.1'
716+
),
717+
mock.call('/os-security-groups', microversion='2.1'),
718+
]
719+
)
720+
self.assertEqual(data['security_groups'][0], result)
721+
722+
def test_find_security_group_not_found(self):
723+
data = {'security_groups': []}
724+
self.compute_sdk_client.get.side_effect = [
725+
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
726+
fakes.FakeResponse(data=data),
727+
]
728+
self.assertRaises(
729+
osc_lib_exceptions.NotFound,
730+
compute.find_security_group,
731+
self.compute_sdk_client,
732+
'invalid-sg',
733+
)
734+
735+
def test_find_security_group_by_name_duplicate(self):
736+
sg_name = 'name-' + uuid.uuid4().hex
737+
data = {
738+
'security_groups': [
739+
{
740+
'id': uuid.uuid4().hex,
741+
'name': sg_name,
742+
'description': 'description-' + uuid.uuid4().hex,
743+
'tenant_id': 'project-id-' + uuid.uuid4().hex,
744+
'rules': [],
745+
},
746+
{
747+
'id': uuid.uuid4().hex,
748+
'name': sg_name,
749+
'description': 'description-' + uuid.uuid4().hex,
750+
'tenant_id': 'project-id-' + uuid.uuid4().hex,
751+
'rules': [],
752+
},
753+
],
754+
}
755+
self.compute_sdk_client.get.side_effect = [
756+
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
757+
fakes.FakeResponse(data=data),
758+
]
759+
760+
self.assertRaises(
761+
osc_lib_exceptions.NotFound,
762+
compute.find_security_group,
763+
self.compute_sdk_client,
764+
sg_name,
765+
)

0 commit comments

Comments
 (0)