Change v3 security_groups API to v2.1

This patch changes v3 security_groups API to v2.1 and makes v2 unit tests
share between v2 and v2.1.
This patch doesn't revert /os-security-groups, /os-security-group-rules
and /servers/server_id/os-security-groups because these features are
implemented in Networking API. On v2.1 API, we decide we don't need to
implement proxying features.

The differences between v2 and v3 are described on the wiki page
https://wiki.openstack.org/wiki/NovaAPIv2tov3 .

Partially implements blueprint v2-on-v3-api

Change-Id: I82660fd514d041bdd1fad0e689fa4fe90d441c47
This commit is contained in:
Ken'ichi Ohmichi 2014-08-28 01:38:37 +00:00
parent fab6c89d2f
commit bbc7f8a4c6
18 changed files with 56 additions and 386 deletions

View File

@ -66,7 +66,7 @@
"os-server-usage:launched_at": "2013-09-23T13:37:00.880302",
"os-server-usage:terminated_at": null,
"progress": 0,
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "default"
}

View File

@ -14,7 +14,7 @@
],
"accessIPv4": "",
"accessIPv6": "",
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "default"
}

View File

@ -67,7 +67,7 @@
"os-server-usage:launched_at": "2013-09-23T13:53:12.774549",
"os-server-usage:terminated_at": null,
"progress": 0,
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "default"
}

View File

@ -47,7 +47,7 @@
},
"name": "new-server-test",
"progress": 0,
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "test"
}

View File

@ -6,6 +6,6 @@
"metadata" : {
"My Server Name" : "Apache1"
},
"os-security-groups:security_groups": [{"name": "test"}]
"security_groups": [{"name": "test"}]
}
}

View File

@ -12,7 +12,7 @@
"rel": "bookmark"
}
],
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "test"
}

View File

@ -48,7 +48,7 @@
},
"name": "new-server-test",
"progress": 0,
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "test"
}

View File

@ -29,7 +29,7 @@ from nova.openstack.common import jsonutils
ALIAS = 'os-security-groups'
ATTRIBUTE_NAME = '%s:security_groups' % ALIAS
ATTRIBUTE_NAME = 'security_groups'
authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)
softauth = extensions.soft_extension_authorizer('compute', 'v3:' + ALIAS)

View File

