Externalization of heat template

Reasons: preparing trove to support multi-datastores
Changes: template loading, heat-support update

This code refactors the earlier code of instance creation using heat.

- Configuration templates stored at trove/templates/(datastore_type)/config.template
- Heat templates are stored in trove/templates/(datastore_type)/heat.template
- One template per datastore type
- New parameter - ImageId
- Removed SSH key parameter
- All resources are updated according to available OS resources
  (for more details please visit heat repository).

Author: Denis Makogon (dmakogon) <dmakogon@mirantis.com>
Co-Authored-By: Nilakhya Chatterjee (SnowDust) <nilakhya.chatterjee@globallogic.com>

Change-Id: Ie6863a625e1525e152ed2886a630dc4f233be8dc
Implements: blueprint provide-custom-heat-templates-for-each-service-in-trove
This commit is contained in:
Nilakhya Chatterjee
2013-10-29 14:25:12 +02:00
committed by Nilakhya
parent 12f2b0d47c
commit c3d2466f76
8 changed files with 148 additions and 71 deletions

View File

@@ -77,6 +77,9 @@ agent_call_high_timeout = 150
# Whether to use nova's contrib api for create server with volume
use_nova_server_volume = False
# Datastore templates
template_path = /etc/trove/templates/
# ============ notifer queue kombu connection options ========================
notifier_queue_hostname = localhost

View File

@@ -216,6 +216,9 @@ common_opts = [
help='Extention for default service managers.'
' Allows to use custom managers for each of'
' service type supported in trove'),
cfg.StrOpt('template_path',
default='/etc/trove/templates/',
help='Path which leads to datastore templates'),
]
CONF = cfg.CONF

View File

@@ -13,20 +13,27 @@
# under the License.
import jinja2
from trove.common import cfg
from trove.common import exception
from trove.openstack.common import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
ENV = jinja2.Environment(loader=jinja2.ChoiceLoader([
jinja2.FileSystemLoader("/etc/trove/templates"),
jinja2.PackageLoader("trove", "templates")
jinja2.FileSystemLoader(CONF.template_path),
jinja2.PackageLoader("trove", "templates"),
]))
class SingleInstanceConfigTemplate(object):
""" This class selects a single configuration file by database type for
rendering on the guest """
def __init__(self, service_type, flavor_dict, instance_id):
def __init__(self, datastore_type, flavor_dict, instance_id):
""" Constructor
:param service_type: The database type.
:param datastore_type: The database type.
:type name: str.
:param flavor_dict: dict containing flavor details for use in jinja.
:type flavor_dict: dict.
@@ -35,7 +42,7 @@ class SingleInstanceConfigTemplate(object):
"""
self.flavor_dict = flavor_dict
template_filename = "%s.config.template" % service_type
template_filename = "%s/config.template" % datastore_type
self.template = ENV.get_template(template_filename)
self.instance_id = instance_id
@@ -59,60 +66,12 @@ class SingleInstanceConfigTemplate(object):
return abs(hash(self.instance_id) % (2 ** 31))
class HeatTemplate(object):
template_contents = """HeatTemplateFormatVersion: '2012-12-12'
Description: Instance creation
Parameters:
KeyName: {Type: String}
Flavor: {Type: String}
VolumeSize: {Type: Number}
ServiceType: {Type: String}
InstanceId: {Type: String}
AvailabilityZone : {Type: String}
Resources:
BaseInstance:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
files:
/etc/guest_info:
content:
Fn::Join:
- ''
- ["[DEFAULT]\\nguest_id=", {Ref: InstanceId},
"\\nservice_type=", {Ref: ServiceType}]
mode: '000644'
owner: root
group: root
Properties:
ImageId:
Fn::Join:
- ''
- ["ubuntu_", {Ref: ServiceType}]
InstanceType: {Ref: Flavor}
KeyName: {Ref: KeyName}
AvailabilityZone: {Ref: AvailabilityZone}
UserData:
Fn::Base64:
Fn::Join:
- ''
- ["#!/bin/bash -v\\n",
"/opt/aws/bin/cfn-init\\n",
"sudo service trove-guest start\\n"]
DataVolume:
Type: AWS::EC2::Volume
Properties:
Size: {Ref: VolumeSize}
AvailabilityZone: {Ref: AvailabilityZone}
Tags:
- {Key: Usage, Value: Test}
MountPoint:
Type: AWS::EC2::VolumeAttachment
Properties:
InstanceId: {Ref: BaseInstance}
VolumeId: {Ref: DataVolume}
Device: /dev/vdb"""
def template(self):
return self.template_contents
def load_heat_template(datastore_type):
template_filename = "%s/heat.template" % datastore_type
try:
template_obj = ENV.get_template(template_filename)
return template_obj
except jinja2.TemplateNotFound:
msg = "Missing heat template for %s" % datastore_type
LOG.error(msg)
raise exception.TroveError(msg)

