Change v3 flavor_access to v2.1

This patch changes v3 flavor_access API to v2.1 and makes v2
unit tests share between v2 and v2.1.

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: I75891e76753fba937b4a01f592afb4bd03aacd9c
This commit is contained in:
jichenjc
2014-07-24 15:27:23 +08:00
committed by Chris Yeoh
parent 4f0ce8ac8c
commit d9b729f5ad
35 changed files with 184 additions and 554 deletions

View File

@@ -1,5 +1,5 @@
{
"add_tenant_access": {
"tenant_id": "fake_tenant"
"addTenantAccess": {
"tenant": "fake_tenant"
}
}
}

View File

@@ -5,6 +5,6 @@
"vcpus": 2,
"disk": 10,
"id": "10",
"flavor-access:is_public": false
"os-flavor-access:is_public": false
}
}
}

View File

@@ -3,7 +3,7 @@
"disabled": false,
"disk": 10,
"ephemeral": 0,
"flavor-access:is_public": false,
"os-flavor-access:is_public": false,
"id": "10",
"links": [
{
@@ -20,4 +20,4 @@
"swap": 0,
"vcpus": 2
}
}
}

View File

@@ -4,7 +4,7 @@
"disabled": false,
"disk": 1,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{
@@ -25,7 +25,7 @@
"disabled": false,
"disk": 20,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "2",
"links": [
{
@@ -46,7 +46,7 @@
"disabled": false,
"disk": 40,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "3",
"links": [
{
@@ -67,7 +67,7 @@
"disabled": false,
"disk": 80,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "4",
"links": [
{
@@ -88,7 +88,7 @@
"disabled": false,
"disk": 160,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "5",
"links": [
{
@@ -106,4 +106,4 @@
"vcpus": 8
}
]
}
}

View File

@@ -1,5 +1,5 @@
{
"remove_tenant_access": {
"tenant_id": "fake_tenant"
"removeTenantAccess": {
"tenant": "fake_tenant"
}
}
}

View File

@@ -3,7 +3,7 @@
"disabled": false,
"disk": 1,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{
@@ -20,4 +20,4 @@
"swap": 0,
"vcpus": 1
}
}
}

View File

@@ -3,7 +3,7 @@
"disabled": false,
"disk": 10,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "10",
"links": [
{
@@ -20,4 +20,4 @@
"swap": 0,
"vcpus": 2
}
}
}

View File

@@ -3,7 +3,7 @@
"disabled": false,
"disk": 1,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{
@@ -20,4 +20,4 @@
"swap": 0,
"vcpus": 1
}
}
}

View File

