~mdeslaur/vmbuilder/centos-support

358 by Marc Deslauriers
- Add initial CentOS support
1
#
2
#    Uncomplicated VM Builder
3
#    Copyright (C) 2007-2009 Canonical Ltd.
4
#    
5
#    See AUTHORS for list of contributors
6
#
7
#    This program is free software: you can redistribute it and/or modify
8
#    it under the terms of the GNU General Public License version 3, as
9
#    published by the Free Software Foundation.
10
#
11
#    This program is distributed in the hope that it will be useful,
12
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#    GNU General Public License for more details.
15
#
16
#    You should have received a copy of the GNU General Public License
17
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
#
19
import logging
20
import os, subprocess
21
import socket
22
import types
23
import shutil
24
import VMBuilder
25
from   VMBuilder           import register_distro, Distro
26
from   VMBuilder.util      import run_cmd
27
from   VMBuilder.exception import VMBuilderUserError, VMBuilderException
28
29
class Centos(Distro):
30
    name = 'Centos'
31
    arg = 'centos'
32
    suites = ['centos-4', 'centos-5']
33
    
34
    # Maps host arch to valid guest archs
35
    valid_archs = { 'amd64' : ['amd64', 'i386' ],
36
                    'i386' : [ 'i386' ],
37
                    'lpia' : [ 'i386' ] }
38
39
    xen_kernel = ''
40
41
    def register_options(self):
42
        group = self.vm.setting_group('Package options')
43
        group.add_option('--addpkg', action='append', metavar='PKG', help='Install PKG into the guest (can be specfied multiple times).')
44
        group.add_option('--removepkg', action='append', metavar='PKG', help='Remove PKG from the guest (can be specfied multiple times)')
45
        self.vm.register_setting_group(group)
46
47
        group = self.vm.setting_group('General OS options')
48
        self.host_arch = run_cmd('dpkg', '--print-architecture').rstrip()
49
        group.add_option('-a', '--arch', default=self.host_arch, help='Specify the target architecture.  Valid options: amd64 i386 (defaults to host arch)')
50
        group.add_option('--hostname', default='centos', help='Set NAME as the hostname of the guest. Default: centos. Also uses this name as the VM name.')
51
        self.vm.register_setting_group(group)
52
53
        group = self.vm.setting_group('Installation options')
54
        group.add_option('--suite', default='centos-4', help='Suite to install. Valid options: %s [default: %%default]' % ' '.join(self.suites))
55
        group.add_option('--flavour', '--kernel-flavour', help='Kernel flavour to use. Default and valid options depend on architecture and suite')
360 by Marc Deslauriers
- Make --mirror work
56
        group.add_option('--mirror', metavar='URL', help='Use Centos mirror at URL instead of the default CentOS mirror list')
366 by Marc Deslauriers
- Add proxy support
57
        group.add_option('--proxy', metavar='URL', help='Use proxy at URL for Yum packages')
360 by Marc Deslauriers
- Make --mirror work
58
        group.add_option('--install-mirror', metavar='URL', help='Use Centos mirror at URL for the installation only. Yum will still use the default or the URL set by --mirror. Default is: http://mirror.bytemark.co.uk/centos')
358 by Marc Deslauriers
- Add initial CentOS support
59
        group.add_option('--lang', metavar='LANG', default=self.get_locale(), help='Set the locale to LANG [default: %default]')
60
        group.add_option('--timezone', metavar='TZ', default='UTC', help='Set the timezone to TZ in the vm. [default: %default]')
61
        self.vm.register_setting_group(group)
62
63
        group = self.vm.setting_group('Settings for the initial user')
64
        group.add_option('--user', default='centos', help='Username of initial user [default: %default]')
65
        group.add_option('--name', default='Centos', help='Full name of initial user [default: %default]')
66
        group.add_option('--pass', default='centos', help='Password of initial user [default: %default]')
67
        group.add_option('--rootpass', default='centos', help='Password of root user [default: %default]')
