Revision 22a6d232 image_creator/disk.py

b/image_creator/disk.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
from image_creator.util import get_command
34
from image_creator.util import get_command, warn, progress_generator
35 35
from image_creator import FatalError
36
from clint.textui import progress
36
from clint.textui import indent, puts, colored
37 37

  
38 38
import stat
39 39
import os
......
99 99
        This instance is a snapshot of the original source media of
100 100
        the Disk instance.
101 101
        """
102
        sourcedev = self.source
103
        mode = os.stat(self.source).st_mode
104
        if stat.S_ISDIR(mode):
105
            return self._losetup(self._dir_to_disk())
106
        elif stat.S_ISREG(mode):
107
            sourcedev = self._losetup(self.source)
108
        elif not stat.S_ISBLK(mode):
109
            raise ValueError("Value for self.source is invalid")
102

  
103
        puts("Examining source media `%s'" % self.source)
104
        with indent(4):
105
            sourcedev = self.source
106
            mode = os.stat(self.source).st_mode
107
            if stat.S_ISDIR(mode):
108
                puts(colored.green('Looks like a directory'))
109
                return self._losetup(self._dir_to_disk())
110
            elif stat.S_ISREG(mode):
111
                puts(colored.green('Looks like an image file'))
112
                sourcedev = self._losetup(self.source)
113
            elif not stat.S_ISBLK(mode):
114
                raise ValueError("Invalid media source. Only block devices, "
115
                                "regular files and directories are supported.")
116
            else:
117
                puts(colored.green('Looks like a block device'))
118
            #puts()
110 119

  
111 120
        # Take a snapshot and return it to the user
112
        size = blockdev('--getsize', sourcedev)
113
        cowfd, cow = tempfile.mkstemp()
114
        os.close(cowfd)
115
        self._add_cleanup(os.unlink, cow)
116
        # Create 1G cow sparse file
117
        dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', 'seek=%d' % (1024 * 1024))
118
        cowdev = self._losetup(cow)
119

  
120
        snapshot = uuid.uuid4().hex
121
        tablefd, table = tempfile.mkstemp()
122
        try:
123
            os.write(tablefd, "0 %d snapshot %s %s n 8" % \
124
                                        (int(size), sourcedev, cowdev))
125
            dmsetup('create', snapshot, table)
126
            self._add_cleanup(dmsetup, 'remove', snapshot)
127
        finally:
128
            os.unlink(table)
121
        puts("Snapshotting media source")
122
        with indent(4):
123
            size = blockdev('--getsize', sourcedev)
124
            cowfd, cow = tempfile.mkstemp()
125
            os.close(cowfd)
126
            self._add_cleanup(os.unlink, cow)
127
            # Create 1G cow sparse file
128
            dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', \
129
                                            'seek=%d' % (1024 * 1024))
130
            cowdev = self._losetup(cow)
131
    
132
            snapshot = uuid.uuid4().hex
133
            tablefd, table = tempfile.mkstemp()
134
            try:
135
                os.write(tablefd, "0 %d snapshot %s %s n 8" % \
136
                                            (int(size), sourcedev, cowdev))
137
                dmsetup('create', snapshot, table)
138
                self._add_cleanup(dmsetup, 'remove', snapshot)
139
                # Sometimes dmsetup remove fails with Device or resource busy,
140
                # although everything is cleaned up and the snapshot is not
141
                # used by anyone. Add a 2 seconds delay to be on the safe side.
142
                self._add_cleanup(time.sleep, 2)
143

  
144
            finally:
145
                os.unlink(table)
146
            puts(colored.green('Done'))
147
        # puts()
129 148
        new_device = DiskDevice("/dev/mapper/%s" % snapshot)
130 149
        self._devices.append(new_device)
131 150
        new_device.enable()
......
139 158
        device.destroy()
140 159

  
141 160

  
142
def progress_generator(label=''):
143
    position = 0
144
    for i in progress.bar(range(100), label):
145
        if i < position:
146
            continue
147
        position = yield
148
    yield  # suppress the StopIteration exception
149

  
150

  
151 161
class DiskDevice(object):
152 162
    """This class represents a block device hosting an Operating System
153 163
    as created by the device-mapper.
......
170 180

  
171 181
    def enable(self):
172 182
        """Enable a newly created DiskDevice"""
173

  
174
        self.progressbar = progress_generator("VM lauch: ")
