Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ c71f38be

History | View | Annotate | Download (6.9 kB)

1 121f3bc0 Nikos Skalkotos
# -*- coding: utf-8 -*-
2 121f3bc0 Nikos Skalkotos
#
3 ae48a082 Nikos Skalkotos
# Copyright 2012 GRNET S.A. All rights reserved.
4 ae48a082 Nikos Skalkotos
#
5 ae48a082 Nikos Skalkotos
# Redistribution and use in source and binary forms, with or
6 ae48a082 Nikos Skalkotos
# without modification, are permitted provided that the following
7 ae48a082 Nikos Skalkotos
# conditions are met:
8 ae48a082 Nikos Skalkotos
#
9 ae48a082 Nikos Skalkotos
#   1. Redistributions of source code must retain the above
10 ae48a082 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
11 ae48a082 Nikos Skalkotos
#      disclaimer.
12 ae48a082 Nikos Skalkotos
#
13 ae48a082 Nikos Skalkotos
#   2. Redistributions in binary form must reproduce the above
14 ae48a082 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
15 ae48a082 Nikos Skalkotos
#      disclaimer in the documentation and/or other materials
16 ae48a082 Nikos Skalkotos
#      provided with the distribution.
17 ae48a082 Nikos Skalkotos
#
18 ae48a082 Nikos Skalkotos
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 ae48a082 Nikos Skalkotos
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 ae48a082 Nikos Skalkotos
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 ae48a082 Nikos Skalkotos
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 ae48a082 Nikos Skalkotos
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 ae48a082 Nikos Skalkotos
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 ae48a082 Nikos Skalkotos
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 ae48a082 Nikos Skalkotos
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 ae48a082 Nikos Skalkotos
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 ae48a082 Nikos Skalkotos
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 ae48a082 Nikos Skalkotos
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 ae48a082 Nikos Skalkotos
# POSSIBILITY OF SUCH DAMAGE.
30 ae48a082 Nikos Skalkotos
#
31 ae48a082 Nikos Skalkotos
# The views and conclusions contained in the software and
32 ae48a082 Nikos Skalkotos
# documentation are those of the authors and should not be
33 ae48a082 Nikos Skalkotos
# interpreted as representing official policies, either expressed
34 ae48a082 Nikos Skalkotos
# or implied, of GRNET S.A.
35 d57775d4 Nikos Skalkotos
36 121f3bc0 Nikos Skalkotos
"""Module hosting the Disk class."""
37 121f3bc0 Nikos Skalkotos
38 979096dd Nikos Skalkotos
from image_creator.util import get_command
39 f3845095 Nikos Skalkotos
from image_creator.util import try_fail_repeat
40 c16850ae Nikos Skalkotos
from image_creator.util import free_space
41 f5174d2c Nikos Skalkotos
from image_creator.util import FatalError
42 a939e3b8 Nikos Skalkotos
from image_creator.bundle_volume import BundleVolume
43 f5174d2c Nikos Skalkotos
from image_creator.image import Image
44 8eea5572 Nikos Skalkotos
45 d57775d4 Nikos Skalkotos
import stat
46 d57775d4 Nikos Skalkotos
import os
47 d57775d4 Nikos Skalkotos
import tempfile
48 d57775d4 Nikos Skalkotos
import uuid
49 f6ea8d02 Nikos Skalkotos
import shutil
50 8c574358 Nikos Skalkotos
51 3ccb2618 Nikos Skalkotos
dd = get_command('dd')
52 3ccb2618 Nikos Skalkotos
dmsetup = get_command('dmsetup')
53 3ccb2618 Nikos Skalkotos
losetup = get_command('losetup')
54 3ccb2618 Nikos Skalkotos
blockdev = get_command('blockdev')
55 01a7cff3 Nikos Skalkotos
56 01a7cff3 Nikos Skalkotos
57 121f3bc0 Nikos Skalkotos
def get_tmp_dir(default=None):
58 121f3bc0 Nikos Skalkotos
    """Check tmp directory candidates and return the one with the most
59 121f3bc0 Nikos Skalkotos
    available space.
60 121f3bc0 Nikos Skalkotos
    """
61 121f3bc0 Nikos Skalkotos
    if default is not None:
62 121f3bc0 Nikos Skalkotos
        return default
63 121f3bc0 Nikos Skalkotos
64 121f3bc0 Nikos Skalkotos
    TMP_CANDIDATES = ['/var/tmp', os.path.expanduser('~'), '/mnt']
65 121f3bc0 Nikos Skalkotos
66 121f3bc0 Nikos Skalkotos
    space = map(free_space, TMP_CANDIDATES)
67 121f3bc0 Nikos Skalkotos
68 121f3bc0 Nikos Skalkotos
    max_idx = 0
69 121f3bc0 Nikos Skalkotos
    max_val = space[0]
70 121f3bc0 Nikos Skalkotos
    for i, val in zip(range(len(space)), space):
71 121f3bc0 Nikos Skalkotos
        if val > max_val:
72 121f3bc0 Nikos Skalkotos
            max_val = val
