Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ c54fc0e8

History | View | Annotate | Download (7.8 kB)

1 d57775d4 Nikos Skalkotos
#!/usr/bin/env python
2 d57775d4 Nikos Skalkotos
3 3ccb2618 Nikos Skalkotos
from image_creator.util import get_command
4 0ae01e26 Nikos Skalkotos
from image_creator import FatalError
5 3ccb2618 Nikos Skalkotos
from clint.textui import progress
6 3ccb2618 Nikos Skalkotos
7 d57775d4 Nikos Skalkotos
import stat
8 d57775d4 Nikos Skalkotos
import os
9 d57775d4 Nikos Skalkotos
import tempfile
10 d57775d4 Nikos Skalkotos
import uuid
11 d57775d4 Nikos Skalkotos
import re
12 1377b8a7 Nikos Skalkotos
import sys
13 1377b8a7 Nikos Skalkotos
import guestfs
14 0db22eac Nikos Skalkotos
import time
15 1377b8a7 Nikos Skalkotos
16 8c574358 Nikos Skalkotos
17 8c574358 Nikos Skalkotos
class DiskError(Exception):
18 8c574358 Nikos Skalkotos
    pass
19 8c574358 Nikos Skalkotos
20 3ccb2618 Nikos Skalkotos
dd = get_command('dd')
21 3ccb2618 Nikos Skalkotos
dmsetup = get_command('dmsetup')
22 3ccb2618 Nikos Skalkotos
losetup = get_command('losetup')
23 3ccb2618 Nikos Skalkotos
blockdev = get_command('blockdev')
24 01a7cff3 Nikos Skalkotos
25 01a7cff3 Nikos Skalkotos
26 d57775d4 Nikos Skalkotos
class Disk(object):
27 3b2f6619 Nikos Skalkotos
    """This class represents a hard disk hosting an Operating System
28 3b2f6619 Nikos Skalkotos

29 3b2f6619 Nikos Skalkotos
    A Disk instance never alters the source media it is created from.
30 3b2f6619 Nikos Skalkotos
    Any change is done on a snapshot created by the device-mapper of
31 3b2f6619 Nikos Skalkotos
    the Linux kernel.
32 3b2f6619 Nikos Skalkotos
    """
33 d57775d4 Nikos Skalkotos
34 d57775d4 Nikos Skalkotos
    def __init__(self, source):
35 3b2f6619 Nikos Skalkotos
        """Create a new Disk instance out of a source media. The source
36 3b2f6619 Nikos Skalkotos
        media can be an image file, a block device or a directory."""
37 d57775d4 Nikos Skalkotos
        self._cleanup_jobs = []
38 d57775d4 Nikos Skalkotos
        self._devices = []
39 d57775d4 Nikos Skalkotos
        self.source = source
40 d57775d4 Nikos Skalkotos
41 d57775d4 Nikos Skalkotos
    def _add_cleanup(self, job, *args):
42 d57775d4 Nikos Skalkotos
        self._cleanup_jobs.append((job, args))
43 d57775d4 Nikos Skalkotos
44 d57775d4 Nikos Skalkotos
    def _losetup(self, fname):
45 3ccb2618 Nikos Skalkotos
        loop = losetup('-f', '--show', fname)
46 3ccb2618 Nikos Skalkotos
        loop = loop.strip() # remove the new-line char
47 3ccb2618 Nikos Skalkotos
        self._add_cleanup(losetup, '-d', loop)
48 3ccb2618 Nikos Skalkotos
        return loop
49 d57775d4 Nikos Skalkotos
50 d57775d4 Nikos Skalkotos
    def _dir_to_disk(self):
51 d57775d4 Nikos Skalkotos
        raise NotImplementedError
52 d57775d4 Nikos Skalkotos
53 d57775d4 Nikos Skalkotos
    def cleanup(self):