@@ -4,7 +4,7 @@
"disabled": false,
"disk": 1,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{
@@ -25,7 +25,7 @@
"disabled": false,
"disk": 20,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "2",
"links": [
{
@@ -46,7 +46,7 @@
"disabled": false,
"disk": 40,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "3",
"links": [
{
@@ -67,7 +67,7 @@
"disabled": false,
"disk": 80,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "4",
"links": [
{
@@ -88,7 +88,7 @@
"disabled": false,
"disk": 160,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "5",
"links": [
{
@@ -106,4 +106,4 @@
"vcpus": 8
}
]
}
}

View File

@@ -3,7 +3,7 @@
"disabled": false,
"disk": 1,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{

View File

@@ -4,7 +4,7 @@
"disabled": false,
"disk": 1,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "1",
"links": [
{
@@ -26,7 +26,7 @@
"disabled": false,
"disk": 20,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "2",
"links": [
{
@@ -48,7 +48,7 @@
"disabled": false,
"disk": 40,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "3",
"links": [
{
@@ -70,7 +70,7 @@
"disabled": false,
"disk": 80,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "4",
"links": [
{
@@ -92,7 +92,7 @@
"disabled": false,
"disk": 160,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "5",
"links": [
{

View File

@@ -3,7 +3,7 @@
"disabled": false,
"disk": 10,
"ephemeral": 0,
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"id": "100",
"links": [
{

View File

@@ -123,10 +123,10 @@
"compute_extension:flavor_access": "",
"compute_extension:flavor_access:addTenantAccess": "rule:admin_api",
"compute_extension:flavor_access:removeTenantAccess": "rule:admin_api",
"compute_extension:v3:flavor-access": "",
"compute_extension:v3:flavor-access:discoverable": "",
"compute_extension:v3:flavor-access:remove_tenant_access": "rule:admin_api",
"compute_extension:v3:flavor-access:add_tenant_access": "rule:admin_api",
"compute_extension:v3:os-flavor-access": "",
"compute_extension:v3:os-flavor-access:discoverable": "",
"compute_extension:v3:os-flavor-access:remove_tenant_access": "rule:admin_api",
"compute_extension:v3:os-flavor-access:add_tenant_access": "rule:admin_api",
"compute_extension:flavor_disabled": "",
"compute_extension:flavor_rxtx": "",
"compute_extension:v3:os-flavor-rxtx": "",

View File

@@ -64,12 +64,12 @@ CONF.register_opts(api_opts, api_opts_group)
# TODO(cyeoh): Expand this list as the core APIs are ported to V3
API_V3_CORE_EXTENSIONS = set(['consoles',
'extensions',
'flavor-access',
'flavor-extra-specs',
'flavor-manage',
'flavors',
'ips',
'os-keypairs',
'os-flavor-access',
'server-metadata',
'servers',
'versions'])

View File

@@ -25,7 +25,7 @@ from nova import exception
from nova.i18n import _
from nova import objects
ALIAS = 'flavor-access'
ALIAS = 'os-flavor-access'
soft_authorize = extensions.soft_extension_authorizer('compute',
'v3:' + ALIAS)
authorize = extensions.extension_authorizer('compute', 'v3:%s' % ALIAS)
@@ -106,14 +106,14 @@ class FlavorActionController(wsgi.Controller):
self._extend_flavor(resp_obj.obj['flavor'], db_flavor)
@extensions.expected_errors((400, 403, 404, 409))
@wsgi.action("add_tenant_access")
@wsgi.action("addTenantAccess")
@validation.schema(flavor_access.add_tenant_access)
def _add_tenant_access(self, req, id, body):
context = req.environ['nova.context']
authorize(context, action="add_tenant_access")
vals = body['add_tenant_access']
tenant = vals['tenant_id']
vals = body['addTenantAccess']
tenant = vals['tenant']
flavor = objects.Flavor(context=context, flavorid=id)
try:
@@ -127,14 +127,14 @@ class FlavorActionController(wsgi.Controller):
return _marshall_flavor_access(flavor)
@extensions.expected_errors((400, 403, 404))
@wsgi.action("remove_tenant_access")
@wsgi.action("removeTenantAccess")
@validation.schema(flavor_access.remove_tenant_access)
def _remove_tenant_access(self, req, id, body):
context = req.environ['nova.context']
authorize(context, action="remove_tenant_access")
vals = body['remove_tenant_access']
tenant = vals['tenant_id']
vals = body['removeTenantAccess']
tenant = vals['tenant']
flavor = objects.Flavor(context=context, flavorid=id)
try:

View File

@@ -66,7 +66,7 @@ class FlavorManageController(wsgi.Controller):
ephemeral_gb = vals.get('ephemeral', 0)
swap = vals.get('swap', 0)
rxtx_factor = vals.get('rxtx_factor', 1.0)
is_public = vals.get('flavor-access:is_public', True)
is_public = vals.get('os-flavor-access:is_public', True)
try:
flavor = flavors.create(name, memory, vcpus, root_gb,

View File

@@ -15,19 +15,19 @@
add_tenant_access = {
'type': 'object',
'properties': {
'add_tenant_access': {
'addTenantAccess': {
'type': 'object',
'properties': {
'tenant_id': {
'tenant': {
# defined from project_id in instance_type_projects table
'type': 'string', 'minLength': 1, 'maxLength': 255,
},
},
'required': ['tenant_id'],
'required': ['tenant'],
'additionalProperties': False,
},
},
'required': ['add_tenant_access'],
'required': ['addTenantAccess'],
'additionalProperties': False,
}
@@ -35,18 +35,18 @@ add_tenant_access = {
remove_tenant_access = {
'type': 'object',
'properties': {
'remove_tenant_access': {
'removeTenantAccess': {
'type': 'object',
'properties': {
'tenant_id': {
'tenant': {
# defined from project_id in instance_type_projects table
'type': 'string', 'minLength': 1, 'maxLength': 255,
},
},
'required': ['tenant_id'],
'required': ['tenant'],
'additionalProperties': False,
},
},
'required': ['remove_tenant_access'],
'required': ['removeTenantAccess'],
'additionalProperties': False,
}

View File

@@ -59,7 +59,7 @@ create = {
'pattern': '^[0-9]+(\.[0-9]+)?$',
'minimum': 0, 'exclusiveMinimum': True
},
'flavor-access:is_public': parameter_types.boolean,
'os-flavor-access:is_public': parameter_types.boolean,
},
# TODO(oomichi): 'id' should be required with v2.1+microversions.
# On v2.0 API, nova-api generates a flavor-id automatically if

View File

@@ -18,8 +18,11 @@ import datetime
from lxml import etree
from webob import exc
from nova.api.openstack.compute.contrib import flavor_access
from nova.api.openstack.compute.contrib import flavor_access \
as flavor_access_v2
from nova.api.openstack.compute import flavors as flavors_api
from nova.api.openstack.compute.plugins.v3 import flavor_access \
as flavor_access_v3
from nova import context
from nova import db
from nova import exception
@@ -117,12 +120,16 @@ class FakeResponse(object):
pass
class FlavorAccessTest(test.NoDBTestCase):
class FlavorAccessTestV21(test.NoDBTestCase):
api_version = "2.1"
FlavorAccessController = flavor_access_v3.FlavorAccessController
FlavorActionController = flavor_access_v3.FlavorActionController
_prefix = "/v3"
validation_ex = exception.ValidationError
def setUp(self):
super(FlavorAccessTest, self).setUp()
super(FlavorAccessTestV21, self).setUp()
self.flavor_controller = flavors_api.Controller()
self.flavor_access_controller = flavor_access.FlavorAccessController()
self.flavor_action_controller = flavor_access.FlavorActionController()
self.req = FakeRequest()
self.context = self.req.environ['nova.context']
self.stubs.Set(db, 'flavor_get_by_flavor_id',
@@ -132,6 +139,9 @@ class FlavorAccessTest(test.NoDBTestCase):
self.stubs.Set(db, 'flavor_access_get_by_flavor_id',
fake_get_flavor_access_by_flavor_id)
self.flavor_access_controller = self.FlavorAccessController()
self.flavor_action_controller = self.FlavorActionController()
def _verify_flavor_list(self, result, expected):
# result already sorted by flavor_id
self.assertEqual(len(result), len(expected))
@@ -153,14 +163,19 @@ class FlavorAccessTest(test.NoDBTestCase):
self.assertEqual(result, expected)
def test_list_with_no_context(self):
req = fakes.HTTPRequest.blank('/v2/flavors/fake/flavors')
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/fake/flavors')
def fake_authorize(context, target=None, action=None):
raise exception.PolicyNotAuthorized(action='index')
self.stubs.Set(flavor_access,
'authorize',
fake_authorize)
if self.api_version == "2.1":
self.stubs.Set(flavor_access_v3,
'authorize',
fake_authorize)
else:
self.stubs.Set(flavor_access_v2,
'authorize',
fake_authorize)
self.assertRaises(exception.PolicyNotAuthorized,
self.flavor_access_controller.index,
@@ -168,7 +183,7 @@ class FlavorAccessTest(test.NoDBTestCase):
def test_list_flavor_with_admin_default_proj1(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors',
req = fakes.HTTPRequest.blank(self._prefix + '/fake/flavors',
use_admin_context=True)
req.environ['nova.context'].project_id = 'proj1'
result = self.flavor_controller.index(req)
@@ -176,7 +191,7 @@ class FlavorAccessTest(test.NoDBTestCase):
def test_list_flavor_with_admin_default_proj2(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}, {'id': '2'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors',
req = fakes.HTTPRequest.blank(self._prefix + '/flavors',
use_admin_context=True)
req.environ['nova.context'].project_id = 'proj2'
result = self.flavor_controller.index(req)
@@ -184,21 +199,24 @@ class FlavorAccessTest(test.NoDBTestCase):
def test_list_flavor_with_admin_ispublic_true(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=true',
url = self._prefix + '/flavors?is_public=true'
req = fakes.HTTPRequest.blank(url,
use_admin_context=True)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_admin_ispublic_false(self):
expected = {'flavors': [{'id': '2'}, {'id': '3'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=false',
url = self._prefix + '/flavors?is_public=false'
req = fakes.HTTPRequest.blank(url,
use_admin_context=True)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_admin_ispublic_false_proj2(self):
expected = {'flavors': [{'id': '2'}, {'id': '3'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=false',
url = self._prefix + '/flavors?is_public=false'
req = fakes.HTTPRequest.blank(url,
use_admin_context=True)
req.environ['nova.context'].project_id = 'proj2'
result = self.flavor_controller.index(req)
@@ -207,35 +225,39 @@ class FlavorAccessTest(test.NoDBTestCase):
def test_list_flavor_with_admin_ispublic_none(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}, {'id': '2'},
{'id': '3'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=none',
url = self._prefix + '/flavors?is_public=none'
req = fakes.HTTPRequest.blank(url,
use_admin_context=True)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_no_admin_default(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors',
req = fakes.HTTPRequest.blank(self._prefix + '/flavors',
use_admin_context=False)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_no_admin_ispublic_true(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=true',
url = self._prefix + '/flavors?is_public=true'
req = fakes.HTTPRequest.blank(url,
use_admin_context=False)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_no_admin_ispublic_false(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=false',
url = self._prefix + '/flavors?is_public=false'
req = fakes.HTTPRequest.blank(url,
use_admin_context=False)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_no_admin_ispublic_none(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=none',
url = self._prefix + '/flavors?is_public=none'
req = fakes.HTTPRequest.blank(url,
use_admin_context=False)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
@@ -262,6 +284,18 @@ class FlavorAccessTest(test.NoDBTestCase):
self.assertEqual({'id': '0', 'os-flavor-access:is_public': True},
resp.obj['flavor'])
def _get_add_access(self):
if self.api_version == "2.1":
return self.flavor_action_controller._add_tenant_access
else:
return self.flavor_action_controller._addTenantAccess
def _get_remove_access(self):
if self.api_version == "2.1":
return self.flavor_action_controller._remove_tenant_access
else:
return self.flavor_action_controller._removeTenantAccess
def test_add_tenant_access(self):
def stub_add_flavor_access(context, flavorid, projectid):
self.assertEqual('3', flavorid, "flavorid")
@@ -271,31 +305,31 @@ class FlavorAccessTest(test.NoDBTestCase):
expected = {'flavor_access':
[{'flavor_id': '3', 'tenant_id': 'proj3'}]}
body = {'addTenantAccess': {'tenant': 'proj2'}}
req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action',
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
result = self.flavor_action_controller.\
_addTenantAccess(req, '3', body)
add_access = self._get_add_access()
result = add_access(req, '3', body=body)
self.assertEqual(result, expected)
def test_add_tenant_access_with_no_admin_user(self):
req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action',
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=False)
body = {'addTenantAccess': {'tenant': 'proj2'}}
add_access = self._get_add_access()
self.assertRaises(exception.PolicyNotAuthorized,
self.flavor_action_controller._addTenantAccess,
req, '2', body)
add_access, req, '2', body=body)
def test_add_tenant_access_with_no_tenant(self):
req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action',
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
body = {'addTenantAccess': {'foo': 'proj2'}}
self.assertRaises(exc.HTTPBadRequest,
self.flavor_action_controller._addTenantAccess,
req, '2', body)
add_access = self._get_add_access()
self.assertRaises(self.validation_ex,
add_access, req, '2', body=body)
body = {'addTenantAccess': {'tenant': ''}}
self.assertRaises(exc.HTTPBadRequest,
self.flavor_action_controller._addTenantAccess,
req, '2', body)
self.assertRaises(self.validation_ex,
add_access, req, '2', body=body)
def test_add_tenant_access_with_already_added_access(self):
def stub_add_flavor_access(context, flavorid, projectid):
@@ -304,9 +338,9 @@ class FlavorAccessTest(test.NoDBTestCase):
self.stubs.Set(db, 'flavor_access_add',
stub_add_flavor_access)
body = {'addTenantAccess': {'tenant': 'proj2'}}
add_access = self._get_add_access()
self.assertRaises(exc.HTTPConflict,
self.flavor_action_controller._addTenantAccess,
self.req, '3', body)
add_access, self.req, '3', body=body)
def test_remove_tenant_access_with_bad_access(self):
def stub_remove_flavor_access(context, flavorid, projectid):
@@ -315,34 +349,41 @@ class FlavorAccessTest(test.NoDBTestCase):
self.stubs.Set(db, 'flavor_access_remove',
stub_remove_flavor_access)
body = {'removeTenantAccess': {'tenant': 'proj2'}}
remove_access = self._get_remove_access()
self.assertRaises(exc.HTTPNotFound,
self.flavor_action_controller._removeTenantAccess,
self.req, '3', body)
remove_access, self.req, '3', body=body)
def test_delete_tenant_access_with_no_tenant(self):
req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action',
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
remove_access = self._get_remove_access()
body = {'removeTenantAccess': {'foo': 'proj2'}}
self.assertRaises(exc.HTTPBadRequest,
self.flavor_action_controller._removeTenantAccess,
req, '2', body)
self.assertRaises(self.validation_ex,
remove_access, req, '2', body=body)
body = {'removeTenantAccess': {'tenant': ''}}
self.assertRaises(exc.HTTPBadRequest,
self.flavor_action_controller._removeTenantAccess,
req, '2', body)
self.assertRaises(self.validation_ex,
remove_access, req, '2', body=body)
def test_remove_tenant_access_with_no_admin_user(self):
req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action',
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=False)
body = {'removeTenantAccess': {'tenant': 'proj2'}}
remove_access = self._get_remove_access()
self.assertRaises(exception.PolicyNotAuthorized,
self.flavor_action_controller._removeTenantAccess,
req, '2', body)
remove_access, req, '2', body=body)
class FlavorAccessTestV20(FlavorAccessTestV21):
api_version = "2.0"
FlavorAccessController = flavor_access_v2.FlavorAccessController
FlavorActionController = flavor_access_v2.FlavorActionController
_prefix = "/v2/fake"
validation_ex = exc.HTTPBadRequest
class FlavorAccessSerializerTest(test.NoDBTestCase):
def test_serializer_empty(self):
serializer = flavor_access.FlavorAccessTemplate()
serializer = flavor_access_v2.FlavorAccessTemplate()
text = serializer.serialize(dict(flavor_access=[]))
tree = etree.fromstring(text)
self.assertEqual(len(tree), 0)
@@ -356,6 +397,6 @@ class FlavorAccessSerializerTest(test.NoDBTestCase):
access_list = [{'flavor_id': '2', 'tenant_id': 'proj2'},
{'flavor_id': '2', 'tenant_id': 'proj3'}]
serializer = flavor_access.FlavorAccessTemplate()
serializer = flavor_access_v2.FlavorAccessTemplate()
text = serializer.serialize(dict(flavor_access=access_list))
self.assertEqual(text, expected)

View File

@@ -1,412 +0,0 @@
# Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# 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
from webob import exc
from nova.api.openstack.compute import flavors as flavors_api
from nova.api.openstack.compute.plugins.v3 import flavor_access
from nova import context
from nova import db
from nova import exception
from nova import test
from nova.tests.api.openstack import fakes
def generate_flavor(flavorid, ispublic):
return {
'id': flavorid,
'flavorid': str(flavorid),
'root_gb': 1,
'ephemeral_gb': 1,
'name': u'test',
'deleted': False,
'created_at': datetime.datetime(2012, 1, 1, 1, 1, 1, 1),
'updated_at': None,
'memory_mb': 512,
'vcpus': 1,
'swap': 512,
'rxtx_factor': 1.0,
'disabled': False,
'extra_specs': {},
'deleted_at': None,
'vcpu_weight': None,
'is_public': bool(ispublic)
}
INSTANCE_TYPES = {
'0': generate_flavor(0, True),
'1': generate_flavor(1, True),
'2': generate_flavor(2, False),
'3': generate_flavor(3, False)}
ACCESS_LIST = [{'flavor_id': '2', 'project_id': 'proj2'},
{'flavor_id': '2', 'project_id': 'proj3'},
{'flavor_id': '3', 'project_id': 'proj3'}]
def fake_get_flavor_access_by_flavor_id(context, flavorid):
res = []
for access in ACCESS_LIST:
if access['flavor_id'] == flavorid:
res.append(access)
return res
def fake_get_flavor_by_flavor_id(context, flavorid, read_deleted):
return INSTANCE_TYPES[flavorid]
def _has_flavor_access(flavorid, projectid):
for access in ACCESS_LIST:
if access['flavor_id'] == flavorid and \
access['project_id'] == projectid:
return True
return False
def fake_get_all_flavors_sorted_list(context, inactive=False,
filters=None, sort_key='flavorid',
sort_dir='asc', limit=None, marker=None):
if filters is None or filters['is_public'] is None:
return sorted(INSTANCE_TYPES.values(), key=lambda item: item[sort_key])
res = {}
for k, v in INSTANCE_TYPES.iteritems():
if filters['is_public'] and _has_flavor_access(k, context.project_id):
res.update({k: v})
continue
if v['is_public'] == filters['is_public']:
res.update({k: v})
res = sorted(res.values(), key=lambda item: item[sort_key])
return res
class FakeRequest(object):
environ = {"nova.context": context.get_admin_context()}
def get_db_flavor(self, flavor_id):
return INSTANCE_TYPES[flavor_id]
class FakeResponse(object):
obj = {'flavor': {'id': '0'},
'flavors': [
{'id': '0'},
{'id': '2'}]
}
def attach(self, **kwargs):
pass
class FlavorAccessTest(test.NoDBTestCase):
def setUp(self):
super(FlavorAccessTest, self).setUp()
self.flavor_controller = flavors_api.Controller()
self.flavor_access_controller = flavor_access.FlavorAccessController()
self.flavor_action_controller = flavor_access.FlavorActionController()
self.req = FakeRequest()
self.context = self.req.environ['nova.context']
self.stubs.Set(db, 'flavor_get_by_flavor_id',
fake_get_flavor_by_flavor_id)
self.stubs.Set(db, 'flavor_get_all',
fake_get_all_flavors_sorted_list)
self.stubs.Set(db, 'flavor_access_get_by_flavor_id',
fake_get_flavor_access_by_flavor_id)
def _verify_flavor_list(self, result, expected):
# result already sorted by flavor_id
self.assertEqual(len(result), len(expected))
for d1, d2 in zip(result, expected):
self.assertEqual(d1['id'], d2['id'])
def test_list_flavor_access_public(self):
# query flavor-access on public flavor should return 404
self.assertRaises(exc.HTTPNotFound,
self.flavor_access_controller.index,
self.req, '1')
def test_list_flavor_access_private(self):
expected = {'flavor_access': [
{'flavor_id': '2', 'tenant_id': 'proj2'},
{'flavor_id': '2', 'tenant_id': 'proj3'}]}
result = self.flavor_access_controller.index(self.req, '2')
self.assertEqual(result, expected)
def test_list_with_no_context(self):
req = fakes.HTTPRequestV3.blank('/flavors/2/flavor-access')
def fake_authorize(context, target=None, action=None):
raise exception.PolicyNotAuthorized(action='index')
self.stubs.Set(flavor_access, 'authorize', fake_authorize)
self.assertRaises(exception.PolicyNotAuthorized,
self.flavor_access_controller.index,
req, '2')
def test_list_flavor_with_admin_default_proj1(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequestV3.blank('/flavors',
use_admin_context=True)
req.environ['nova.context'].project_id = 'proj1'
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_admin_default_proj2(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}, {'id': '2'}]}
req = fakes.HTTPRequestV3.blank('/flavors',
use_admin_context=True)
req.environ['nova.context'].project_id = 'proj2'
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_admin_ispublic_true(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequestV3.blank('/flavors?is_public=true',
use_admin_context=True)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_admin_ispublic_false(self):
expected = {'flavors': [{'id': '2'}, {'id': '3'}]}
req = fakes.HTTPRequestV3.blank('/flavors?is_public=false',
use_admin_context=True)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_admin_ispublic_false_proj2(self):
expected = {'flavors': [{'id': '2'}, {'id': '3'}]}
req = fakes.HTTPRequestV3.blank('/flavors?is_public=false',
use_admin_context=True)
req.environ['nova.context'].project_id = 'proj2'
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_admin_ispublic_none(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}, {'id': '2'},
{'id': '3'}]}
req = fakes.HTTPRequestV3.blank('/flavors?is_public=none',
use_admin_context=True)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_no_admin_default(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequestV3.blank('/flavors',
use_admin_context=False)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_no_admin_ispublic_true(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequestV3.blank('/flavors?is_public=true',
use_admin_context=False)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_no_admin_ispublic_false(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequestV3.blank('/flavors?is_public=false',
use_admin_context=False)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_list_flavor_with_no_admin_ispublic_none(self):
expected = {'flavors': [{'id': '0'}, {'id': '1'}]}
req = fakes.HTTPRequestV3.blank('/flavors?is_public=none',
use_admin_context=False)
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
def test_show(self):
resp = FakeResponse()
self.flavor_action_controller.show(self.req, resp, '0')
self.assertEqual({'id': '0', 'flavor-access:is_public': True},
resp.obj['flavor'])
self.flavor_action_controller.show(self.req, resp, '2')
self.assertEqual({'id': '0', 'flavor-access:is_public': False},
resp.obj['flavor'])
def test_detail(self):
resp = FakeResponse()
self.flavor_action_controller.detail(self.req, resp)
self.assertEqual([{'id': '0', 'flavor-access:is_public': True},
{'id': '2', 'flavor-access:is_public': False}],
resp.obj['flavors'])
def test_create(self):
resp = FakeResponse()
self.flavor_action_controller.create(self.req, {}, resp)
self.assertEqual({'id': '0', 'flavor-access:is_public': True},
resp.obj['flavor'])
def test_add_tenant_access(self):
def stub_add_flavor_access(context, flavorid, projectid):
self.assertEqual('3', flavorid, "flavorid")
self.assertEqual("proj2", projectid, "projectid")
self.stubs.Set(db, 'flavor_access_add',
stub_add_flavor_access)
expected = {'flavor_access':
[{'flavor_id': '3', 'tenant_id': 'proj3'}]}
body = {'add_tenant_access': {'tenant_id': 'proj2'}}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
result = self.flavor_action_controller.\
_add_tenant_access(req, '3', body=body)
self.assertEqual(result, expected)
def test_add_tenant_access_with_non_existed_flavor(self):
def stub_flavor_access_add(context, flavorid, project_id):
raise exception.FlavorNotFound(flavor_id=flavorid)
self.stubs.Set(db, 'flavor_access_add',
stub_flavor_access_add)
body = {'add_tenant_access': {'tenant_id': 'proj2'}}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
self.assertRaises(exc.HTTPNotFound,
self.flavor_action_controller._add_tenant_access,
req, '3', body=body)
def test_add_tenant_access_with_no_admin_user(self):
req = fakes.HTTPRequestV3.blank('/flavors/2/action')
body = {'add_tenant_access': {'tenant_id': 'proj2'}}
self.assertRaises(exception.PolicyNotAuthorized,
self.flavor_action_controller._add_tenant_access,
req, '2', body=body)
def test_add_tenant_access_without_policy_check(self):
req = fakes.HTTPRequestV3.blank('/flavors/3/action')
body = {'add_tenant_access': {'tenant_id': 'proj2'}}
def fake_authorize(context, target=None, action=None):
pass
self.stubs.Set(flavor_access, 'authorize', fake_authorize)
self.assertRaises(exc.HTTPForbidden,
self.flavor_action_controller._add_tenant_access,
req, '3', body=body)
def test_add_tenant_access_without_tenant_id(self):
def stub_add_flavor_access(context, flavorid, projectid):
raise exception.FlavorNotFound(flavor_id=flavorid)
self.stubs.Set(db, 'flavor_access_add',
stub_add_flavor_access)
body = {'add_tenant_access': {}}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
self.assertRaises(exception.ValidationError,
self.flavor_action_controller._add_tenant_access,
req, '3', body=body)
def test_add_tenant_access_with_invalid_request(self):
def stub_add_flavor_access(context, flavorid, projectid):
raise exception.FlavorNotFound(flavor_id=flavorid)
self.stubs.Set(db, 'flavor_access_add',
stub_add_flavor_access)
body = {'add_tenant_access': None}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
self.assertRaises(exception.ValidationError,
self.flavor_action_controller._add_tenant_access,
req, '3', body=body)
def test_add_tenant_access_with_already_added_access(self):
def stub_add_flavor_access(context, flavorid, projectid):
raise exception.FlavorAccessExists(flavor_id=flavorid,
project_id=projectid)
self.stubs.Set(db, 'flavor_access_add',
stub_add_flavor_access)
body = {'add_tenant_access': {'tenant_id': 'proj2'}}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
self.assertRaises(exc.HTTPConflict,
self.flavor_action_controller._add_tenant_access,
req, '3', body=body)
def test_remove_tenant_access_with_bad_access(self):
def stub_remove_flavor_access(context, flavorid, projectid):
raise exception.FlavorAccessNotFound(flavor_id=flavorid,
project_id=projectid)
self.stubs.Set(db, 'flavor_access_remove',
stub_remove_flavor_access)
body = {'remove_tenant_access': {'tenant_id': 'proj2'}}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
self.assertRaises(exc.HTTPNotFound,
self.flavor_action_controller._remove_tenant_access,
req, '3', body=body)
def test_remove_tenant_access_with_non_existed_flavor(self):
def stub_remove_flavor_access(context, flavorid, projectid):
raise exception.FlavorNotFound(flavor_id=flavorid)
self.stubs.Set(db, 'flavor_access_remove',
stub_remove_flavor_access)
body = {'remove_tenant_access': {'tenant_id': 'proj2'}}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
self.assertRaises(exc.HTTPNotFound,
self.flavor_action_controller._remove_tenant_access,
req, '3', body=body)
def test_remove_tenant_access_without_tenant_id(self):
def stub_remove_flavor_access(context, flavorid, projectid):
raise exception.FlavorNotFound(flavor_id=flavorid)
self.stubs.Set(db, 'flavor_access_remove',
stub_remove_flavor_access)
body = {'remove_tenant_access': {}}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
self.assertRaises(exception.ValidationError,
self.flavor_action_controller._remove_tenant_access,
req, '3', body=body)
def test_remove_tenant_access_with_invalid_request(self):
def stub_remove_flavor_access(context, flavorid, projectid):
raise exception.FlavorNotFound(flavor_id=flavorid)
self.stubs.Set(db, 'flavor_access_remove',
stub_remove_flavor_access)
body = {'remove_tenant_access': None}
req = fakes.HTTPRequestV3.blank('/flavors/3/action',
use_admin_context=True)
self.assertRaises(exception.ValidationError,
self.flavor_action_controller._remove_tenant_access,
req, '3', body=body)
def test_remove_tenant_access_with_no_admin_user(self):
req = fakes.HTTPRequestV3.blank('flavors/2/action',
use_admin_context=False)
body = {'remove_tenant_access': {'tenant_id': 'proj2'}}
self.assertRaises(exception.PolicyNotAuthorized,
self.flavor_action_controller._remove_tenant_access,
req, '2', body=body)
def test_remove_tenant_access_without_policy_check(self):
req = fakes.HTTPRequestV3.blank('/flavors/2/action')
body = {'remove_tenant_access': {'tenant_id': 'proj2'}}
def fake_authorize(context, target=None, action=None):
pass
self.stubs.Set(flavor_access, 'authorize', fake_authorize)
self.assertRaises(exc.HTTPForbidden,
self.flavor_action_controller._remove_tenant_access,
req, '2', body=body)

View File

@@ -94,7 +94,7 @@ class FlavorManageTest(test.NoDBTestCase):
self.app = fakes.wsgi_app_v3(init_only=('servers', 'flavors',
'flavor-manage',
'os-flavor-rxtx',
'flavor-access'))
'os-flavor-access'))
self.base_request_dict = {
"flavor": {
@@ -106,7 +106,7 @@ class FlavorManageTest(test.NoDBTestCase):
"id": unicode('1234'),
"swap": 512,
"rxtx_factor": 1,
"flavor-access:is_public": True,
"os-flavor-access:is_public": True,
}
}
@@ -120,7 +120,7 @@ class FlavorManageTest(test.NoDBTestCase):
"id": unicode('1234'),
"swap": 512,
"rxtx_factor": 1,
"flavor-access:is_public": True,
"os-flavor-access:is_public": True,
}
}
@@ -232,7 +232,7 @@ class FlavorManageTest(test.NoDBTestCase):
"id": unicode('1234'),
"swap": 512,
"rxtx_factor": 1,
"flavor-access:is_public": True,
"os-flavor-access:is_public": True,
}
}
url = '/v3/flavors'
@@ -308,7 +308,8 @@ class FlavorManageTest(test.NoDBTestCase):
def test_create_with_non_boolean_is_public(self):
is_public = 1234
self.base_request_dict['flavor']['flavor-access:is_public'] = is_public
self.base_request_dict['flavor']['os-flavor-access:is_public'] =\
is_public
self._test_create_bad_request(self.base_request_dict)
@@ -339,7 +340,7 @@ class PrivateFlavorManageTest(test.TestCase):
"id": unicode('1234'),
"swap": 512,
"rxtx_factor": 1,
"flavor-access:is_public": False
"os-flavor-access:is_public": False
}
}
@@ -371,7 +372,7 @@ class PrivateFlavorManageTest(test.TestCase):
flavor_access_body["flavor_access"])
def test_create_public_flavor_should_not_create_flavor_access(self):
self.base_request_dict['flavor']['flavor-access:is_public'] = True
self.base_request_dict['flavor']['os-flavor-access:is_public'] = True
expected = {
"flavor": {
"name": "test",

View File

@@ -184,10 +184,10 @@ policy_data = """
"compute_extension:flavor_access": "",
"compute_extension:flavor_access:addTenantAccess": "rule:admin_api",
"compute_extension:flavor_access:removeTenantAccess": "rule:admin_api",
"compute_extension:v3:flavor-access": "",
"compute_extension:v3:flavor-access:remove_tenant_access":
"compute_extension:v3:os-flavor-access": "",
"compute_extension:v3:os-flavor-access:remove_tenant_access":
"rule:admin_api",
"compute_extension:v3:flavor-access:add_tenant_access":
"compute_extension:v3:os-flavor-access:add_tenant_access":
"rule:admin_api",
"compute_extension:flavor_disabled": "",
"compute_extension:v3:os-flavor-disabled": "",

View File

@@ -1,5 +1,5 @@
{
"add_tenant_access": {
"tenant_id": "%(tenant_id)s"
"addTenantAccess": {
"tenant": "%(tenant_id)s"
}
}

View File

@@ -5,6 +5,6 @@
"vcpus": 2,
"disk": 10,
"id": "%(flavor_id)s",
"flavor-access:is_public": false
"os-flavor-access:is_public": false
}
}

View File

@@ -13,7 +13,7 @@
}
],
"name": "%(flavor_name)s",
"flavor-access:is_public": false,
"os-flavor-access:is_public": false,
"ram": 1024,
"vcpus": 2,
"disabled": false,

View File

@@ -16,7 +16,7 @@
}
],
"name": "m1.tiny",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 512,
"swap": 0,
"vcpus": 1
@@ -37,7 +37,7 @@
}
],
"name": "m1.small",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 2048,
"swap": 0,
"vcpus": 1
@@ -58,7 +58,7 @@
}
],
"name": "m1.medium",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 4096,
"swap": 0,
"vcpus": 2
@@ -79,7 +79,7 @@
}
],
"name": "m1.large",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 8192,
"swap": 0,
"vcpus": 4
@@ -100,7 +100,7 @@
}
],
"name": "m1.xlarge",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 16384,
"swap": 0,
"vcpus": 8

View File

@@ -1,5 +1,5 @@
{
"remove_tenant_access": {
"tenant_id": "%(tenant_id)s"
"removeTenantAccess": {
"tenant": "%(tenant_id)s"
}
}

View File

@@ -13,7 +13,7 @@
}
],
"name": "m1.tiny",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 512,
"vcpus": 1,
"disabled": false,

View File

@@ -13,7 +13,7 @@
}
],
"name": "%(flavor_name)s",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 1024,
"vcpus": 2,
"disabled": false,

View File

@@ -13,7 +13,7 @@
}
],
"name": "m1.tiny",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 512,
"vcpus": 1,
"disabled": false,

View File

@@ -16,7 +16,7 @@
}
],
"name": "m1.tiny",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 512,
"swap": 0,
"vcpus": 1
@@ -37,7 +37,7 @@
}
],
"name": "m1.small",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 2048,
"swap": 0,
"vcpus": 1
@@ -58,7 +58,7 @@
}
],
"name": "m1.medium",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 4096,
"swap": 0,
"vcpus": 2
@@ -79,7 +79,7 @@
}
],
"name": "m1.large",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 8192,
"swap": 0,
"vcpus": 4
@@ -100,7 +100,7 @@
}
],
"name": "m1.xlarge",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 16384,
"swap": 0,
"vcpus": 8

View File

@@ -15,7 +15,7 @@
}
],
"name": "m1.tiny",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 512,
"rxtx_factor": 1.0,
"swap": 0,

View File

@@ -16,7 +16,7 @@
}
],
"name": "m1.tiny",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 512,
"rxtx_factor": 1.0,
"swap": 0,
@@ -38,7 +38,7 @@
}
],
"name": "m1.small",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 2048,
"rxtx_factor": 1.0,
"swap": 0,
@@ -60,7 +60,7 @@
}
],
"name": "m1.medium",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 4096,
"rxtx_factor": 1.0,
"swap": 0,
@@ -82,7 +82,7 @@
}
],
"name": "m1.large",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 8192,
"rxtx_factor": 1.0,
"swap": 0,
@@ -104,7 +104,7 @@
}
],
"name": "m1.xlarge",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 16384,
"rxtx_factor": 1.0,
"swap": 0,

View File

@@ -13,7 +13,7 @@
}
],
"name": "%(flavor_name)s",
"flavor-access:is_public": true,
"os-flavor-access:is_public": true,
"ram": 1024,
"rxtx_factor": 2.0,
"vcpus": 2,

View File

@@ -52,7 +52,7 @@ class FlavorAccessSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):
self._create_flavor()
self._add_tenant()
flavor_id = 10
response = self._do_get('flavors/%s/flavor-access' % flavor_id)
response = self._do_get('flavors/%s/os-flavor-access' % flavor_id)
subs = {
'flavor_id': flavor_id,
'tenant_id': 'fake_tenant',