Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ f5174d2c

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