API validations for Flavor-synchronization.

Initial commit for flavor synchronization.
Performs some basic validations like
-Check whether the flavor is present in source-region.
-Check whether the access to sync flavor is restricted only to admin.
-Added test-cases for the same.

Partially Implements: resource-syncing

Change-Id: Ieca068aec981b0db6ffc4f4d563b021342c58d60
This commit is contained in:
Goutham Pratapa 2017-07-27 17:03:03 +05:30
parent ea7caa8567
commit 2f6dab7fc1
6 changed files with 139 additions and 4 deletions

View File

@ -154,6 +154,24 @@ class ResourceSyncController(object):
source_resources,
resource_type, job_id)
return self._image_sync(job_id, payload, context, result)
elif resource_type == consts.FLAVOR:
if not context.is_admin:
pecan.abort(403, _('Admin required'))
session = EndpointCache().get_session_from_token(
context.auth_token, context.project)
# Create Source Region object
source_nova_client = NovaClient(source_region, session)
for flavor in source_resources:
source_flavor = source_nova_client.get_flavor(flavor)
if not source_flavor:
pecan.abort(404)
result = self._entries_to_database(context, target_regions,
source_region,
source_resources,
resource_type, job_id)
return self._flavor_sync(job_id, payload, context, result)
else:
pecan.abort(400, _('Bad resource_type'))
@ -210,3 +228,16 @@ class ResourceSyncController(object):
self.rpc_client.image_sync(context, job_id, payload)
return {'job_status': {'id': result.id, 'status': result.sync_status,
'created_at': result.created_at}}
def _flavor_sync(self, job_id, payload, context, result):
"""Make an rpc call to engine.
:param job_id: ID of the job to update values in database based on
the job_id.
:param payload: payload object.
:param context: context of the request.
:param result: Result object to return an output.
"""
self.rpc_client.flavor_sync(context, job_id, payload)
return {'job_status': {'id': result.id, 'status': result.sync_status,
'created_at': result.created_at}}

View File

@ -49,3 +49,5 @@ JOB_SUCCESS = "SUCCESS"
JOB_FAILURE = "FAILURE"
IMAGE = "image"
FLAVOR = "flavor"

View File

@ -132,3 +132,14 @@ class NovaClient(base.DriverBase):
return self.nova_client.keypairs. \
create(keypair.name,
public_key=keypair.public_key)
def get_flavor(self, res_id):
"""Get Flavor for a specified context."""
try:
flavor = self.nova_client.flavors.get(res_id)
LOG.info("Source Flavor: %s", flavor.name)
return flavor
except exceptions.ResourceNotFound():
LOG.error('Exception Occurred: Source Flavor %s not available',
res_id)
pass

View File

@ -79,3 +79,8 @@ class EngineClient(object):
return self.cast(
ctxt,
self.make_msg('image_sync', job_id=job_id, payload=payload))
def flavor_sync(self, ctxt, job_id, payload):
return self.cast(
ctxt,
self.make_msg('flavor_sync', job_id=job_id, payload=payload))

View File

