c269285568
Move these to the central place. There's a large amount of test damage but it's pretty trivial. Change-Id: If581eb7aa463c9dde13714f34f0f1b41549a7130 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
258 lines
9.0 KiB
Python
258 lines
9.0 KiB
Python
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# 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 testscenarios
|
|
|
|
from nova import context
|
|
from nova import exception as ex
|
|
from nova import objects
|
|
from nova import test
|
|
from nova.tests import fixtures as nova_fixtures
|
|
from nova.tests.functional import integrated_helpers as helper
|
|
|
|
|
|
def rand_flavor(**kwargs):
|
|
flav = {
|
|
'name': 'name-%s' % helper.generate_random_alphanumeric(10),
|
|
'id': helper.generate_random_alphanumeric(10),
|
|
'ram': int(helper.generate_random_numeric(2)) + 1,
|
|
'disk': int(helper.generate_random_numeric(3)),
|
|
'vcpus': int(helper.generate_random_numeric(1)) + 1,
|
|
}
|
|
flav.update(kwargs)
|
|
return flav
|
|
|
|
|
|
class FlavorManageFullstack(testscenarios.WithScenarios, test.TestCase):
|
|
"""Tests for flavors manage administrative command.
|
|
|
|
Extension: os-flavors-manage
|
|
|
|
os-flavors-manage adds a set of admin functions to the flavors
|
|
resource for the creation and deletion of flavors.
|
|
|
|
POST /v2/flavors:
|
|
|
|
::
|
|
|
|
{
|
|
'name': NAME, # string, required unique
|
|
'id': ID, # string, required unique
|
|
'ram': RAM, # in MB, required
|
|
'vcpus': VCPUS, # int value, required
|
|
'disk': DISK, # in GB, required
|
|
'OS-FLV-EXT-DATA:ephemeral', # in GB, ephemeral disk size
|
|
'is_public': IS_PUBLIC, # boolean
|
|
'swap': SWAP, # in GB?
|
|
'rxtx_factor': RXTX, # ???
|
|
}
|
|
|
|
Returns Flavor
|
|
|
|
DELETE /v2/flavors/ID
|
|
|
|
|
|
Functional Test Scope:
|
|
|
|
This test starts the wsgi stack for the nova api services, uses an
|
|
in memory database to ensure the path through the wsgi layer to
|
|
the database.
|
|
|
|
"""
|
|
|
|
_additional_fixtures = []
|
|
|
|
scenarios = [
|
|
# test v2.1 base microversion
|
|
('v2_1', {
|
|
'api_major_version': 'v2.1'}),
|
|
]
|
|
|
|
def setUp(self):
|
|
super(FlavorManageFullstack, self).setUp()
|
|
|
|
# load any additional fixtures specified by the scenario
|
|
for fix in self._additional_fixtures:
|
|
self.useFixture(fix())
|
|
|
|
self.useFixture(nova_fixtures.RealPolicyFixture())
|
|
api_fixture = self.useFixture(
|
|
nova_fixtures.OSAPIFixture(
|
|
api_version=self.api_major_version))
|
|
|
|
# NOTE(sdague): because this test is primarily an admin API
|
|
# test default self.api to the admin api.
|
|
self.api = api_fixture.admin_api
|
|
self.user_api = api_fixture.api
|
|
|
|
def assertFlavorDbEqual(self, flav, flavdb):
|
|
# a mapping of the REST params to the db fields
|
|
mapping = {
|
|
'name': 'name',
|
|
'disk': 'root_gb',
|
|
'ram': 'memory_mb',
|
|
'vcpus': 'vcpus',
|
|
'id': 'flavorid',
|
|
'swap': 'swap'
|
|
}
|
|
for k, v in mapping.items():
|
|
if k in flav:
|
|
self.assertEqual(flav[k], flavdb[v],
|
|
"%s != %s" % (flav, flavdb))
|
|
|
|
def assertFlavorAPIEqual(self, flav, flavapi):
|
|
# for all keys in the flavor, ensure they are correctly set in
|
|
# flavapi response.
|
|
for k in flav:
|
|
if k in flavapi:
|
|
self.assertEqual(flav[k], flavapi[k],
|
|
"%s != %s" % (flav, flavapi))
|
|
else:
|
|
self.fail("Missing key: %s in flavor: %s" % (k, flavapi))
|
|
|
|
def assertFlavorInList(self, flav, flavlist):
|
|
for item in flavlist['flavors']:
|
|
if flav['id'] == item['id']:
|
|
self.assertEqual(flav['name'], item['name'])
|
|
return
|
|
self.fail("%s not found in %s" % (flav, flavlist))
|
|
|
|
def assertFlavorNotInList(self, flav, flavlist):
|
|
for item in flavlist['flavors']:
|
|
if flav['id'] == item['id']:
|
|
self.fail("%s found in %s" % (flav, flavlist))
|
|
|
|
def test_flavor_manage_func_negative(self):
|
|
"""Test flavor manage edge conditions.
|
|
|
|
- Bogus body is a 400
|
|
- Unknown flavor is a 404
|
|
- Deleting unknown flavor is a 404
|
|
"""
|
|
# Test for various API failure conditions
|
|
# bad body is 400
|
|
resp = self.api.api_post('flavors', '', check_response_status=False)
|
|
self.assertEqual(400, resp.status)
|
|
|
|
# get unknown flavor is 404
|
|
resp = self.api.api_delete('flavors/foo', check_response_status=False)
|
|
self.assertEqual(404, resp.status)
|
|
|
|
# delete unknown flavor is 404
|
|
resp = self.api.api_delete('flavors/foo', check_response_status=False)
|
|
self.assertEqual(404, resp.status)
|
|
|
|
ctx = context.get_admin_context()
|
|
# bounds conditions - invalid vcpus
|
|
flav = {'flavor': rand_flavor(vcpus=0)}
|
|
resp = self.api.api_post('flavors', flav, check_response_status=False)
|
|
self.assertEqual(400, resp.status, resp)
|
|
# ... and ensure that we didn't leak it into the db
|
|
self.assertRaises(ex.FlavorNotFound,
|
|
objects.Flavor.get_by_flavor_id,
|
|
ctx, flav['flavor']['id'])
|
|
|
|
# bounds conditions - invalid ram
|
|
flav = {'flavor': rand_flavor(ram=0)}
|
|
|
|
resp = self.api.api_post('flavors', flav, check_response_status=False)
|
|
self.assertEqual(400, resp.status)
|
|
# ... and ensure that we didn't leak it into the db
|
|
self.assertRaises(ex.FlavorNotFound,
|
|
objects.Flavor.get_by_flavor_id,
|
|
ctx, flav['flavor']['id'])
|
|
|
|
# NOTE(sdague): if there are other bounds conditions that
|
|
# should be checked, stack them up here.
|
|
|
|
def test_flavor_manage_deleted(self):
|
|
"""Ensure the behavior around a deleted flavor is stable.
|
|
|
|
- Fetching a deleted flavor works, and returns the flavor info.
|
|
- Listings should not contain deleted flavors
|
|
|
|
"""
|
|
# create a deleted flavor
|
|
new_flav = {'flavor': rand_flavor()}
|
|
self.api.api_post('flavors', new_flav)
|
|
self.api.api_delete('flavors/%s' % new_flav['flavor']['id'])
|
|
|
|
# deleted flavor should not show up in a list
|
|
resp = self.api.api_get('flavors')
|
|
self.assertFlavorNotInList(new_flav['flavor'], resp.body)
|
|
|
|
def test_flavor_manage_func(self):
|
|
"""Basic flavor creation lifecycle testing.
|
|
|
|
- Creating a flavor
|
|
- Ensure it's in the database
|
|
- Ensure it's in the listing
|
|
- Delete it
|
|
- Ensure it's hidden in the database
|
|
"""
|
|
|
|
ctx = context.get_admin_context()
|
|
flav1 = {
|
|
'flavor': rand_flavor(),
|
|
}
|
|
|
|
# Create flavor and ensure it made it to the database
|
|
self.api.api_post('flavors', flav1)
|
|
|
|
flav1db = objects.Flavor.get_by_flavor_id(ctx, flav1['flavor']['id'])
|
|
self.assertFlavorDbEqual(flav1['flavor'], flav1db)
|
|
|
|
# Ensure new flavor is seen in the listing
|
|
resp = self.api.api_get('flavors')
|
|
self.assertFlavorInList(flav1['flavor'], resp.body)
|
|
|
|
# Delete flavor and ensure it was removed from the database
|
|
self.api.api_delete('flavors/%s' % flav1['flavor']['id'])
|
|
self.assertRaises(ex.FlavorNotFound,
|
|
objects.Flavor.get_by_flavor_id,
|
|
ctx, flav1['flavor']['id'])
|
|
|
|
resp = self.api.api_delete('flavors/%s' % flav1['flavor']['id'],
|
|
check_response_status=False)
|
|
self.assertEqual(404, resp.status)
|
|
|
|
def test_flavor_manage_permissions(self):
|
|
"""Ensure that regular users can't create or delete flavors.
|
|
|
|
"""
|
|
ctx = context.get_admin_context()
|
|
flav1 = {'flavor': rand_flavor()}
|
|
|
|
# Ensure user can't create flavor
|
|
resp = self.user_api.api_post('flavors', flav1,
|
|
check_response_status=False)
|
|
self.assertEqual(403, resp.status)
|
|
# ... and that it didn't leak through
|
|
self.assertRaises(ex.FlavorNotFound,
|
|
objects.Flavor.get_by_flavor_id,
|
|
ctx, flav1['flavor']['id'])
|
|
|
|
# Create the flavor as the admin user
|
|
self.api.api_post('flavors', flav1)
|
|
|
|
# Ensure user can't delete flavors from our cloud
|
|
resp = self.user_api.api_delete('flavors/%s' % flav1['flavor']['id'],
|
|
check_response_status=False)
|
|
self.assertEqual(403, resp.status)
|
|
# ... and ensure that we didn't actually delete the flavor,
|
|
# this will throw an exception if we did.
|
|
objects.Flavor.get_by_flavor_id(ctx, flav1['flavor']['id'])
|