]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Move dbinstance into a TemplateResource
authorAngus Salkeld <asalkeld@redhat.com>
Thu, 5 Sep 2013 10:47:07 +0000 (20:47 +1000)
committerSteve Baker <sbaker@redhat.com>
Fri, 6 Sep 2013 23:04:39 +0000 (11:04 +1200)
The old dbinstance will soon be overtaken by the trove resource.
- The only reason for keeping this around is for people that don't
  have trove installed and want to use the AWS resource.
- Seperating it out into a TemplateResource really makes it easier
  for deployers and users to customise it.
- The old dbinstance did nothing "special" in python, and was really
  one of the first "TemplateResources" as it attempted to convert properties
  into parameters. Since this in now done a lot better in the TemplateResource
  lets just make use of that.

This will make it easier to migrate to other distros and versions.

Partial-Bug: #1215797
Change-Id: If72e1f40f67dc831551e0db8df8caaa002aaaeda

etc/heat/environment.d/default.yaml
etc/heat/templates/AWS_RDS_DBInstance.yaml [new file with mode: 0644]
heat/engine/resources/dbinstance.py [deleted file]
heat/tests/test_dbinstance.py

index 1f4c848a6b16a02ec8ab4ad281b30d249508a415..dae1e37b21a0fb161c6ec77e01927c6c0389e855 100644 (file)
@@ -6,3 +6,4 @@ resource_registry:
     #"AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml"
     "AWS::CloudWatch::Alarm": "OS::Heat::CWLiteAlarm"
     "OS::Metering::Alarm": "OS::Ceilometer::Alarm"
+    "AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml"
diff --git a/etc/heat/templates/AWS_RDS_DBInstance.yaml b/etc/heat/templates/AWS_RDS_DBInstance.yaml
new file mode 100644 (file)
index 0000000..6f2dd6b
--- /dev/null
@@ -0,0 +1,98 @@
+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}
diff --git a/heat/engine/resources/dbinstance.py b/heat/engine/resources/dbinstance.py
deleted file mode 100644 (file)
index f602282..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-# 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,
-    }
index fb88aa80bc01c6c609167ef37ebf656a58ea6bb7..2eaa60f80af3eece26e35d63554aaccbb6c97ead 100644 (file)
 #    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 = '''
@@ -52,64 +48,71 @@ 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))