~frankban/lpsetup/lxcinstall-fixes

« back to all changes in this revision

Viewing changes to lpsetup/subcommands/lxcinstall.py

  • Committer: Francesco Banconi
  • Date: 2012-03-12 16:22:02 UTC
  • mfrom: (1.1.8 split-files)
  • Revision ID: francesco.banconi@canonical.com-20120312162202-8sxdbww55uxtpkws
[r=benji] Base structure for the project.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# Copyright 2012 Canonical Ltd.  This software is licensed under the
 
3
# GNU Affero General Public License version 3 (see the file LICENSE).
 
4
 
 
5
"""LXC install subcommand: create a Launchpad environment inside an LXC."""
 
6
 
 
7
__metaclass__ = type
 
8
__all__ = [
 
9
    'create_lxc',
 
10
    'initialize_lxc',
 
11
    'SubCommand',
 
12
    'setup_launchpad_lxc',
 
13
    'start_lxc',
 
14
    'stop_lxc',
 
15
    'wait_for_lxc',
 
16
    ]
 
17
 
 
18
import os
 
19
import shutil
 
20
import subprocess
 
21
import time
 
22
 
 
23
from shelltoolbox import (
 
24
    apt_get_install,
 
25
    file_append,
 
26
    file_prepend,
 
27
    mkdirs,
 
28
    ssh,
 
29
    )
 
30
 
 
31
from lpsetup.settings import (
 
32
    BASE_PACKAGES,
 
33
    DHCP_FILE,
 
34
    LXC_CONFIG_TEMPLATE,
 
35
    LXC_GUEST_ARCH,
 
36
    LXC_GUEST_CHOICES,
 
37
    LXC_GUEST_OS,
 
38
    LXC_NAME,
 
39
    LXC_OPTIONS,
 
40
    LXC_PACKAGES,
 
41
    RESOLV_FILE,
 
42
    )
 
43
from lpsetup.subcommands import install
 
44
from lpsetup.utils import (
 
45
    call,
 
46
    get_container_path,
 
47
    get_lxc_gateway,
 
48
    lxc_stopped,
 
49
    this_command,
 
50
    )
 
51
 
 
52
 
 
53
def create_lxc(user, lxc_name, lxc_arch, lxc_os):
 
54
    """Create the LXC named `lxc_name` sharing `user` home directory.
 
55
 
 
56
    The container will be used as development environment or as base template
 
57
    for parallel testing using ephemeral instances.
 
58
    """
 
59
    # Install necessary deb packages.
 
60
    apt_get_install(*LXC_PACKAGES)
 
61
    # XXX 2012-02-02 gmb bug=925024:
 
62
    #     These calls need to be removed once the lxc vs. apparmor bug
 
63
    #     is resolved, since having apparmor enabled for lxc is very
 
64
    #     much a Good Thing.
 
65
    # Disable the apparmor profiles for lxc so that we don't have
 
66
    # problems installing postgres.
 
67
    call('ln', '-s',
 
68
         '/etc/apparmor.d/usr.bin.lxc-start', '/etc/apparmor.d/disable/')
 
69
    call('apparmor_parser', '-R', '/etc/apparmor.d/usr.bin.lxc-start')
 
70
    # Update resolv file in order to get the ability to ssh into the LXC
 
71
    # container using its name.
 
72
    lxc_gateway_name, lxc_gateway_address = get_lxc_gateway()
 
73
    file_prepend(RESOLV_FILE, 'nameserver {0}\n'.format(lxc_gateway_address))
 
74
    file_append(
 
75
        DHCP_FILE,
 
76
        'prepend domain-name-servers {0};\n'.format(lxc_gateway_address))
 
77
    # Container configuration template.
 
78
    content = LXC_OPTIONS.format(interface=lxc_gateway_name)
 
79
    with open(LXC_CONFIG_TEMPLATE, 'w') as f:
 
80
        f.write(content)
 
81
    # Creating container.
 
82
    call(
 
83
        'lxc-create',
 
84
        '-t', 'ubuntu',
 
85
        '-n', lxc_name,
 
86
        '-f', LXC_CONFIG_TEMPLATE,
 
87
        '--',
 
88
        '-r {os} -a {arch} -b {user}'.format(
 
89
            os=lxc_os, arch=lxc_arch, user=user),
 
90
        )
 
91
    # Set up root ssh key.
 
92
    user_authorized_keys = os.path.expanduser(
 
93
        '~' + user + '/.ssh/authorized_keys')
 