@ -15,11 +15,14 @@
from nova.api.validation import parameter_types
server_create = {
'os-security-groups:security_groups': {
'security_groups': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
# NOTE(oomichi): allocate_for_instance() of neutronv2/api.py
# gets security_group names or UUIDs from this parameter.
# parameter_types.name allows both format.
'name': parameter_types.name,
},
'additionalProperties': False,

View File

@ -19,7 +19,7 @@ import mox
from oslo.config import cfg
import webob
from nova.api.openstack.compute.contrib import security_groups
from nova.api.openstack.compute.contrib import security_groups as secgroups_v2
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
from nova import compute
@ -121,14 +121,17 @@ def return_server_nonexistent(context, server_id, columns_to_join=None):
raise exception.InstanceNotFound(instance_id=server_id)
# NOTE(oomichi): v2.1 API does not support security group management (create/
# update/delete a security group). We don't need to test this class against
# v2.1 API.
class TestSecurityGroups(test.TestCase):
def setUp(self):
super(TestSecurityGroups, self).setUp()
self.controller = security_groups.SecurityGroupController()
self.controller = secgroups_v2.SecurityGroupController()
self.server_controller = (
security_groups.ServerSecurityGroupController())
self.manager = security_groups.SecurityGroupActionController()
secgroups_v2.ServerSecurityGroupController())
self.manager = secgroups_v2.SecurityGroupActionController()
# This needs to be done here to set fake_id because the derived
# class needs to be called first if it wants to set
@ -789,11 +792,14 @@ class TestSecurityGroups(test.TestCase):
self.manager._removeSecurityGroup(req, '1', body)
# NOTE(oomichi): v2.1 API does not support security group management (create/
# update/delete a security group). We don't need to test this class against
# v2.1 API.
class TestSecurityGroupRules(test.TestCase):
def setUp(self):
super(TestSecurityGroupRules, self).setUp()
self.controller = security_groups.SecurityGroupController()
self.controller = secgroups_v2.SecurityGroupController()
if self.controller.security_group_api.id_is_uuid:
id1 = '11111111-1111-1111-1111-111111111111'
id2 = '22222222-2222-2222-2222-222222222222'
@ -823,7 +829,7 @@ class TestSecurityGroupRules(test.TestCase):
self.parent_security_group = db2
self.controller = security_groups.SecurityGroupRulesController()
self.controller = secgroups_v2.SecurityGroupRulesController()
def test_create_by_cidr(self):
rule = security_group_rule_template(cidr='10.2.3.124/24',
@ -1298,7 +1304,7 @@ class TestSecurityGroupRulesXMLDeserializer(test.TestCase):
def setUp(self):
super(TestSecurityGroupRulesXMLDeserializer, self).setUp()
self.deserializer = security_groups.SecurityGroupRulesXMLDeserializer()
self.deserializer = secgroups_v2.SecurityGroupRulesXMLDeserializer()
def test_create_request(self):
serial_request = """
@ -1356,7 +1362,7 @@ class TestSecurityGroupXMLDeserializer(test.TestCase):
def setUp(self):
super(TestSecurityGroupXMLDeserializer, self).setUp()
self.deserializer = security_groups.SecurityGroupXMLDeserializer()
self.deserializer = secgroups_v2.SecurityGroupXMLDeserializer()
def test_create_request(self):
serial_request = """
@ -1409,9 +1415,9 @@ class TestSecurityGroupXMLSerializer(test.TestCase):
def setUp(self):
super(TestSecurityGroupXMLSerializer, self).setUp()
self.namespace = wsgi.XMLNS_V11
self.rule_serializer = security_groups.SecurityGroupRuleTemplate()
self.index_serializer = security_groups.SecurityGroupsTemplate()
self.default_serializer = security_groups.SecurityGroupTemplate()
self.rule_serializer = secgroups_v2.SecurityGroupRuleTemplate()
self.index_serializer = secgroups_v2.SecurityGroupsTemplate()
self.default_serializer = secgroups_v2.SecurityGroupTemplate()
def _tag(self, elem):
tagname = elem.tag
@ -1616,12 +1622,12 @@ def fake_get_instances_security_groups_bindings(inst, context, servers):
return result
class SecurityGroupsOutputTest(test.TestCase):
class SecurityGroupsOutputTestV21(test.TestCase):
base_url = '/v3/servers'
content_type = 'application/json'
def setUp(self):
super(SecurityGroupsOutputTest, self).setUp()
self.controller = security_groups.SecurityGroupController()
super(SecurityGroupsOutputTestV21, self).setUp()
fakes.stub_out_nw_api(self.stubs)
self.stubs.Set(compute.api.API, 'get', fake_compute_get)
self.stubs.Set(compute.api.API, 'get_all', fake_compute_get_all)
@ -1630,6 +1636,10 @@ class SecurityGroupsOutputTest(test.TestCase):
osapi_compute_extension=[
'nova.api.openstack.compute.contrib.select_extensions'],
osapi_compute_ext_list=['Security_groups'])
self.app = self._setup_app()
def _setup_app(self):
return fakes.wsgi_app_v3(init_only=('os-security-groups', 'servers'))
def _make_request(self, url, body=None):
req = webob.Request.blank(url)
@ -1638,7 +1648,7 @@ class SecurityGroupsOutputTest(test.TestCase):
req.body = self._encode_body(body)
req.content_type = self.content_type
req.headers['Accept'] = self.content_type
res = req.get_response(fakes.wsgi_app(init_only=('servers',)))
res = req.get_response(self.app)
return res
def _encode_body(self, body):
@ -1654,10 +1664,9 @@ class SecurityGroupsOutputTest(test.TestCase):
return server.get('security_groups')
def test_create(self):
url = '/v2/fake/servers'
image_uuid = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
server = dict(name='server_test', imageRef=image_uuid, flavorRef=2)
res = self._make_request(url, {'server': server})
res = self._make_request(self.base_url, {'server': server})
self.assertEqual(res.status_int, 202)
server = self._get_server(res.body)
for i, group in enumerate(self._get_groups(server)):
@ -1665,7 +1674,7 @@ class SecurityGroupsOutputTest(test.TestCase):
self.assertEqual(group.get('name'), name)
def test_show(self):
url = '/v2/fake/servers/%s' % UUID3
url = self.base_url + '/' + UUID3
res = self._make_request(url)
self.assertEqual(res.status_int, 200)
@ -1675,7 +1684,7 @@ class SecurityGroupsOutputTest(test.TestCase):
self.assertEqual(group.get('name'), name)
def test_detail(self):
url = '/v2/fake/servers/detail'
url = self.base_url + '/detail'
res = self._make_request(url)
self.assertEqual(res.status_int, 200)
@ -1690,13 +1699,20 @@ class SecurityGroupsOutputTest(test.TestCase):
raise exception.InstanceNotFound(instance_id='fake')
self.stubs.Set(compute.api.API, 'get', fake_compute_get)
url = '/v2/fake/servers/70f6db34-de8d-4fbd-aafb-4065bdfa6115'
url = self.base_url + '/70f6db34-de8d-4fbd-aafb-4065bdfa6115'
res = self._make_request(url)
self.assertEqual(res.status_int, 404)
class SecurityGroupsOutputXmlTest(SecurityGroupsOutputTest):
class SecurityGroupsOutputTestV2(SecurityGroupsOutputTestV21):
base_url = '/v2/fake/servers'
def _setup_app(self):
return fakes.wsgi_app(init_only=('servers',))
class SecurityGroupsOutputXmlTest(SecurityGroupsOutputTestV2):
content_type = 'application/xml'
class MinimalCreateServerTemplate(xmlutil.TemplateBuilder):

