source: old/trunk/vtest/vm.py @ 2644

Revision 2644, 9.9 KB checked in by kcr, 5 months ago (diff)

up memory usage

Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3# Copyright © 2009 by Karl Ramm
4#
5# All rights reserved.
6#
7# Permission to use, copy, modify, and distribute this software and
8# its documentation for any purpose and without fee is hereby granted,
9# provided that the above copyright notice appear in all copies and
10# that both that copyright notice and this permission notice appear in
11# supporting documentation, and that the name of Karl Ramm not be used
12# in advertising or publicity pertaining to distribution of the
13# software without specific, written prior permission.
14#
15# KARL RAMM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
17# NO EVENT SHALL KARL RAMM BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
19# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
20# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
21# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22
23import os
24import shlex
25from os import execvp, _exit, dup2, fork, waitpid, close, chdir, \
26    environ, WNOHANG, getgroups, kill
27
28from time import sleep, time
29from sys import exit
30from pty import openpty
31from itertools import count
32from threading import Thread
33from subprocess import Popen, PIPE
34from logging import getLogger, basicConfig, DEBUG
35from tempfile import NamedTemporaryFile
36from grp import getgrnam
37from signal import SIGTERM
38
39environ['PAGER'] = 'cat'
40
41basicConfig(level=DEBUG,
42            format='%(asctime)s %(name)s.%(funcName)s:%(lineno)d %(message)s')
43log = getLogger('vm')
44
45runseq = count().next
46def runcmd(cmd):
47    this = runseq()
48    if hasattr(cmd, 'split'):
49        cmd = cmd.split()
50    log.info('(%d) starting %s', this, cmd)
51    p = Popen(cmd, stdout=PIPE, stderr=PIPE, close_fds=True)
52    stdout, stderr = p.communicate()
53    retcode = p.wait()
54    log.info('(%d) returned %d', this, retcode)
55    if stdout and stdout.strip():
56        log.info('(%d) stdout = [%s]', this, stdout.strip())
57    if stderr and stderr.strip():
58        log.info('(%d) stderr = [%s]', this, stderr.strip())
59    return retcode, stdout, stderr
60
61vmcmd = 'qemu'
62# probe for kvm_
63retval, stdout, stderr = runcmd(['sh','-c','lsmod | grep kvm_'])
64if retval == 0:
65    try:
66        gid = getgrnam('kvm').gr_gid
67        if gid in getgroups():
68            vmcmd='kvm'
69    except:
70        pass
71
72class testmachine(object):
73    testmachines = {}
74    initparams = [
75        ('basedir', os.getcwd()),
76        ('domain', 'example.com'),
77        ('hostname', 'test-%(index)d.%(domain)s'),
78        ('base_image', '%(basedir)s/master.img'),
79        ('image', '%(basedir)s/%(hostname)s.img'),
80        ('mkimage', 'qemu-img create -b %(base_image)s -f qcow2 %(image)s'),
81        ('qemu_binary', vmcmd),
82        ('qemuopts', '-nographic'),
83        ('memory', 256),
84        ('netargs', '-net nic,vlan=1,macaddr=02:00:00:00:00:%(index)02d -net vde,vlan=1,sock=/var/run/vde2/tap0.ctl'),
85        ('diskargs', '-hda %(image)s'),
86        ('qemu', '%(qemu_binary)s -m %(memory)s %(netargs)s %(diskargs)s %(qemuopts)s'),
87        ('logdir', '%(basedir)s'),
88        ('logfile', '%(logdir)s/%(hostname)s.log'),
89        ]
90    def __init__(self, index=None, **kw):
91        if index is not None:
92            if index in self.testmachines:
93                raise Exception('Test Machine #%d already exists' % index)
94        else:
95            for index in count():
96                if index not in self.testmachines:
97                    break
98        self.testmachines[index] = self
99
100        self.params = {'index': index}
101        self.params.update(kw)
102        for (name, template) in self.initparams:
103            if name not in self.params:
104                if isinstance(template, basestring): # we can't substitute, say, ints
105                    self.params[name] = template % self.params
106                else:
107                    self.params[name] = template
108
109        self.dead = False
110
111        status, out, err = runcmd(self.params['mkimage'])
112        if status != 0:
113            raise Exception('Error creating image')
114
115        self.qemuproc = loggedproc(self.params['qemu'], self.params['logfile'])
116        self.boottime = time()
117
118    def wait(self):
119        if self.dead:
120            raise Exception('dead testmachine')
121        code = self.qemuproc.wait()
122        self.dead = True
123        log.debug('vm returned %d', code)
124        del testmachine.testmachines[self.params['index']]
125        return code
126
127    def alive(self):
128        if self.dead:
129            return False
130        alive = self.qemuproc.alive()
131        if not alive:
132            self.dead = True
133        return alive
134
135    def shoot(self):
136        self.qemuproc.murder(SIGTERM)
137        sleep(1)
138        if self.qemuproc.alive():
139            self.qemuproc.murder(SIGKILL)
140
141class sshtestmachine(testmachine):
142    initparams = testmachine.initparams + [
143        ('sshsock', '%(basedir)s/ssh.%%h.%%r.%%p'),
144        ('sshtarget', 'root@%(hostname)s'),
145        ('sshmaster', 'ssh -NMS %(sshsock)s -o stricthostkeychecking=no -o userknownhostsfile=./known-hosts -i ./ssh_key %(sshtarget)s'),
146        ('sshopt', '-o controlpath=%(sshsock)s -o stricthostkeychecking=no -o userknownhostsfile=./known-hosts -i ./ssh_key'),
147        ('ssh', 'ssh %(sshopt)s %(sshtarget)s'),
148        ('scp', 'scp %(sshopt)s'),
149        ]
150
151    def __init__(self, index=None, **kw):
152        super(sshtestmachine, self).__init__(index, **kw)
153
154        self.sshstate = 'go'
155        self.sshrunning = None
156        self.sshstart = None
157        if self.params['sshmaster']:
158            self.sshthread = Thread(target=self.sshmaster)
159            self.sshthread.start()
160
161    def sshmaster(self):
162        log.info('starting sshmaster thread')
163        wait = 20
164        log.debug('waiting for %d seconds', wait)
165        sleep(wait)
166        while self.sshstate == 'go':
167            self.sshstart = time()
168            self.sshrunning = True
169            runcmd(self.params['sshmaster'])
170            self.sshrunning = False
171            log.debug('sshmaster duration = %f, relative start = %f',
172                      time() - self.sshstart,
173                      self.sshstart - self.boottime)
174            log.debug('pausing for a second')
175            sleep(1)
176
177    def run(self, cmd):
178        log.debug('%s: running %s', self.params['hostname'], cmd)
179        return runcmd(self.params['ssh'] + ' ' + cmd)
180
181    def _scp(self, source, dest):
182        retval, stdout, stderr = runcmd(' '.join([self.params['scp'],
183                                        source, dest]))
184        if retval != 0:
185            raise Exception('scp failed', retval, stdout, stderr)
186
187    def putfile(self, local, remote):
188        log.debug('%s: copying local %s to remote %s', self.params['hostname'], local, remote)
189        self._scp(local, '%s:%s' % (self.params['sshtarget'], remote))
190
191    def getfile(self, remote, local):
192        log.debug('%s: copying remote %s to local %s', self.params['hostname'], remote, local)
193        self._scp('%s:%s' % (self.params['sshtarget'], remote), local)
194
195    def putstr(self, remote, string):
196        log.debug("%s: putting [%s] in remote %s", self.params['hostname'], string, remote)
197        fp = NamedTemporaryFile()
198        if string[-1] != '\n':
199            string += '\n'
200        fp.write(string)
201        fp.flush()
202        return self.putfile(fp.name, remote)
203
204    def doscript(self, script):
205        if hasattr(script, 'splitlines'):
206            script = script.splitlines()
207        for cmd in script:
208            cmd = cmd.lstrip()
209            if cmd and cmd[0] != '#':
210                yield (cmd,) + self.run(cmd)
211
212    def script(self, script, bail=True):
213        results=[]
214        for cmd, retval, stdout, stderr in self.doscript(script):
215            if retval:
216                raise(Exception(self.params['hostname'], cmd, retval, stdout, stderr))
217            results.append((cmd, retval, stdout, stderr))
218        return results #I'm not sure we should even bother
219
220    def shutdown(self):
221        log.debug('shutting down machine')
222        self.sshstate = 'stop'
223        self.run('poweroff')
224
225    def shoot(self):
226        self.sshstate='stop'
227        super(sshtestmachine, self).shoot()
228
229    def ready(self):
230        t = time()
231        log.debug('sshrunning = %s sshstart = %s t = %f',
232                  self.sshrunning, self.sshstart, t)
233        return self.sshrunning and self.sshstart < (t - 15.0)
234
235class proc(object):
236    def __init__(self, cmd):
237        if hasattr(cmd, 'split'):
238            self.cmdlist = shlex.split(cmd)
239        else:
240            self.cmdlist = cmd
241        self.status = None
242        self.start()
243    def alive(self):
244        if self.status is not None:
245            return False
246        pid, status  = waitpid(self.pid, WNOHANG)
247        if pid:
248            log.debug('%d returned %d in check', pid, status)
249            self.status = status
250            return False
251        return True
252    def start(self):
253        log.debug('starting %s', self.cmdlist)
254        self.pid = fork()
255        if self.pid == 0:
256            self._launch()
257    def _launch(self):
258        execvp(self.cmdlist[0], self.cmdlist)
259        _exit(99)
260    def wait(self):
261        if self.status is None:
262            pid, self.status = waitpid(self.pid, 0)
263        return self.status
264    def murder(self, sig=SIGTERM):
265        kill(self.pid, sig)
266
267class loggedproc(proc):
268    def __init__(self, cmdlist, logfile):
269        self.logfile = logfile
270        self.master = None
271        self.slave = None
272        super(loggedproc, self).__init__(cmdlist)
273    def start(self):
274        self.master, self.slave = openpty()
275        self.logfp = open(self.logfile, 'w')
276        super(loggedproc, self).start()
277        close(self.slave)
278        self.logfp.close()
279    def _launch(self):
280        close(self.master)
281        dup2(self.slave, 0) # stdin
282        dup2(self.logfp.fileno(), 1) # stdout
283        dup2(self.logfp.fileno(), 2) # stderr
284        self.logfp.close()
285        close(self.slave)
286        super(loggedproc, self)._launch()
287
288def main():
289    pass
290
291if __name__ == '__main__':
292    main()
Note: See TracBrowser for help on using the repository browser.