73 121f3bc0 Nikos Skalkotos
            max_idx = i
74 121f3bc0 Nikos Skalkotos
75 121f3bc0 Nikos Skalkotos
    # Return the candidate path with more available space
76 121f3bc0 Nikos Skalkotos
    return TMP_CANDIDATES[max_idx]
77 c16850ae Nikos Skalkotos
78 c16850ae Nikos Skalkotos
79 d57775d4 Nikos Skalkotos
class Disk(object):
80 3b2f6619 Nikos Skalkotos
    """This class represents a hard disk hosting an Operating System
81 3b2f6619 Nikos Skalkotos

82 3b2f6619 Nikos Skalkotos
    A Disk instance never alters the source media it is created from.
83 3b2f6619 Nikos Skalkotos
    Any change is done on a snapshot created by the device-mapper of
84 3b2f6619 Nikos Skalkotos
    the Linux kernel.
85 3b2f6619 Nikos Skalkotos
    """
86 d57775d4 Nikos Skalkotos
87 c16850ae Nikos Skalkotos
    def __init__(self, source, output, tmp=None):
88 3b2f6619 Nikos Skalkotos
        """Create a new Disk instance out of a source media. The source
89 f3845095 Nikos Skalkotos
        media can be an image file, a block device or a directory.
90 f3845095 Nikos Skalkotos
        """
91 d57775d4 Nikos Skalkotos
        self._cleanup_jobs = []
92 f5174d2c Nikos Skalkotos
        self._images = []
93 d57775d4 Nikos Skalkotos
        self.source = source
94 e77e66a9 Nikos Skalkotos
        self.out = output
95 9517bf29 Nikos Skalkotos
        self.meta = {}
96 c16850ae Nikos Skalkotos
        self.tmp = tempfile.mkdtemp(prefix='.snf_image_creator.',
97 121f3bc0 Nikos Skalkotos
                                    dir=get_tmp_dir(tmp))
98 c16850ae Nikos Skalkotos
99 f6ea8d02 Nikos Skalkotos
        self._add_cleanup(shutil.rmtree, self.tmp)
100 c16850ae Nikos Skalkotos
101 d57775d4 Nikos Skalkotos
    def _add_cleanup(self, job, *args):
102 88f83027 Nikos Skalkotos
        """Add a new job in the cleanup list"""
103 d57775d4 Nikos Skalkotos
        self._cleanup_jobs.append((job, args))
104 d57775d4 Nikos Skalkotos
105 d57775d4 Nikos Skalkotos
    def _losetup(self, fname):
106 88f83027 Nikos Skalkotos
        """Setup a loop device and add it to the cleanup list. The loop device
107 88f83027 Nikos Skalkotos
        will be detached when cleanup is called.
108 88f83027 Nikos Skalkotos
        """
109 3ccb2618 Nikos Skalkotos
        loop = losetup('-f', '--show', fname)
110 ae48a082 Nikos Skalkotos
        loop = loop.strip()  # remove the new-line char
111 f3845095 Nikos Skalkotos
        self._add_cleanup(try_fail_repeat, losetup, '-d', loop)
112 3ccb2618 Nikos Skalkotos
        return loop
113 d57775d4 Nikos Skalkotos
114 d57775d4 Nikos Skalkotos
    def _dir_to_disk(self):
115 88f83027 Nikos Skalkotos
        """Create a disk out of a directory"""
116 9e4b4de2 Nikos Skalkotos
        if self.source == '/':
117 8eea5572 Nikos Skalkotos
            bundle = BundleVolume(self.out, self.meta)
118 c16850ae Nikos Skalkotos
            image = '%s/%s.diskdump' % (self.tmp, uuid.uuid4().hex)
119 567891a6 Nikos Skalkotos
120 567891a6 Nikos Skalkotos
            def check_unlink(path):
121 567891a6 Nikos Skalkotos
                if os.path.exists(path):
122 567891a6 Nikos Skalkotos
                    os.unlink(path)
123 567891a6 Nikos Skalkotos
124 567891a6 Nikos Skalkotos
            self._add_cleanup(check_unlink, image)
125 567891a6 Nikos Skalkotos
            bundle.create_image(image)
126 69a4f894 Nikos Skalkotos
            return self._losetup(image)
127 9e4b4de2 Nikos Skalkotos
        raise FatalError("Using a directory as media source is supported")
128 d57775d4 Nikos Skalkotos
129 d57775d4 Nikos Skalkotos
    def cleanup(self):
130 3b2f6619 Nikos Skalkotos
        """Cleanup internal data. This needs to be called before the
131 3b2f6619 Nikos Skalkotos
        program ends.
132 3b2f6619 Nikos Skalkotos
        """
133 9c354f13 Nikos Skalkotos
        try:
134 f5174d2c Nikos Skalkotos
            while len(self._images):
135 f5174d2c Nikos Skalkotos
                image = self._images.pop()
136 f5174d2c Nikos Skalkotos
                image.destroy()
137 9c354f13 Nikos Skalkotos
        finally:
138 9c354f13 Nikos Skalkotos
            # Make sure those are executed even if one of the device.destroy
