Implement OS::Sahara::ClusterTemplate resource

Change-Id: I19ffbc19340342f716aa0c23e2b9dd4332503cdc
Implements: blueprint sahara-as-heat-resource
This commit is contained in:
Pavlo Shchelokovskyy 2014-06-23 11:47:57 +03:00
parent 11018c8cd2
commit 065ff9baf8
2 changed files with 263 additions and 0 deletions

View File

@ -170,7 +170,168 @@ class SaharaNodeGroupTemplate(resource.Resource):
raise exception.StackValidationFailed(message=msg)
class SaharaClusterTemplate(resource.Resource):
PROPERTIES = (
NAME, PLUGIN_NAME, HADOOP_VERSION, DESCRIPTION,
ANTI_AFFINITY, MANAGEMENT_NETWORK,
CLUSTER_CONFIGS, NODE_GROUPS, IMAGE_ID,
) = (
'name', 'plugin_name', 'hadoop_version', 'description',
'anti_affinity', 'neutron_management_network',
'cluster_configs', 'node_groups', 'default_image_id',
)
_NODE_GROUP_KEYS = (
NG_NAME, COUNT, NG_TEMPLATE_ID,
) = (
'name', 'count', 'node_group_template_id',
)
properties_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_("Name for the Sahara Cluster Template."),
constraints=[
constraints.Length(min=1, max=50),
constraints.AllowedPattern(SAHARA_NAME_REGEX),
],
),
DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_('Description of the Sahara Group Template.'),
default="",
),
PLUGIN_NAME: properties.Schema(
properties.Schema.STRING,
_('Plugin name.'),
required=True,
),
HADOOP_VERSION: properties.Schema(
properties.Schema.STRING,
_('Version of Hadoop running on instances.'),
required=True,
),
IMAGE_ID: properties.Schema(
properties.Schema.STRING,
_("ID of the default image to use for the template."),
constraints=[
constraints.CustomConstraint('glance.image')
],
),
MANAGEMENT_NETWORK: properties.Schema(
properties.Schema.STRING,
_('Name or UUID of Neutron network.'),
constraints=[
constraints.CustomConstraint('neutron.network')
],
),
ANTI_AFFINITY: properties.Schema(
properties.Schema.LIST,
_("List of processes to enable anti-affinity for."),
schema=properties.Schema(
properties.Schema.STRING,
),
),
CLUSTER_CONFIGS: properties.Schema(
properties.Schema.MAP,
_('Cluster configs dictionary.'),
),
NODE_GROUPS: properties.Schema(
properties.Schema.LIST,
_('Node groups.'),
schema=properties.Schema(
properties.Schema.MAP,
schema={
NG_NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the Node group.'),
required=True
),
COUNT: properties.Schema(
properties.Schema.INTEGER,
_("Number of instances in the Node group."),
required=True,
constraints=[
constraints.Range(min=1)
]
),
NG_TEMPLATE_ID: properties.Schema(
properties.Schema.STRING,
_("ID of the Node Group Template."),
required=True
),
}
),
),
}
default_client_name = 'sahara'
physical_resource_name_limit = 50
def _cluster_template_name(self):
name = self.properties.get(self.NAME)
if name:
return name
return self.physical_resource_name()
def handle_create(self):
plugin_name = self.properties[self.PLUGIN_NAME]
hadoop_version = self.properties[self.HADOOP_VERSION]
description = self.properties.get(self.DESCRIPTION)
image_id = self.properties.get(self.IMAGE_ID)
net_id = self.properties.get(self.MANAGEMENT_NETWORK)
if net_id:
net_id = self.client_plugin('neutron').find_neutron_resource(
self.properties, self.MANAGEMENT_NETWORK, 'network')
anti_affinity = self.properties.get(self.ANTI_AFFINITY)
cluster_configs = self.properties.get(self.CLUSTER_CONFIGS)
node_groups = self.properties.get(self.NODE_GROUPS)
cluster_template = self.client().cluster_templates.create(
self._cluster_template_name(),
plugin_name, hadoop_version,
description=description,
default_image_id=image_id,
anti_affinity=anti_affinity,
net_id=net_id,
cluster_configs=cluster_configs,
node_groups=node_groups
)
LOG.info(_("Cluster Template '%s' has been created"
) % cluster_template.name)
self.resource_id_set(cluster_template.id)
return self.resource_id
def handle_delete(self):
if not self.resource_id:
return
try:
self.client().cluster_templates.delete(
self.resource_id)
except Exception as ex:
self.client_plugin().ignore_not_found(ex)
LOG.info(_("Cluster Template '%s' has been deleted."
) % self._cluster_template_name())
def validate(self):
res = super(SaharaClusterTemplate, self).validate()
if res:
return res
# check if running on neutron and MANAGEMENT_NETWORK missing
#NOTE(pshchelo): on nova-network with MANAGEMENT_NETWORK present
# overall stack validation will fail due to neutron.network constraint,
# although the message will be not really relevant.
if (self.is_using_neutron() and
not self.properties.get(self.MANAGEMENT_NETWORK)):
msg = _("%s must be provided"
) % self.MANAGEMENT_NETWORK
raise exception.StackValidationFailed(message=msg)
def resource_mapping():
return {
'OS::Sahara::NodeGroupTemplate': SaharaNodeGroupTemplate,
'OS::Sahara::ClusterTemplate': SaharaClusterTemplate,
}