View File

@@ -145,7 +145,13 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
availability_zone, root_password):
security_groups = None
if CONF.trove_security_groups_support:
# If security group support is enabled and heat based instance
# orchestration is disabled, create a security group.
#
# Heat based orchestration handles security group(resource)
# in the template defination.
if CONF.trove_security_groups_support and not use_heat:
try:
security_groups = self._create_secgroup()
except Exception as e:
@@ -322,17 +328,24 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
client = create_heat_client(self.context)
novaclient = create_nova_client(self.context)
cinderclient = create_cinder_client(self.context)
heat_template = template.HeatTemplate().template()
parameters = {"KeyName": "heatkey",
"Flavor": flavor["name"],
template_obj = template.load_heat_template(service_type)
heat_template_unicode = template_obj.render()
try:
heat_template = heat_template_unicode.encode('ascii')
except UnicodeEncodeError:
LOG.error(_("heat template ascii encode issue"))
raise TroveError("heat template ascii encode issue")
parameters = {"Flavor": flavor["name"],
"VolumeSize": volume_size,
"ServiceType": service_type,
"InstanceId": self.id,
"ImageId": image_id,
"AvailabilityZone": availability_zone}
stack_name = 'trove-%s' % self.id
stack = client.stacks.create(stack_name=stack_name,
template=heat_template,
parameters=parameters)
client.stacks.create(stack_name=stack_name,
template=heat_template,
parameters=parameters)
stack = client.stacks.get(stack_name)
utils.poll_until(

View File

@@ -0,0 +1,72 @@
HeatTemplateFormatVersion: '2012-12-12'
Description: Instance creation template for mysql
Parameters:
Flavor:
Type: String
VolumeSize:
Type: Number
Default : '1'
InstanceId:
Type: String
ImageId:
Type: String
AvailabilityZone:
Type: String
Default: nova
Resources:
BaseInstance:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
files:
/etc/guest_info:
content:
Fn::Join:
- ''
- ["[DEFAULT]\nguest_id=", {Ref: InstanceId},
"\nservice_type=mysql"]
mode: '000644'
owner: root
group: root
Properties:
ImageId: {Ref: ImageId}
InstanceType: {Ref: Flavor}
AvailabilityZone: {Ref: AvailabilityZone}
SecurityGroups : [{Ref: MySqlDbaasSG}]
UserData:
Fn::Base64:
Fn::Join:
- ''
- ["#!/bin/bash -v\n",
"/opt/aws/bin/cfn-init\n",
"sudo service trove-guest start\n"]
DataVolume:
Type: AWS::EC2::Volume
Properties:
Size: {Ref: VolumeSize}
AvailabilityZone: {Ref: AvailabilityZone}
Tags:
- {Key: Usage, Value: Test}
MountPoint:
Type: AWS::EC2::VolumeAttachment
Properties:
InstanceId: {Ref: BaseInstance}
VolumeId: {Ref: DataVolume}
Device: /dev/vdb
MySqlDbaasSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Default Security group for MySQL
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: "3306"
ToPort: "3306"
CidrIp: "0.0.0.0/0"
DatabaseIPAddress:
Type: AWS::EC2::EIP
DatabaseIPAssoc :
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId: {Ref: BaseInstance}
EIP: {Ref: DatabaseIPAddress}

View File

@@ -12,9 +12,11 @@
import testtools
import mock
import re
from trove.common import template
from trove.common import exception
from trove.tests.unittests.util import util
@@ -23,7 +25,7 @@ class TemplateTest(testtools.TestCase):
super(TemplateTest, self).setUp()
util.init_db()
self.env = template.ENV
self.template = self.env.get_template("mysql.config.template")
self.template = self.env.get_template("mysql/config.template")
self.flavor_dict = {'ram': 1024}
self.server_id = "180b5ed1-3e57-4459-b7a3-2aeee4ac012a"
@@ -60,3 +62,28 @@ class TemplateTest(testtools.TestCase):
self.server_id)
self.validate_template(config.render(), "query_cache_size",
self.flavor_dict, self.server_id)
class HeatTemplateLoadTest(testtools.TestCase):
def setUp(self):
super(HeatTemplateLoadTest, self).setUp()
self.fException = mock.Mock(side_effect=
lambda *args, **kwargs:
_raise(template.jinja2.
TemplateNotFound("Test")))
def _raise(ex):
raise ex
def tearDown(self):
super(HeatTemplateLoadTest, self).tearDown()
def test_heat_template_load_fail(self):
self.assertRaises(exception.TroveError,
template.load_heat_template,
'mysql-blah')
def test_heat_template_load_success(self):
htmpl = template.load_heat_template('mysql')
self.assertNotEqual(None, htmpl)