source: trunk/web/controls.py @ 247

Last change on this file since 247 was 247, checked in by ecprice, 16 years ago

Makes deleting VMs work with the access controls.

File size: 9.6 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_size, 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_size  * 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_id=machine.machine_id, 
114                    guest_device_name='hda', size=disk_size)
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        for access in machine.users:
205            ctx.current.delete(access)
206        ctx.current.delete(machine)
207        transaction.commit()
208    except:
209        transaction.rollback()
210        raise
211    for mname, dname in delete_disk_pairs:
212        remctl('web', 'lvremove', mname, dname)
213    unregisterMachine(machine)
214
215def commandResult(user, fields):
216    start_time = 0
217    print >> sys.stderr, time.time()-start_time
218    machine = validation.testMachineId(user, fields.getfirst('machine_id'))
219    action = fields.getfirst('action')
220    cdrom = fields.getfirst('cdrom')
221    print >> sys.stderr, time.time()-start_time
222    if cdrom is not None and not CDROM.get(cdrom):
223        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
224    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 
225                      'Delete VM'):
226        raise CodeError("Invalid action '%s'" % action)
227    if action == 'Reboot':
228        if cdrom is not None:
229            out, err = remctl('control', machine.name, 'reboot', cdrom,
230                              err=True)
231        else:
232            out, err = remctl('control', machine.name, 'reboot',
233                              err=True)
234        if err:
235            if re.match("Error: Domain '.*' does not exist.", err):
236                raise InvalidInput("action", "reboot", 
237                                   "Machine is not on")
238            else:
239                print >> sys.stderr, 'Error on reboot:'
240                print >> sys.stderr, err
241                raise CodeError('ERROR on remctl')
242               
243    elif action == 'Power on':
244        if validation.maxMemory(user, machine) < machine.memory:
245            raise InvalidInput('action', 'Power on',
246                               "You don't have enough free RAM quota "
247                               "to turn on this machine.")
248        bootMachine(machine, cdrom)
249    elif action == 'Power off':
250        out, err = remctl('control', machine.name, 'destroy', err=True)
251        if err:
252            if re.match("Error: Domain '.*' does not exist.", err):
253                raise InvalidInput("action", "Power off", 
254                                   "Machine is not on.")
255            else:
256                print >> sys.stderr, 'Error on power off:'
257                print >> sys.stderr, err
258                raise CodeError('ERROR on remctl')
259    elif action == 'Shutdown':
260        out, err = remctl('control', machine.name, 'shutdown', err=True)
261        if err:
262            if re.match("Error: Domain '.*' does not exist.", err):
263                raise InvalidInput("action", "Shutdown", 
264                                   "Machine is not on.")
265            else:
266                print >> sys.stderr, 'Error on Shutdown:'
267                print >> sys.stderr, err
268                raise CodeError('ERROR on remctl')
269    elif action == 'Delete VM':
270        deleteVM(machine)
271    print >> sys.stderr, time.time()-start_time
272
273    d = dict(user=user,
274             command=action,
275             machine=machine)
276    return d
277
278def resizeDisk(machine_name, disk_name, new_size):
279    remctl("web", "lvresize", machine_name, disk_name, new_size)
280
281def renameMachine(machine, old_name, new_name):
282    for disk in machine.disks:
283        remctl("web", "lvrename", old_name, 
284               disk.guest_device_name, new_name)
285    remctl("web", "moveregister", old_name, new_name)
286   
Note: See TracBrowser for help on using the repository browser.