~dooferlad/+junk/python-ci-config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# Android CI Job

import logging
import sys
import paramiko
import time
import re

class Checkout():
    uid = 0

class CommandFailed(Exception):
    def __init__(self, cmd, return_code, command_output):
        self.return_code = return_code
        self.command_output = command_output
        self.cmd = cmd

    def __str__(self):
        return repr(self.cmd + "\n" +
                    self.return_code + "\n" +
                    self.command_output)

class CISlave(object):
    """Generic CI slave base class"""
    def __init__(self, tags=""):
        self.got_machine = False
        self.tags = tags
        self.shell = None
        # Have a few pre-defined classes

    def _test_return_code(self, cmd, command_output):
        rx = self._in_shell_cmd("echo $?\n", quiet=True)
        if rx[1] != "0":
            logging.error(":-(\n:-(\n:-(\n")
            logging.error(rx)
            raise CommandFailed(cmd, rx[1], command_output)

    def _in_shell_cmd(self, cmd, quiet=False):
        if not quiet:
            logging.info(">>>>>>>>>> " + cmd + " >>>>>>>>>")
        self.shell.send(cmd + "\n")
        lines = []
        rx = ""
        saved_chunk = ""
        while True:
            chunk = saved_chunk + self.shell.recv(102400)
            saved_chunk = ""
            rx = ''.join([rx, chunk])  # faster than += string?
            lines = rx.splitlines()
            if chunk[-1] != "\n":
                chunk_lines = chunk.splitlines()
                saved_chunk = chunk_lines[-1] # Save unfinished line
                if not quiet:
                    for l in chunk_lines[0:-1]:
                        logging.info(l)

            elif not quiet:
                chunk = chunk.rstrip()  # Avoid extra newlines (logging adds one)
                logging.info(chunk)

            # This is a very specific match for a prompt. Suggest prompt is
            # set to known long random string so chances of it showing up
            last_line = lines[-1]
            if re.search("dooferlad@homework\d+:.*?\$\s*$", last_line):
                break

            # XXX HACK!!! (for android build)
            # Probably need to have a expect/response simple set up
            if last_line.rstrip() == "Enable color display in " \
                                     "this user account (y/N)?":
                print "[Linaro CI]...Answering no"
                self.shell.send("N\n")

            time.sleep(0.25)

        if not quiet:
            logging.info(saved_chunk)
            logging.info("<<<<<<<<<< " + cmd + " <<<<<<<<<")
        return lines

    def _cmd(self, cmd, in_shell=True):
        if in_shell:
            rx = self._in_shell_cmd(cmd)
            self._test_return_code(cmd, rx)
            return rx
        else:
            logging.info("> %s" % (cmd))
            stdin, stdout, stderr = self.ssh.exec_command(cmd)
            print stdout.readlines()
            print stderr.readlines()
            return stdout, stderr

    def cmd(self, command, comment):
        logging.info("cmd: %s #%s" % (command, comment))
        # Return excludes the first line, which is always the command and the
        # last line which is always a prompt.
        return self._cmd(command)[1:-1]

    def boot(self):
        logging.info("boot")

    def disconnect(self):
        logging.info("disconnect")

    def checkout(self, vcs_type, url, branch=None, filename=None, depth=None):
        logging.info("checkout: %s, %s, %s, %s" %
                     (vcs_type, url, branch, filename))

        if vcs_type == "repo":
            self._cmd("pwd")
            # XXX Just a hack so Git is always configured. In the future we can
            # check for "git config --global -l" returning
            # "fatal: unable to read config file"...
            #self._cmd("git config --global user.name 'Linaro Robot'")
            #self._cmd("git config --global user.email 'infrastructure@linaro.org'")
            rx = self._cmd("git config --global -l")
            if re.search("^fatal: unable to read config file", rx[-2]):
                # Set up git as Linaro Infrastructure Robot user
                self._cmd("git config --global user.email 'infrastructure@linaro.org'")
                self._cmd("git config --global user.name  'Infrastructure Robot'")
            self._cmd("~/bin/repo init -u %s -b %s -m %s" % (url, branch, filename))
            self._cmd("~/bin/repo sync")

        elif vcs_type == "git":
            if depth:
                depth_string = "--depth %d" % (depth)
            else:
                depth_string = ""
            self._cmd("git clone %s %s" % (depth_string, url))

        elif vcs_type == "bzr":
            self._cmd("bzr checkout --quiet %s" % (url))

        return Checkout()

    def install_deps(self, packages):
        logging.info("install_deps: %s" % (" ".join(packages)))

    def use(self, name, tags):
        """Use the output of another job as an input to this job"""
        logging.info("use: %s, %s" % (name, " ".join(tags)))

        # This is a massive hack and really doesn't work for anything other
        # than the intial investigation phase
        if name == "android-toolchain":
            self._cmd("wget -nc -nv --no-check-certificate "
                      "http://android-build.linaro.org/download/"
                      "linaro-android_toolchain-4.7-bzr/lastSuccessful/archive/"
                      "build/out/"
                      "android-toolchain-eabi-4.7-daily-linux-x86.tar.bz2")
            self._cmd("tar -jxvf android-toolchain-eabi-*")

        if name == "linaro-gnu-toolchain":
            if self._cmd("find -type d -name toolchain")[1] != "./toolchain":
                # Don't bother re-downloading the toolchain
                self._cmd("wget -nc -nv --no-check-certificate "
                          "https://launchpad.net/linaro-toolchain-binaries/trunk/"
                          "2012.10/+download/gcc-linaro-arm-linux-gnueabihf-"
                          "4.7-2012.10-20121022_linux.tar.bz2")
                self.mkdir("toolchain")
                self._cmd("tar -C \"toolchain\"  --strip-components 1 -xf "
                          "gcc-linaro-arm-linux-gnueabihf-*")

    def build(self,
              target="make",
              build_command="make",
              jobs=1,
              cached=False,
              source_uid=None,
              env=""):
        """Call a build command, such as "make"

        build_command: Command to run
        target: target to pass to build command
        jobs: How many CPUs wide to run the make job
        cached: If we have already run this build on this source, use
                cached result if available.
        source_uid: UID of source checkout (for example VCS URL+rev)
        env: environment string for command (?? do we need this or just make it part of build command??)
        """
        logging.info("build:\n %s\n %s\n jobs=%s\n cached=%s\n source_uuid=%s\n env=%s" %
                       (build_command,
                        target,
                        jobs,
                        cached,
                        source_uid,
                        env))

        self._cmd(build_command)

    def in_directory(self, directory):
        self._cmd("mkdir -p " + directory)
        self._cmd("cd " + directory)

    def mkdir(self, directory):
        self._cmd("mkdir -p " + directory)

    def chdir(self, directory):
        self._cmd("cd " + directory)

    def copy(self, source, dest):
        self._cmd("cp %s %s" % (source, dest))

    def set_disk_image(self, image):
        pass

    def append_to_file(self, string, file_name):
        self._cmd('echo "%s" >> %s' % (string, file_name))

    def cwd(self):
        return self._cmd("pwd")[1:-1][0]

    def set_env(self, name, value):
        self._cmd("export %s='%s'" % (name, value))

    def publish(self, base_dir, glob_list, destination, license):
        pass

