Merge "api: Add response body schemas for security group APIs"

This commit is contained in:
Zuul
2025-11-20 23:35:27 +00:00
committed by Gerrit Code Review
17 changed files with 204 additions and 51 deletions

View File

@@ -1,9 +1,9 @@
{
"security_group_rule": {
"parent_group_id": "21111111-1111-1111-1111-111111111112",
"parent_group_id": "d6f86d8c-06ef-4bef-8e7c-8bf8f9ba9b7a",
"ip_protocol": "tcp",
"from_port": 22,
"to_port": 22,
"cidr": "10.0.0.0/24"
}
}
}

View File

@@ -2,12 +2,12 @@
"security_group_rule": {
"from_port": 22,
"group": {},
"id": "00000000-0000-0000-0000-000000000000",
"id": "baed7fb4-16b9-4b99-a2fd-02d0b1a4d9b2",
"ip_protocol": "tcp",
"ip_range": {
"cidr": "10.0.0.0/24"
},
"parent_group_id": "11111111-1111-1111-1111-111111111111",
"parent_group_id": "d6f86d8c-06ef-4bef-8e7c-8bf8f9ba9b7a",
"to_port": 22
}
}
}

View File

@@ -1,9 +1,9 @@
{
"security_group": {
"description": "default",
"id": 1,
"id": "4e469db4-3b60-43c7-8dfa-2c60e2f27075",
"name": "default",
"rules": [],
"tenant_id": "6f70656e737461636b20342065766572"
}
}
}

View File

@@ -1,9 +1,9 @@
{
"security_group": {
"description": "default",
"id": 1,
"id": "4e469db4-3b60-43c7-8dfa-2c60e2f27075",
"name": "default",
"rules": [],
"tenant_id": "6f70656e737461636b20342065766572"
}
}
}

View File

@@ -2,10 +2,10 @@
"security_groups": [
{
"description": "default",
"id": 1,
"id": "4e469db4-3b60-43c7-8dfa-2c60e2f27075",
"name": "default",
"rules": [],
"tenant_id": "6f70656e737461636b20342065766572"
}
]
}
}

View File

@@ -2,10 +2,10 @@
"security_groups": [
{
"description": "default",
"id": 1,
"id": "4e469db4-3b60-43c7-8dfa-2c60e2f27075",
"name": "default",
"rules": [],
"tenant_id": "6f70656e737461636b20342065766572"
}
]
}
}

View File

