~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to nova/objectstore/image.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
# Copyright [2010] [Anso Labs, LLC]
 
3
#
 
4
#    Licensed under the Apache License, Version 2.0 (the "License");
 
5
#    you may not use this file except in compliance with the License.
 
6
#    You may obtain a copy of the License at
 
7
#
 
8
#        http://www.apache.org/licenses/LICENSE-2.0
 
9
#
 
10
#    Unless required by applicable law or agreed to in writing, software
 
11
#    distributed under the License is distributed on an "AS IS" BASIS,
 
12
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
#    See the License for the specific language governing permissions and
 
14
#    limitations under the License.
 
15
 
 
16
"""
 
17
Take uploaded bucket contents and register them as disk images (AMIs).
 
18
Requires decryption using keys in the manifest.
 
19
"""
 
20
 
 
21
# TODO(jesse): Got these from Euca2ools, will need to revisit them
 
22
 
 
23
import binascii
 
24
import glob
 
25
import json
 
26
import os
 
27
import shutil
 
28
import tarfile
 
29
import tempfile
 
30
from xml.etree import ElementTree
 
31
 
 
32
from nova import exception
 
33
from nova import flags
 
34
from nova import utils
 
35
from nova.objectstore import bucket
 
36
 
 
37
 
 
38
FLAGS = flags.FLAGS
 
39
flags.DEFINE_string('images_path', utils.abspath('../images'),
 
40
                        'path to decrypted images')
 
41
 
 
42
class Image(object):
 
43
    def __init__(self, image_id):
 
44
        self.image_id = image_id
 
45
        self.path = os.path.abspath(os.path.join(FLAGS.images_path, image_id))
 
46
        if not self.path.startswith(os.path.abspath(FLAGS.images_path)) or \
 
47
           not os.path.isdir(self.path):
 
48
             raise exception.NotFound
 
49
 
 
50
    def delete(self):
 
51
        for fn in ['info.json', 'image']:
 
52
            try:
 
53
                os.unlink(os.path.join(self.path, fn))
 
54
            except:
 
55
                pass
 
56
        try:
 
57
            os.rmdir(self.path)
 
58
        except:
 
59
            pass
 
60
 
 
61
    def is_authorized(self, user):
 
62
        try:
 
63
            return self.metadata['isPublic'] or self.metadata['imageOwnerId'] == user.id
 
64
        except:
 
65
            return False
 
66
 
 
67
    def set_public(self, state):
 
68
        md = self.metadata
 
69
        md['isPublic'] = state
 
70
        with open(os.path.join(self.path, 'info.json'), 'w') as f:
 
71
            json.dump(md, f)
 
72
 
 
73
    @staticmethod
 
74
    def all():
 
75
        images = []
 
76
        for fn in glob.glob("%s/*/info.json" % FLAGS.images_path):
 
77
            try:
 
78
                image_id = fn.split('/')[-2]
 
79
                images.append(Image(image_id))
 
80
            except:
 
81
                pass
 
82
        return images
 
83
 
 
84
    @property
 
85
    def owner_id(self):
 
86
        return self.metadata['imageOwnerId']
 
87
 
 
88
    @property
 
89
    def metadata(self):
 
90
        with open(os.path.join(self.path, 'info.json')) as f:
 
91
            return json.load(f)
 
92
 
 
93
    @staticmethod
 
94
    def create(image_id, image_location, user):
 
95
        image_path = os.path.join(FLAGS.images_path, image_id)
 
96
        os.makedirs(image_path)
 
97
 
 
98
        bucket_name = image_location.split("/")[0]
 
99
        manifest_path = image_location[len(bucket_name)+1:]
 
100
        bucket_object = bucket.Bucket(bucket_name)
 
101
 
 
102
        manifest = ElementTree.fromstring(bucket_object[manifest_path].read())
 
103
        image_type = 'machine'
 
104
 
 
105
        try:
 
106
            kernel_id = manifest.find("machine_configuration/kernel_id").text
 
107
            if kernel_id == 'true':
 
108
                image_type = 'kernel'
 
109
        except:
 
110
            pass
 
111
 
 
112
        try:
 
113
            ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text
 
114
            if ramdisk_id == 'true':
 
115
                image_type = 'ramdisk'
 
116
        except:
 
117
            pass
 
118
 
 
119
        info = {
 
120
            'imageId': image_id,
 
121
            'imageLocation': image_location,
 
122
            'imageOwnerId': user.id,
 
123
            'isPublic': False, # FIXME: grab public from manifest
 
124
            'architecture': 'x86_64', # FIXME: grab architecture from manifest
 
125
            'type' : image_type
 
126
        }
 
127
 
 
128
        def write_state(state):
 
129
            info['imageState'] = state
 
130
            with open(os.path.join(image_path, 'info.json'), "w") as f:
 
131
                json.dump(info, f)
 
132
 
 
133
        write_state('pending')
 
134
 
 
135
        encrypted_filename = os.path.join(image_path, 'image.encrypted')
 
136
        with open(encrypted_filename, 'w') as f:
 
137
            for filename in manifest.find("image").getiterator("filename"):
 
138
                shutil.copyfileobj(bucket_object[filename.text].file, f)
 
139
 
 
140
        write_state('decrypting')
 
141
 
 
142
        # FIXME: grab kernelId and ramdiskId from bundle manifest
 
143
        encrypted_key = binascii.a2b_hex(manifest.find("image/ec2_encrypted_key").text)
 
144
        encrypted_iv = binascii.a2b_hex(manifest.find("image/ec2_encrypted_iv").text)
 
145
        cloud_private_key = os.path.join(FLAGS.ca_path, "private/cakey.pem")
 
146
 
 
147
        decrypted_filename = os.path.join(image_path, 'image.tar.gz')
 
148
        Image.decrypt_image(encrypted_filename, encrypted_key, encrypted_iv, cloud_private_key, decrypted_filename)
 
149
 
 
150
        write_state('untarring')
 
151
 
 
152
        image_file = Image.untarzip_image(image_path, decrypted_filename)
 
153
        shutil.move(os.path.join(image_path, image_file), os.path.join(image_path, 'image'))
 
154
 
 
155
        write_state('available')
 
156
        os.unlink(decrypted_filename)
 
157
        os.unlink(encrypted_filename)
 
158
 
 
159
    @staticmethod
 
160
    def decrypt_image(encrypted_filename, encrypted_key, encrypted_iv, cloud_private_key, decrypted_filename):
 
161
        key, err = utils.execute('openssl rsautl -decrypt -inkey %s' % cloud_private_key, encrypted_key)
 
162
        if err:
 
163
            raise exception.Error("Failed to decrypt private key: %s" % err)
 
164
        iv, err = utils.execute('openssl rsautl -decrypt -inkey %s' % cloud_private_key, encrypted_iv)
 
165
        if err:
 
166
            raise exception.Error("Failed to decrypt initialization vector: %s" % err)
 
167
        out, err = utils.execute('openssl enc -d -aes-128-cbc -in %s -K %s -iv %s -out %s' % (encrypted_filename, key, iv, decrypted_filename))
 
168
        if err:
 
169
            raise exception.Error("Failed to decrypt image file %s : %s" % (encrypted_filename, err))
 
170
 
 
171
    @staticmethod
 
172
    def untarzip_image(path, filename):
 
173
        tar_file = tarfile.open(filename, "r|gz")
 
174
        tar_file.extractall(path)
 
175
        image_file = tar_file.getnames()[0]
 
176
        tar_file.close()
 
177
        return image_file