View File

@ -1,349 +0,0 @@
# Copyright 2011 OpenStack Foundation
# Copyright 2012 Justin Santa Barbara
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import uuid
from oslo.config import cfg
import webob
from nova.api.openstack.compute import plugins
from nova.api.openstack.compute.plugins.v3 import security_groups
from nova.api.openstack.compute.plugins.v3 import servers
from nova import compute
from nova.compute import api as compute_api
from nova.compute import flavors
from nova import db
from nova import exception
from nova.network import manager
from nova import objects
from nova.objects import instance as instance_obj
from nova.openstack.common import jsonutils
from nova import test
from nova.tests.api.openstack import fakes
from nova.tests import fake_instance
import nova.tests.image.fake
CONF = cfg.CONF
FAKE_UUID = fakes.FAKE_UUID
FAKE_UUID1 = 'a47ae74e-ab08-447f-8eee-ffd43fc46c16'
def fake_gen_uuid():
return FAKE_UUID
def return_security_group(context, instance_id, security_group_id):
pass
UUID1 = '00000000-0000-0000-0000-000000000001'
UUID2 = '00000000-0000-0000-0000-000000000002'
UUID3 = '00000000-0000-0000-0000-000000000003'
def fake_compute_get_all(*args, **kwargs):
base = {'id': 1, 'description': 'foo', 'user_id': 'bar',
'project_id': 'baz', 'deleted': False, 'deleted_at': None,
'updated_at': None, 'created_at': None}
db_list = [
fakes.stub_instance(
1, uuid=UUID1,
security_groups=[dict(base, **{'name': 'fake-0-0'}),
dict(base, **{'name': 'fake-0-1'})]),
fakes.stub_instance(
2, uuid=UUID2,
security_groups=[dict(base, **{'name': 'fake-1-0'}),
dict(base, **{'name': 'fake-1-1'})])
]
return instance_obj._make_instance_list(args[1],
objects.InstanceList(),
db_list,
['metadata', 'system_metadata',
'security_groups', 'info_cache'])
def fake_compute_get(*args, **kwargs):
inst = fakes.stub_instance(1, uuid=UUID3,
security_groups=[{'name': 'fake-2-0'},
{'name': 'fake-2-1'}])
return fake_instance.fake_instance_obj(args[1],
expected_attrs=instance_obj.INSTANCE_DEFAULT_FIELDS, **inst)
def fake_compute_create(*args, **kwargs):
return ([fake_compute_get(*args, **kwargs)], '')
def fake_get_instance_security_groups(*args, **kwargs):
return [{'name': 'fake'}]
def fake_get_instances_security_groups_bindings(inst, context, servers):
groups = {UUID1: [{'name': 'fake-0-0'}, {'name': 'fake-0-1'}],
UUID2: [{'name': 'fake-1-0'}, {'name': 'fake-1-1'}],
UUID3: [{'name': 'fake-2-0'}, {'name': 'fake-2-1'}]}
result = {}
for server in servers:
result[server['id']] = groups.get(server['id'])
return result
class SecurityGroupsOutputTest(test.TestCase):
content_type = 'application/json'
def setUp(self):
super(SecurityGroupsOutputTest, self).setUp()
CONF.set_override('security_group_api', 'nova')
fakes.stub_out_nw_api(self.stubs)
self.stubs.Set(compute.api.API, 'get', fake_compute_get)
self.stubs.Set(compute.api.API, 'get_all', fake_compute_get_all)
self.stubs.Set(compute.api.API, 'create', fake_compute_create)
self.app = fakes.wsgi_app_v3(init_only=('servers',
'os-security-groups'))
def _make_request(self, url, body=None):
req = webob.Request.blank(url)
if body:
req.method = 'POST'
req.body = self._encode_body(body)
req.content_type = self.content_type
req.headers['Accept'] = self.content_type
res = req.get_response(self.app)
return res
def _encode_body(self, body):
return jsonutils.dumps(body)
def _get_server(self, body):
return jsonutils.loads(body).get('server')
def _get_servers(self, body):
return jsonutils.loads(body).get('servers')
def _get_groups(self, server):
return server.get('os-security-groups:security_groups')
def test_create(self):
url = '/v3/servers'
image_uuid = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
server = dict(name='server_test', imageRef=image_uuid, flavorRef=2)
res = self._make_request(url, {'server': server})
self.assertEqual(res.status_int, 202)
server = self._get_server(res.body)
for i, group in enumerate(self._get_groups(server)):
name = 'fake-2-%s' % i
self.assertEqual(group.get('name'), name)
def test_show(self):
url = '/v3/servers/%s' % UUID3
res = self._make_request(url)
self.assertEqual(res.status_int, 200)
server = self._get_server(res.body)
for i, group in enumerate(self._get_groups(server)):
name = 'fake-2-%s' % i
self.assertEqual(group.get('name'), name)
def test_detail(self):
url = '/v3/servers/detail'
res = self._make_request(url)
self.assertEqual(res.status_int, 200)
for i, server in enumerate(self._get_servers(res.body)):
for j, group in enumerate(self._get_groups(server)):
name = 'fake-%s-%s' % (i, j)
self.assertEqual(group.get('name'), name)
def test_no_instance_passthrough_404(self):
def fake_compute_get(*args, **kwargs):
raise exception.InstanceNotFound(instance_id='fake')
self.stubs.Set(compute.api.API, 'get', fake_compute_get)
url = '/v3/servers/70f6db34-de8d-4fbd-aafb-4065bdfa6115'
res = self._make_request(url)
self.assertEqual(res.status_int, 404)
class ServersControllerCreateTest(test.TestCase):
def setUp(self):
"""Shared implementation for tests below that create instance."""
super(ServersControllerCreateTest, self).setUp()
self.flags(verbose=True,
enable_instance_password=True)
self.instance_cache_num = 0
self.instance_cache_by_id = {}
self.instance_cache_by_uuid = {}
ext_info = plugins.LoadedExtensionInfo()
self.controller = servers.ServersController(extension_info=ext_info)
CONF.set_override('extensions_blacklist', 'os-security-groups',
'osapi_v3')
self.no_security_groups_controller = servers.ServersController(
extension_info=ext_info)
def instance_create(context, inst):
inst_type = flavors.get_flavor_by_flavor_id(3)
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
def_image_ref = 'http://localhost/images/%s' % image_uuid
self.instance_cache_num += 1
instance = fake_instance.fake_db_instance(**{
'id': self.instance_cache_num,
'display_name': inst['display_name'] or 'test',
'uuid': FAKE_UUID,
'instance_type': dict(inst_type),
'access_ip_v4': '1.2.3.4',
'access_ip_v6': 'fead::1234',
'image_ref': inst.get('image_ref', def_image_ref),
'user_id': 'fake',
'project_id': 'fake',
'reservation_id': inst['reservation_id'],
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
"user_data": None,
"progress": 0,
"fixed_ips": [],
"task_state": "",
"vm_state": "",
"root_device_name": inst.get('root_device_name', 'vda'),
})
self.instance_cache_by_id[instance['id']] = instance
self.instance_cache_by_uuid[instance['uuid']] = instance
return instance
def instance_get(context, instance_id):
"""Stub for compute/api create() pulling in instance after
scheduling
"""
return self.instance_cache_by_id[instance_id]
def instance_update(context, uuid, values):
instance = self.instance_cache_by_uuid[uuid]
instance.update(values)
return instance
def server_update(context, instance_uuid, params):
inst = self.instance_cache_by_uuid[instance_uuid]
inst.update(params)
return (inst, inst)
def fake_method(*args, **kwargs):
pass
def project_get_networks(context, user_id):
return dict(id='1', host='localhost')
def queue_get_for(context, *args):
return 'network_topic'
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_key_pair_funcs(self.stubs)
nova.tests.image.fake.stub_out_image_service(self.stubs)
fakes.stub_out_nw_api(self.stubs)
self.stubs.Set(uuid, 'uuid4', fake_gen_uuid)
self.stubs.Set(db, 'instance_add_security_group',
return_security_group)
self.stubs.Set(db, 'project_get_networks',
project_get_networks)
self.stubs.Set(db, 'instance_create', instance_create)
self.stubs.Set(db, 'instance_system_metadata_update',
fake_method)
self.stubs.Set(db, 'instance_get', instance_get)
self.stubs.Set(db, 'instance_update', instance_update)
self.stubs.Set(db, 'instance_update_and_get_original',
server_update)
self.stubs.Set(manager.VlanManager, 'allocate_fixed_ip',
fake_method)
def _test_create_extra(self, params, no_image=False,
override_controller=None):
image_uuid = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
server = dict(name='server_test', imageRef=image_uuid, flavorRef=2)
if no_image:
server.pop('imageRef', None)
server.update(params)
body = dict(server=server)
req = fakes.HTTPRequestV3.blank('/servers')
req.method = 'POST'
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
if override_controller:
server = override_controller.create(req, body=body).obj['server']
else:
server = self.controller.create(req, body=body).obj['server']
def test_create_instance_with_security_group_enabled(self):
group = 'foo'
old_create = compute_api.API.create
def sec_group_get(ctx, proj, name):
if name == group:
return True
else:
raise exception.SecurityGroupNotFoundForProject(
project_id=proj, security_group_id=name)
def create(*args, **kwargs):
self.assertEqual(kwargs['security_group'], [group])
return old_create(*args, **kwargs)
self.stubs.Set(db, 'security_group_get_by_name', sec_group_get)
# negative test
self.assertRaises(webob.exc.HTTPBadRequest,
self._test_create_extra,
{security_groups.ATTRIBUTE_NAME:
[{'name': 'bogus'}]})
# positive test - extra assert in create path
self.stubs.Set(compute_api.API, 'create', create)
self._test_create_extra({security_groups.ATTRIBUTE_NAME:
[{'name': group}]})
def test_create_instance_with_security_group_disabled(self):
group = 'foo'
params = {'security_groups': [{'name': group}]}
old_create = compute_api.API.create
def create(*args, **kwargs):
# NOTE(vish): if the security groups extension is not
# enabled, then security groups passed in
# are ignored.
self.assertNotIn('security_group', kwargs)
return old_create(*args, **kwargs)
self.stubs.Set(compute_api.API, 'create', create)
self._test_create_extra(params,
override_controller=self.no_security_groups_controller)
def test_create_with_invalid_key_security_group(self):
param = {security_groups.ATTRIBUTE_NAME: [{'invalid': 'group'}]}
self.assertRaises(exception.ValidationError,
self._test_create_extra, param)
def test_create_with_no_string_value_security_group(self):
param = {security_groups.ATTRIBUTE_NAME: [{'name': 12345}]}
self.assertRaises(exception.ValidationError,
self._test_create_extra, param)
def test_create_with_too_long_value_security_group(self):
param = {security_groups.ATTRIBUTE_NAME: [{'name': ('a' * 260)}]}
self.assertRaises(exception.ValidationError,
self._test_create_extra, param)