@ -26,6 +26,7 @@ from kingbird.tests import utils
DEFAULT_FORCE = False
SOURCE_KEYPAIR = 'fake_key1'
SOURCE_FLAVOR = 'fake_flavor1'
SOURCE_IMAGE_NAME = 'fake_image'
SOURCE_IMAGE_ID = utils.UUID4
WRONG_SOURCE_IMAGE_ID = utils.UUID5
@ -41,7 +42,8 @@ fake_user = utils.UUID3
FAKE_STATUS = consts.JOB_PROGRESS
FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin',
'X-Identity-Status': 'Confirmed'}
NON_ADMIN_HEADERS = {'X-Tenant-Id': FAKE_TENANT}
NON_ADMIN_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'fake_admin',
'X-Identity-Status': 'Confirmed'}
class FakeKeypair(object):
@ -50,6 +52,17 @@ class FakeKeypair(object):
self.public_key = public_key
class Fake_Flavor(object):
def __init__(self, id, ram, cores, disks, name, swap, is_public=True):
self.id = id
self.ram = ram
self.vcpus = cores
self.disk = disks
self.name = name
self.is_public = is_public
self.swap = swap
class FakeImage(object):
def __init__(self, id, name):
self.id = id
@ -104,6 +117,50 @@ class TestResourceManager(testroot.KBApiTest):
mock_db_api.sync_job_create.call_count)
self.assertEqual(response.status_int, 200)
@mock.patch.object(rpc_client, 'EngineClient')
@mock.patch.object(sync_manager, 'NovaClient')
@mock.patch.object(sync_manager, 'EndpointCache')
@mock.patch.object(sync_manager, 'db_api')
def test_post_flavor_sync_admin(self, mock_db_api, mock_endpoint_cache,
mock_nova, mock_rpc_client):
time_now = timeutils.utcnow()
data = {"resource_set": {"resources": [SOURCE_FLAVOR],
"resource_type": "flavor",
"force": "True",
"source": FAKE_SOURCE_REGION,
"target": [FAKE_TARGET_REGION]}}
fake_flavor = Fake_Flavor('fake_id', 512, 2, 30, 'fake_flavor', 1)
sync_job_result = SyncJob(FAKE_JOB, consts.JOB_PROGRESS, time_now)
mock_endpoint_cache().get_session_from_token.\
return_value = 'fake_session'
mock_nova().get_flavor.return_value = fake_flavor
mock_db_api.sync_job_create.return_value = sync_job_result
response = self.app.post_json(FAKE_URL,
headers=FAKE_HEADERS,
params=data)
self.assertEqual(1,
mock_nova().get_flavor.call_count)
self.assertEqual(1,
mock_db_api.resource_sync_create.call_count)
self.assertEqual(1,
mock_db_api.sync_job_create.call_count)
self.assertEqual(response.status_int, 200)
@mock.patch.object(rpc_client, 'EngineClient')
@mock.patch.object(sync_manager, 'NovaClient')
@mock.patch.object(sync_manager, 'db_api')
def test_post_flavor_sync_non_admin(self, mock_db_api,
mock_nova, mock_rpc_client):
data = {"resource_set": {"resources": [SOURCE_FLAVOR],
"resource_type": "flavor",
"force": "True",
"source": FAKE_SOURCE_REGION,
"target": [FAKE_TARGET_REGION]}}
self.assertRaisesRegexp(webtest.app.AppError, "403 *",
self.app.post_json, FAKE_URL,
headers=NON_ADMIN_HEADERS,
params=data)
@mock.patch.object(rpc_client, 'EngineClient')
@mock.patch.object(sync_manager, 'GlanceClient')
@mock.patch.object(sync_manager, 'db_api')
@ -224,6 +281,24 @@ class TestResourceManager(testroot.KBApiTest):
self.app.post_json, FAKE_URL,
headers=FAKE_HEADERS, params=data)
@mock.patch.object(rpc_client, 'EngineClient')
@mock.patch.object(sync_manager, 'EndpointCache')
@mock.patch.object(sync_manager, 'NovaClient')
def test_post_no_flavors_in_source_region(self, mock_nova,
mock_endpoint_cache,
mock_rpc_client):
data = {"resource_set": {"resources": [SOURCE_FLAVOR],
"resource_type": "flavor",
"force": "True",
"source": FAKE_SOURCE_REGION,
"target": [FAKE_TARGET_REGION]}}
mock_endpoint_cache().get_session_from_token.\
return_value = 'fake_session'
mock_nova().get_flavor.return_value = None
self.assertRaisesRegexp(webtest.app.AppError, "404 *",
self.app.post_json, FAKE_URL,
headers=FAKE_HEADERS, params=data)
@mock.patch.object(rpc_client, 'EngineClient')
@mock.patch.object(sync_manager, 'db_api')
def test_delete_jobs(self, mock_db_api, mock_rpc_client):

View File

@ -26,16 +26,18 @@ class Server(object):
self.flavor['id'] = id
class Flavor(object):
def __init__(self, id, ram, cores, disks):
class Fake_Flavor(object):
def __init__(self, id, ram, cores, disks, name, swap, is_public=True):
self.id = id
self.ram = ram
self.vcpus = cores
self.disk = disks
self.name = name
self.is_public = is_public
self.swap = swap
s1 = Server(1, {'mkey': 'mvalue'})
s2 = Server(1, {'mkey': 'mvalue', 'm2key': 'm2value'})
FAKE_FLAVOR = Flavor(1, 512, 10, 50)
DISABLED_QUOTAS = ["floating_ips", "fixed_ips", "security_groups"]
FAKE_KEYPAIRS = ['key1', 'key2']
FAKE_LIMITS = {'absolute':
@ -146,3 +148,12 @@ class TestNovaClient(base.KingbirdTestCase):
mock_novaclient.Client().keypairs.create.\
assert_called_once_with(fake_key.name,
public_key=fake_key.public_key)
@mock.patch.object(nova_v2, 'client')
def test_get_flavor(self, mock_novaclient):
nv_client = nova_v2.NovaClient('fake_region', self.session,
DISABLED_QUOTAS)
fake_flavor = Fake_Flavor('fake_id', 512, 2, 30, 'fake_flavor', 1)
nv_client.get_flavor(fake_flavor.id)
mock_novaclient.Client().flavors.get.\
assert_called_once_with(fake_flavor.id)