check the license headers carefully for all files
[platform/upstream/mic.git] / mic / imager / raw.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright 2011 Intel, Inc.
4 #
5 # This copyrighted material is made available to anyone wishing to use, modify,
6 # copy, or redistribute it subject to the terms and conditions of the GNU
7 # General Public License v.2.  This program is distributed in the hope that it
8 # will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
9 # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 # See the GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License along with
13 # this program; if not, write to the Free Software Foundation, Inc., 51
14 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  Any Red Hat
15 # trademarks that are incorporated in the source code or documentation are not
16 # subject to the GNU General Public License and may only be used or replicated
17 # with the express permission of Red Hat, Inc.
18 #
19
20 import os
21 import stat
22 import shutil
23 import subprocess
24
25 import mic.utils.fs_related as fs_related
26 import urlgrabber.progress as progress
27 from baseimager import BaseImageCreator
28 from mic.utils.partitionedfs import PartitionedMount
29 from mic.utils.errors import CreatorError, MountError
30 from mic import kickstart
31 from mic import msger
32
33 class RawImageCreator(BaseImageCreator):
34     """Installs a system into a file containing a partitioned disk image.
35
36         ApplianceImageCreator is an advanced ImageCreator subclass; a sparse file
37         is formatted with a partition table, each partition loopback mounted
38         and the system installed into an virtual disk. The disk image can
39         subsequently be booted in a virtual machine or accessed with kpartx
40     """
41
42     def __init__(self, *args):
43         """Initialize a ApplianceImageCreator instance.
44
45             This method takes the same arguments as ImageCreator.__init__()
46         """
47         BaseImageCreator.__init__(self, *args)
48
49         self.__instloop = None
50         self.__imgdir = None
51         self.__disks = {}
52         self.__disk_format = "raw"
53         self.vmem = 512
54         self.vcpu = 1
55         self.checksum = False
56         self.appliance_version = None
57         self.appliance_release = None
58         #self.getsource = False
59         #self.listpkg = False
60
61         self._dep_checks.extend(["sync", "kpartx", "parted", "extlinux"])
62
63     def configure(self, repodata = None):
64         def chroot():
65
66             os.chroot(self._instroot)
67             os.chdir("/")
68
69         if os.path.exists(self._instroot + "/usr/bin/Xorg"):
70             subprocess.call(["/bin/chmod", "u+s", "/usr/bin/Xorg"], preexec_fn = chroot)
71         BaseImageCreator.configure(self, repodata)
72
73     def _get_fstab(self):
74         s = ""
75         for mp in self.__instloop.mountOrder:
76             p = None
77             for p1 in self.__instloop.partitions:
78                 if p1['mountpoint'] == mp:
79                     p = p1
80                     break
81
82             s += "%(device)s  %(mountpoint)s         %(fstype)s   %(fsopts)s 0 0\n" %  {
83                  'device': "/dev/%s%-d" % (p['disk'], p['num']),
84                  'mountpoint': p['mountpoint'],
85                  'fstype': p['fstype'],
86                  'fsopts': "defaults,noatime" if not p['fsopts'] else p['fsopts']}
87
88             if p['mountpoint'] == "/":
89                 for subvol in self.__instloop.subvolumes:
90                     if subvol['mountpoint'] == "/":
91                         continue
92                     s += "%(device)s  %(mountpoint)s         %(fstype)s   %(fsopts)s 0 0\n" %  {
93                          'device': "/dev/%s%-d" % (p['disk'], p['num']),
94                          'mountpoint': subvol['mountpoint'],
95                          'fstype': p['fstype'],
96                          'fsopts': "defaults,noatime" if not subvol['fsopts'] else subvol['fsopts']}
97
98         s += "devpts     /dev/pts  devpts  gid=5,mode=620   0 0\n"
99         s += "tmpfs      /dev/shm  tmpfs   defaults         0 0\n"
100         s += "proc       /proc     proc    defaults         0 0\n"
101         s += "sysfs      /sys      sysfs   defaults         0 0\n"
102         return s
103
104     def _create_mkinitrd_config(self):
105         #write  to tell which modules to be included in initrd
106
107         mkinitrd = ""
108         mkinitrd += "PROBE=\"no\"\n"
109         mkinitrd += "MODULES+=\"ext3 ata_piix sd_mod libata scsi_mod\"\n"
110         mkinitrd += "rootfs=\"ext3\"\n"
111         mkinitrd += "rootopts=\"defaults\"\n"
112
113         msger.debug("Writing mkinitrd config %s/etc/sysconfig/mkinitrd" % self._instroot)
114         os.makedirs(self._instroot + "/etc/sysconfig/",mode=644)
115         cfg = open(self._instroot + "/etc/sysconfig/mkinitrd", "w")
116         cfg.write(mkinitrd)
117         cfg.close()
118
119     #
120     # Actual implementation
121     #
122     def _mount_instroot(self, base_on = None):
123         self.__imgdir = self._mkdtemp()
124
125         #Set a default partition if no partition is given out
126         if not self.ks.handler.partition.partitions:
127             partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
128             args = partstr.split()
129             pd = self.ks.handler.partition.parse(args[1:])
130             if pd not in self.ks.handler.partition.partitions:
131                 self.ks.handler.partition.partitions.append(pd)
132
133         #list of partitions from kickstart file
134         parts = kickstart.get_partitions(self.ks)
135
136         #list of disks where a disk is an dict with name: and size
137         disks = []
138
139         for i in range(len(parts)):
140             if parts[i].disk:
141                 disk = parts[i].disk
142             else:
143                 raise CreatorError("Failed to create disks, no --ondisk specified in partition line of ks file")
144
145             if not parts[i].fstype:
146                  raise CreatorError("Failed to create disks, no --fstype specified in partition line of ks file")
147
148             size =   parts[i].size * 1024L * 1024L
149
150             found = False
151             for j in range(len(disks)):
152                 if disks[j]['name'] == disk:
153                     disks[j]['size'] = disks[j]['size'] + size
154                     found = True
155                     break
156                 else:
157                     found = False
158             if not found:
159                 disks.append({ 'name': disk, 'size': size })
160
161         #create disk
162         for item in disks:
163             msger.debug("Adding disk %s as %s/%s-%s.raw" % (item['name'], self.__imgdir,self.name, item['name']))
164             disk = fs_related.SparseLoopbackDisk("%s/%s-%s.raw" % (self.__imgdir,self.name, item['name']),item['size'])
165             self.__disks[item['name']] = disk
166
167         self.__instloop = PartitionedMount(self.__disks, self._instroot)
168
169         for p in parts:
170             self.__instloop.add_partition(int(p.size), p.disk, p.mountpoint, p.fstype, fsopts = p.fsopts, boot = p.active)
171
172         try:
173             self.__instloop.mount()
174         except MountError, e:
175             raise CreatorError("Failed mount disks : %s" % e)
176
177         self._create_mkinitrd_config()
178
179     def _get_required_packages(self):
180         required_packages = BaseImageCreator._get_required_packages(self)
181         if not self.target_arch or not self.target_arch.startswith("arm"):
182             required_packages += ["syslinux", "syslinux-extlinux"]
183         return required_packages
184
185     def _get_excluded_packages(self):
186         return BaseImageCreator._get_excluded_packages(self)
187
188     def _get_syslinux_boot_config(self):
189         bootdevnum = None
190         rootdevnum = None
191         rootdev = None
192         for p in self.__instloop.partitions:
193             if p['mountpoint'] == "/boot":
194                 bootdevnum = p['num'] - 1
195             elif p['mountpoint'] == "/" and bootdevnum is None:
196                 bootdevnum = p['num'] - 1
197
198             if p['mountpoint'] == "/":
199                 rootdevnum = p['num'] - 1
200                 rootdev = "/dev/%s%-d" % (p['disk'], p['num'])
201
202         prefix = ""
203         if bootdevnum == rootdevnum:
204             prefix = "/boot"
205
206         return (bootdevnum, rootdevnum, rootdev, prefix)
207
208     def _create_syslinux_config(self):
209         #Copy splash
210         splash = "%s/usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg" % self._instroot
211         if os.path.exists(splash):
212             shutil.copy(splash, "%s%s/splash.jpg" % (self._instroot, "/boot/extlinux"))
213             splashline = "menu background splash.jpg"
214         else:
215             splashline = ""
216
217         (bootdevnum, rootdevnum, rootdev, prefix) = self._get_syslinux_boot_config()
218         options = self.ks.handler.bootloader.appendLine
219
220         #XXX don't hardcode default kernel - see livecd code
221         syslinux_conf = ""
222         syslinux_conf += "prompt 0\n"
223         syslinux_conf += "timeout 1\n"
224         syslinux_conf += "\n"
225         syslinux_conf += "default vesamenu.c32\n"
226         syslinux_conf += "menu autoboot Starting %s...\n" % self.distro_name
227         syslinux_conf += "menu hidden\n"
228         syslinux_conf += "\n"
229         syslinux_conf += "%s\n" % splashline
230         syslinux_conf += "menu title Welcome to %s!\n" % self.distro_name
231         syslinux_conf += "menu color border 0 #ffffffff #00000000\n"
232         syslinux_conf += "menu color sel 7 #ffffffff #ff000000\n"
233         syslinux_conf += "menu color title 0 #ffffffff #00000000\n"
234         syslinux_conf += "menu color tabmsg 0 #ffffffff #00000000\n"
235         syslinux_conf += "menu color unsel 0 #ffffffff #00000000\n"
236         syslinux_conf += "menu color hotsel 0 #ff000000 #ffffffff\n"
237         syslinux_conf += "menu color hotkey 7 #ffffffff #ff000000\n"
238         syslinux_conf += "menu color timeout_msg 0 #ffffffff #00000000\n"
239         syslinux_conf += "menu color timeout 0 #ffffffff #00000000\n"
240         syslinux_conf += "menu color cmdline 0 #ffffffff #00000000\n"
241
242         versions = []
243         kernels = self._get_kernel_versions()
244         for kernel in kernels:
245             for version in kernels[kernel]:
246                 versions.append(version)
247
248         footlabel = 0
249         for v in versions:
250             shutil.copy("%s/boot/vmlinuz-%s" %(self._instroot, v),
251                         "%s%s/vmlinuz-%s" % (self._instroot, "/boot/extlinux/", v))
252             syslinux_conf += "label %s%d\n" % (self.distro_name.lower(), footlabel)
253             syslinux_conf += "\tmenu label %s (%s)\n" % (self.distro_name, v)
254             syslinux_conf += "\tkernel vmlinuz-%s\n" % v
255             syslinux_conf += "\tappend ro root=%s quiet vga=current %s\n" % (rootdev, options)
256             if footlabel == 0:
257                syslinux_conf += "\tmenu default\n"
258             footlabel += 1;
259
260         msger.debug("Writing syslinux config %s/boot/extlinux/extlinux.conf" % self._instroot)
261         cfg = open(self._instroot + "/boot/extlinux/extlinux.conf", "w")
262         cfg.write(syslinux_conf)
263         cfg.close()
264
265     def _install_syslinux(self):
266         i = 0
267         for name in self.__disks.keys():
268             loopdev = self.__disks[name].device
269             i =i+1
270
271         msger.debug("Installing syslinux bootloader to %s" % loopdev)
272
273         (bootdevnum, rootdevnum, rootdev, prefix) = self._get_syslinux_boot_config()
274
275
276         #Set MBR
277         mbrsize = os.stat("%s/usr/share/syslinux/mbr.bin" % self._instroot)[stat.ST_SIZE]
278         ddcmd = fs_related.find_binary_path("dd")
279         rc = subprocess.call([ddcmd, "if=%s/usr/share/syslinux/mbr.bin" % self._instroot, "of=" + loopdev])
280         if rc != 0:
281             raise MountError("Unable to set MBR to %s" % loopdev)
282
283         #Set Bootable flag
284         parted = fs_related.find_binary_path("parted")
285         dev_null = os.open("/dev/null", os.O_WRONLY)
286         rc = subprocess.call([parted, "-s", loopdev, "set", "%d" % (bootdevnum + 1), "boot", "on"],
287                              stdout = dev_null, stderr = dev_null)
288         os.close(dev_null)
289         #XXX disabled return code check because parted always fails to
290         #reload part table with loop devices. Annoying because we can't
291         #distinguish this failure from real partition failures :-(
292         if rc != 0 and 1 == 0:
293             raise MountError("Unable to set bootable flag to %sp%d" % (loopdev, (bootdevnum + 1)))
294
295
296         #Ensure all data is flushed to disk before doing syslinux install
297         subprocess.call(["sync"])
298
299         fullpathsyslinux = fs_related.find_binary_path("extlinux")
300         rc = subprocess.call([fullpathsyslinux, "-i", "%s/boot/extlinux" % self._instroot])
301         if rc != 0:
302             raise MountError("Unable to install syslinux bootloader to %sp%d" % (loopdev, (bootdevnum + 1)))
303
304     def _create_bootconfig(self):
305         #If syslinux is available do the required configurations.
306         if os.path.exists("%s/usr/share/syslinux/" % (self._instroot)) \
307            and os.path.exists("%s/boot/extlinux/" % (self._instroot)):
308             self._create_syslinux_config()
309             self._install_syslinux()
310
311     def _unmount_instroot(self):
312         if not self.__instloop is None:
313             self.__instloop.cleanup()
314
315     def _resparse(self, size = None):
316         return self.__instloop.resparse(size)
317
318     def _stage_final_image(self):
319         """Stage the final system image in _outdir.
320            write meta data
321         """
322         self._resparse()
323
324         msger.debug("moving disks to stage location")
325         for name in self.__disks.keys():
326             src = "%s/%s-%s.raw" % (self.__imgdir, self.name,name)
327             self._img_name = "%s-%s.%s" % (self.name, name, self.__disk_format)
328             dst = "%s/%s" % (self._outdir, self._img_name)
329             msger.debug("moving %s to %s" % (src,dst))
330             shutil.move(src,dst)
331         self._write_image_xml()
332
333     def _write_image_xml(self):
334         imgarch = "i686"
335         if self.target_arch and self.target_arch.startswith("arm"):
336             imgarch = "arm"
337         xml = "<image>\n"
338
339         name_attributes = ""
340         if self.appliance_version:
341             name_attributes += " version='%s'" % self.appliance_version
342         if self.appliance_release:
343             name_attributes += " release='%s'" % self.appliance_release
344         xml += "  <name%s>%s</name>\n" % (name_attributes, self.name)
345         xml += "  <domain>\n"
346         # XXX don't hardcode - determine based on the kernel we installed for grub
347         # baremetal vs xen
348         xml += "    <boot type='hvm'>\n"
349         xml += "      <guest>\n"
350         xml += "        <arch>%s</arch>\n" % imgarch
351         xml += "      </guest>\n"
352         xml += "      <os>\n"
353         xml += "        <loader dev='hd'/>\n"
354         xml += "      </os>\n"
355
356         i = 0
357         for name in self.__disks.keys():
358             xml += "      <drive disk='%s-%s.%s' target='hd%s'/>\n" % (self.name,name, self.__disk_format,chr(ord('a')+i))
359             i = i + 1
360
361         xml += "    </boot>\n"
362         xml += "    <devices>\n"
363         xml += "      <vcpu>%s</vcpu>\n" % self.vcpu
364         xml += "      <memory>%d</memory>\n" %(self.vmem * 1024)
365         for network in self.ks.handler.network.network:
366             xml += "      <interface/>\n"
367         xml += "      <graphics/>\n"
368         xml += "    </devices>\n"
369         xml += "  </domain>\n"
370         xml += "  <storage>\n"
371
372         if self.checksum is True:
373             for name in self.__disks.keys():
374                 diskpath = "%s/%s-%s.%s" % (self._outdir,self.name,name, self.__disk_format)
375                 disk_size = os.path.getsize(diskpath)
376                 meter_ct = 0
377                 meter = progress.TextMeter()
378                 meter.start(size=disk_size, text="Generating disk signature for %s-%s.%s" % (self.name,name,self.__disk_format))
379                 xml += "    <disk file='%s-%s.%s' use='system' format='%s'>\n" % (self.name,name, self.__disk_format, self.__disk_format)
380
381                 try:
382                     import hashlib
383                     m1 = hashlib.sha1()
384                     m2 = hashlib.sha256()
385                 except:
386                     import sha
387                     m1 = sha.new()
388                     m2 = None
389                 f = open(diskpath,"r")
390                 while 1:
391                     chunk = f.read(65536)
392                     if not chunk:
393                         break
394                     m1.update(chunk)
395                     if m2:
396                        m2.update(chunk)
397                     meter.update(meter_ct)
398                     meter_ct = meter_ct + 65536
399
400                 sha1checksum = m1.hexdigest()
401                 xml +=  """      <checksum type='sha1'>%s</checksum>\n""" % sha1checksum
402
403                 if m2:
404                     sha256checksum = m2.hexdigest()
405                     xml += """      <checksum type='sha256'>%s</checksum>\n""" % sha256checksum
406                 xml += "    </disk>\n"
407         else:
408             for name in self.__disks.keys():
409                 xml += "    <disk file='%s-%s.%s' use='system' format='%s'/>\n" % (self.name,name, self.__disk_format, self.__disk_format)
410
411         xml += "  </storage>\n"
412         xml += "</image>\n"
413
414         msger.debug("writing image XML to %s/%s.xml" %  (self._outdir, self.name))
415         cfg = open("%s/%s.xml" % (self._outdir, self.name), "w")
416         cfg.write(xml)
417         cfg.close()
418         #print "Wrote: %s.xml" % self.name