View File

@ -62,7 +62,7 @@
"os-server-usage:launched_at": "%(strtime)s",
"os-server-usage:terminated_at": null,
"progress": 0,
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "default"
}

View File

@ -12,7 +12,7 @@
"rel": "bookmark"
}
],
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "default"
}

View File

@ -63,7 +63,7 @@
"os-server-usage:launched_at": "%(strtime)s",
"os-server-usage:terminated_at": null,
"progress": 0,
"os-security-groups:security_groups": [
"security_groups": [
{
"name": "default"
}

View File

@ -50,7 +50,7 @@
"tenant_id": "openstack",
"updated": "%(isotime)s",
"user_id": "fake",
"os-security-groups:security_groups": [{"name": "test"}],
"security_groups": [{"name": "test"}],
"key_name": null
}
}

View File

@ -6,6 +6,6 @@
"metadata" : {
"My Server Name" : "Apache1"
},
"os-security-groups:security_groups": [{"name": "test"}]
"security_groups": [{"name": "test"}]
}
}

View File

@ -12,6 +12,6 @@
"rel": "bookmark"
}
],
"os-security-groups:security_groups": [{"name": "test"}]
"security_groups": [{"name": "test"}]
}
}

View File

@ -51,7 +51,7 @@
"status": "ACTIVE",
"tenant_id": "openstack",
"user_id": "fake",
"os-security-groups:security_groups": [{"name": "test"}],
"security_groups": [{"name": "test"}],
"key_name": null
}]
}