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:
parent
ea7caa8567
commit
2f6dab7fc1
|
@ -154,6 +154,24 @@ class ResourceSyncController(object):
|
||||||
source_resources,
|
source_resources,
|
||||||
resource_type, job_id)
|
resource_type, job_id)
|
||||||
return self._image_sync(job_id, payload, context, result)
|
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:
|
else:
|
||||||
pecan.abort(400, _('Bad resource_type'))
|
pecan.abort(400, _('Bad resource_type'))
|
||||||
|
|
||||||
|
@ -210,3 +228,16 @@ class ResourceSyncController(object):
|
||||||
self.rpc_client.image_sync(context, job_id, payload)
|
self.rpc_client.image_sync(context, job_id, payload)
|
||||||
return {'job_status': {'id': result.id, 'status': result.sync_status,
|
return {'job_status': {'id': result.id, 'status': result.sync_status,
|
||||||
'created_at': result.created_at}}
|
'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}}
|
||||||
|
|
|
@ -49,3 +49,5 @@ JOB_SUCCESS = "SUCCESS"
|
||||||
JOB_FAILURE = "FAILURE"
|
JOB_FAILURE = "FAILURE"
|
||||||
|
|
||||||
IMAGE = "image"
|
IMAGE = "image"
|
||||||
|
|
||||||
|
FLAVOR = "flavor"
|
||||||
|
|
|
@ -132,3 +132,14 @@ class NovaClient(base.DriverBase):
|
||||||
return self.nova_client.keypairs. \
|
return self.nova_client.keypairs. \
|
||||||
create(keypair.name,
|
create(keypair.name,
|
||||||
public_key=keypair.public_key)
|
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
|
||||||
|
|
|
@ -79,3 +79,8 @@ class EngineClient(object):
|
||||||
return self.cast(
|
return self.cast(
|
||||||
ctxt,
|
ctxt,
|
||||||
self.make_msg('image_sync', job_id=job_id, payload=payload))
|
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))
|
||||||
|
|
|
@ -26,6 +26,7 @@ from kingbird.tests import utils
|
||||||
|
|
||||||
DEFAULT_FORCE = False
|
DEFAULT_FORCE = False
|
||||||
SOURCE_KEYPAIR = 'fake_key1'
|
SOURCE_KEYPAIR = 'fake_key1'
|
||||||
|
SOURCE_FLAVOR = 'fake_flavor1'
|
||||||
SOURCE_IMAGE_NAME = 'fake_image'
|
SOURCE_IMAGE_NAME = 'fake_image'
|
||||||
SOURCE_IMAGE_ID = utils.UUID4
|
SOURCE_IMAGE_ID = utils.UUID4
|
||||||
WRONG_SOURCE_IMAGE_ID = utils.UUID5
|
WRONG_SOURCE_IMAGE_ID = utils.UUID5
|
||||||
|
@ -41,7 +42,8 @@ fake_user = utils.UUID3
|
||||||
FAKE_STATUS = consts.JOB_PROGRESS
|
FAKE_STATUS = consts.JOB_PROGRESS
|
||||||
FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin',
|
FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin',
|
||||||
'X-Identity-Status': 'Confirmed'}
|
'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):
|
class FakeKeypair(object):
|
||||||
|
@ -50,6 +52,17 @@ class FakeKeypair(object):
|
||||||
self.public_key = public_key
|
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):
|
class FakeImage(object):
|
||||||
def __init__(self, id, name):
|
def __init__(self, id, name):
|
||||||
self.id = id
|
self.id = id
|
||||||
|
@ -104,6 +117,50 @@ class TestResourceManager(testroot.KBApiTest):
|
||||||
mock_db_api.sync_job_create.call_count)
|
mock_db_api.sync_job_create.call_count)
|
||||||
self.assertEqual(response.status_int, 200)
|
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(rpc_client, 'EngineClient')
|
||||||
@mock.patch.object(sync_manager, 'GlanceClient')
|
@mock.patch.object(sync_manager, 'GlanceClient')
|
||||||
@mock.patch.object(sync_manager, 'db_api')
|
@mock.patch.object(sync_manager, 'db_api')
|
||||||
|
@ -224,6 +281,24 @@ class TestResourceManager(testroot.KBApiTest):
|
||||||
self.app.post_json, FAKE_URL,
|
self.app.post_json, FAKE_URL,
|
||||||
headers=FAKE_HEADERS, params=data)
|
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(rpc_client, 'EngineClient')
|
||||||
@mock.patch.object(sync_manager, 'db_api')
|
@mock.patch.object(sync_manager, 'db_api')
|
||||||
def test_delete_jobs(self, mock_db_api, mock_rpc_client):
|
def test_delete_jobs(self, mock_db_api, mock_rpc_client):
|
||||||
|
|
|
@ -26,16 +26,18 @@ class Server(object):
|
||||||
self.flavor['id'] = id
|
self.flavor['id'] = id
|
||||||
|
|
||||||
|
|
||||||
class Flavor(object):
|
class Fake_Flavor(object):
|
||||||
def __init__(self, id, ram, cores, disks):
|
def __init__(self, id, ram, cores, disks, name, swap, is_public=True):
|
||||||
self.id = id
|
self.id = id
|
||||||
self.ram = ram
|
self.ram = ram
|
||||||
self.vcpus = cores
|
self.vcpus = cores
|
||||||
self.disk = disks
|
self.disk = disks
|
||||||
|
self.name = name
|
||||||
|
self.is_public = is_public
|
||||||
|
self.swap = swap
|
||||||
|
|
||||||
s1 = Server(1, {'mkey': 'mvalue'})
|
s1 = Server(1, {'mkey': 'mvalue'})
|
||||||
s2 = Server(1, {'mkey': 'mvalue', 'm2key': 'm2value'})
|
s2 = Server(1, {'mkey': 'mvalue', 'm2key': 'm2value'})
|
||||||
FAKE_FLAVOR = Flavor(1, 512, 10, 50)
|
|
||||||
DISABLED_QUOTAS = ["floating_ips", "fixed_ips", "security_groups"]
|
DISABLED_QUOTAS = ["floating_ips", "fixed_ips", "security_groups"]
|
||||||
FAKE_KEYPAIRS = ['key1', 'key2']
|
FAKE_KEYPAIRS = ['key1', 'key2']
|
||||||
FAKE_LIMITS = {'absolute':
|
FAKE_LIMITS = {'absolute':
|
||||||
|
@ -146,3 +148,12 @@ class TestNovaClient(base.KingbirdTestCase):
|
||||||
mock_novaclient.Client().keypairs.create.\
|
mock_novaclient.Client().keypairs.create.\
|
||||||
assert_called_once_with(fake_key.name,
|
assert_called_once_with(fake_key.name,
|
||||||
public_key=fake_key.public_key)
|
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)
|
||||||
|
|
Loading…
Reference in New Issue