View File

@ -43,6 +43,19 @@ resources:
- jobtracker
"""
cluster_template = """
heat_template_version: 2013-05-23
description: Sahara Cluster Template
resources:
cluster-template:
type: OS::Sahara::ClusterTemplate
properties:
name: test-cluster-template
plugin_name: vanilla
hadoop_version: 2.3.0
neutron_management_network: some_network
"""
class FakeNodeGroupTemplate(object):
def __init__(self):
@ -50,6 +63,12 @@ class FakeNodeGroupTemplate(object):
self.name = "test-cluster-template"
class FakeClusterTemplate(object):
def __init__(self):
self.id = "some_ct_id"
self.name = "node-group-template"
class SaharaNodeGroupTemplateTest(HeatTestCase):
def setUp(self):
super(SaharaNodeGroupTemplateTest, self).setUp()
@ -131,3 +150,86 @@ class SaharaNodeGroupTemplateTest(HeatTestCase):
ex = self.assertRaises(exception.StackValidationFailed, ngt.validate)
self.assertEqual('floating_ip_pool must be provided.',
six.text_type(ex))
class SaharaClusterTemplateTest(HeatTestCase):
def setUp(self):
super(SaharaClusterTemplateTest, self).setUp()
self.patchobject(st.constraints.CustomConstraint, '_is_valid'
).return_value = True
self.patchobject(neutron.NeutronClientPlugin, '_create')
self.patchobject(neutron.NeutronClientPlugin, 'find_neutron_resource'
).return_value = 'some_network_id'
sahara_mock = mock.MagicMock()
self.ct_mgr = sahara_mock.cluster_templates
self.patchobject(sahara.SaharaClientPlugin,
'_create').return_value = sahara_mock
self.fake_ct = FakeClusterTemplate()
self.t = template_format.parse(cluster_template)
def _init_ct(self, template):
stack = utils.parse_stack(template)
return stack['cluster-template']
def test_ct_resource_mapping(self):
ct = self._init_ct(self.t)
mapping = st.resource_mapping()
self.assertEqual(st.SaharaClusterTemplate,
mapping['OS::Sahara::ClusterTemplate'])
self.assertIsInstance(ct,
st.SaharaClusterTemplate)
def _create_ct(self, template):
ct = self._init_ct(template)
self.ct_mgr.create.return_value = self.fake_ct
scheduler.TaskRunner(ct.create)()
self.assertEqual((ct.CREATE, ct.COMPLETE), ct.state)
self.assertEqual(self.fake_ct.id, ct.resource_id)
return ct
def test_ct_create(self):
self._create_ct(self.t)
expected_args = ('test-cluster-template', 'vanilla',
'2.3.0')
expected_kwargs = {'description': '',
'default_image_id': None,
'net_id': 'some_network_id',
'anti_affinity': None,
'node_groups': None,
'cluster_configs': None
}
self.ct_mgr.create.assert_called_once_with(*expected_args,
**expected_kwargs)
def test_ct_delete(self):
ct = self._create_ct(self.t)
scheduler.TaskRunner(ct.delete)()
self.ct_mgr.delete.assert_called_once_with(self.fake_ct.id)
self.assertEqual((ct.DELETE, ct.COMPLETE), ct.state)
def test_ngt_delete_ignores_not_found(self):
ct = self._create_ct(self.t)
self.ct_mgr.delete.side_effect = sahara.sahara_base.APIException(
error_code=404)
scheduler.TaskRunner(ct.delete)()
self.ct_mgr.delete.assert_called_once_with(self.fake_ct.id)
def test_ngt_delete_fails(self):
ct = self._create_ct(self.t)
self.ct_mgr.delete.side_effect = sahara.sahara_base.APIException()
delete_task = scheduler.TaskRunner(ct.delete)
ex = self.assertRaises(exception.ResourceFailure, delete_task)
expected = "APIException: None"
self.assertEqual(expected, six.text_type(ex))
self.ct_mgr.delete.assert_called_once_with(self.fake_ct.id)
def test_ct_validate_no_network_on_neutron_fails(self):
self.t['resources']['cluster-template']['properties'].pop(
'neutron_management_network')
ct = self._init_ct(self.t)
self.patchobject(ct, 'is_using_neutron', return_value=True)
ex = self.assertRaises(exception.StackValidationFailed,
ct.validate)
self.assertEqual("neutron_management_network must be provided",
six.text_type(ex))