source: trunk/web/controls.py @ 241

Last change on this file since 241 was 236, checked in by ecprice, 17 years ago

Move out of the templates directory.

File size: 9.5 KB
Line 
1"""
2Functions to perform remctls.
3"""
4
5from sipb_xen_database import Machine, Disk, Type, NIC, CDROM, ctx, meta
6import validation
7from webcommon import CodeError, InvalidInput
8import random
9import subprocess
10import sys
11import time
12import re
13
14# ... and stolen from xend/uuid.py
15def randomUUID():
16    """Generate a random UUID."""
17
18    return [ random.randint(0, 255) for _ in range(0, 16) ]
19
20def uuidToString(u):
21    """Turn a numeric UUID to a hyphen-seperated one."""
22    return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
23                     "%02x" * 6]) % tuple(u)
24# end stolen code
25
26def kinit(username = 'daemon/sipb-xen.mit.edu', keytab = '/etc/sipb-xen.keytab'):
27    """Kinit with a given username and keytab"""
28
29    p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
30                         stderr=subprocess.PIPE)
31    e = p.wait()
32    if e:
33        raise CodeError("Error %s in kinit: %s" % (e, p.stderr.read()))
34
35def checkKinit():
36    """If we lack tickets, kinit."""
37    p = subprocess.Popen(['klist', '-s'])
38    if p.wait():
39        kinit()
40
41def remctl(*args, **kws):
42    """Perform a remctl and return the output.
43
44    kinits if necessary, and outputs errors to stderr.
45    """
46    checkKinit()
47    p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
48                         + list(args),
49                         stdout=subprocess.PIPE,
50                         stderr=subprocess.PIPE)
51    v = p.wait()
52    if kws.get('err'):
53        return p.stdout.read(), p.stderr.read()
54    if v:
55        print >> sys.stderr, 'Error', v, 'on remctl', args, ':'
56        print >> sys.stderr, p.stderr.read()
57        raise CodeError('ERROR on remctl')
58    return p.stdout.read()
59
60def lvcreate(machine, disk):
61    """Create a single disk for a machine"""
62    remctl('web', 'lvcreate', machine.name,
63           disk.guest_device_name, str(disk.size))
64   
65def makeDisks(machine):
66    """Update the lvm partitions to add a disk."""
67    for disk in machine.disks:
68        lvcreate(machine, disk)
69
70def bootMachine(machine, cdtype):
71    """Boot a machine with a given boot CD.
72
73    If cdtype is None, give no boot cd.  Otherwise, it is the string
74    id of the CD (e.g. 'gutsy_i386')
75    """
76    if cdtype is not None:
77        remctl('control', machine.name, 'create', 
78               cdtype)
79    else:
80        remctl('control', machine.name, 'create')
81
82def registerMachine(machine):
83    """Register a machine to be controlled by the web interface"""
84    remctl('web', 'register', machine.name)
85
86def unregisterMachine(machine):
87    """Unregister a machine to not be controlled by the web interface"""
88    remctl('web', 'unregister', machine.name)
89
90def createVm(owner, contact, name, memory, disk, is_hvm, cdrom):
91    """Create a VM and put it in the database"""
92    # put stuff in the table
93    transaction = ctx.current.create_transaction()
94    try:
95        validation.validMemory(owner, memory)
96        validation.validDisk(owner, disk  * 1. / 1024)
97        validation.validAddVm(owner)
98        res = meta.engine.execute('select nextval('
99                                  '\'"machines_machine_id_seq"\')')
100        id = res.fetchone()[0]
101        machine = Machine()
102        machine.machine_id = id
103        machine.name = name
104        machine.memory = memory
105        machine.owner = owner
106        machine.administrator = owner
107        machine.contact = contact
108        machine.uuid = uuidToString(randomUUID())
109        machine.boot_off_cd = True
110        machine_type = Type.get_by(hvm=is_hvm)
111        machine.type_id = machine_type.type_id
112        ctx.current.save(machine)
113        disk = Disk(machine.machine_id, 
114                    'hda', disk)
115        open_nics = NIC.select_by(machine_id=None)
116        if not open_nics: #No IPs left!
117            raise CodeError("No IP addresses left!  "
118                            "Contact sipb-xen-dev@mit.edu")
119        nic = open_nics[0]
120        nic.machine_id = machine.machine_id
121        nic.hostname = name
122        ctx.current.save(nic)   
123        ctx.current.save(disk)
124        transaction.commit()
125    except:
126        transaction.rollback()
127        raise
128    registerMachine(machine)
129    makeDisks(machine)
130    # tell it to boot with cdrom
131    bootMachine(machine, cdrom)
132    return machine
133
134def getUptimes(machines=None):
135    """Return a dictionary mapping machine names to uptime strings"""
136    value_string = remctl('web', 'listvms')
137    lines = value_string.splitlines()
138    d = {}
139    for line in lines:
140        lst = line.split()
141        name, id = lst[:2]
142        uptime = ' '.join(lst[2:])
143        d[name] = uptime
144    ans = {}
145    for m in machines:
146        ans[m] = d.get(m.name)
147    return ans
148
149def parseStatus(s):
150    """Parse a status string into nested tuples of strings.
151
152    s = output of xm list --long <machine_name>
153    """
154    values = re.split('([()])', s)
155    stack = [[]]
156    for v in values[2:-2]: #remove initial and final '()'
157        if not v:
158            continue
159        v = v.strip()
160        if v == '(':
161            stack.append([])
162        elif v == ')':
163            if len(stack[-1]) == 1:
164                stack[-1].append('')
165            stack[-2].append(stack[-1])
166            stack.pop()
167        else:
168            if not v:
169                continue
170            stack[-1].extend(v.split())
171    return stack[-1]
172
173def statusInfo(machine):
174    """Return the status list for a given machine.
175
176    Gets and parses xm list --long
177    """
178    value_string, err_string = remctl('control', machine.name, 'list-long', 
179                                      err=True)
180    if 'Unknown command' in err_string:
181        raise CodeError("ERROR in remctl list-long %s is not registered" % 
182                        (machine.name,))
183    elif 'does not exist' in err_string:
184        return None
185    elif err_string:
186        raise CodeError("ERROR in remctl list-long %s%s" % 
187                        (machine.name, err_string))
188    status = parseStatus(value_string)
189    return status
190
191def deleteVM(machine):
192    """Delete a VM."""
193    remctl('control', machine.name, 'destroy', err=True)
194    transaction = ctx.current.create_transaction()
195    delete_disk_pairs = [(machine.name, d.guest_device_name) 
196                         for d in machine.disks]
197    try:
198        for nic in machine.nics:
199            nic.machine_id = None
200            nic.hostname = None
201            ctx.current.save(nic)
202        for disk in machine.disks:
203            ctx.current.delete(disk)
204        ctx.current.delete(machine)
205        transaction.commit()
206    except:
207        transaction.rollback()
208        raise
209    for mname, dname in delete_disk_pairs:
210        remctl('web', 'lvremove', mname, dname)
211    unregisterMachine(machine)
212
213def commandResult(user, fields):
214    start_time = 0
215    print >> sys.stderr, time.time()-start_time
216    machine = validation.testMachineId(user, fields.getfirst('machine_id'))
217    action = fields.getfirst('action')
218    cdrom = fields.getfirst('cdrom')
219    print >> sys.stderr, time.time()-start_time
220    if cdrom is not None and not CDROM.get(cdrom):
221        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
222    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 
223                      'Delete VM'):
224        raise CodeError("Invalid action '%s'" % action)
225    if action == 'Reboot':
226        if cdrom is not None:
227            out, err = remctl('control', machine.name, 'reboot', cdrom,
228                              err=True)
229        else:
230            out, err = remctl('control', machine.name, 'reboot',
231                              err=True)
232        if err:
233            if re.match("Error: Domain '.*' does not exist.", err):
234                raise InvalidInput("action", "reboot", 
235                                   "Machine is not on")
236            else:
237                print >> sys.stderr, 'Error on reboot:'
238                print >> sys.stderr, err
239                raise CodeError('ERROR on remctl')
240               
241    elif action == 'Power on':
242        if validation.maxMemory(user, machine) < machine.memory:
243            raise InvalidInput('action', 'Power on',
244                               "You don't have enough free RAM quota "
245                               "to turn on this machine.")
246        bootMachine(machine, cdrom)
247    elif action == 'Power off':
248        out, err = remctl('control', machine.name, 'destroy', err=True)
249        if err:
250            if re.match("Error: Domain '.*' does not exist.", err):
251                raise InvalidInput("action", "Power off", 
252                                   "Machine is not on.")
253            else:
254                print >> sys.stderr, 'Error on power off:'
255                print >> sys.stderr, err
256                raise CodeError('ERROR on remctl')
257    elif action == 'Shutdown':
258        out, err = remctl('control', machine.name, 'shutdown', err=True)
259        if err:
260            if re.match("Error: Domain '.*' does not exist.", err):
261                raise InvalidInput("action", "Shutdown", 
262                                   "Machine is not on.")
263            else:
264                print >> sys.stderr, 'Error on Shutdown:'
265                print >> sys.stderr, err
266                raise CodeError('ERROR on remctl')
267    elif action == 'Delete VM':
268        deleteVM(machine)
269    print >> sys.stderr, time.time()-start_time
270
271    d = dict(user=user,
272             command=action,
273             machine=machine)
274    return d
275
276def resizeDisk(machine_name, disk_name, new_size):
277    remctl("web", "lvresize", machine_name, disk_name, new_size)
278
279def renameMachine(machine, old_name, new_name):
280    for disk in machine.disks:
281        remctl("web", "lvrename", old_name, 
282               disk.guest_device_name, new_name)
283    remctl("web", "moveregister", old_name, new_name)
284   
Note: See TracBrowser for help on using the repository browser.