54 3b2f6619 Nikos Skalkotos
        """Cleanup internal data. This needs to be called before the
55 3b2f6619 Nikos Skalkotos
        program ends.
56 3b2f6619 Nikos Skalkotos
        """
57 1377b8a7 Nikos Skalkotos
        while len(self._devices):
58 1377b8a7 Nikos Skalkotos
            device = self._devices.pop()
59 1377b8a7 Nikos Skalkotos
            device.destroy()
60 8c574358 Nikos Skalkotos
61 d57775d4 Nikos Skalkotos
        while len(self._cleanup_jobs):
62 d57775d4 Nikos Skalkotos
            job, args = self._cleanup_jobs.pop()
63 d57775d4 Nikos Skalkotos
            job(*args)
64 d57775d4 Nikos Skalkotos
65 d57775d4 Nikos Skalkotos
    def get_device(self):
66 3b2f6619 Nikos Skalkotos
        """Returns a newly created DiskDevice instance.
67 c408053f Nikos Skalkotos

68 3b2f6619 Nikos Skalkotos
        This instance is a snapshot of the original source media of
69 3b2f6619 Nikos Skalkotos
        the Disk instance.
70 3b2f6619 Nikos Skalkotos
        """
71 d57775d4 Nikos Skalkotos
        sourcedev = self.source
72 d57775d4 Nikos Skalkotos
        mode = os.stat(self.source).st_mode
73 d57775d4 Nikos Skalkotos
        if stat.S_ISDIR(mode):
74 d57775d4 Nikos Skalkotos
            return self._losetup(self._dir_to_disk())
75 d57775d4 Nikos Skalkotos
        elif stat.S_ISREG(mode):
76 d57775d4 Nikos Skalkotos
            sourcedev = self._losetup(self.source)
77 d57775d4 Nikos Skalkotos
        elif not stat.S_ISBLK(mode):
78 d57775d4 Nikos Skalkotos
            raise ValueError("Value for self.source is invalid")
79 d57775d4 Nikos Skalkotos
80 d57775d4 Nikos Skalkotos
        # Take a snapshot and return it to the user
81 d57775d4 Nikos Skalkotos
        size = blockdev('--getsize', sourcedev)
82 d57775d4 Nikos Skalkotos
        cowfd, cow = tempfile.mkstemp()
83 0db22eac Nikos Skalkotos
        os.close(cowfd)
84 d57775d4 Nikos Skalkotos
        self._add_cleanup(os.unlink, cow)
85 8c574358 Nikos Skalkotos
        # Create 1G cow sparse file
86 8c574358 Nikos Skalkotos
        dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', 'seek=%d' % (1024 * 1024))
87 d57775d4 Nikos Skalkotos
        cowdev = self._losetup(cow)
88 d57775d4 Nikos Skalkotos
89 d57775d4 Nikos Skalkotos
        snapshot = uuid.uuid4().hex
90 d57775d4 Nikos Skalkotos
        tablefd, table = tempfile.mkstemp()
91 d57775d4 Nikos Skalkotos
        try:
92 d57775d4 Nikos Skalkotos
            os.write(tablefd, "0 %d snapshot %s %s n 8" % \
93 d57775d4 Nikos Skalkotos
                                        (int(size), sourcedev, cowdev))
94 d57775d4 Nikos Skalkotos
            dmsetup('create', snapshot, table)
95 d57775d4 Nikos Skalkotos
            self._add_cleanup(dmsetup, 'remove', snapshot)
96 d57775d4 Nikos Skalkotos
        finally:
97 d57775d4 Nikos Skalkotos
            os.unlink(table)
98 1377b8a7 Nikos Skalkotos
        new_device = DiskDevice("/dev/mapper/%s" % snapshot)
99 d57775d4 Nikos Skalkotos
        self._devices.append(new_device)
100 0db22eac Nikos Skalkotos
        new_device.enable()
101 d57775d4 Nikos Skalkotos
        return new_device
102 d57775d4 Nikos Skalkotos
103 1377b8a7 Nikos Skalkotos
    def destroy_device(self, device):