139 9c354f13 Nikos Skalkotos
            # methods throws exeptions.
140 9c354f13 Nikos Skalkotos
            while len(self._cleanup_jobs):
141 9c354f13 Nikos Skalkotos
                job, args = self._cleanup_jobs.pop()
142 9c354f13 Nikos Skalkotos
                job(*args)
143 d57775d4 Nikos Skalkotos
144 e22aa3a9 Nikos Skalkotos
    def snapshot(self):
145 e22aa3a9 Nikos Skalkotos
        """Creates a snapshot of the original source media of the Disk
146 e22aa3a9 Nikos Skalkotos
        instance.
147 3b2f6619 Nikos Skalkotos
        """
148 22a6d232 Nikos Skalkotos
149 663f5f80 Nikos Skalkotos
        self.out.output("Examining source media `%s' ..." % self.source, False)
150 3f70f242 Nikos Skalkotos
        sourcedev = self.source
151 3f70f242 Nikos Skalkotos
        mode = os.stat(self.source).st_mode
152 3f70f242 Nikos Skalkotos
        if stat.S_ISDIR(mode):
153 279f2c7d Nikos Skalkotos
            self.out.success('looks like a directory')
154 8eea5572 Nikos Skalkotos
            return self._dir_to_disk()
155 3f70f242 Nikos Skalkotos
        elif stat.S_ISREG(mode):
156 279f2c7d Nikos Skalkotos
            self.out.success('looks like an image file')
157 3f70f242 Nikos Skalkotos
            sourcedev = self._losetup(self.source)
158 3f70f242 Nikos Skalkotos
        elif not stat.S_ISBLK(mode):
159 e9a2d318 Nikos Skalkotos
            raise FatalError("Invalid media source. Only block devices, "
160 f99fe99d Nikos Skalkotos
                             "regular files and directories are supported.")
161 3f70f242 Nikos Skalkotos
        else:
162 e77e66a9 Nikos Skalkotos
            self.out.success('looks like a block device')
163 d57775d4 Nikos Skalkotos
164 d57775d4 Nikos Skalkotos
        # Take a snapshot and return it to the user
165 3afe6b44 Nikos Skalkotos
        self.out.output("Snapshotting media source ...", False)
166 8fed77f7 Nikos Skalkotos
        size = blockdev('--getsz', sourcedev)
167 c16850ae Nikos Skalkotos
        cowfd, cow = tempfile.mkstemp(dir=self.tmp)
168 3f70f242 Nikos Skalkotos
        os.close(cowfd)
169 3f70f242 Nikos Skalkotos
        self._add_cleanup(os.unlink, cow)
170 8fed77f7 Nikos Skalkotos
        # Create cow sparse file
171 8fed77f7 Nikos Skalkotos
        dd('if=/dev/null', 'of=%s' % cow, 'bs=512', 'seek=%d' % int(size))
172 3f70f242 Nikos Skalkotos
        cowdev = self._losetup(cow)
173 3f70f242 Nikos Skalkotos
174 3f70f242 Nikos Skalkotos
        snapshot = uuid.uuid4().hex
175 3f70f242 Nikos Skalkotos
        tablefd, table = tempfile.mkstemp()
176 3f70f242 Nikos Skalkotos
        try:
177 f99fe99d Nikos Skalkotos
            os.write(tablefd, "0 %d snapshot %s %s n 8" %
178 f99fe99d Nikos Skalkotos
                              (int(size), sourcedev, cowdev))
179 3f70f242 Nikos Skalkotos
            dmsetup('create', snapshot, table)
180 f3845095 Nikos Skalkotos
            self._add_cleanup(try_fail_repeat, dmsetup, 'remove', snapshot)
181 3f70f242 Nikos Skalkotos
182 3f70f242 Nikos Skalkotos
        finally:
183 3f70f242 Nikos Skalkotos
            os.unlink(table)
184 e77e66a9 Nikos Skalkotos
        self.out.success('done')
185 e22aa3a9 Nikos Skalkotos
        return "/dev/mapper/%s" % snapshot
186 e22aa3a9 Nikos Skalkotos
187 f5174d2c Nikos Skalkotos
    def get_image(self, media):
188 c78ac789 Nikos Skalkotos
        """Returns a newly created Image instance."""
189 e22aa3a9 Nikos Skalkotos
190 f5174d2c Nikos Skalkotos
        image = Image(media, self.out)
191 f5174d2c Nikos Skalkotos
        self._images.append(image)
192 f5174d2c Nikos Skalkotos
        image.enable()
193 f5174d2c Nikos Skalkotos
        return image
194 d57775d4 Nikos Skalkotos
195 f5174d2c Nikos Skalkotos
    def destroy_image(self, image):
196 c78ac789 Nikos Skalkotos
        """Destroys an Image instance previously created by get_image method.
197 3b2f6619 Nikos Skalkotos
        """
198 f5174d2c Nikos Skalkotos
        self._images.remove(image)
199 f5174d2c Nikos Skalkotos
        image.destroy()
200 d603d80d Nikos Skalkotos
201 d57775d4 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :