compute: Migrate 'server add/remove security group' to SDK
We need to work around SDK's (intentional) lack of support for Nova's deprecated '/os-security-groups' API but it's nothing too crazy. Change-Id: I03ca1a73f98aa77b288148607baff336dae69fb1 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
9d39437282
commit
abef798f30
@ -11,9 +11,16 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Compute v2 API Library"""
|
||||
"""Compute v2 API Library
|
||||
|
||||
A collection of wrappers for deprecated Compute v2 APIs that are not
|
||||
intentionally supported by SDK. Most of these are proxy APIs.
|
||||
"""
|
||||
|
||||
import http
|
||||
|
||||
from keystoneauth1 import exceptions as ksa_exceptions
|
||||
from openstack import exceptions as sdk_exceptions
|
||||
from osc_lib.api import api
|
||||
from osc_lib import exceptions
|
||||
from osc_lib.i18n import _
|
||||
@ -577,3 +584,40 @@ class APIv2(api.BaseAPI):
|
||||
return self.delete(f'/{url}/{security_group_rule_id}')
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def find_security_group(compute_client, name_or_id):
|
||||
"""Find the name for a given security group name or ID
|
||||
|
||||
https://docs.openstack.org/api-ref/compute/#show-security-group-details
|
||||
|
||||
:param compute_client: A compute client
|
||||
:param name_or_id: The name or ID of the security group to look up
|
||||
:returns: A security group object
|
||||
:raises exception.NotFound: If a matching security group could not be
|
||||
found or more than one match was found
|
||||
"""
|
||||
response = compute_client.get(
|
||||
f'/os-security-groups/{name_or_id}', microversion='2.1'
|
||||
)
|
||||
if response.status_code != http.HTTPStatus.NOT_FOUND:
|
||||
# there might be other, non-404 errors
|
||||
sdk_exceptions.raise_from_response(response)
|
||||
return response.json()['security_group']
|
||||
|
||||
response = compute_client.get('/os-security-groups', microversion='2.1')
|
||||
sdk_exceptions.raise_from_response(response)
|
||||
found = None
|
||||
security_groups = response.json()['security_groups']
|
||||
for security_group in security_groups:
|
||||
if security_group['name'] == name_or_id:
|
||||
if found:
|
||||
raise exceptions.NotFound(
|
||||
f'multiple matches found for {name_or_id}'
|
||||
)
|
||||
found = security_group
|
||||
|
||||
if not found:
|
||||
raise exceptions.NotFound(f'{name_or_id} not found')
|
||||
|
||||
return found
|
||||
|
@ -32,6 +32,7 @@ from osc_lib.command import command
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.api import compute_v2
|
||||
from openstackclient.common import pagination
|
||||
from openstackclient.i18n import _
|
||||
from openstackclient.identity import common as identity_common
|
||||
@ -677,17 +678,21 @@ class AddServerSecurityGroup(command.Command):
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
compute_client = self.app.client_manager.compute
|
||||
compute_client = self.app.client_manager.sdk_connection.compute
|
||||
|
||||
server = utils.find_resource(
|
||||
compute_client.servers,
|
||||
parsed_args.server,
|
||||
server = compute_client.find_server(
|
||||
parsed_args.server, ignore_missing=False
|
||||
)
|
||||
security_group = compute_client.api.security_group_find(
|
||||
parsed_args.group,
|
||||
)
|
||||
|
||||
server.add_security_group(security_group['id'])
|
||||
if self.app.client_manager.is_network_endpoint_enabled():
|
||||
# the server handles both names and IDs for neutron SGs, so just
|
||||
# pass things through
|
||||
security_group = parsed_args.group
|
||||
else:
|
||||
# however, if using nova-network then it needs a name, not an ID
|
||||
security_group = compute_v2.find_security_group(
|
||||
compute_client, parsed_args.group
|
||||
)['name']
|
||||
compute_client.add_security_group_to_server(server, security_group)
|
||||
|
||||
|
||||
class AddServerVolume(command.ShowOne):
|
||||
@ -3960,28 +3965,34 @@ class RemoveServerSecurityGroup(command.Command):
|
||||
parser.add_argument(
|
||||
'server',
|
||||
metavar='<server>',
|
||||
help=_('Name or ID of server to use'),
|
||||
help=_('Server (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'group',
|
||||
metavar='<group>',
|
||||
help=_('Name or ID of security group to remove from server'),
|
||||
help=_('Security group to remove (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
compute_client = self.app.client_manager.compute
|
||||
compute_client = self.app.client_manager.sdk_connection.compute
|
||||
|
||||
server = utils.find_resource(
|
||||
compute_client.servers,
|
||||
parsed_args.server,
|
||||
server = compute_client.find_server(
|
||||
parsed_args.server, ignore_missing=False
|
||||
)
|
||||
security_group = compute_client.api.security_group_find(
|
||||
parsed_args.group,
|
||||
if self.app.client_manager.is_network_endpoint_enabled():
|
||||
# the server handles both names and IDs for neutron SGs, so just
|
||||
# pass things through
|
||||
security_group = parsed_args.group
|
||||
else:
|
||||
# however, if using nova-network then it needs a name, not an ID
|
||||
security_group = compute_v2.find_security_group(
|
||||
compute_client, parsed_args.group
|
||||
)['name']
|
||||
compute_client.remove_security_group_from_server(
|
||||
server, security_group
|
||||
)
|
||||
|
||||
server.remove_security_group(security_group['id'])
|
||||
|
||||
|
||||
class RemoveServerVolume(command.Command):
|
||||
_description = _(
|
||||
|
@ -13,11 +13,17 @@
|
||||
|
||||
"""Compute v2 API Library Tests"""
|
||||
|
||||
import http
|
||||
from unittest import mock
|
||||
import uuid
|
||||
|
||||
from keystoneauth1 import session
|
||||
from openstack.compute.v2 import _proxy
|
||||
from osc_lib import exceptions as osc_lib_exceptions
|
||||
from requests_mock.contrib import fixture
|
||||
|
||||
from openstackclient.api import compute_v2 as compute
|
||||
from openstackclient.tests.unit import fakes
|
||||
from openstackclient.tests.unit import utils
|
||||
|
||||
|
||||
@ -648,3 +654,112 @@ class TestSecurityGroupRule(TestComputeAPIv2):
|
||||
ret = self.api.security_group_rule_delete('1')
|
||||
self.assertEqual(202, ret.status_code)
|
||||
self.assertEqual("", ret.text)
|
||||
|
||||
|
||||
class TestFindSecurityGroup(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.compute_sdk_client = mock.Mock(_proxy.Proxy)
|
||||
|
||||
def test_find_security_group_by_id(self):
|
||||
sg_id = uuid.uuid4().hex
|
||||
sg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'security_group': {
|
||||
'id': sg_id,
|
||||
'name': sg_name,
|
||||
'description': 'description-' + uuid.uuid4().hex,
|
||||
'tenant_id': 'project-id-' + uuid.uuid4().hex,
|
||||
'rules': [],
|
||||
}
|
||||
}
|
||||
self.compute_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
result = compute.find_security_group(self.compute_sdk_client, sg_id)
|
||||
|
||||
self.compute_sdk_client.get.assert_has_calls(
|
||||
[
|
||||
mock.call(f'/os-security-groups/{sg_id}', microversion='2.1'),
|
||||
]
|
||||
)
|
||||
self.assertEqual(data['security_group'], result)
|
||||
|
||||
def test_find_security_group_by_name(self):
|
||||
sg_id = uuid.uuid4().hex
|
||||
sg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'security_groups': [
|
||||
{
|
||||
'id': sg_id,
|
||||
'name': sg_name,
|
||||
'description': 'description-' + uuid.uuid4().hex,
|
||||
'tenant_id': 'project-id-' + uuid.uuid4().hex,
|
||||
'rules': [],
|
||||
}
|
||||
],
|
||||
}
|
||||
self.compute_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
result = compute.find_security_group(self.compute_sdk_client, sg_name)
|
||||
|
||||
self.compute_sdk_client.get.assert_has_calls(
|
||||
[
|
||||
mock.call(
|
||||
f'/os-security-groups/{sg_name}', microversion='2.1'
|
||||
),
|
||||
mock.call('/os-security-groups', microversion='2.1'),
|
||||
]
|
||||
)
|
||||
self.assertEqual(data['security_groups'][0], result)
|
||||
|
||||
def test_find_security_group_not_found(self):
|
||||
data = {'security_groups': []}
|
||||
self.compute_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
self.assertRaises(
|
||||
osc_lib_exceptions.NotFound,
|
||||
compute.find_security_group,
|
||||
self.compute_sdk_client,
|
||||
'invalid-sg',
|
||||
)
|
||||
|
||||
def test_find_security_group_by_name_duplicate(self):
|
||||
sg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'security_groups': [
|
||||
{
|
||||
'id': uuid.uuid4().hex,
|
||||
'name': sg_name,
|
||||
'description': 'description-' + uuid.uuid4().hex,
|
||||
'tenant_id': 'project-id-' + uuid.uuid4().hex,
|
||||
'rules': [],
|
||||
},
|
||||
{
|
||||
'id': uuid.uuid4().hex,
|
||||
'name': sg_name,
|
||||
'description': 'description-' + uuid.uuid4().hex,
|
||||
'tenant_id': 'project-id-' + uuid.uuid4().hex,
|
||||
'rules': [],
|
||||
},
|
||||
],
|
||||
}
|
||||
self.compute_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
self.assertRaises(
|
||||
osc_lib_exceptions.NotFound,
|
||||
compute.find_security_group,
|
||||
self.compute_sdk_client,
|
||||
sg_name,
|
||||
)
|
||||
|
@ -18,7 +18,6 @@ import getpass
|
||||
import json
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
from unittest.mock import call
|
||||
|
||||
import iso8601
|
||||
from openstack import exceptions as sdk_exceptions
|
||||
@ -26,6 +25,7 @@ from osc_lib.cli import format_columns
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils as common_utils
|
||||
|
||||
from openstackclient.api import compute_v2
|
||||
from openstackclient.compute.v2 import server
|
||||
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
|
||||
from openstackclient.tests.unit.image.v2 import fakes as image_fakes
|
||||
@ -148,7 +148,7 @@ class TestServer(compute_fakes.TestComputev2):
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
calls = [call(s.id) for s in servers]
|
||||
calls = [mock.call(s.id) for s in servers]
|
||||
method = getattr(self.compute_sdk_client, method_name)
|
||||
method.assert_has_calls(calls)
|
||||
self.assertIsNone(result)
|
||||
@ -1221,42 +1221,65 @@ class TestServerAddNetwork(TestServer):
|
||||
)
|
||||
|
||||
|
||||
@mock.patch('openstackclient.api.compute_v2.APIv2.security_group_find')
|
||||
class TestServerAddSecurityGroup(TestServer):
|
||||
class TestServerAddSecurityGroup(compute_fakes.TestComputev2):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.security_group = compute_fakes.create_one_security_group()
|
||||
|
||||
attrs = {'security_groups': [{'name': self.security_group['id']}]}
|
||||
methods = {
|
||||
'add_security_group': None,
|
||||
}
|
||||
|
||||
self.server = compute_fakes.create_one_server(
|
||||
attrs=attrs, methods=methods
|
||||
self.server = compute_fakes.create_one_sdk_server()
|
||||
self.compute_sdk_client.find_server.return_value = self.server
|
||||
self.compute_sdk_client.add_security_group_to_server.return_value = (
|
||||
None
|
||||
)
|
||||
# This is the return value for utils.find_resource() for server
|
||||
self.servers_mock.get.return_value = self.server
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = server.AddServerSecurityGroup(self.app, None)
|
||||
|
||||
def test_server_add_security_group(self, sg_find_mock):
|
||||
sg_find_mock.return_value = self.security_group
|
||||
arglist = [self.server.id, self.security_group['id']]
|
||||
def test_server_add_security_group__nova_network(self):
|
||||
arglist = [self.server.id, 'fake_sg']
|
||||
verifylist = [
|
||||
('server', self.server.id),
|
||||
('group', self.security_group['id']),
|
||||
('group', 'fake_sg'),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
with mock.patch.object(
|
||||
self.app.client_manager,
|
||||
'is_network_endpoint_enabled',
|
||||
return_value=False,
|
||||
):
|
||||
with mock.patch.object(
|
||||
compute_v2,
|
||||
'find_security_group',
|
||||
return_value={'name': 'fake_sg'},
|
||||
) as mock_find_nova_net_sg:
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.compute_sdk_client.find_server.assert_called_once_with(
|
||||
self.server.id, ignore_missing=False
|
||||
)
|
||||
self.compute_sdk_client.add_security_group_to_server.assert_called_once_with(
|
||||
self.server, 'fake_sg'
|
||||
)
|
||||
mock_find_nova_net_sg.assert_called_once_with(
|
||||
self.compute_sdk_client, 'fake_sg'
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_server_add_security_group(self):
|
||||
arglist = [self.server.id, 'fake_sg']
|
||||
verifylist = [
|
||||
('server', self.server.id),
|
||||
('group', 'fake_sg'),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
sg_find_mock.assert_called_with(
|
||||
self.security_group['id'],
|
||||
|
||||
self.compute_sdk_client.find_server.assert_called_once_with(
|
||||
self.server.id, ignore_missing=False
|
||||
)
|
||||
self.servers_mock.get.assert_called_with(self.server.id)
|
||||
self.server.add_security_group.assert_called_with(
|
||||
self.security_group['id'],
|
||||
self.compute_sdk_client.add_security_group_to_server.assert_called_once_with(
|
||||
self.server, 'fake_sg'
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@ -4452,7 +4475,7 @@ class TestServerDelete(TestServer):
|
||||
|
||||
calls = []
|
||||
for s in servers:
|
||||
calls.append(call(s.id))
|
||||
calls.append(mock.call(s.id))
|
||||
self.servers_mock.delete.assert_has_calls(calls)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@ -7260,42 +7283,65 @@ class TestServerRemoveNetwork(TestServer):
|
||||
self.find_network.assert_not_called()
|
||||
|
||||
|
||||
@mock.patch('openstackclient.api.compute_v2.APIv2.security_group_find')
|
||||
class TestServerRemoveSecurityGroup(TestServer):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.security_group = compute_fakes.create_one_security_group()
|
||||
|
||||
attrs = {'security_groups': [{'name': self.security_group['id']}]}
|
||||
methods = {
|
||||
'remove_security_group': None,
|
||||
}
|
||||
|
||||
self.server = compute_fakes.create_one_server(
|
||||
attrs=attrs, methods=methods
|
||||
self.server = compute_fakes.create_one_sdk_server()
|
||||
self.compute_sdk_client.find_server.return_value = self.server
|
||||
self.compute_sdk_client.remove_security_group_from_server.return_value = (
|
||||
None
|
||||
)
|
||||
# This is the return value for utils.find_resource() for server
|
||||
self.servers_mock.get.return_value = self.server
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = server.RemoveServerSecurityGroup(self.app, None)
|
||||
|
||||
def test_server_remove_security_group(self, sg_find_mock):
|
||||
sg_find_mock.return_value = self.security_group
|
||||
arglist = [self.server.id, self.security_group['id']]
|
||||
def test_server_remove_security_group__nova_network(self):
|
||||
arglist = [self.server.id, 'fake_sg']
|
||||
verifylist = [
|
||||
('server', self.server.id),
|
||||
('group', self.security_group['id']),
|
||||
('group', 'fake_sg'),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
with mock.patch.object(
|
||||
self.app.client_manager,
|
||||
'is_network_endpoint_enabled',
|
||||
return_value=False,
|
||||
):
|
||||
with mock.patch.object(
|
||||
compute_v2,
|
||||
'find_security_group',
|
||||
return_value={'name': 'fake_sg'},
|
||||
) as mock_find_nova_net_sg:
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.compute_sdk_client.find_server.assert_called_once_with(
|
||||
self.server.id, ignore_missing=False
|
||||
)
|
||||
self.compute_sdk_client.remove_security_group_from_server.assert_called_once_with(
|
||||
self.server, 'fake_sg'
|
||||
)
|
||||
mock_find_nova_net_sg.assert_called_once_with(
|
||||
self.compute_sdk_client, 'fake_sg'
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_server_remove_security_group(self):
|
||||
arglist = [self.server.id, 'fake_sg']
|
||||
verifylist = [
|
||||
('server', self.server.id),
|
||||
('group', 'fake_sg'),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
sg_find_mock.assert_called_with(
|
||||
self.security_group['id'],
|
||||
|
||||
self.compute_sdk_client.find_server.assert_called_once_with(
|
||||
self.server.id, ignore_missing=False
|
||||
)
|
||||
self.servers_mock.get.assert_called_with(self.server.id)
|
||||
self.server.remove_security_group.assert_called_with(
|
||||
self.security_group['id'],
|
||||
self.compute_sdk_client.remove_security_group_from_server.assert_called_once_with(
|
||||
self.server, 'fake_sg'
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user