104 3b2f6619 Nikos Skalkotos
        """Destroys a DiskDevice instance previously created by
105 3b2f6619 Nikos Skalkotos
        get_device method.
106 3b2f6619 Nikos Skalkotos
        """
107 1377b8a7 Nikos Skalkotos
        self._devices.remove(device)
108 1377b8a7 Nikos Skalkotos
        device.destroy()
109 1377b8a7 Nikos Skalkotos
110 8c574358 Nikos Skalkotos
111 0db22eac Nikos Skalkotos
def progress_generator(label=''):
112 586da0a0 Nikos Skalkotos
    position = 0;
113 0ae01e26 Nikos Skalkotos
    for i in progress.bar(range(100),label):
114 586da0a0 Nikos Skalkotos
        if i < position:
115 586da0a0 Nikos Skalkotos
            continue
116 586da0a0 Nikos Skalkotos
        position = yield
117 e7c01d1e Nikos Skalkotos
    yield #suppress the StopIteration exception
118 586da0a0 Nikos Skalkotos
119 586da0a0 Nikos Skalkotos
120 d57775d4 Nikos Skalkotos
class DiskDevice(object):
121 3b2f6619 Nikos Skalkotos
    """This class represents a block device hosting an Operating System
122 3b2f6619 Nikos Skalkotos
    as created by the device-mapper.
123 3b2f6619 Nikos Skalkotos
    """
124 d57775d4 Nikos Skalkotos
125 8c574358 Nikos Skalkotos
    def __init__(self, device, bootable=True):
126 3b2f6619 Nikos Skalkotos
        """Create a new DiskDevice."""
127 0db22eac Nikos Skalkotos
128 333ff548 Nikos Skalkotos
        self.device = device
129 1377b8a7 Nikos Skalkotos
        self.bootable = bootable
130 586da0a0 Nikos Skalkotos
        self.progress_bar = None
131 1377b8a7 Nikos Skalkotos
132 1377b8a7 Nikos Skalkotos
        self.g = guestfs.GuestFS()
133 0db22eac Nikos Skalkotos
        self.g.add_drive_opts(self.device, readonly=0)
134 0d5a999d Nikos Skalkotos
135 586da0a0 Nikos Skalkotos
        #self.g.set_trace(1)
136 586da0a0 Nikos Skalkotos
        #self.g.set_verbose(1)
137 0d5a999d Nikos Skalkotos
138 0db22eac Nikos Skalkotos
        self.guestfs_enabled = False
139 0db22eac Nikos Skalkotos
    
140 0db22eac Nikos Skalkotos
    def enable(self):
141 0db22eac Nikos Skalkotos
        """Enable a newly created DiskDevice"""
142 0db22eac Nikos Skalkotos
143 0ae01e26 Nikos Skalkotos
        self.progressbar = progress_generator("VM lauch: ")
144 0db22eac Nikos Skalkotos
        self.progressbar.next()
145 586da0a0 Nikos Skalkotos
        eh = self.g.set_event_callback(self.progress_callback, guestfs.EVENT_PROGRESS)
146 1377b8a7 Nikos Skalkotos
        self.g.launch()
147 0db22eac Nikos Skalkotos
        self.guestfs_enabled = True
148 586da0a0 Nikos Skalkotos
        self.g.delete_event_callback(eh)
149 0db22eac Nikos Skalkotos
        if self.progressbar is not None:
150 0db22eac Nikos Skalkotos
            self.progressbar.send(100)
151 0db22eac Nikos Skalkotos
            self.progressbar = None
152 586da0a0 Nikos Skalkotos
        
153 1377b8a7 Nikos Skalkotos
        roots = self.g.inspect_os()
154 1377b8a7 Nikos Skalkotos
        if len(roots) == 0:
155 0ae01e26 Nikos Skalkotos
            raise FatalError("No operating system found")
