Merge "Merge flavor extensions controller code"
This commit is contained in:
commit
1878039f8f
|
@ -58,41 +58,6 @@ class FlavorAccessController(wsgi.Controller):
|
|||
|
||||
class FlavorActionController(wsgi.Controller):
|
||||
"""The flavor access API controller for the OpenStack API."""
|
||||
def _extend_flavor(self, flavor_rval, flavor_ref):
|
||||
key = "os-flavor-access:is_public"
|
||||
flavor_rval[key] = flavor_ref['is_public']
|
||||
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
context = req.environ['nova.context']
|
||||
if context.can(fa_policies.BASE_POLICY_NAME, fatal=False):
|
||||
db_flavor = req.get_db_flavor(id)
|
||||
|
||||
self._extend_flavor(resp_obj.obj['flavor'], db_flavor)
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['nova.context']
|
||||
if context.can(fa_policies.BASE_POLICY_NAME, fatal=False):
|
||||
flavors = list(resp_obj.obj['flavors'])
|
||||
for flavor_rval in flavors:
|
||||
db_flavor = req.get_db_flavor(flavor_rval['id'])
|
||||
self._extend_flavor(flavor_rval, db_flavor)
|
||||
|
||||
@wsgi.extends(action='create')
|
||||
def create(self, req, body, resp_obj):
|
||||
context = req.environ['nova.context']
|
||||
if context.can(fa_policies.BASE_POLICY_NAME, fatal=False):
|
||||
db_flavor = req.get_db_flavor(resp_obj.obj['flavor']['id'])
|
||||
|
||||
self._extend_flavor(resp_obj.obj['flavor'], db_flavor)
|
||||
|
||||
@wsgi.extends(action='update')
|
||||
def update(self, req, id, body, resp_obj):
|
||||
context = req.environ['nova.context']
|
||||
if context.can(fa_policies.BASE_POLICY_NAME, fatal=False):
|
||||
db_flavor = req.get_db_flavor(resp_obj.obj['flavor']['id'])
|
||||
self._extend_flavor(resp_obj.obj['flavor'], db_flavor)
|
||||
|
||||
@extensions.expected_errors((400, 403, 404, 409))
|
||||
@wsgi.action("addTenantAccess")
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""The Flavor Rxtx API extension."""
|
||||
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.policies import flavor_rxtx as fr_policies
|
||||
|
||||
ALIAS = 'os-flavor-rxtx'
|
||||
|
||||
|
||||
class FlavorRxtxController(wsgi.Controller):
|
||||
def _extend_flavors(self, req, flavors):
|
||||
for flavor in flavors:
|
||||
db_flavor = req.get_db_flavor(flavor['id'])
|
||||
key = 'rxtx_factor'
|
||||
flavor[key] = db_flavor['rxtx_factor'] or ""
|
||||
|
||||
def _show(self, req, resp_obj):
|
||||
context = req.environ['nova.context']
|
||||
if not context.can(fr_policies.BASE_POLICY_NAME, fatal=False):
|
||||
return
|
||||
if 'flavor' in resp_obj.obj:
|
||||
self._extend_flavors(req, [resp_obj.obj['flavor']])
|
||||
|
||||
@wsgi.extends
|
||||
def show(self, req, resp_obj, id):
|
||||
return self._show(req, resp_obj)
|
||||
|
||||
@wsgi.extends(action='create')
|
||||
def create(self, req, resp_obj, body):
|
||||
return self._show(req, resp_obj)
|
||||
|
||||
@wsgi.extends(action='update')
|
||||
def update(self, req, id, body, resp_obj):
|
||||
return self._show(req, resp_obj)
|
||||
|
||||
@wsgi.extends
|
||||
def detail(self, req, resp_obj):
|
||||
context = req.environ['nova.context']
|
||||
if not context.can(fr_policies.BASE_POLICY_NAME, fatal=False):
|
||||
return
|
||||
self._extend_flavors(req, list(resp_obj.obj['flavors']))
|
|
@ -43,7 +43,6 @@ from nova.api.openstack.compute import extension_info
|
|||
from nova.api.openstack.compute import fixed_ips
|
||||
from nova.api.openstack.compute import flavor_access
|
||||
from nova.api.openstack.compute import flavor_manage
|
||||
from nova.api.openstack.compute import flavor_rxtx
|
||||
from nova.api.openstack.compute import flavors
|
||||
from nova.api.openstack.compute import flavors_extraspecs
|
||||
from nova.api.openstack.compute import floating_ip_dns
|
||||
|
@ -160,10 +159,7 @@ fixed_ips_controller = functools.partial(_create_controller,
|
|||
|
||||
flavor_controller = functools.partial(_create_controller,
|
||||
flavors.FlavorsController,
|
||||
[
|
||||
flavor_rxtx.FlavorRxtxController,
|
||||
flavor_access.FlavorActionController
|
||||
],
|
||||
[],
|
||||
[
|
||||
flavor_manage.FlavorManageController,
|
||||
flavor_access.FlavorActionController
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack import common
|
||||
from nova.policies import flavor_access as fa_policies
|
||||
from nova.policies import flavor_rxtx as fr_policies
|
||||
|
||||
FLAVOR_DESCRIPTION_MICROVERSION = '2.55'
|
||||
|
||||
|
@ -23,7 +25,11 @@ class ViewBuilder(common.ViewBuilder):
|
|||
|
||||
_collection_name = "flavors"
|
||||
|
||||
def basic(self, request, flavor, include_description=False):
|
||||
def basic(self, request, flavor, include_description=False,
|
||||
update_is_public=None, update_rxtx_factor=None):
|
||||
# update_is_public & update_rxtx_factor are placeholder param
|
||||
# which are not used in this method as basic() method is used by
|
||||
# index() (GET /flavors) which does not return those keys in response.
|
||||
flavor_dict = {
|
||||
"flavor": {
|
||||
"id": flavor["flavorid"],
|
||||
|
@ -39,7 +45,8 @@ class ViewBuilder(common.ViewBuilder):
|
|||
|
||||
return flavor_dict
|
||||
|
||||
def show(self, request, flavor, include_description=False):
|
||||
def show(self, request, flavor, include_description=False,
|
||||
update_is_public=None, update_rxtx_factor=None):
|
||||
flavor_dict = {
|
||||
"flavor": {
|
||||
"id": flavor["flavorid"],
|
||||
|
@ -59,6 +66,26 @@ class ViewBuilder(common.ViewBuilder):
|
|||
if include_description:
|
||||
flavor_dict['flavor']['description'] = flavor.description
|
||||
|
||||
# TODO(gmann): 'update_is_public' & 'update_rxtx_factor' are policies
|
||||
# checks. Once os-flavor-access & os-flavor-rxtx policies are
|
||||
# removed, 'os-flavor-access:is_public' and 'rxtx_factor' need to be
|
||||
# added in response without any check.
|
||||
|
||||
# Evaluate the policies when using show method directly.
|
||||
context = request.environ['nova.context']
|
||||
if update_is_public is None:
|
||||
update_is_public = context.can(fa_policies.BASE_POLICY_NAME,
|
||||
fatal=False)
|
||||
if update_rxtx_factor is None:
|
||||
update_rxtx_factor = context.can(fr_policies.BASE_POLICY_NAME,
|
||||
fatal=False)
|
||||
if update_is_public:
|
||||
flavor_dict['flavor'].update({
|
||||
"os-flavor-access:is_public": flavor['is_public']})
|
||||
if update_rxtx_factor:
|
||||
flavor_dict['flavor'].update(
|
||||
{"rxtx_factor": flavor['rxtx_factor'] or ""})
|
||||
|
||||
return flavor_dict
|
||||
|
||||
def index(self, request, flavors):
|
||||
|
@ -74,11 +101,19 @@ class ViewBuilder(common.ViewBuilder):
|
|||
coll_name = self._collection_name + '/detail'
|
||||
include_description = api_version_request.is_supported(
|
||||
request, FLAVOR_DESCRIPTION_MICROVERSION)
|
||||
context = request.environ['nova.context']
|
||||
update_is_public = context.can(fa_policies.BASE_POLICY_NAME,
|
||||
fatal=False)
|
||||
update_rxtx_factor = context.can(fr_policies.BASE_POLICY_NAME,
|
||||
fatal=False)
|
||||
return self._list_view(self.show, request, flavors, coll_name,
|
||||
include_description=include_description)
|
||||
include_description=include_description,
|
||||
update_is_public=update_is_public,
|
||||
update_rxtx_factor=update_rxtx_factor)
|
||||
|
||||
def _list_view(self, func, request, flavors, coll_name,
|
||||
include_description=False):
|
||||
include_description=False, update_is_public=None,
|
||||
update_rxtx_factor=None):
|
||||
"""Provide a view for a list of flavors.
|
||||
|
||||
:param func: Function used to format the flavor data
|
||||
|
@ -88,10 +123,15 @@ class ViewBuilder(common.ViewBuilder):
|
|||
for a pagination query
|
||||
:param include_description: If the flavor.description should be
|
||||
included in the response dict.
|
||||
:param update_is_public: If the flavor.is_public field should be
|
||||
included in the response dict.
|
||||
:param update_rxtx_factor: If the flavor.rxtx_factor field should be
|
||||
included in the response dict.
|
||||
|
||||
:returns: Flavor reply data in dictionary format
|
||||
"""
|
||||
flavor_list = [func(request, flavor, include_description)["flavor"]
|
||||
flavor_list = [func(request, flavor, include_description,
|
||||
update_is_public, update_rxtx_factor)["flavor"]
|
||||
for flavor in flavors]
|
||||
flavors_links = self._get_collection_links(request,
|
||||
flavors,
|
||||
|
|
|
@ -257,28 +257,6 @@ class FlavorAccessTestV21(test.NoDBTestCase):
|
|||
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', 'os-flavor-access:is_public': True},
|
||||
resp.obj['flavor'])
|
||||
self.flavor_action_controller.show(self.req, resp, '2')
|
||||
self.assertEqual({'id': '0', 'os-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', 'os-flavor-access:is_public': True},
|
||||
{'id': '2', 'os-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', 'os-flavor-access:is_public': True},
|
||||
resp.obj['flavor'])
|
||||
|
||||
def test_add_tenant_access(self):
|
||||
def stub_add_flavor_access(context, flavor_id, projectid):
|
||||
self.assertEqual(3, flavor_id, "flavor_id")
|
||||
|
@ -423,21 +401,6 @@ class FlavorAccessPolicyEnforcementV21(test.NoDBTestCase):
|
|||
"Policy doesn't allow %s to be performed." % rule_name,
|
||||
exc.format_message())
|
||||
|
||||
def test_extend_create_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-flavor-access"
|
||||
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||
self.act_controller.create(self.req, None, None)
|
||||
|
||||
def test_extend_show_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-flavor-access"
|
||||
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||
self.act_controller.show(self.req, None, None)
|
||||
|
||||
def test_extend_detail_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-flavor-access"
|
||||
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||
self.act_controller.detail(self.req, None)
|
||||
|
||||
def test_index_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-flavor-access"
|
||||
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||
|
|
|
@ -361,6 +361,7 @@ class FlavorManageTestV2_55(FlavorManageTestV21):
|
|||
ephemeral_gb=flavor['OS-FLV-EXT-DATA:ephemeral'],
|
||||
disabled=flavor['OS-FLV-DISABLED:disabled'],
|
||||
is_public=flavor['os-flavor-access:is_public'],
|
||||
rxtx_factor=flavor['rxtx_factor'],
|
||||
description=flavor['description'])
|
||||
# Now null out the flavor description.
|
||||
flavor = self.controller._update(
|
||||
|
@ -535,7 +536,8 @@ class FlavorManagerPolicyEnforcementV21(test.TestCase):
|
|||
default_flavor_policy = "os_compute_api:os-flavor-manage"
|
||||
create_flavor_policy = "os_compute_api:os-flavor-manage:create"
|
||||
rules = {default_flavor_policy: 'is_admin:True',
|
||||
create_flavor_policy: 'rule:%s' % default_flavor_policy}
|
||||
create_flavor_policy: 'rule:%s' % default_flavor_policy,
|
||||
"os_compute_api:os-flavor-access": "project:non_fake"}
|
||||
self.policy.set_rules(rules)
|
||||
body = {
|
||||
"flavor": {
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from nova import test
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
|
||||
|
||||
class FlavorRxtxTestV21(test.NoDBTestCase):
|
||||
content_type = 'application/json'
|
||||
_prefix = "/v2/fake"
|
||||
|
||||
def setUp(self):
|
||||
super(FlavorRxtxTestV21, self).setUp()
|
||||
fakes.stub_out_nw_api(self)
|
||||
fakes.stub_out_flavor_get_all(self)
|
||||
fakes.stub_out_flavor_get_by_flavor_id(self)
|
||||
|
||||
def _make_request(self, url):
|
||||
req = fakes.HTTPRequest.blank(url)
|
||||
req.headers['Accept'] = self.content_type
|
||||
res = req.get_response(self._get_app())
|
||||
return res
|
||||
|
||||
def _get_app(self):
|
||||
return fakes.wsgi_app_v21()
|
||||
|
||||
def _get_flavor(self, body):
|
||||
return jsonutils.loads(body).get('flavor')
|
||||
|
||||
def _get_flavors(self, body):
|
||||
return jsonutils.loads(body).get('flavors')
|
||||
|
||||
def assertFlavorRxtx(self, flavor, rxtx):
|
||||
self.assertEqual(flavor.get('rxtx_factor'), rxtx or u'')
|
||||
|
||||
def test_show(self):
|
||||
url = self._prefix + '/flavors/1'
|
||||
res = self._make_request(url)
|
||||
|
||||
self.assertEqual(res.status_int, 200)
|
||||
self.assertFlavorRxtx(self._get_flavor(res.body),
|
||||
fakes.FLAVORS['1'].rxtx_factor)
|
||||
|
||||
def test_detail(self):
|
||||
url = self._prefix + '/flavors/detail'
|
||||
res = self._make_request(url)
|
||||
|
||||
self.assertEqual(res.status_int, 200)
|
||||
flavors = self._get_flavors(res.body)
|
||||
self.assertFlavorRxtx(flavors[0],
|
||||
fakes.FLAVORS['1'].rxtx_factor)
|
||||
self.assertFlavorRxtx(flavors[1],
|
||||
fakes.FLAVORS['2'].rxtx_factor)
|
|
@ -91,6 +91,8 @@ class FlavorsTestV21(test.TestCase):
|
|||
"ram": fakes.FLAVORS['1'].memory_mb,
|
||||
"disk": fakes.FLAVORS['1'].root_gb,
|
||||
"vcpus": fakes.FLAVORS['1'].vcpus,
|
||||
"os-flavor-access:is_public": True,
|
||||
"rxtx_factor": 1.0,
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
|
@ -121,6 +123,8 @@ class FlavorsTestV21(test.TestCase):
|
|||
"ram": fakes.FLAVORS['1'].memory_mb,
|
||||
"disk": fakes.FLAVORS['1'].root_gb,
|
||||
"vcpus": fakes.FLAVORS['1'].vcpus,
|
||||
"os-flavor-access:is_public": True,
|
||||
"rxtx_factor": 1.0,
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
|
@ -237,6 +241,8 @@ class FlavorsTestV21(test.TestCase):
|
|||
"ram": fakes.FLAVORS['1'].memory_mb,
|
||||
"disk": fakes.FLAVORS['1'].root_gb,
|
||||
"vcpus": fakes.FLAVORS['1'].vcpus,
|
||||
"os-flavor-access:is_public": True,
|
||||
"rxtx_factor": 1.0,
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
|
@ -360,6 +366,8 @@ class FlavorsTestV21(test.TestCase):
|
|||
"ram": fakes.FLAVORS['1'].memory_mb,
|
||||
"disk": fakes.FLAVORS['1'].root_gb,
|
||||
"vcpus": fakes.FLAVORS['1'].vcpus,
|
||||
"os-flavor-access:is_public": True,
|
||||
"rxtx_factor": 1.0,
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
|
@ -379,6 +387,8 @@ class FlavorsTestV21(test.TestCase):
|
|||
"ram": fakes.FLAVORS['2'].memory_mb,
|
||||
"disk": fakes.FLAVORS['2'].root_gb,
|
||||
"vcpus": fakes.FLAVORS['2'].vcpus,
|
||||
"os-flavor-access:is_public": True,
|
||||
"rxtx_factor": '',
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
|
@ -490,6 +500,8 @@ class FlavorsTestV21(test.TestCase):
|
|||
"ram": fakes.FLAVORS['2'].memory_mb,
|
||||
"disk": fakes.FLAVORS['2'].root_gb,
|
||||
"vcpus": fakes.FLAVORS['2'].vcpus,
|
||||
"os-flavor-access:is_public": True,
|
||||
"rxtx_factor": '',
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
|
@ -515,6 +527,54 @@ class FlavorsTestV2_55(FlavorsTestV21):
|
|||
expect_description = True
|
||||
|
||||
|
||||
class FlavorsPolicyEnforcementV21(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FlavorsPolicyEnforcementV21, self).setUp()
|
||||
self.flavor_controller = flavors_v21.FlavorsController()
|
||||
fakes.stub_out_flavor_get_by_flavor_id(self)
|
||||
fakes.stub_out_flavor_get_all(self)
|
||||
self.req = fakes.HTTPRequest.blank('')
|
||||
|
||||
def test_show_flavor_access_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-flavor-access"
|
||||
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||
resp = self.flavor_controller.show(self.req, '1')
|
||||
self.assertNotIn('os-flavor-access:is_public', resp['flavor'])
|
||||
|
||||
def test_detail_flavor_access_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-flavor-access"
|
||||
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||
resp = self.flavor_controller.detail(self.req)
|
||||
self.assertNotIn('os-flavor-access:is_public', resp['flavors'][0])
|
||||
|
||||
def test_show_flavor_rxtx_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-flavor-rxtx"
|
||||
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||
resp = self.flavor_controller.show(self.req, '1')
|
||||
self.assertNotIn('rxtx_factor', resp['flavor'])
|
||||
|
||||
def test_detail_flavor_rxtx_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-flavor-rxtx"
|
||||
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||
resp = self.flavor_controller.detail(self.req)
|
||||
self.assertNotIn('rxtx_factor', resp['flavors'][0])
|
||||
|
||||
def test_create_flavor_extended_policy_failed(self):
|
||||
rules = {"os_compute_api:os-flavor-rxtx": "project:non_fake",
|
||||
"os_compute_api:os-flavor-access": "project:non_fake"}
|
||||
self.policy.set_rules(rules)
|
||||
resp = self.flavor_controller.detail(self.req)
|
||||
self.assertNotIn('rxtx_factor', resp['flavors'][0])
|
||||
|
||||
def test_update_flavor_extended_policy_failed(self):
|
||||
rules = {"os_compute_api:os-flavor-rxtx": "project:non_fake",
|
||||
"os_compute_api:os-flavor-access": "project:non_fake"}
|
||||
self.policy.set_rules(rules)
|
||||
resp = self.flavor_controller.detail(self.req)
|
||||
self.assertNotIn('rxtx_factor', resp['flavors'][0])
|
||||
|
||||
|
||||
class DisabledFlavorsWithRealDBTestV21(test.TestCase):
|
||||
"""Tests that disabled flavors should not be shown nor listed."""
|
||||
Controller = flavors_v21.FlavorsController
|
||||
|
|
Loading…
Reference in New Issue