~ubuntu-branches/ubuntu/saucy/nova/saucy-proposed

« back to all changes in this revision

Viewing changes to nova/virt/disk/api.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Chuck Short, Adam Gandleman
  • Date: 2012-01-13 09:51:10 UTC
  • mfrom: (1.1.40)
  • Revision ID: package-import@ubuntu.com-20120113095110-ffd6163drcg77wez
Tags: 2012.1~e3~20120113.12049-0ubuntu1
[Chuck Short]
* New upstream version.
* debian/nova_sudoers, debian/nova-common.install, 
  Switch out to nova-rootwrap. (LP: #681774)
* Add "get-origsource-git" which allows developers to 
  generate a tarball from github, by doing:
  fakeroot debian/rules get-orig-source-git
* debian/debian/nova-objectstore.logrotate: Dont determine
  if we are running Debian or Ubuntu. (LP: #91379)

[Adam Gandleman]
* Removed python-nova.postinst, let dh_python2 generate instead since
  python-support is not a dependency. (LP: #907543)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2010 United States Government as represented by the
 
4
# Administrator of the National Aeronautics and Space Administration.
 
5
#
 
6
# Copyright 2011, Piston Cloud Computing, Inc.
 
7
#
 
8
# All Rights Reserved.
 
9
#
 
10
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
11
#    not use this file except in compliance with the License. You may obtain
 
12
#    a copy of the License at
 
13
#
 
14
#         http://www.apache.org/licenses/LICENSE-2.0
 
15
#
 
16
#    Unless required by applicable law or agreed to in writing, software
 
17
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
18
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
19
#    License for the specific language governing permissions and limitations
 
20
#    under the License.
 
21
"""
 
22
Utility methods to resize, repartition, and modify disk images.
 
23
 
 
24
Includes injection of SSH PGP keys into authorized_keys file.
 
25
 
 
26
"""
 
27
 
 
28
import json
 
29
import os
 
30
import tempfile
 
31
 
 
32
from nova import exception
 
33
from nova import flags
 
34
from nova import log as logging
 
35
from nova import utils
 
36
from nova.virt.disk import guestfs
 
37
from nova.virt.disk import loop
 
38
from nova.virt.disk import nbd
 
39
 
 
40
LOG = logging.getLogger('nova.compute.disk')
 
41
FLAGS = flags.FLAGS
 
42
flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10,
 
43
                     'minimum size in bytes of root partition')
 
44
flags.DEFINE_string('injected_network_template',
 
45
                    utils.abspath('virt/interfaces.template'),
 
46
                    'Template file for injected network')
 
47
flags.DEFINE_list('img_handlers', ['loop', 'nbd', 'guestfs'],
 
48
                    'Order of methods used to mount disk images')
 
49
 
 
50
 
 
51
# NOTE(yamahata): DEFINE_list() doesn't work because the command may
 
52
#                 include ','. For example,
 
53
#                 mkfs.ext3 -O dir_index,extent -E stride=8,stripe-width=16
 
54
#                 --label %(fs_label)s %(target)s
 
55
#
 
56
#                 DEFINE_list() parses its argument by
 
57
#                 [s.strip() for s in argument.split(self._token)]
 
58
#                 where self._token = ','
 
59
#                 No escape nor exceptional handling for ','.
 
60
#                 DEFINE_list() doesn't give us what we need.
 
61
flags.DEFINE_multistring('virt_mkfs',
 
62
                         ['windows=mkfs.ntfs --fast --label %(fs_label)s '
 
63
                          '%(target)s',
 
64
                          # NOTE(yamahata): vfat case
 
65
                          #'windows=mkfs.vfat -n %(fs_label)s %(target)s',
 
66
                          'linux=mkfs.ext3 -L %(fs_label)s -F %(target)s',
 
67
                          'default=mkfs.ext3 -L %(fs_label)s -F %(target)s'],
 
68
                         'mkfs commands for ephemeral device. The format is'
 
69
                         '<os_type>=<mkfs command>')
 
70
 
 
71
 
 
72
_MKFS_COMMAND = {}
 
73
_DEFAULT_MKFS_COMMAND = None
 
74
 
 
75
 
 
76
for s in FLAGS.virt_mkfs:
 
77
    # NOTE(yamahata): mkfs command may includes '=' for its options.
 
78
    #                 So item.partition('=') doesn't work here
 
79
    os_type, mkfs_command = s.split('=', 1)
 
80
    if os_type:
 
81
        _MKFS_COMMAND[os_type] = mkfs_command
 
82
    if os_type == 'default':
 
83
        _DEFAULT_MKFS_COMMAND = mkfs_command
 
84
 
 
85
 
 
86
def mkfs(os_type, fs_label, target):
 
87
    mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
 
88
                    '') % locals()
 
89
    if mkfs_command:
 
90
        utils.execute(*mkfs_command.split())
 
91
 
 
92
 
 
93
def extend(image, size):
 
94
    """Increase image to size"""
 
95
    file_size = os.path.getsize(image)
 
96
    if file_size >= size:
 
97
        return
 
98
    utils.execute('qemu-img', 'resize', image, size)
 
99
    # NOTE(vish): attempts to resize filesystem
 
100
    utils.execute('e2fsck', '-fp', image, check_exit_code=False)
 
101
    utils.execute('resize2fs', image, check_exit_code=False)
 
102
 
 
103
 
 
104
class _DiskImage(object):
 
105
    """Provide operations on a disk image file."""
 
106
 
 
107
    def __init__(self, image, partition=None, use_cow=False,
 
108
                 disable_auto_fsck=False, mount_dir=None):
 
109
        # These passed to each mounter
 
110
        self.image = image
 
111
        self.partition = partition
 
112
        self.disable_auto_fsck = disable_auto_fsck
 
113
        self.mount_dir = mount_dir
 
114
 
 
115
        # Internal
 
116
        self._mkdir = False
 
117
        self._mounter = None
 
118
        self._errors = []
 
119
 
 
120
        # As a performance tweak, don't bother trying to
 
121
        # directly loopback mount a cow image.
 
122
        self.handlers = FLAGS.img_handlers[:]
 
123
        if use_cow:
 
124
            self.handlers.remove('loop')
 
125
 
 
126
    @property
 
127
    def errors(self):
 
128
        """Return the collated errors from all operations."""
 
129
        return '\n--\n'.join([''] + self._errors)
 
130
 
 
131
    @staticmethod
 
132
    def _handler_class(mode):
 
133
        """Look up the appropriate class to use based on MODE."""
 
134
        for cls in (loop.Mount, nbd.Mount, guestfs.Mount):
 
135
            if cls.mode == mode:
 
136
                return cls
 
137
        raise exception.Error(_("unknown disk image handler: %s" % mode))
 
138
 
 
139
    def mount(self):
 
140
        """Mount a disk image, using the object attributes.
 
141
 
 
142
        The first supported means provided by the mount classes is used.
 
143
 
 
144
        True, or False is returned and the 'errors' attribute
 
145
        contains any diagnostics.
 
146
        """
 
147
        if self._mounter:
 
148
            raise exception.Error(_('image already mounted'))
 
149
 
 
150
        if not self.mount_dir:
 
151
            self.mount_dir = tempfile.mkdtemp()
 
152
            self._mkdir = True
 
153
 
 
154
        try:
 
155
            for h in self.handlers:
 
156
                mounter_cls = self._handler_class(h)
 
157
                mounter = mounter_cls(image=self.image,
 
158
                                      partition=self.partition,
 
159
                                      disable_auto_fsck=self.disable_auto_fsck,
 
160
                                      mount_dir=self.mount_dir)
 
161
                if mounter.do_mount():
 
162
                    self._mounter = mounter
 
163
                    break
 
164
                else:
 
165
                    LOG.debug(mounter.error)
 
166
                    self._errors.append(mounter.error)
 
167
        finally:
 
168
            if not self._mounter:
 
169
                self.umount()  # rmdir
 
170
 
 
171
        return bool(self._mounter)
 
172
 
 
173
    def umount(self):
 
174
        """Unmount a disk image from the file system."""
 
175
        try:
 
176
            if self._mounter:
 
177
                self._mounter.do_umount()
 
178
        finally:
 
179
            if self._mkdir:
 
180
                os.rmdir(self.mount_dir)
 
181
 
 
182
 
 
183
# Public module functions
 
184
 
 
185
def inject_data(image, key=None, net=None, metadata=None,
 
186
                partition=None, use_cow=False, disable_auto_fsck=True):
 
187
    """Injects a ssh key and optionally net data into a disk image.
 
188
 
 
189
    it will mount the image as a fully partitioned disk and attempt to inject
 
190
    into the specified partition number.
 
191
 
 
192
    If partition is not specified it mounts the image as a single partition.
 
193
 
 
194
    """
 
195
    img = _DiskImage(image=image, partition=partition, use_cow=use_cow,
 
196
                     disable_auto_fsck=disable_auto_fsck)
 
197
    if img.mount():
 
198
        try:
 
199
            inject_data_into_fs(img.mount_dir, key, net, metadata,
 
200
                                utils.execute)
 
201
        finally:
 
202
            img.umount()
 
203
    else:
 
204
        raise exception.Error(img.errors)
 
205
 
 
206
 
 
207
def setup_container(image, container_dir=None, use_cow=False):
 
208
    """Setup the LXC container.
 
209
 
 
210
    It will mount the loopback image to the container directory in order
 
211
    to create the root filesystem for the container.
 
212
 
 
213
    LXC does not support qcow2 images yet.
 
214
    """
 
215
    try:
 
216
        img = _DiskImage(image=image, use_cow=use_cow, mount_dir=container_dir)
 
217
        if img.mount():
 
218
            return img
 
219
        else:
 
220
            raise exception.Error(img.errors)
 
221
    except Exception, exn:
 
222
        LOG.exception(_('Failed to mount filesystem: %s'), exn)
 
223
 
 
224
 
 
225
def destroy_container(img):
 
226
    """Destroy the container once it terminates.
 
227
 
 
228
    It will umount the container that is mounted,
 
229
    and delete any  linked devices.
 
230
 
 
231
    LXC does not support qcow2 images yet.
 
232
    """
 
233
    try:
 
234
        if img:
 
235
            img.umount()
 
236
    except Exception, exn:
 
237
        LOG.exception(_('Failed to remove container: %s'), exn)
 
238
 
 
239
 
 
240
def inject_data_into_fs(fs, key, net, metadata, execute):
 
241
    """Injects data into a filesystem already mounted by the caller.
 
242
    Virt connections can call this directly if they mount their fs
 
243
    in a different way to inject_data
 
244
    """
 
245
    if key:
 
246
        _inject_key_into_fs(key, fs, execute=execute)
 
247
    if net:
 
248
        _inject_net_into_fs(net, fs, execute=execute)
 
249
    if metadata:
 
250
        _inject_metadata_into_fs(metadata, fs, execute=execute)
 
251
 
 
252
 
 
253
def _inject_metadata_into_fs(metadata, fs, execute=None):
 
254
    metadata_path = os.path.join(fs, "meta.js")
 
255
    metadata = dict([(m.key, m.value) for m in metadata])
 
256
 
 
257
    utils.execute('tee', metadata_path,
 
258
                  process_input=json.dumps(metadata), run_as_root=True)
 
259
 
 
260
 
 
261
def _inject_key_into_fs(key, fs, execute=None):
 
262
    """Add the given public ssh key to root's authorized_keys.
 
263
 
 
264
    key is an ssh key string.
 
265
    fs is the path to the base of the filesystem into which to inject the key.
 
266
    """
 
267
    sshdir = os.path.join(fs, 'root', '.ssh')
 
268
    utils.execute('mkdir', '-p', sshdir, run_as_root=True)
 
269
    utils.execute('chown', 'root', sshdir, run_as_root=True)
 
270
    utils.execute('chmod', '700', sshdir, run_as_root=True)
 
271
    keyfile = os.path.join(sshdir, 'authorized_keys')
 
272
    utils.execute('tee', '-a', keyfile,
 
273
                  process_input='\n' + key.strip() + '\n', run_as_root=True)
 
274
 
 
275
 
 
276
def _inject_net_into_fs(net, fs, execute=None):
 
277
    """Inject /etc/network/interfaces into the filesystem rooted at fs.
 
278
 
 
279
    net is the contents of /etc/network/interfaces.
 
280
    """
 
281
    netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
 
282
    utils.execute('mkdir', '-p', netdir, run_as_root=True)
 
283
    utils.execute('chown', 'root:root', netdir, run_as_root=True)
 
284
    utils.execute('chmod', 755, netdir, run_as_root=True)
 
285
    netfile = os.path.join(netdir, 'interfaces')
 
286
    utils.execute('tee', netfile, process_input=net, run_as_root=True)