94
    with open(user_authorized_keys, 'a') as f:
 
95
        f.write(open('/root/.ssh/id_rsa.pub').read())
 
96
    dst = get_container_path(lxc_name, '/root/.ssh/')
 
97
    mkdirs(dst)
 
98
    shutil.copy(user_authorized_keys, dst)
 
99
 
 
100
 
 
101
def start_lxc(lxc_name):
 
102
    """Start the lxc instance named `lxc_name`."""
 
103
    call('lxc-start', '-n', lxc_name, '-d')
 
104
 
 
105
 
 
106
def wait_for_lxc(lxc_name, trials=60, sleep_seconds=1):
 
107
    """Try to ssh as `user` into the LXC container named `lxc_name`."""
 
108
    sshcall = ssh(lxc_name)
 
109
    while True:
 
110
        trials -= 1
 
111
        try:
 
112
            sshcall('true')
 
113
        except subprocess.CalledProcessError:
 
114
            if not trials:
 
115
                raise
 
116
            time.sleep(sleep_seconds)
 
117
        else:
 
118
            break
 
119
 
 
120
 
 
121
def initialize_lxc(lxc_name, lxc_os):
 
122
    """Initialize LXC container."""
 
123
    base_packages = list(BASE_PACKAGES)
 
124
    if lxc_os == 'lucid':
 
125
        # Install argparse to be able to run this script inside a lucid lxc.
 
126
        base_packages.append('python-argparse')
 
127
    ssh(lxc_name)(
 
128
        'DEBIAN_FRONTEND=noninteractive '
 
129
        'apt-get install -y ' + ' '.join(base_packages))
 
130
 
 
131
 
 
132
def setup_launchpad_lxc(
 
133
    user, dependencies_dir, directory, valid_ssh_keys, lxc_name):
 
134
    """Set up the Launchpad environment inside an LXC."""
 
135
    # Use ssh to call this script from inside the container.
 
136
    args = [
 
137
        'install', '-u', user, '-a', 'setup_apt', 'setup_launchpad',
 
138
        '-d', dependencies_dir, '-c', directory
 
139
        ]
 
140
    cmd = this_command(directory, args)
 
141
    ssh(lxc_name)(cmd)
 
142
 
 
143
 
 
144
def stop_lxc(lxc_name):
 
145
    """Stop the lxc instance named `lxc_name`."""
 
146
    ssh(lxc_name)('poweroff')
 
147
    if not lxc_stopped(lxc_name):
 
148
        subprocess.call(['lxc-stop', '-n', lxc_name])
 
149
 
 
150
 
 
151
class SubCommand(install.SubCommand):
 
152
    """Install the Launchpad environment inside an LXC."""
 
153
 
 
154
    actions = (
 
155
        (install.initialize,
 
156
         'user', 'full_name', 'email', 'lpuser', 'private_key',
 
157
         'public_key', 'valid_ssh_keys', 'dependencies_dir', 'directory'),
 
158
        (create_lxc,
 
159
         'user', 'lxc_name', 'lxc_arch', 'lxc_os'),
 
160
        (start_lxc, 'lxc_name'),
 
161
        (wait_for_lxc, 'lxc_name'),
 
162
        (initialize_lxc,
 
163
         'lxc_name', 'lxc_os'),
 
164
        (setup_launchpad_lxc,
 
165
         'user', 'dependencies_dir', 'directory', 'valid_ssh_keys',
 
166
         'lxc_name'),
 
167
        (stop_lxc, 'lxc_name'),
 
168
        )
 
169
    help = __doc__
 
170
 
 
171
    def add_arguments(self, parser):
 
172
        super(SubCommand, self).add_arguments(parser)
 
173
        parser.add_argument(
 
174
            '-n', '--lxc-name', default=LXC_NAME,
 
175
            help='The LXC container name to setup. '
 
176
                 '[DEFAULT={0}]'.format(LXC_NAME))
 
177
        parser.add_argument(
 
178
            '-A', '--lxc-arch', default=LXC_GUEST_ARCH,
 
179
            help='The LXC container architecture. '
 
180
                 '[DEFAULT={0}]'.format(LXC_GUEST_ARCH))
 
181
        parser.add_argument(
 
182
            '-r', '--lxc-os', default=LXC_GUEST_OS,
 
183
            choices=LXC_GUEST_CHOICES,
 
184
            help='The LXC container distro codename. '
 
185
                 '[DEFAULT={0}]'.format(LXC_GUEST_OS))