156 1377b8a7 Nikos Skalkotos
        if len(roots) > 1:
157 0ae01e26 Nikos Skalkotos
            raise FatalError("Multiple operating systems found")
158 1377b8a7 Nikos Skalkotos
159 1377b8a7 Nikos Skalkotos
        self.root = roots[0]
160 aa2062ba Nikos Skalkotos
        self.ostype = self.g.inspect_get_type(self.root)
161 aa2062ba Nikos Skalkotos
        self.distro = self.g.inspect_get_distro(self.root)
162 8c574358 Nikos Skalkotos
163 1377b8a7 Nikos Skalkotos
    def destroy(self):
164 3b2f6619 Nikos Skalkotos
        """Destroy this DiskDevice instance."""
165 0db22eac Nikos Skalkotos
166 0db22eac Nikos Skalkotos
        if self.guestfs_enabled:
167 0db22eac Nikos Skalkotos
            self.g.umount_all()
168 0db22eac Nikos Skalkotos
            self.g.sync()
169 0db22eac Nikos Skalkotos
170 0db22eac Nikos Skalkotos
        # Close the guestfs handler if open
171 aa2062ba Nikos Skalkotos
        self.g.close()
172 8c574358 Nikos Skalkotos
173 586da0a0 Nikos Skalkotos
    def progress_callback(self, ev, eh, buf, array):
174 586da0a0 Nikos Skalkotos
        position = array[2]
175 586da0a0 Nikos Skalkotos
        total = array[3]