@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from nova.api.validation import parameter_types
create = {
@@ -93,7 +95,7 @@ index_query = {
}
# TODO(stephenfin): Remove additionalProperties in a future API version
server_sg_index_query = {
index_server_query = {
'type': 'object',
'properties': {},
'additionalProperties': True,
@@ -139,6 +141,108 @@ remove_security_group = {
'additionalProperties': True,
}
_security_group_rule_response = {
'type': 'object',
'properties': {
'from_port': {'type': ['integer', 'null'], 'minimum': -1},
'group': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'tenant_id': parameter_types.project_id,
},
'required': [],
'additionalProperties': False,
},
'id': {'type': 'string', 'format': 'uuid'},
'ip_protocol': {'type': ['string', 'null']},
'ip_range': {
'type': 'object',
'properties': {
'cidr': {'type': 'string', 'format': 'cidr'},
},
'required': [],
'additionalProperties': False,
},
'parent_group_id': {'type': 'string', 'format': 'uuid'},
'to_port': {'type': ['integer', 'null'], 'minimum': -1},
},
'required': [
'from_port',
'group',
'id',
'ip_protocol',
'ip_range',
'parent_group_id',
'to_port',
],
'additionalProperties': False,
}
_security_group_response = {
'type': 'object',
'properties': {
'description': {'type': ['string', 'null']},
'id': {'type': 'string', 'format': 'uuid'},
'name': {'type': 'string'},
'rules': {'type': 'array', 'items': _security_group_rule_response},
'tenant_id': parameter_types.project_id,
},
'required': ['description', 'id', 'name', 'rules', 'tenant_id'],
'additionalProperties': False,
}
show_response = {
'type': 'object',
'properties': {
'security_group': _security_group_response,
},
'required': ['security_group'],
'additionalProperties': False,
}
delete_response = {'type': 'null'}
index_response = {
'type': 'object',
'properties': {
'security_groups': {
'type': 'array',
'items': _security_group_response,
},
},
'required': ['security_groups'],
'additionalProperties': False,
}
create_response = copy.deepcopy(show_response)
update_response = copy.deepcopy(show_response)
create_rule_response = {
'type': 'object',
'properties': {
'security_group_rule': _security_group_rule_response,
},
'required': ['security_group_rule'],
'additionalProperties': False,
}
delete_rule_response = {'type': 'null'}
index_server_response = {
'type': 'object',
'properties': {
'security_groups': {
'type': 'array',
'items': _security_group_response,
},
},
'required': ['security_groups'],
'additionalProperties': False,
}
add_security_group_response = {
'type': 'null',
}

View File

@@ -131,12 +131,14 @@ class SecurityGroupControllerBase(object):
return group_rule_data_by_rule_group_id
@validation.validated
class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
"""The Security group API controller for the OpenStack API."""
@wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404))
@validation.query_schema(schema.show_query)
@validation.response_body_schema(schema.show_response)
def show(self, req, id):
"""Return data about the given security group."""
context = req.environ['nova.context']
@@ -157,6 +159,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
@wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404))
@wsgi.response(202)
@validation.response_body_schema(schema.delete_response)
def delete(self, req, id):
"""Delete a security group."""
context = req.environ['nova.context']
@@ -175,6 +178,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
@wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@validation.query_schema(schema.index_query)
@wsgi.expected_errors(404)
@validation.response_body_schema(schema.index_response)
def index(self, req):
"""Returns a list of security groups."""
context = req.environ['nova.context']
@@ -199,6 +203,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
@wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 403))
@validation.schema(schema.create)
@validation.response_body_schema(schema.create_response)
def create(self, req, body):
"""Creates a new security group."""
context = req.environ['nova.context']
@@ -222,6 +227,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
@wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404))
@validation.schema(schema.update)
@validation.response_body_schema(schema.update_response)
def update(self, req, id, body):
"""Update a security group."""
context = req.environ['nova.context']
@@ -251,12 +257,15 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
group_ref)}
class SecurityGroupRulesController(SecurityGroupControllerBase,
wsgi.Controller):
@validation.validated
class SecurityGroupRulesController(
SecurityGroupControllerBase, wsgi.Controller,
):
@wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 403, 404))
@validation.schema(schema.create_rules)
@validation.response_body_schema(schema.create_rule_response)
def create(self, req, body):
context = req.environ['nova.context']
context.can(sg_policies.POLICY_NAME % 'rule:create',
@@ -330,6 +339,7 @@ class SecurityGroupRulesController(SecurityGroupControllerBase,
@wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404, 409))
@wsgi.response(202)
@validation.response_body_schema(schema.delete_rule_response)
def delete(self, req, id):
context = req.environ['nova.context']
context.can(sg_policies.POLICY_NAME % 'rule:delete',
@@ -350,12 +360,14 @@ class SecurityGroupRulesController(SecurityGroupControllerBase,
raise exc.HTTPBadRequest(explanation=exp.format_message())
@validation.validated
class ServerSecurityGroupController(
SecurityGroupControllerBase, wsgi.Controller
):
@wsgi.expected_errors(404)
@validation.query_schema(schema.server_sg_index_query)
@validation.query_schema(schema.index_server_query)
@validation.response_body_schema(schema.index_server_response)
def index(self, req, server_id):
"""Returns a list of security groups for the given instance."""
context = req.environ['nova.context']

View File

@@ -1,6 +1,6 @@
{
"security_group_rule": {
"parent_group_id": "21111111-1111-1111-1111-111111111112",
"parent_group_id": "d6f86d8c-06ef-4bef-8e7c-8bf8f9ba9b7a",
"ip_protocol": "tcp",
"from_port": 22,
"to_port": 22,

View File

@@ -4,10 +4,10 @@
"group": {},
"ip_protocol": "tcp",
"to_port": 22,
"parent_group_id": "11111111-1111-1111-1111-111111111111",
"parent_group_id": "d6f86d8c-06ef-4bef-8e7c-8bf8f9ba9b7a",
"ip_range": {
"cidr": "10.0.0.0/24"
},
"id": "00000000-0000-0000-0000-000000000000"
"id": "%(uuid)s"
}
}

View File

@@ -1,7 +1,7 @@
{
"security_group": {
"description": "%(description)s",
"id": 1,
"id": "%(uuid)s",
"name": "%(group_name)s",
"rules": [],
"tenant_id": "6f70656e737461636b20342065766572"

View File

@@ -1,7 +1,7 @@
{
"security_group": {
"description": "default",
"id": 1,
"id": "%(uuid)s",
"name": "default",
"rules": [],
"tenant_id": "6f70656e737461636b20342065766572"

View File

@@ -2,7 +2,7 @@
"security_groups": [
{
"description": "default",
"id": 1,
"id": "%(uuid)s",
"name": "default",
"rules": [],
"tenant_id": "6f70656e737461636b20342065766572"

View File

@@ -2,7 +2,7 @@
"security_groups": [
{
"description": "default",
"id": 1,
"id": "%(uuid)s",
"name": "default",
"rules": [],
"tenant_id": "6f70656e737461636b20342065766572"

View File

@@ -13,13 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils.fixture import uuidsentinel as uuids
from nova.tests.functional.api_sample_tests import test_servers
import nova.tests.functional.api_samples_test_base as astb
def fake_get(*args, **kwargs):
nova_group = {}
nova_group['id'] = 1
nova_group['id'] = uuids.security_group
nova_group['description'] = 'default'
nova_group['name'] = 'default'
nova_group['project_id'] = astb.PROJECT_ID
@@ -47,8 +49,7 @@ def fake_list(context, names=None, ids=None, project=None, search_opts=None):
return [fake_get()]
def fake_get_instance_security_groups(context, instance_uuid,
detailed=False):
def fake_get_instance_security_groups(context, instance_uuid, detailed=False):
return [fake_get()]
@@ -61,8 +62,8 @@ def fake_create_security_group_rule(context, security_group, new_rule):
'from_port': 22,
'to_port': 22,
'cidr': '10.0.0.0/24',
'id': '00000000-0000-0000-0000-000000000000',
'parent_group_id': '11111111-1111-1111-1111-111111111111',
'id': uuids.security_group_rule,
'parent_group_id': 'd6f86d8c-06ef-4bef-8e7c-8bf8f9ba9b7a',
'protocol': 'tcp',
'group_id': None
}
@@ -75,7 +76,7 @@ def fake_remove_rules(context, security_group, rule_ids):
def fake_get_rule(context, id):
return {
'id': id,
'parent_group_id': '11111111-1111-1111-1111-111111111111'
'parent_group_id': 'd6f86d8c-06ef-4bef-8e7c-8bf8f9ba9b7a'
}
@@ -83,7 +84,7 @@ class SecurityGroupsJsonTest(test_servers.ServersSampleBase):
sample_dir = 'os-security-groups'
def setUp(self):
super(SecurityGroupsJsonTest, self).setUp()
super().setUp()
path = 'nova.network.security_group_api.'
self.stub_out(path + 'get', fake_get)
self.stub_out(path + 'get_instances_security_groups_bindings',
@@ -97,15 +98,13 @@ class SecurityGroupsJsonTest(test_servers.ServersSampleBase):
fake_create_security_group)
self.stub_out(path + 'create_security_group_rule',
fake_create_security_group_rule)
self.stub_out(path + 'remove_rules',
fake_remove_rules)
self.stub_out(path + 'get_rule',
fake_get_rule)
self.stub_out(path + 'remove_rules', fake_remove_rules)
self.stub_out(path + 'get_rule', fake_get_rule)
def _get_create_subs(self):
return {
'group_name': 'default',
"description": "default",
'group_name': 'default',
"description": "default",
}
def _create_security_group(self):
@@ -115,7 +114,7 @@ class SecurityGroupsJsonTest(test_servers.ServersSampleBase):
def _add_group(self, uuid):
subs = {
'group_name': 'test'
'group_name': 'test'
}
return self._do_post('servers/%s/action' % uuid,
'security-group-add-post-req', subs)
@@ -134,8 +133,7 @@ class SecurityGroupsJsonTest(test_servers.ServersSampleBase):
def test_security_groups_get(self):
# Get api sample of security groups get request.
security_group_id = '11111111-1111-1111-1111-111111111111'
response = self._do_get('os-security-groups/%s' % security_group_id)
response = self._do_get('os-security-groups/%s' % uuids.security_group)
self._verify_response('security-groups-get-resp', {}, response, 200)
def test_security_groups_list_server(self):
@@ -157,7 +155,7 @@ class SecurityGroupsJsonTest(test_servers.ServersSampleBase):
uuid = self._post_server()
self._add_group(uuid)
subs = {
'group_name': 'test'
'group_name': 'test'
}
response = self._do_post('servers/%s/action' % uuid,
'security-group-remove-post-req', subs)
@@ -172,5 +170,5 @@ class SecurityGroupsJsonTest(test_servers.ServersSampleBase):
def test_security_group_rules_remove(self):
response = self._do_delete(
'os-security-group-rules/00000000-0000-0000-0000-000000000000')
f'os-security-group-rules/{uuids.security_group_rule}')
self.assertEqual(202, response.status_code)

View File

@@ -513,14 +513,14 @@ class TestSecurityGroupsV21(test.TestCase):
rule1 = security_group_rule_template(
cidr='10.2.3.124/24',
parent_group_id=uuids.parent_group_id,
group_id={}, id=88,
group_id={}, id=uuids.sg_rule_1,
protocol='TCP')
rule2 = security_group_rule_template(
cidr='10.2.3.125/24',
parent_group_id=uuids.parent_group_id,
id=99, protocol=88,
id=uuids.sg_rule_2, protocol=88,
group_id='HAS_BEEN_DELETED')
sg = security_group_template(id=1,
sg = security_group_template(id=uuids.sg,
name='test',
description='test-desc',
rules=[rule1, rule2])
@@ -533,8 +533,8 @@ class TestSecurityGroupsV21(test.TestCase):
expected_rule = security_group_rule_template(
ip_range={'cidr': '10.2.3.124/24'},
parent_group_id=uuids.parent_group_id,
group={}, id=88, ip_protocol='TCP')
expected = security_group_template(id=1,
group={}, id=uuids.sg_rule_1, ip_protocol='TCP')
expected = security_group_template(id=uuids.sg,
name='test',
description='test-desc',
rules=[expected_rule])
@@ -542,10 +542,9 @@ class TestSecurityGroupsV21(test.TestCase):
expected = {'security_groups': [expected]}
with mock.patch(
'nova.network.security_group_api.list',
return_value=[
security_group_db(
secgroup) for secgroup in groups]) as mock_list:
'nova.network.security_group_api.list',
return_value=[security_group_db(secgroup) for secgroup in groups],
) as mock_list:
res_dict = self.controller.index(self.req)
self.assertEqual(res_dict, expected)

View File

@@ -170,6 +170,8 @@ class SecurityGroupsPolicyTest(base.BasePolicyTest):
@mock.patch('nova.network.security_group_api.list')
def test_list_security_groups_policy(self, mock_get):
mock_get.return_value = []
rule_name = policies.POLICY_NAME % 'get'
self.common_policy_auth(self.project_reader_authorized_contexts,
rule_name,
@@ -178,6 +180,14 @@ class SecurityGroupsPolicyTest(base.BasePolicyTest):
@mock.patch('nova.network.security_group_api.get')
def test_show_security_groups_policy(self, mock_get):
mock_get.return_value = {
'id': uuids.sg_id,
'description': None,
'name': 'foo',
'project_id': uuids.project_id,
'rules': [],
}
rule_name = policies.POLICY_NAME % 'show'
self.common_policy_auth(self.project_reader_authorized_contexts,
rule_name,
@@ -187,6 +197,14 @@ class SecurityGroupsPolicyTest(base.BasePolicyTest):
@mock.patch('nova.network.security_group_api.get')
@mock.patch('nova.network.security_group_api.update_security_group')
def test_update_security_groups_policy(self, mock_update, mock_get):
mock_update.return_value = {
'id': uuids.sg_id,
'description': None,
'name': 'foo',
'project_id': uuids.project_id,
'rules': [],
}
rule_name = policies.POLICY_NAME % 'update'
body = {'security_group': {
'name': 'test',
@@ -198,6 +216,14 @@ class SecurityGroupsPolicyTest(base.BasePolicyTest):
@mock.patch('nova.network.security_group_api.create_security_group')
def test_create_security_groups_policy(self, mock_create):
mock_create.return_value = {
'id': uuids.sg_id,
'description': None,
'name': 'foo',
'project_id': uuids.project_id,
'rules': [],
}
rule_name = policies.POLICY_NAME % 'create'
body = {'security_group': {
'name': 'test',
@@ -219,6 +245,20 @@ class SecurityGroupsPolicyTest(base.BasePolicyTest):
@mock.patch('nova.network.security_group_api.get')
@mock.patch('nova.network.security_group_api.create_security_group_rule')
def test_create_security_group_rules_policy(self, mock_create, mock_get):
mock_get.return_value = {
'id': uuids.sg_id,
'name': 'foo',
'project_id': uuids.project_id,
}
mock_create.return_value = {
'id': uuids.sg_rule_id,
'parent_group_id': uuids.sg_id,
'protocol': 'tcp',
'from_port': 22,
'to_port': 22,
'cidr': '10.0.0.0/24',
}
rule_name = policies.POLICY_NAME % 'rule:create'
body = {'security_group_rule': {
'ip_protocol': 'test', 'group_id': uuids.fake_id,