]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Implement the AWS::S3::Bucket resource type.
authorSteve Baker <steve@stevebaker.org>
Tue, 11 Sep 2012 02:58:34 +0000 (14:58 +1200)
committerSteve Baker <steve@stevebaker.org>
Tue, 11 Sep 2012 04:00:17 +0000 (16:00 +1200)
An attempt was made to make created bucket names readable and unique.
Names are of the format heat-<resource name>-<random hex>. eg:
heat-S3Bucket-b420d12d02e5d6e46f13

Only the swift v2 auth is currently supported, which means swift will need to
use keystone for auth. This may be a valid assumption for any environment that
is running Heat.

When DeletionPolicy is Delete then an attempt is made to delete the container,
but the stack will still be deleted if container delete fails.

Run the template S3_Single_Instance.template to give it a try.

Functional tests will be coming in a later change.

Change-Id: Ifa2c3c4fcbdb00a44f8c6b347a61f8e1735e8328

heat/engine/resource_types.py
heat/engine/resources.py
heat/engine/s3.py [new file with mode: 0644]
templates/S3_Single_Instance.template [new file with mode: 0644]
tools/pip-requires

index be2646cd3f95ef072024b0d3e88f5fa7e4643556..4126e965724d7886f4362b162dc8f56f3b4f7fea 100644 (file)
@@ -26,6 +26,7 @@ from heat.engine import dbinstance
 from heat.engine import eip
 from heat.engine import instance
 from heat.engine import loadbalancer
+from heat.engine import s3
 from heat.engine import security_group
 from heat.engine import stack
 from heat.engine import user
@@ -46,6 +47,7 @@ _resource_classes = {
     'AWS::EC2::Volume': volume.Volume,
     'AWS::EC2::VolumeAttachment': volume.VolumeAttachment,
     'AWS::ElasticLoadBalancing::LoadBalancer': loadbalancer.LoadBalancer,
+    'AWS::S3::Bucket': s3.S3Bucket,
     'AWS::IAM::User': user.User,
     'AWS::IAM::AccessKey': user.AccessKey,
     'HEAT::HA::Restarter': instance.Restarter,
index df9422839665cb765bf7a273a0e6b0a9e60321e6..5c39033a8b2afdcb54f4bcd5e228b10945f03b67 100644 (file)
@@ -18,6 +18,7 @@ from datetime import datetime
 
 from novaclient.v1_1 import client as nc
 from keystoneclient.v2_0 import client as kc
+from swiftclient import client as swiftclient
 
 from heat.common import exception
 from heat.common import config
@@ -159,6 +160,7 @@ class Resource(object):
             self.id = None
         self._nova = {}
         self._keystone = None
+        self._swift = None
 
     def __eq__(self, other):
         '''Allow == comparison of two resources'''
@@ -227,6 +229,17 @@ class Resource(object):
                                                      service_name=None)
         return self._nova[service_type]
 
+    def swift(self):
+        if self._swift:
+            return self._swift
+
+        con = self.context
+        self._swift = swiftclient.Connection(
+            con.auth_url, con.username, con.password,
+            tenant_name=con.tenant, auth_version='2')
+
+        return self._swift
+
     def calculate_properties(self):
         for p, v in self.parsed_template('Properties').items():
             self.properties[p] = v