176 586da0a0 Nikos Skalkotos
177 c54fc0e8 Nikos Skalkotos
        self.progressbar.send((position * 100)//total)
178 586da0a0 Nikos Skalkotos
179 586da0a0 Nikos Skalkotos
        if position == total:
180 c54fc0e8 Nikos Skalkotos
            self.progressbar = None
181 586da0a0 Nikos Skalkotos
182 1377b8a7 Nikos Skalkotos
    def mount(self):
183 3b2f6619 Nikos Skalkotos
        """Mount all disk partitions in a correct order."""
184 0d5a999d Nikos Skalkotos
        mps = self.g.inspect_get_mountpoints(self.root)
185 8c574358 Nikos Skalkotos
186 1377b8a7 Nikos Skalkotos
        # Sort the keys to mount the fs in a correct order.
187 1377b8a7 Nikos Skalkotos
        # / should be mounted befor /boot, etc
188 8c574358 Nikos Skalkotos
        def compare(a, b):
189 8c574358 Nikos Skalkotos
            if len(a[0]) > len(b[0]):
190 8c574358 Nikos Skalkotos
                return 1
191 8c574358 Nikos Skalkotos
            elif len(a[0]) == len(b[0]):
192 8c574358 Nikos Skalkotos
                return 0
193 8c574358 Nikos Skalkotos
            else:
194 8c574358 Nikos Skalkotos
                return -1
195 1377b8a7 Nikos Skalkotos
        mps.sort(compare)
196 1377b8a7 Nikos Skalkotos
        for mp, dev in mps:
197 1377b8a7 Nikos Skalkotos
            try:
198 1377b8a7 Nikos Skalkotos
                self.g.mount(dev, mp)
199 1377b8a7 Nikos Skalkotos
            except RuntimeError as msg:
200 1377b8a7 Nikos Skalkotos
                print "%s (ignored)" % msg
201 d57775d4 Nikos Skalkotos
202 8c574358 Nikos Skalkotos
    def umount(self):
203 3b2f6619 Nikos Skalkotos
        """Umount all mounted filesystems."""
204 8c574358 Nikos Skalkotos
        self.g.umount_all()
205 8c574358 Nikos Skalkotos
206 8c574358 Nikos Skalkotos
    def shrink(self):
207 3b2f6619 Nikos Skalkotos
        """Shrink the disk.
208 3b2f6619 Nikos Skalkotos

209 3b2f6619 Nikos Skalkotos
        This is accomplished by shrinking the last filesystem in the
210 3b2f6619 Nikos Skalkotos
        disk and then updating the partition table. The new disk size
211 3b2f6619 Nikos Skalkotos
        (in bytes) is returned.
212 3b2f6619 Nikos Skalkotos
        """
213 8c574358 Nikos Skalkotos
        dev = self.g.part_to_dev(self.root)
214 8c574358 Nikos Skalkotos
        parttype = self.g.part_get_parttype(dev)
215 8c574358 Nikos Skalkotos
        if parttype != 'msdos':
216 0ae01e26 Nikos Skalkotos
            raise FatalError("You have a %s partition table. "
217 8c574358 Nikos Skalkotos
                "Only msdos partitions are supported" % parttype)
218 8c574358 Nikos Skalkotos
219 8c574358 Nikos Skalkotos
        last_partition = self.g.part_list(dev)[-1]
220 8c574358 Nikos Skalkotos
221 8c574358 Nikos Skalkotos
        if last_partition['part_num'] > 4:
222 0ae01e26 Nikos Skalkotos
            raise FatalError("This disk contains logical partitions. "
223 8c574358 Nikos Skalkotos
                "Only primary partitions are supported.")
224 8c574358 Nikos Skalkotos
225 8c574358 Nikos Skalkotos
        part_dev = "%s%d" % (dev, last_partition['part_num'])
226 8c574358 Nikos Skalkotos
        fs_type = self.g.vfs_type(part_dev)
227 8c574358 Nikos Skalkotos
        if not re.match("ext[234]", fs_type):
228 a1a5ca4b Nikos Skalkotos
            print "Warning: Don't know how to resize %s partitions." % vfs_type
229 8c574358 Nikos Skalkotos
            return
230 8c574358 Nikos Skalkotos
231 8c574358 Nikos Skalkotos
        self.g.e2fsck_f(part_dev)
232 8c574358 Nikos Skalkotos
        self.g.resize2fs_M(part_dev)
233 8c574358 Nikos Skalkotos
        output = self.g.tune2fs_l(part_dev)
234 8c574358 Nikos Skalkotos
        block_size = int(filter(lambda x: x[0] == 'Block size', output)[0][1])
235 8c574358 Nikos Skalkotos
        block_cnt = int(filter(lambda x: x[0] == 'Block count', output)[0][1])
236 8c574358 Nikos Skalkotos
237 8c574358 Nikos Skalkotos
        sector_size = self.g.blockdev_getss(dev)
238 8c574358 Nikos Skalkotos
239 8c574358 Nikos Skalkotos
        start = last_partition['part_start'] / sector_size
240 8c574358 Nikos Skalkotos
        end = start + (block_size * block_cnt) / sector_size - 1
241 8c574358 Nikos Skalkotos
242 c408053f Nikos Skalkotos
        self.g.part_del(dev, last_partition['part_num'])
243 c408053f Nikos Skalkotos
        self.g.part_add(dev, 'p', start, end)
244 c408053f Nikos Skalkotos
245 e3aac3f9 Nikos Skalkotos
        return (end + 1) * sector_size
246 e3aac3f9 Nikos Skalkotos
247 c408053f Nikos Skalkotos
    def size(self):
248 c408053f Nikos Skalkotos
        """Returns the "payload" size of the device.
249 c408053f Nikos Skalkotos

250 c408053f Nikos Skalkotos
        The size returned by this method is the size of the space occupied by
251 c408053f Nikos Skalkotos
        the partitions (including the space before the first partition).
252 c408053f Nikos Skalkotos
        """
253 c408053f Nikos Skalkotos
        dev = self.g.part_to_dev(self.root)
254 c408053f Nikos Skalkotos
        last = self.g.part_list(dev)[-1]
255 c408053f Nikos Skalkotos
256 c408053f Nikos Skalkotos
        return last['part_end']
257 8c574358 Nikos Skalkotos
258 d57775d4 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :