--- /dev/null
+HeatTemplateFormatVersion: '2012-12-12'
+Description: 'Builtin AWS::RDS::DBInstance'
+Parameters:
+ AllocatedStorage:
+ Type: String
+ DBInstanceClass:
+ Type: String
+ DBName:
+ Type: String
+ DBSecurityGroups:
+ Type: CommaDelimitedList
+ Default: ''
+ Engine:
+ Type: String
+ AllowedValues: ['MySQL']
+ MasterUsername:
+ Type: String
+ MasterUserPassword:
+ Type: String
+ Port:
+ Type: String
+ Default: '3306'
+ KeyName:
+ Type: String
+ Default: ''
+
+Mappings:
+ DBInstanceToInstance:
+ db.m1.small: {Instance: m1.small}
+ db.m1.large: {Instance: m1.large}
+ db.m1.xlarge: {Instance: m1.xlarge}
+ db.m2.xlarge: {Instance: m2.xlarge}
+ db.m2.2xlarge: {Instance: m2.2xlarge}
+ db.m2.4xlarge: {Instance: m2.4xlarge}
+
+Resources:
+ DatabaseInstance:
+ Type: AWS::EC2::Instance
+ Metadata:
+ AWS::CloudFormation::Init:
+ config:
+ packages:
+ yum:
+ mysql : []
+ mysql-server : []
+ services:
+ systemd:
+ mysqld:
+ enabled: true
+ ensureRunning: true
+ Properties:
+ ImageId: F17-x86_64-cfntools
+ InstanceType: {'Fn::FindInMap': [DBInstanceToInstance,
+ {Ref: DBInstanceClass}, Instance]}
+ KeyName: {Ref: KeyName}
+
+ UserData:
+ Fn::Base64:
+ Fn::Replace:
+ - 'AWS::StackName': {Ref: 'AWS::StackName'}
+ 'AWS::Region': {Ref: 'AWS::Region'}
+ MasterUsername: {Ref: MasterUsername}
+ MasterUserPassword: {Ref: MasterUserPassword}
+ DBName: {Ref: DBName}
+ WaitHandle: {Ref: WaitHandle}
+ - |
+ #!/bin/bash -v
+ # Helper function
+ function error_exit
+ {
+ /opt/aws/bin/cfn-signal -e 1 -r \"$1\" 'WaitHandle'
+ exit 1
+ }
+ /opt/aws/bin/cfn-init -s AWS::StackName -r DatabaseInstance --region AWS::Region || error_exit 'Failed to run cfn-init'
+ # Setup MySQL root password and create a user
+ mysqladmin -u root password 'MasterUserPassword'
+ cat << EOF | mysql -u root --password='MasterUserPassword'
+ CREATE DATABASE DBName;
+ GRANT ALL PRIVILEGES ON DBName.* TO "MasterUsername"@"%"
+ IDENTIFIED BY "MasterUserPassword";
+ FLUSH PRIVILEGES;
+ EXIT
+ EOF
+ # Database setup completed, signal success
+ /opt/aws/bin/cfn-signal -e 0 -r "MySQL server setup complete" 'WaitHandle'
+
+ WaitHandle:
+ Type: AWS::CloudFormation::WaitConditionHandle
+ WaitCondition:
+ Type: AWS::CloudFormation::WaitCondition
+ DependsOn: DatabaseInstance
+ Properties:
+ Handle: {Ref: WaitHandle}
+ Timeout: "600"
+
+Outputs:
+ Endpoint.Address: {'Fn::GetAtt': [DatabaseInstance, PublicIp]}
+ Endpoint.Port: {Ref: Port}
+++ /dev/null
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-#
-# 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.
-
-from heat.common import template_format
-from heat.engine import stack_resource
-from heat.openstack.common import log as logging
-
-logger = logging.getLogger(__name__)
-
-mysql_template = r'''
-{
- "AWSTemplateFormatVersion": "2010-09-09",
- "Description": "Builtin RDS::DBInstance",
- "Parameters" : {
- "DBInstanceClass" : {
- "Type": "String"
- },
-
- "DBName" : {
- "Type": "String"
- },
-
- "MasterUsername" : {
- "Type": "String"
- },
-
- "MasterUserPassword" : {
- "Type": "String"
- },
-
- "AllocatedStorage" : {
- "Type": "String"
- },
-
- "DBSecurityGroups" : {
- "Type": "CommaDelimitedList",
- "Default": ""
- },
-
- "Port" : {
- "Type": "String"
- },
-
- "KeyName" : {
- "Type" : "String"
- }
- },
-
- "Mappings" : {
- "DBInstanceToInstance" : {
- "db.m1.small": {"Instance": "m1.small"},
- "db.m1.large": {"Instance": "m1.large"},
- "db.m1.xlarge": {"Instance": "m1.xlarge"},
- "db.m2.xlarge": {"Instance": "m2.xlarge"},
- "db.m2.2xlarge": {"Instance": "m2.2xlarge"},
- "db.m2.4xlarge": {"Instance": "m2.4xlarge"}
- }
- },
-
-
- "Resources": {
- "DatabaseInstance": {
- "Type": "AWS::EC2::Instance",
- "Metadata": {
- "AWS::CloudFormation::Init": {
- "config": {
- "packages": {
- "yum": {
- "mysql" : [],
- "mysql-server" : []
- }
- },
- "services": {
- "systemd": {
- "mysqld" : { "enabled" : "true", "ensureRunning" : "true" }
- }
- }
- }
- }
- },
- "Properties": {
- "ImageId": "F17-x86_64-cfntools",
- "InstanceType": { "Fn::FindInMap": [ "DBInstanceToInstance",
- { "Ref": "DBInstanceClass" },
- "Instance" ] },
- "KeyName": { "Ref": "KeyName" },
- "UserData": { "Fn::Base64": { "Fn::Join": ["", [
- "#!/bin/bash -v\n",
- "# Helper function\n",
- "function error_exit\n",
- "{\n",
- " /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '",
- { "Ref" : "WaitHandle" }, "'\n",
- " exit 1\n",
- "}\n",
-
- "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackName" },
- " -r DatabaseInstance",
- " --region ", { "Ref" : "AWS::Region" },
- " || error_exit 'Failed to run cfn-init'\n",
- "# Setup MySQL root password and create a user\n",
- "mysqladmin -u root password '", {"Ref":"MasterUserPassword"},"'\n",
- "cat << EOF | mysql -u root --password='",
- { "Ref" : "MasterUserPassword" }, "'\n",
- "CREATE DATABASE ", { "Ref" : "DBName" }, ";\n",
- "GRANT ALL PRIVILEGES ON ", { "Ref" : "DBName" },
- ".* TO \"", { "Ref" : "MasterUsername" }, "\"@\"%\"\n",
- "IDENTIFIED BY \"", { "Ref" : "MasterUserPassword" }, "\";\n",
- "FLUSH PRIVILEGES;\n",
- "EXIT\n",
- "EOF\n",
- "# Database setup completed, signal success\n",
- "/opt/aws/bin/cfn-signal -e 0 -r \"MySQL server setup complete\" '",
- { "Ref" : "WaitHandle" }, "'\n"
-
- ]]}}
- }
- },
-
- "WaitHandle" : {
- "Type" : "AWS::CloudFormation::WaitConditionHandle"
- },
-
- "WaitCondition" : {
- "Type" : "AWS::CloudFormation::WaitCondition",
- "DependsOn" : "DatabaseInstance",
- "Properties" : {
- "Handle" : {"Ref" : "WaitHandle"},
- "Timeout" : "600"
- }
- }
- },
-
- "Outputs": {
- }
-}
-'''
-
-
-class DBInstance(stack_resource.StackResource):
-
- properties_schema = {
- 'DBSnapshotIdentifier': {'Type': 'String',
- 'Implemented': False},
- 'AllocatedStorage': {'Type': 'String',
- 'Required': True},
- 'AvailabilityZone': {'Type': 'String',
- 'Implemented': False},
- 'BackupRetentionPeriod': {'Type': 'String',
- 'Implemented': False},
- 'DBInstanceClass': {'Type': 'String',
- 'Required': True},
- 'DBName': {'Type': 'String',
- 'Required': False},
- 'DBParameterGroupName': {'Type': 'String',
- 'Implemented': False},
- 'DBSecurityGroups': {'Type': 'List',
- 'Required': False, 'Default': []},
- 'DBSubnetGroupName': {'Type': 'String',
- 'Implemented': False},
- 'Engine': {'Type': 'String',
- 'AllowedValues': ['MySQL'],
- 'Required': True},
- 'EngineVersion': {'Type': 'String',
- 'Implemented': False},
- 'LicenseModel': {'Type': 'String',
- 'Implemented': False},
- 'MasterUsername': {'Type': 'String',
- 'Required': True},
- 'MasterUserPassword': {'Type': 'String',
- 'Required': True},
- 'Port': {'Type': 'String',
- 'Default': '3306',
- 'Required': False},
- 'PreferredBackupWindow': {'Type': 'String',
- 'Implemented': False},
- 'PreferredMaintenanceWindow': {'Type': 'String',
- 'Implemented': False},
- 'MultiAZ': {'Type': 'Boolean',
- 'Implemented': False},
- }
-
- # We only support a couple of the attributes right now
- attributes_schema = {
- "Endpoint.Address": "Connection endpoint for the database.",
- "Endpoint.Port": ("The port number on which the database accepts "
- "connections.")
- }
-
- def _params(self):
- params = {
- 'KeyName': {'Ref': 'KeyName'},
- }
-
- # Add the DBInstance parameters specified in the user's template
- # Ignore the not implemented ones
- for key, value in self.properties_schema.items():
- if value.get('Implemented', True) and key != 'Engine':
- # There is a mismatch between the properties "List" format
- # and the parameters "CommaDelimitedList" format, so we need
- # to translate lists into the expected comma-delimited form
- if isinstance(self.properties[key], list):
- params[key] = ','.join(self.properties[key])
- else:
- params[key] = self.properties[key]
- p = self.stack.resolve_static_data(params)
- return p
-
- def handle_create(self):
- templ = template_format.parse(mysql_template)
- return self.create_with_template(templ, self._params())
-
- def handle_delete(self):
- return self.delete_nested()
-
- def _resolve_attribute(self, name):
- '''
- We don't really support any of these yet.
- '''
- if name == 'Endpoint.Address':
- if self.nested() and 'DatabaseInstance' in self.nested().resources:
- return self.nested().resources['DatabaseInstance']._ipaddress()
- else:
- return '0.0.0.0'
- elif name == 'Endpoint.Port':
- return self.properties['Port']
-
-
-def resource_mapping():
- return {
- 'AWS::RDS::DBInstance': DBInstance,
- }
# License for the specific language governing permissions and limitations
# under the License.
-
-import mox
-
-from heat.common import exception
from heat.common import template_format
-from heat.engine import scheduler
-from heat.engine.resources import dbinstance as dbi
+from heat.engine import resource
from heat.tests.common import HeatTestCase
from heat.tests import utils
+from heat.engine import parser
rds_template = '''
'''
+class DBInstance(resource.Resource):
+ """This is copied from the old DBInstance
+ to verify the schema of the new TemplateResource.
+ """
+ properties_schema = {
+ 'DBSnapshotIdentifier': {'Type': 'String',
+ 'Implemented': False},
+ 'AllocatedStorage': {'Type': 'String',
+ 'Required': True},
+ 'AvailabilityZone': {'Type': 'String',
+ 'Implemented': False},
+ 'BackupRetentionPeriod': {'Type': 'String',
+ 'Implemented': False},
+ 'DBInstanceClass': {'Type': 'String',
+ 'Required': True},
+ 'DBName': {'Type': 'String',
+ 'Required': False},
+ 'DBParameterGroupName': {'Type': 'String',
+ 'Implemented': False},
+ 'DBSecurityGroups': {'Type': 'List',
+ 'Required': False, 'Default': []},
+ 'DBSubnetGroupName': {'Type': 'String',
+ 'Implemented': False},
+ 'Engine': {'Type': 'String',
+ 'AllowedValues': ['MySQL'],
+ 'Required': True},
+ 'EngineVersion': {'Type': 'String',
+ 'Implemented': False},
+ 'LicenseModel': {'Type': 'String',
+ 'Implemented': False},
+ 'MasterUsername': {'Type': 'String',
+ 'Required': True},
+ 'MasterUserPassword': {'Type': 'String',
+ 'Required': True},
+ 'Port': {'Type': 'String',
+ 'Default': '3306',
+ 'Required': False},
+ 'PreferredBackupWindow': {'Type': 'String',
+ 'Implemented': False},
+ 'PreferredMaintenanceWindow': {'Type': 'String',
+ 'Implemented': False},
+ 'MultiAZ': {'Type': 'Boolean',
+ 'Implemented': False},
+ }
+
+ # We only support a couple of the attributes right now
+ attributes_schema = {
+ "Endpoint.Address": "Connection endpoint for the database.",
+ "Endpoint.Port": ("The port number on which the database accepts "
+ "connections.")
+ }
+
+
class DBInstanceTest(HeatTestCase):
def setUp(self):
super(DBInstanceTest, self).setUp()
utils.setup_dummy_db()
- self.m.StubOutWithMock(dbi.DBInstance, 'create_with_template')
- self.m.StubOutWithMock(dbi.DBInstance, 'check_create_complete')
- self.m.StubOutWithMock(dbi.DBInstance, 'nested')
-
- def create_dbinstance(self, t, stack, resource_name):
- resource = dbi.DBInstance(resource_name,
- t['Resources'][resource_name],
- stack)
- self.assertEqual(None, resource.validate())
- scheduler.TaskRunner(resource.create)()
- self.assertEqual((resource.CREATE, resource.COMPLETE), resource.state)
- return resource
def test_dbinstance(self):
-
- class FakeDatabaseInstance(object):
- def _ipaddress(self):
- return '10.0.0.1'
-
- class FakeNested(object):
- resources = {'DatabaseInstance': FakeDatabaseInstance()}
-
- params = {'DBSecurityGroups': '',
- 'MasterUsername': u'admin',
- 'MasterUserPassword': u'admin',
- 'DBName': u'wordpress',
- 'KeyName': u'test',
- 'AllocatedStorage': u'5',
- 'DBInstanceClass': u'db.m1.small',
- 'Port': '3306'}
-
- dbi.DBInstance.create_with_template(mox.IgnoreArg(),
- params).AndReturn(None)
- dbi.DBInstance.check_create_complete(mox.IgnoreArg()).AndReturn(True)
-
- fn = FakeNested()
-
- dbi.DBInstance.nested().AndReturn(None)
- dbi.DBInstance.nested().MultipleTimes().AndReturn(fn)
- self.m.ReplayAll()
-
- t = template_format.parse(rds_template)
- s = utils.parse_stack(t)
- resource = self.create_dbinstance(t, s, 'DatabaseServer')
-
- self.assertEqual('0.0.0.0', resource.FnGetAtt('Endpoint.Address'))
- self.assertEqual('10.0.0.1', resource.FnGetAtt('Endpoint.Address'))
- self.assertEqual('3306', resource.FnGetAtt('Endpoint.Port'))
-
- try:
- resource.FnGetAtt('foo')
- except exception.InvalidTemplateAttribute:
- pass
- else:
- raise Exception('Expected InvalidTemplateAttribute')
-
- self.m.VerifyAll()
+ """test that the Template is parsable and
+ publishes the correct properties.
+ """
+ templ = parser.Template(template_format.parse(rds_template))
+ stack = parser.Stack(utils.dummy_context(), 'test_stack',
+ templ)
+
+ res = stack['DatabaseServer']
+ self.assertEquals(None, res._validate_against_facade(DBInstance))