68
        group.add_option('--uid', help='Initial UID value.')
69
        group.add_option('--gid', help='Initial GID value.')
70
        group.add_option('--lock-user', action='store_true', help='Lock the initial user [default %default]')
71
        self.vm.register_setting_group(group)
72
73
        group = self.vm.setting_group('Other options')
74
        group.add_option('--ssh-key', metavar='PATH', help='Add PATH to root\'s ~/.ssh/authorized_keys (WARNING: this has strong security implications).')
75
        group.add_option('--ssh-user-key', help='Add PATH to the user\'s ~/.ssh/authorized_keys.')
76
        group.add_option('--manifest', metavar='PATH', help='If passed, a manifest will be written to PATH')
77
        self.vm.register_setting_group(group)
78
79
    def set_defaults(self):
364 by Marc Deslauriers
- More work on mirror logic
80
        pass
358 by Marc Deslauriers
- Add initial CentOS support
81
82
    def get_locale(self):
83
        return os.getenv('LANG')
84
85
    def preflight_check(self):
86
        """While not all of these are strictly checks, their failure would inevitably
87
        lead to failure, and since we can check them before we start setting up disk
88
        and whatnot, we might as well go ahead an do this now."""
89
90
        if not self.vm.suite in self.suites:
91
            raise VMBuilderUserError('Invalid suite. Valid suites are: %s' % ' '.join(self.suites))
92
        
93
        modname = 'VMBuilder.plugins.centos.%s' % (self.vm.suite.replace('-',''), )
94
        mod = __import__(modname, fromlist=[self.vm.suite.replace('-','')])
95
        self.suite = getattr(mod, self.vm.suite.replace('-','').capitalize())(self.vm)
96
97
        if self.vm.arch not in self.valid_archs[self.host_arch] or  \
98
            not self.suite.check_arch_validity(self.vm.arch):
99
            raise VMBuilderUserError('%s is not a valid architecture. Valid architectures are: %s' % (self.vm.arch, 
100
                                                                                                      ' '.join(self.valid_archs[self.host_arch])))
101
102
        if self.vm.hypervisor.name == 'Xen':
103
            logging.info('Xen kernel default: linux-image-%s %s', self.suite.xen_kernel_flavour, self.xen_kernel_version())
104
105
        self.vm.virtio_net = self.use_virtio_net()
106
107
        if self.vm.lang:
108
            try:
109
                run_cmd('locale-gen', '%s' % self.vm.lang)
110
            except VMBuilderException, e:
111
                msg = "locale-gen does not recognize your locale '%s'" % self.vm.lang
112
                raise VMBuilderUserError(msg)
113
359 by Marc Deslauriers
- VMBuilder/plugins/centos/distro.py: remove unused options, make sure
114
        # Make sure rinse is installed
115
        try:
116
            run_cmd('/usr/sbin/rinse')
117
        except VMBuilderException, e:
118
            msg = "The 'rinse' utility doesn't seem to be installed."
119
            raise VMBuilderUserError(msg)
120
358 by Marc Deslauriers
- Add initial CentOS support
121
        if getattr(self.vm, 'ec2', False):
122
            self.get_ec2_kernel()
123
            self.get_ec2_ramdisk()
124
            self.apply_ec2_settings()
125
126
    def install(self, destdir):
127
        self.destdir = destdir
128
        self.suite.install(destdir)
129
130
    def install_vmbuilder_log(self, logfile, rootdir):
131
        self.suite.install_vmbuilder_log(logfile, rootdir)
132
133
    def post_mount(self, fs):
134
        self.suite.post_mount(fs)
135
136
    def use_virtio_net(self):
137
        return self.suite.virtio_net
138
139
    def install_bootloader_cleanup(self):
140
        self.vm.cancel_cleanup(self.install_bootloader_cleanup)
141
        tmpdir = '%s/tmp/vmbuilder-grub' % self.destdir
142
        for disk in os.listdir(tmpdir):
143
            if disk != 'device.map':
144
                run_cmd('umount', os.path.join(tmpdir, disk))
