Merge "Validate cluster flavor with image metadata"
This commit is contained in:
commit
f8bdd62339
|
@ -20,6 +20,7 @@
|
|||
"""
|
||||
import sys
|
||||
|
||||
from novaclient import exceptions as nova_exc
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
|
@ -31,6 +32,7 @@ from wsme import types as wtypes
|
|||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cue.api.controllers import base
|
||||
import cue.client as client
|
||||
from cue.common import exception
|
||||
from cue.common.i18n import _ # noqa
|
||||
from cue.common.i18n import _LI # noqa
|
||||
|
@ -198,6 +200,49 @@ def delete_complete_cluster(context, cluster_id):
|
|||
class ClusterController(rest.RestController):
|
||||
"""Manages operations on specific Cluster of nodes."""
|
||||
|
||||
def _validate_flavor(self, image_id, cluster_flavor):
|
||||
"""Checks if flavor satisfies minimum requirement of image metadata.
|
||||
|
||||
:param image_id: image id of the broker.
|
||||
:param cluster_flavor: flavor id of the cluster.
|
||||
:raises: exception.ConfigurationError
|
||||
:raises: exception.InternalServerError
|
||||
:raises: exception.Invalid
|
||||
"""
|
||||
nova_client = client.nova_client()
|
||||
|
||||
# get image metadata
|
||||
try:
|
||||
image_metadata = nova_client.images.get(image_id)
|
||||
image_minRam = image_metadata.minRam
|
||||
image_minDisk = image_metadata.minDisk
|
||||
except nova_exc.ClientException as ex:
|
||||
if ex.http_status == 404:
|
||||
raise exception.ConfigurationError(_('Invalid image %s '
|
||||
'configured') % image_id)
|
||||
else:
|
||||
raise exception.InternalServerError
|
||||
|
||||
# get flavor metadata
|
||||
try:
|
||||
flavor_metadata = nova_client.flavors.get(cluster_flavor)
|
||||
flavor_ram = flavor_metadata.ram
|
||||
flavor_disk = flavor_metadata.disk
|
||||
except nova_exc.ClientException as ex:
|
||||
if ex.http_status == 404:
|
||||
raise exception.Invalid(_('Invalid flavor %s provided') %
|
||||
cluster_flavor)
|
||||
else:
|
||||
raise exception.InternalServerError
|
||||
|
||||
# validate flavor with broker image metadata
|
||||
if (flavor_disk < image_minDisk):
|
||||
raise exception.Invalid(_("Flavor disk is smaller than the "
|
||||
"minimum %s required for broker") % image_minDisk)
|
||||
elif (flavor_ram < image_minRam):
|
||||
raise exception.Invalid(_("Flavor ram is smaller than the "
|
||||
"minimum %s required for broker") % image_minRam)
|
||||
|
||||
@wsme_pecan.wsexpose(Cluster, wtypes.text, status_code=200)
|
||||
def get_one(self, cluster_id):
|
||||
"""Return this cluster."""
|
||||
|
@ -248,6 +293,8 @@ class ClusterController(rest.RestController):
|
|||
:param data: cluster parameters within the request body.
|
||||
"""
|
||||
context = pecan.request.context
|
||||
request_data = data.as_dict()
|
||||
cluster_flavor = request_data['flavor']
|
||||
|
||||
if data.size <= 0:
|
||||
raise exception.Invalid(_("Invalid cluster size provided"))
|
||||
|
@ -275,7 +322,14 @@ class ClusterController(rest.RestController):
|
|||
default_rabbit_user = data.authentication.token['username']
|
||||
default_rabbit_pass = data.authentication.token['password']
|
||||
|
||||
request_data = data.as_dict()
|
||||
broker_name = CONF.default_broker_name
|
||||
|
||||
# get the image id of default broker
|
||||
image_id = objects.BrokerMetadata.get_image_id_by_broker_name(
|
||||
context, broker_name)
|
||||
|
||||
# validate cluster flavor
|
||||
self._validate_flavor(image_id, cluster_flavor)
|
||||
|
||||
# convert 'network_id' from list to string type for objects/cluster
|
||||
# compatibility
|
||||
|
@ -307,11 +361,6 @@ class ClusterController(rest.RestController):
|
|||
# generate unique erlang cookie to be used by all nodes in the new
|
||||
# cluster, erlang cookies are strings of up to 255 characters
|
||||
erlang_cookie = uuidutils.generate_uuid()
|
||||
broker_name = CONF.default_broker_name
|
||||
|
||||
# get the image id of default broker
|
||||
image_id = objects.BrokerMetadata.get_image_id_by_broker_name(
|
||||
context, broker_name)
|
||||
|
||||
job_args = {
|
||||
'tenant_id': new_cluster.project_id,
|
||||
|
@ -320,9 +369,9 @@ class ClusterController(rest.RestController):
|
|||
'volume_size': cluster.volume_size,
|
||||
'port': '5672',
|
||||
'context': context.to_dict(),
|
||||
# TODO(sputnik13: this needs to come from the create request and
|
||||
# default to a configuration value rather than always using config
|
||||
# value
|
||||
# TODO(sputnik13: this needs to come from the create request
|
||||
# and default to a configuration value rather than always using
|
||||
# config value
|
||||
'security_groups': [CONF.os_security_group],
|
||||
'port': CONF.rabbit_port,
|
||||
'key_name': CONF.openstack.os_key_name,
|
||||
|
@ -337,9 +386,9 @@ class ClusterController(rest.RestController):
|
|||
flow_kwargs=flow_kwargs,
|
||||
tx_uuid=job_uuid)
|
||||
|
||||
LOG.info(_LI('Create Cluster Request Cluster ID %(cluster_id)s Cluster'
|
||||
' size %(size)s network ID %(network_id)s Job ID '
|
||||
'%(job_id)s Broker name %(broker_name)s') % (
|
||||
LOG.info(_LI('Create Cluster Request Cluster ID %(cluster_id)s '
|
||||
'Cluster size %(size)s network ID %(network_id)s '
|
||||
'Job ID %(job_id)s Broker name %(broker_name)s') % (
|
||||
{"cluster_id": cluster.id,
|
||||
"size": cluster.size,
|
||||
"network_id":
|
||||
|
|
|
@ -149,3 +149,8 @@ class VmBuildingException(CueException):
|
|||
|
||||
class VmErrorException(CueException):
|
||||
message = _("VM is not in a building state")
|
||||
|
||||
|
||||
class InternalServerError(CueException):
|
||||
message = _("Internal Server Error")
|
||||
code = 500
|
||||
|
|
|
@ -23,6 +23,7 @@ import pecan.testing
|
|||
|
||||
from cue.api import hooks
|
||||
from cue.tests.functional import base
|
||||
from cue.tests.functional.fixtures import nova
|
||||
|
||||
OPT_GROUP_NAME = 'keystone_authtoken'
|
||||
cfg.CONF.import_group(OPT_GROUP_NAME, "keystonemiddleware.auth_token")
|
||||
|
@ -36,6 +37,9 @@ class APITest(base.FunctionalTestCase):
|
|||
"""
|
||||
|
||||
PATH_PREFIX = ''
|
||||
additional_fixtures = [
|
||||
nova.NovaClient
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(APITest, self).setUp()
|
||||
|
|
|
@ -503,6 +503,64 @@ class TestCreateCluster(api.APITest,
|
|||
self.assertEqual(400, data.status_code,
|
||||
'Invalid status code value received.')
|
||||
|
||||
def test_create_flavor_too_small_disk(self):
|
||||
"""test create a cluster with flavor having too small disk for
|
||||
|
||||
image.
|
||||
"""
|
||||
api_cluster = test_utils.create_api_test_cluster(flavor='x-tiny-disk')
|
||||
data = self.post_json('/clusters', params=api_cluster,
|
||||
headers=self.auth_headers, expect_errors=True)
|
||||
|
||||
self.assertEqual(400, data.status_code,
|
||||
'Invalid status code value received.')
|
||||
|
||||
def test_create_flavor_too_small_ram(self):
|
||||
"""test create a cluster with flavor having too small ram for image."""
|
||||
api_cluster = test_utils.create_api_test_cluster(flavor='x-tiny-ram')
|
||||
data = self.post_json('/clusters', params=api_cluster,
|
||||
headers=self.auth_headers, expect_errors=True)
|
||||
|
||||
self.assertEqual(400, data.status_code,
|
||||
'Invalid status code value received.')
|
||||
|
||||
def test_create_flavor_invalid(self):
|
||||
"""test create a cluster with invalid flavor."""
|
||||
api_cluster = test_utils.create_api_test_cluster(
|
||||
flavor='invalid_flavor')
|
||||
|
||||
data = self.post_json('/clusters', params=api_cluster,
|
||||
headers=self.auth_headers, expect_errors=True)
|
||||
|
||||
self.assertEqual(400, data.status_code,
|
||||
'Invalid status code value received.')
|
||||
|
||||
def test_create_invalid_image(self):
|
||||
"""test create a cluster with invalid image configured."""
|
||||
# add a new broker image that is not in nova image-list
|
||||
self.CONF.config(default_broker_name='rabbitmq1')
|
||||
broker_values = {
|
||||
'name': 'rabbitmq1',
|
||||
'active': '1',
|
||||
}
|
||||
broker = objects.Broker(**broker_values)
|
||||
broker.create_broker(None)
|
||||
broker_list = broker.get_brokers(None)
|
||||
metadata_value = {
|
||||
'key': 'IMAGE',
|
||||
'value': 'ea329926-b8bf-11e5-9912-ba0be0483c18',
|
||||
'broker_id': broker_list[1]['id']
|
||||
}
|
||||
metadata = objects.BrokerMetadata(**metadata_value)
|
||||
metadata.create_broker_metadata(None)
|
||||
|
||||
api_cluster = test_utils.create_api_test_cluster()
|
||||
data = self.post_json('/clusters', params=api_cluster,
|
||||
headers=self.auth_headers, expect_errors=True)
|
||||
|
||||
self.assertEqual(500, data.status_code,
|
||||
'Invalid status code value received.')
|
||||
|
||||
def test_create_invalid_volume_size(self):
|
||||
"""test with invalid volume_size parameter."""
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class ImageDetails(object):
|
|||
updated=None):
|
||||
self.created = created
|
||||
self.human_id = human_id
|
||||
self.id = id
|
||||
self.id = id or str(uuid.uuid4())
|
||||
self.minDisk = minDisk
|
||||
self.minRam = minRam
|
||||
self.name = name
|
||||
|
@ -140,7 +140,14 @@ class NovaClient(base.BaseFixture):
|
|||
image_list = ['cirros-0.3.2-x86_64-uec-kernel']
|
||||
|
||||
if not flavor_list:
|
||||
flavor_list = ['m1.tiny']
|
||||
flavor_tiny = FlavorDetails(name='m1.tiny')
|
||||
flavor_tiny_disk = FlavorDetails(name='x-tiny-disk', disk=1)
|
||||
flavor_tiny_ram = FlavorDetails(name='x-tiny-ram', ram=256)
|
||||
flavor_list = [flavor_tiny, flavor_tiny_disk, flavor_tiny_ram]
|
||||
for flavor in flavor_list:
|
||||
self._flavor_list.update({
|
||||
flavor.id: flavor
|
||||
})
|
||||
|
||||
if not security_group_list:
|
||||
security_group_list = []
|
||||
|
@ -148,17 +155,13 @@ class NovaClient(base.BaseFixture):
|
|||
self._vm_limit = vm_limit if vm_limit else 3
|
||||
|
||||
for image in image_list:
|
||||
image_detail = ImageDetails(name=image)
|
||||
image_detail = ImageDetails(name=image,
|
||||
id='f7e8c49b-7d1e-472f-a78b-7c46a39c85be',
|
||||
minDisk=4, minRam=512)
|
||||
self._image_list.update({
|
||||
image_detail.id: image_detail
|
||||
})
|
||||
|
||||
for flavor in flavor_list:
|
||||
flavor_detail = FlavorDetails(name=flavor)
|
||||
self._flavor_list.update({
|
||||
flavor_detail.id: flavor_detail
|
||||
})
|
||||
|
||||
self._security_group_list = security_group_list
|
||||
|
||||
def setUp(self):
|
||||
|
@ -173,7 +176,9 @@ class NovaClient(base.BaseFixture):
|
|||
v2_client.servers.interface_list = self.list_interfaces
|
||||
v2_client.images.find = self.find_images
|
||||
v2_client.images.list = self.list_images
|
||||
v2_client.images.get = self.get_image
|
||||
v2_client.flavors.find = self.find_flavors
|
||||
v2_client.flavors.get = self.get_flavor
|
||||
v2_client.server_groups.create = self.create_vm_group
|
||||
v2_client.server_groups.delete = self.delete_vm_group
|
||||
v2_client.server_groups.get = self.get_vm_group
|
||||
|
@ -318,6 +323,20 @@ class NovaClient(base.BaseFixture):
|
|||
if image_detail.name == name:
|
||||
return image_detail
|
||||
|
||||
def get_image(self, id, **kwargs):
|
||||
"""Mock'd version of novaclient...image_get().
|
||||
|
||||
Gets an image detail based on provided image id
|
||||
|
||||
:param id: Image name.
|
||||
:return: Image detail matching provided image id.
|
||||
"""
|
||||
for image_detail in self._image_list.values():
|
||||
if image_detail.id == id:
|
||||
return image_detail
|
||||
|
||||
raise nova_exc.NotFound(404)
|
||||
|
||||
def list_images(self, retrieve_all=True, **kwargs):
|
||||
"""Mock'd version of novaclient...list_images().
|
||||
|
||||
|
@ -340,6 +359,20 @@ class NovaClient(base.BaseFixture):
|
|||
if flavor_detail.name == name:
|
||||
return flavor_detail
|
||||
|
||||
def get_flavor(self, name, **kwargs):
|
||||
"""Mock'd version of novaclient...flavors_get().
|
||||
|
||||
Finds a flavor detail based on provided name.
|
||||
|
||||
:param name: Flavor name.
|
||||
:return: Flavor detail matching provided flavor name.
|
||||
"""
|
||||
for flavor_detail in self._flavor_list.values():
|
||||
if flavor_detail.name == name:
|
||||
return flavor_detail
|
||||
|
||||
raise nova_exc.NotFound(404)
|
||||
|
||||
def list_interfaces(self, server, **kwargs):
|
||||
"""Mock'd version of novaclient...interface_list().
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ def get_test_cluster(**kw):
|
|||
'network_id': kw.get('network_id',
|
||||
'3dc26c0b-03f2-4d2e-ae87-c02d7f33c788'),
|
||||
'status': kw.get('status', 'BUILDING'),
|
||||
'flavor': kw.get('flavor', 'flavor1'),
|
||||
'flavor': kw.get('flavor', 'm1.tiny'),
|
||||
'size': kw.get('size', 1),
|
||||
'volume_size': kw.get('volume_size', 10),
|
||||
'deleted': kw.get('deleted', False),
|
||||
|
@ -204,7 +204,7 @@ def get_test_node_dict(**kw):
|
|||
),
|
||||
'instance_id': kw.get('instance_id',
|
||||
'b7cf7433-60f7-4d09-a759-cee12d8a3cb3'),
|
||||
'flavor': kw.get('flavor', 'flavor1'),
|
||||
'flavor': kw.get('flavor', 'm1.tiny'),
|
||||
'status': kw.get('status', 'BUILDING'),
|
||||
'management_ip': kw.get('management_ip', '172.1.1.1'),
|
||||
'created_at': kw.get('created_at', timeutils.utcnow()),
|
||||
|
|
|
@ -114,7 +114,7 @@ function disk_image_create_upload {
|
|||
|
||||
openstack --os-token $token --os-url $GLANCE_SERVICE_PROTOCOL://$GLANCE_HOSTPORT \
|
||||
image create --container-format bare --disk-format qcow2 --public \
|
||||
--file $image_path $image_name
|
||||
--min-disk 2 --file $image_path $image_name
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
|
|
Loading…
Reference in New Issue