Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ bf3a282c

History | View | Annotate | Download (6.9 kB)

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

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