Add sahara image constraint
Sahara uses images with specific tags. These images should be registered in Sahara, otherwise you will get error during creation. This patch adds constraint for sahara images. Change-Id: Idce140ca65cd396cd1febcc052898717d378f88d
This commit is contained in:
parent
5ded08aa19
commit
8ac934471a
|
@ -13,10 +13,20 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
from saharaclient.api import base as sahara_base
|
||||
from saharaclient import client as sahara_client
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.common.i18n import _LI
|
||||
from heat.engine.clients import client_plugin
|
||||
from heat.engine import constraints
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SaharaClientPlugin(client_plugin.ClientPlugin):
|
||||
|
@ -49,3 +59,65 @@ class SaharaClientPlugin(client_plugin.ClientPlugin):
|
|||
def is_conflict(self, ex):
|
||||
return (isinstance(ex, sahara_base.APIException) and
|
||||
ex.error_code == 409)
|
||||
|
||||
def is_not_registered(self, ex):
|
||||
return (isinstance(ex, sahara_base.APIException) and
|
||||
ex.error_code == 400 and
|
||||
ex.error_name == 'IMAGE_NOT_REGISTERED')
|
||||
|
||||
def get_image_id(self, image_identifier):
|
||||
'''
|
||||
Return an id for the specified image name or identifier.
|
||||
|
||||
:param image_identifier: image name or a UUID-like identifier
|
||||
:returns: the id of the requested :image_identifier:
|
||||
:raises: exception.ImageNotFound,
|
||||
exception.PhysicalResourceNameAmbiguity
|
||||
'''
|
||||
if uuidutils.is_uuid_like(image_identifier):
|
||||
try:
|
||||
image_id = self.client().images.get(image_identifier).id
|
||||
except sahara_base.APIException as ex:
|
||||
if self.is_not_registered(ex):
|
||||
image_id = self.get_image_id_by_name(image_identifier)
|
||||
else:
|
||||
image_id = self.get_image_id_by_name(image_identifier)
|
||||
return image_id
|
||||
|
||||
def get_image_id_by_name(self, image_identifier):
|
||||
'''
|
||||
Return an id for the specified image name.
|
||||
|
||||
:param image_identifier: image name
|
||||
:returns: the id of the requested :image_identifier:
|
||||
:raises: exception.ImageNotFound,
|
||||
exception.PhysicalResourceNameAmbiguity
|
||||
'''
|
||||
try:
|
||||
filters = {'name': image_identifier}
|
||||
image_list = self.client().images.find(**filters)
|
||||
except sahara_base.APIException as ex:
|
||||
raise exception.Error(
|
||||
_("Error retrieving image list from sahara: "
|
||||
"%s") % six.text_type(ex))
|
||||
num_matches = len(image_list)
|
||||
if num_matches == 0:
|
||||
LOG.info(_LI("Image %s was not found in sahara images"),
|
||||
image_identifier)
|
||||
raise exception.ImageNotFound(image_name=image_identifier)
|
||||
elif num_matches > 1:
|
||||
LOG.info(_LI("Multiple images %s were found in sahara with name"),
|
||||
image_identifier)
|
||||
raise exception.PhysicalResourceNameAmbiguity(
|
||||
name=image_identifier)
|
||||
else:
|
||||
return image_list[0].id
|
||||
|
||||
|
||||
class ImageConstraint(constraints.BaseCustomConstraint):
|
||||
|
||||
expected_exceptions = (exception.ImageNotFound,
|
||||
exception.PhysicalResourceNameAmbiguity,)
|
||||
|
||||
def validate_with_client(self, client, value):
|
||||
client.client_plugin('sahara').get_image_id(value)
|
||||
|
|
|
@ -91,7 +91,7 @@ class SaharaCluster(resource.Resource):
|
|||
properties.Schema.STRING,
|
||||
_('Default name or UUID of the image used to boot Hadoop nodes.'),
|
||||
constraints=[
|
||||
constraints.CustomConstraint('glance.image')
|
||||
constraints.CustomConstraint('sahara.image'),
|
||||
],
|
||||
),
|
||||
MANAGEMENT_NETWORK: properties.Schema(
|
||||
|
|
|
@ -283,7 +283,7 @@ class SaharaClusterTemplate(resource.Resource):
|
|||
properties.Schema.STRING,
|
||||
_("ID of the default image to use for the template."),
|
||||
constraints=[
|
||||
constraints.CustomConstraint('glance.image')
|
||||
constraints.CustomConstraint('sahara.image'),
|
||||
],
|
||||
),
|
||||
MANAGEMENT_NETWORK: properties.Schema(
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
#
|
||||
# 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 uuid
|
||||
|
||||
import mock
|
||||
from saharaclient.api import base as sahara_base
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.engine.clients.os import sahara
|
||||
from heat.tests import common
|
||||
from heat.tests import utils
|
||||
|
||||
|
||||
class SaharaUtilsTests(common.HeatTestCase):
|
||||
"""
|
||||
Basic tests for the helper methods in
|
||||
:module:'heat.engine.resources.clients.os.sahara'.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(SaharaUtilsTests, self).setUp()
|
||||
self.sahara_client = mock.MagicMock()
|
||||
con = utils.dummy_context()
|
||||
c = con.clients
|
||||
self.sahara_plugin = c.client_plugin('sahara')
|
||||
self.sahara_plugin._client = self.sahara_client
|
||||
self.my_image = mock.MagicMock()
|
||||
|
||||
def test_get_image_id(self):
|
||||
"""Tests the get_image_id function."""
|
||||
img_id = str(uuid.uuid4())
|
||||
img_name = 'myfakeimage'
|
||||
self.my_image.id = img_id
|
||||
self.my_image.name = img_name
|
||||
self.sahara_client.images.get.return_value = self.my_image
|
||||
self.sahara_client.images.find.side_effect = [[self.my_image], []]
|
||||
|
||||
self.assertEqual(img_id, self.sahara_plugin.get_image_id(img_id))
|
||||
self.assertEqual(img_id, self.sahara_plugin.get_image_id(img_name))
|
||||
self.assertRaises(exception.ImageNotFound,
|
||||
self.sahara_plugin.get_image_id, 'noimage')
|
||||
|
||||
calls = [mock.call(name=img_name),
|
||||
mock.call(name='noimage')]
|
||||
self.sahara_client.images.get.assert_called_once_with(img_id)
|
||||
self.sahara_client.images.find.assert_has_calls(calls)
|
||||
|
||||
def test_get_image_id_by_name_in_uuid(self):
|
||||
"""Tests the get_image_id function by name in uuid."""
|
||||
img_id = str(uuid.uuid4())
|
||||
img_name = str(uuid.uuid4())
|
||||
self.my_image.id = img_id
|
||||
self.my_image.name = img_name
|
||||
self.sahara_client.images.get.side_effect = [
|
||||
sahara_base.APIException(error_code=400,
|
||||
error_name='IMAGE_NOT_REGISTERED')]
|
||||
|
||||
self.sahara_client.images.find.return_value = [self.my_image]
|
||||
self.assertEqual(img_id, self.sahara_plugin.get_image_id(img_name))
|
||||
|
||||
self.sahara_client.images.get.assert_called_once_with(img_name)
|
||||
self.sahara_client.images.find.assert_called_once_with(name=img_name)
|
||||
|
||||
def test_get_image_id_sahara_exception(self):
|
||||
"""Test get_image_id when sahara raises an exception."""
|
||||
# Simulate HTTP exception
|
||||
img_name = str(uuid.uuid4())
|
||||
self.sahara_client.images.find.side_effect = [
|
||||
sahara_base.APIException(error_message="Error", error_code=404)]
|
||||
|
||||
expected_error = "Error retrieving image list from sahara: Error"
|
||||
e = self.assertRaises(exception.Error,
|
||||
self.sahara_plugin.get_image_id_by_name,
|
||||
img_name)
|
||||
self.assertEqual(expected_error, six.text_type(e))
|
||||
|
||||
self.sahara_client.images.find.assert_called_once_with(name=img_name)
|
||||
|
||||
def test_get_image_id_not_found(self):
|
||||
"""Tests the get_image_id function while image is not found."""
|
||||
img_name = str(uuid.uuid4())
|
||||
self.my_image.name = img_name
|
||||
self.sahara_client.images.get.side_effect = [
|
||||
sahara_base.APIException(error_code=400,
|
||||
error_name='IMAGE_NOT_REGISTERED')]
|
||||
self.sahara_client.images.find.return_value = []
|
||||
|
||||
self.assertRaises(exception.ImageNotFound,
|
||||
self.sahara_plugin.get_image_id, img_name)
|
||||
|
||||
self.sahara_client.images.get.assert_called_once_with(img_name)
|
||||
self.sahara_client.images.find.assert_called_once_with(name=img_name)
|
||||
|
||||
def test_get_image_id_name_ambiguity(self):
|
||||
"""Tests the get_image_id function while name ambiguity ."""
|
||||
img_name = 'ambiguity_name'
|
||||
self.my_image.name = img_name
|
||||
|
||||
self.sahara_client.images.find.return_value = [self.my_image,
|
||||
self.my_image]
|
||||
self.assertRaises(exception.PhysicalResourceNameAmbiguity,
|
||||
self.sahara_plugin.get_image_id, img_name)
|
||||
self.sahara_client.images.find.assert_called_once_with(name=img_name)
|
||||
|
||||
|
||||
class ImageConstraintTest(common.HeatTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ImageConstraintTest, self).setUp()
|
||||
self.ctx = utils.dummy_context()
|
||||
self.mock_get_image = mock.Mock()
|
||||
self.ctx.clients.client_plugin(
|
||||
'sahara').get_image_id = self.mock_get_image
|
||||
self.constraint = sahara.ImageConstraint()
|
||||
|
||||
def test_validation(self):
|
||||
self.mock_get_image.return_value = "id1"
|
||||
self.assertTrue(self.constraint.validate("foo", self.ctx))
|
||||
|
||||
def test_validation_error(self):
|
||||
self.mock_get_image.side_effect = exception.ImageNotFound(
|
||||
image_name='bar')
|
||||
self.assertFalse(self.constraint.validate("bar", self.ctx))
|
|
@ -70,6 +70,7 @@ heat.constraints =
|
|||
cinder.volume = heat.engine.clients.os.cinder:VolumeConstraint
|
||||
cinder.snapshot = heat.engine.clients.os.cinder:VolumeSnapshotConstraint
|
||||
cinder.vtype = heat.engine.clients.os.cinder:VolumeTypeConstraint
|
||||
sahara.image = heat.engine.clients.os.sahara:ImageConstraint
|
||||
|
||||
heat.stack_lifecycle_plugins =
|
||||
|
||||
|
|
Loading…
Reference in New Issue