diff --git a/heat/engine/s3.py b/heat/engine/s3.py
new file mode 100644 (file)
index 0000000..37271fb
--- /dev/null
@@ -0,0 +1,108 @@
+# 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.
+
+import binascii
+import os
+from urlparse import urlparse
+
+from heat.common import exception
+from heat.engine.resources import Resource
+from heat.openstack.common import log as logging
+from swiftclient.client import ClientException
+
+logger = logging.getLogger('heat.engine.s3')
+
+
+class S3Bucket(Resource):
+    website_schema = {'IndexDocument': {'Type': 'String'},
+                      'ErrorDocument': {'Type': 'String'}}
+    properties_schema = {'AccessControl': {
+                           'Type': 'String',
+                           'AllowedValues': ['Private',
+                                             'PublicRead',
+                                             'PublicReadWrite',
+                                             'AuthenticatedRead',
+                                             'BucketOwnerRead',
+                                             'BucketOwnerFullControl']},
+                        'DeletionPolicy': {
+                            'Type': 'String',
+                            'AllowedValues': ['Delete',
+                                              'Retain']},
+                        'WebsiteConfiguration': {'Type': 'Map',
+                                                 'Schema': website_schema}}
+
+    def __init__(self, name, json_snippet, stack):
+        super(S3Bucket, self).__init__(name, json_snippet, stack)
+
+    def handle_create(self):
+        """Create a bucket."""
+        container = 'heat-%s-%s' % (self.name,
+                                    binascii.hexlify(os.urandom(10)))
+        headers = {}
+        logger.debug('S3Bucket create container %s with headers %s' %
+                     (container, headers))
+        if 'WebsiteConfiguration' in self.properties:
+            site_cfg = self.properties['WebsiteConfiguration']
+            # we will assume that swift is configured for the staticweb
+            # wsgi middleware
+            headers['X-Container-Meta-Web-Index'] = site_cfg['IndexDocument']
+            headers['X-Container-Meta-Web-Error'] = site_cfg['ErrorDocument']
+
+        con = self.context
+        ac = self.properties['AccessControl']
+        tenant_username = '%s:%s' % (con.tenant, con.username)
+        if ac in ('PublicRead', 'PublicReadWrite'):
+            headers['X-Container-Read'] = '.r:*'
+        elif ac == 'AuthenticatedRead':
+            headers['X-Container-Read'] = con.tenant
+        else:
+            headers['X-Container-Read'] = tenant_username
+
+        if ac == 'PublicReadWrite':
+            headers['X-Container-Write'] = '.r:*'
+        else:
+            headers['X-Container-Write'] = tenant_username
+
+        self.swift().put_container(container, headers)
+        self.instance_id_set(container)
+
+    def handle_update(self):
+        return self.UPDATE_REPLACE
+
+    def handle_delete(self):
+        """Perform specified delete policy"""
+        if self.properties['DeletionPolicy'] == 'Retain':
+            return
+        logger.debug('S3Bucket delete container %s' % self.instance_id)
+        if self.instance_id is not None:
+            try:
+                self.swift().delete_container(self.instance_id)
+            except ClientException as ex:
+                logger.warn("Delete container failed: %s" % str(ex))
+
+    def FnGetRefId(self):
+        return unicode(self.instance_id)
+
+    def FnGetAtt(self, key):
+        url, token_id = self.swift().get_auth()
+        parsed = list(urlparse(url))
+        if key == 'DomainName':
+            return parsed[1].split(':')[0]
+        elif key == 'WebsiteURL':
+            return '%s://%s%s/%s' % (parsed[0], parsed[1], parsed[2],
+                                      self.instance_id)
+        else:
+            raise exception.InvalidTemplateAttribute(resource=self.name,
+                                                     key=key)
diff --git a/templates/S3_Single_Instance.template b/templates/S3_Single_Instance.template
new file mode 100644 (file)
index 0000000..33f4425
--- /dev/null
@@ -0,0 +1,30 @@
+{
+  "AWSTemplateFormatVersion" : "2010-09-09",
+
+  "Description" : "Template to test S3 Bucket resources",
+
+  "Resources" : {
+    "S3Bucket" : {
+      "Type" : "AWS::S3::Bucket",
+      "Properties" : {
+        "AccessControl" : "PublicRead",
+        "WebsiteConfiguration" : {
+          "IndexDocument" : "index.html",
+          "ErrorDocument" : "error.html"
+         },
+               "DeletionPolicy" : "Delete"
+      }
+    }
+  },
+
+  "Outputs" : {
+       "WebsiteURL" : {
+      "Value" : { "Fn::GetAtt" : [ "S3Bucket", "WebsiteURL" ] },
+      "Description" : "URL for website hosted on S3"
+    },
+       "DomainName" : {
+          "Value" : { "Fn::GetAtt" : [ "S3Bucket", "DomainName" ] },
+          "Description" : "Domain of S3 host"
+    }
+  }
+}
index b62a35e28062510ee8c7eea8f606fa30d4e6cab9..092419da8d470ef55b7e0c28b08c135d0dd8bed0 100644 (file)
@@ -26,4 +26,5 @@ WebOb
 python-keystoneclient
 glance
 python-memcached
+python-swiftclient