class x86_64(CISlave):
    """Used to run commands on an x86_64 machine"""

    def __init__(self, tags=""):
        self.get_machine()
        self.tags = tags
        # Have a few pre-defined classes

    def cmd(self, command, comment):
        return super(x86_64, self).cmd(command, comment)

    def get_machine(self):
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.ssh.connect("homework1")
        self.shell = self.ssh.invoke_shell()

        # XXX Here follows a bit of a hack!
        # Need to wait for a prompt so we can settle into a run command/wait
        # for prompt routine. Without this, the first command and its following
        # return code check can get confused. A simple "ls" seems to solve it
        # but really just waiting for the first prompt should be enough.
        self._in_shell_cmd("ls")  # Waits for command prompt

class Snowball(CISlave):
    """Used to run commands on a snowball"""
    pass

class Origen(CISlave):
    """Used to run commands on a origen"""
    pass

class Panda(CISlave):
    """Used to run commands on a panda"""
    pass

class LinaroCIJob(object):
    """All you need to run commands on a slave"""
    def run(self, config):
        pass

    def cmd(self):
        pass

    def checkout(self, vcs, branch, ref_info=None):
        # Specialised checkout that will get revision info and other uniquely
        # identifying information so we can track the binaries that this
        # creates.
        pass

    def use(self, job_name, tags=None):
        """Used to provide commands that can be found in the artifacts of other
        builds, for example, use a built GCC."""
        pass

    def post_process(self):
        """Do stuff with logs to produce data that web UI can display and
        CLI can download"""

    def exit_status(self):
        if everything_went_OK:
            return "pass"
        else:
            return "fail"

    def set_triggers(self, triggers):
        self.triggers = triggers