175
        self.progressbar.next()
176
        eh = self.g.set_event_callback(self.progress_callback,
183
        self.progressbar = progress_generator("Launching helper VM: ")
184
        with indent(4):
185
            self.progressbar.next()
186
            eh = self.g.set_event_callback(self.progress_callback,
177 187
                                                        guestfs.EVENT_PROGRESS)
178
        self.g.launch()
179
        self.guestfs_enabled = True
180
        self.g.delete_event_callback(eh)
181
        if self.progressbar is not None:
182
            self.progressbar.send(100)
183
            self.progressbar = None
184

  
185
        roots = self.g.inspect_os()
186
        if len(roots) == 0:
187
            raise FatalError("No operating system found")
188
        if len(roots) > 1:
189
            raise FatalError("Multiple operating systems found")
190

  
191
        self.root = roots[0]
192
        self.ostype = self.g.inspect_get_type(self.root)
193
        self.distro = self.g.inspect_get_distro(self.root)
188
            self.g.launch()
189
            self.guestfs_enabled = True
190
            self.g.delete_event_callback(eh)
191
            if self.progressbar is not None:
192
                self.progressbar.send(100)
193
                self.progressbar = None
194
            puts(colored.green('Done'))
195

  
196
        puts('Inspecting Operating System')
197
        with indent(4):
198
            roots = self.g.inspect_os()
199
            if len(roots) == 0:
200
                raise FatalError("No operating system found")
201
            if len(roots) > 1:
202
                raise FatalError("Multiple operating systems found."
203
                                "We only support images with one filesystem.")
204
            self.root = roots[0]
205
            self.ostype = self.g.inspect_get_type(self.root)
206
            self.distro = self.g.inspect_get_distro(self.root)
207
            puts(colored.green('Found a %s system' % self.distro))
208
        puts()
194 209

  
195 210
    def destroy(self):
196 211
        """Destroy this DiskDevice instance."""
......
242 257
        disk and then updating the partition table. The new disk size
243 258
        (in bytes) is returned.
244 259
        """
260
        puts("Shrinking image (this may take a while)")
261
        
245 262
        dev = self.g.part_to_dev(self.root)
246 263
        parttype = self.g.part_get_parttype(dev)
247 264
        if parttype != 'msdos':
......
257 274
        part_dev = "%s%d" % (dev, last_partition['part_num'])
258 275
        fs_type = self.g.vfs_type(part_dev)
259 276
        if not re.match("ext[234]", fs_type):
260
            print "Warning: Don't know how to resize %s partitions." % vfs_type
277
            warn("Don't know how to resize %s partitions." % vfs_type)
261 278
            return
262 279

  
263
        self.g.e2fsck_f(part_dev)
264
        self.g.resize2fs_M(part_dev)
265
        output = self.g.tune2fs_l(part_dev)
266
        block_size = int(filter(lambda x: x[0] == 'Block size', output)[0][1])
267
        block_cnt = int(filter(lambda x: x[0] == 'Block count', output)[0][1])
280
        with indent(4):
281
            self.g.e2fsck_f(part_dev)
282
            self.g.resize2fs_M(part_dev)
283

  
284
            output = self.g.tune2fs_l(part_dev)
285
            block_size = int(
286
                filter(lambda x: x[0] == 'Block size', output)[0][1])
287
            block_cnt = int(
288
                filter(lambda x: x[0] == 'Block count', output)[0][1])
268 289

  
269
        sector_size = self.g.blockdev_getss(dev)
290
            sector_size = self.g.blockdev_getss(dev)
270 291

  
271
        start = last_partition['part_start'] / sector_size
272
        end = start + (block_size * block_cnt) / sector_size - 1
292
            start = last_partition['part_start'] / sector_size
293
            end = start + (block_size * block_cnt) / sector_size - 1
273 294

  
274
        self.g.part_del(dev, last_partition['part_num'])
275
        self.g.part_add(dev, 'p', start, end)
295
            self.g.part_del(dev, last_partition['part_num'])
296
            self.g.part_add(dev, 'p', start, end)
276 297

  
277
        return (end + 1) * sector_size
298
            new_size = (end + 1) * sector_size
299
            puts("  New image size is %dMB\n" % (new_size // 2 ** 20))
300
        return new_size
278 301

  
279 302
    def size(self):
280 303
        """Returns the "payload" size of the device.

Also available in: Unified diff