145
        shutil.rmtree(tmpdir)
146
147
    def install_bootloader(self):
148
        tmpdir = '/tmp/vmbuilder-grub'
149
        os.makedirs('%s%s' % (self.destdir, tmpdir))
150
        self.vm.add_clean_cb(self.install_bootloader_cleanup)
151
        devmapfile = os.path.join(tmpdir, 'device.map')
152
        devmap = open('%s%s' % (self.destdir, devmapfile), 'w')
153
        for (disk, id) in zip(self.vm.disks, range(len(self.vm.disks))):
154
            new_filename = os.path.join(tmpdir, os.path.basename(disk.filename))
155
            open('%s%s' % (self.destdir, new_filename), 'w').close()
156
            run_cmd('mount', '--bind', disk.filename, '%s%s' % (self.destdir, new_filename))
157
            devmap.write("(hd%d) %s\n" % (id, new_filename))
158
        devmap.close()
159
        #
160
        # There are a couple of reasons why grub installation can fail:
161
        #
162
        # "Error 2: Bad file or directory type" can be caused by an ext3
163
        # partition with 256 bit inodes and an older distro. See workaround
164
        # in disk.py.
165
        #
166
        # "Error 18: Selected cylinder exceeds maximum supported by BIOS"
167
        # can be caused by grub detecting a geometry that may not be
168
        # compatible with an older BIOS. We work around this below by
169
        # setting the geometry with bogus values:
170
        #
171
        self.run_in_target('grub', '--device-map=%s' % devmapfile, '--batch',  stdin='''root (hd0,0)
172
geometry (hd0) 800 800 800
173
setup (hd0)
174
EOT''')
175
        self.install_bootloader_cleanup()
176
177
    def xen_kernel_version(self):
178
        if self.suite.xen_kernel_flavour:
179
            if not self.xen_kernel:
180
                rmad = run_cmd('rmadison', 'linux-image-%s' % self.suite.xen_kernel_flavour)
181
                version = ['0', '0','0', '0']
182
183
                for line in rmad.splitlines():
184
                    sline = line.split('|')
185
                    
186
                    if sline[2].strip().startswith(self.vm.suite):
187
                        vt = sline[1].strip().split('.')
188
                        for i in range(4):
189
                            if int(vt[i]) > int(version[i]):
190
                                version = vt
191
                                break
192
193
                if version[0] == '0':
194
                    raise VMBuilderException('Something is wrong, no valid xen kernel for the suite %s found by rmadison' % self.vm.suite)
195
                
196
                self.xen_kernel = '%s.%s.%s-%s' % (version[0],version[1],version[2],version[3])
197
            return self.xen_kernel
198
        else:
199
            raise VMBuilderUserError('There is no valid xen kernel for the suite selected.')
200
201
    def xen_kernel_initrd_path(self, which):
202
        path = '/boot/%s-%s-%s' % (which, self.xen_kernel_version(), self.suite.xen_kernel_flavour)
203
        return path
204
205
    def xen_kernel_path(self):
206
        return self.xen_kernel_initrd_path('kernel')
207
208
    def xen_ramdisk_path(self):
209
        return self.xen_kernel_initrd_path('ramdisk')
210
211
    def get_ec2_kernel(self):
212
        if self.suite.ec2_kernel_info:
213
            return self.suite.ec2_kernel_info[self.vm.arch]
214
        else:
215
            raise VMBuilderUserError('EC2 is not supported for the suite selected')
216
217
    def get_ec2_ramdisk(self):
218
        if self.suite.ec2_ramdisk_info:
219
            return self.suite.ec2_ramdisk_info[self.vm.arch]
220
        else:
221
            raise VMBuilderUserError('EC2 is not supported for the suite selected')
222
223
    def disable_hwclock_access(self):
224
        return self.suite.disable_hwclock_access()
225
226
    def apply_ec2_settings(self):
227
        return self.suite.apply_ec2_settings()
228
229
register_distro(Centos)