source: trunk/web/controls.py @ 262

Last change on this file since 262 was 261, checked in by ecprice, 17 years ago

Use cached ACLs

File size: 10.0 KB
RevLine 
[209]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
[216]26def kinit(username = 'daemon/sipb-xen.mit.edu', keytab = '/etc/sipb-xen.keytab'):
[209]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:
[261]77        out, err = remctl('control', machine.name, 'create', 
78                          cdtype, err=True)
[209]79    else:
[261]80        out, err = remctl('control', machine.name, 'create',
81                          err=True)
82    if 'already exists' in out:
83        raise InvalidInput('action', 'create',
84                           'VM %s is already on' % machine.name)
85    elif err:
86        raise CodeError('"%s" on "control %s create %s' 
87                        % (err, machine.name, cdtype))
88    else:
89        raise CodeError('"%s" on "control %s create %s' 
90                        % (err, machine.name, cdtype))
[209]91
92def registerMachine(machine):
93    """Register a machine to be controlled by the web interface"""
94    remctl('web', 'register', machine.name)
95
96def unregisterMachine(machine):
97    """Unregister a machine to not be controlled by the web interface"""
98    remctl('web', 'unregister', machine.name)
99
[243]100def createVm(owner, contact, name, memory, disk_size, is_hvm, cdrom):
[209]101    """Create a VM and put it in the database"""
102    # put stuff in the table
103    transaction = ctx.current.create_transaction()
104    try:
[228]105        validation.validMemory(owner, memory)
[243]106        validation.validDisk(owner, disk_size  * 1. / 1024)
[228]107        validation.validAddVm(owner)
[209]108        res = meta.engine.execute('select nextval('
109                                  '\'"machines_machine_id_seq"\')')
110        id = res.fetchone()[0]
111        machine = Machine()
112        machine.machine_id = id
113        machine.name = name
114        machine.memory = memory
[228]115        machine.owner = owner
116        machine.administrator = owner
117        machine.contact = contact
[209]118        machine.uuid = uuidToString(randomUUID())
119        machine.boot_off_cd = True
120        machine_type = Type.get_by(hvm=is_hvm)
121        machine.type_id = machine_type.type_id
122        ctx.current.save(machine)
[243]123        disk = Disk(machine_id=machine.machine_id, 
124                    guest_device_name='hda', size=disk_size)
[209]125        open_nics = NIC.select_by(machine_id=None)
126        if not open_nics: #No IPs left!
127            raise CodeError("No IP addresses left!  "
128                            "Contact sipb-xen-dev@mit.edu")
129        nic = open_nics[0]
130        nic.machine_id = machine.machine_id
131        nic.hostname = name
132        ctx.current.save(nic)   
133        ctx.current.save(disk)
134        transaction.commit()
135    except:
136        transaction.rollback()
137        raise
138    registerMachine(machine)
139    makeDisks(machine)
140    # tell it to boot with cdrom
141    bootMachine(machine, cdrom)
142    return machine
143
144def getUptimes(machines=None):
145    """Return a dictionary mapping machine names to uptime strings"""
146    value_string = remctl('web', 'listvms')
147    lines = value_string.splitlines()
148    d = {}
149    for line in lines:
150        lst = line.split()
151        name, id = lst[:2]
152        uptime = ' '.join(lst[2:])
153        d[name] = uptime
154    ans = {}
155    for m in machines:
156        ans[m] = d.get(m.name)
157    return ans
158
159def parseStatus(s):
160    """Parse a status string into nested tuples of strings.
161
162    s = output of xm list --long <machine_name>
163    """
164    values = re.split('([()])', s)
165    stack = [[]]
166    for v in values[2:-2]: #remove initial and final '()'
167        if not v:
168            continue
169        v = v.strip()
170        if v == '(':
171            stack.append([])
172        elif v == ')':
173            if len(stack[-1]) == 1:
174                stack[-1].append('')
175            stack[-2].append(stack[-1])
176            stack.pop()
177        else:
178            if not v:
179                continue
180            stack[-1].extend(v.split())
181    return stack[-1]
182
183def statusInfo(machine):
184    """Return the status list for a given machine.
185
186    Gets and parses xm list --long
187    """
188    value_string, err_string = remctl('control', machine.name, 'list-long', 
189                                      err=True)
190    if 'Unknown command' in err_string:
191        raise CodeError("ERROR in remctl list-long %s is not registered" % 
192                        (machine.name,))
193    elif 'does not exist' in err_string:
194        return None
195    elif err_string:
196        raise CodeError("ERROR in remctl list-long %s%s" % 
197                        (machine.name, err_string))
198    status = parseStatus(value_string)
199    return status
200
201def deleteVM(machine):
202    """Delete a VM."""
203    remctl('control', machine.name, 'destroy', err=True)
204    transaction = ctx.current.create_transaction()
205    delete_disk_pairs = [(machine.name, d.guest_device_name) 
206                         for d in machine.disks]
207    try:
208        for nic in machine.nics:
209            nic.machine_id = None
210            nic.hostname = None
211            ctx.current.save(nic)
212        for disk in machine.disks:
213            ctx.current.delete(disk)
[256]214        for access in machine.acl:
[247]215            ctx.current.delete(access)
[209]216        ctx.current.delete(machine)
217        transaction.commit()
218    except:
219        transaction.rollback()
220        raise
221    for mname, dname in delete_disk_pairs:
222        remctl('web', 'lvremove', mname, dname)
223    unregisterMachine(machine)
224
225def commandResult(user, fields):
226    start_time = 0
227    machine = validation.testMachineId(user, fields.getfirst('machine_id'))
228    action = fields.getfirst('action')
229    cdrom = fields.getfirst('cdrom')
230    if cdrom is not None and not CDROM.get(cdrom):
231        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
232    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 
233                      'Delete VM'):
234        raise CodeError("Invalid action '%s'" % action)
235    if action == 'Reboot':
236        if cdrom is not None:
237            out, err = remctl('control', machine.name, 'reboot', cdrom,
238                              err=True)
239        else:
240            out, err = remctl('control', machine.name, 'reboot',
241                              err=True)
242        if err:
243            if re.match("Error: Domain '.*' does not exist.", err):
244                raise InvalidInput("action", "reboot", 
245                                   "Machine is not on")
246            else:
247                print >> sys.stderr, 'Error on reboot:'
248                print >> sys.stderr, err
249                raise CodeError('ERROR on remctl')
250               
251    elif action == 'Power on':
252        if validation.maxMemory(user, machine) < machine.memory:
253            raise InvalidInput('action', 'Power on',
254                               "You don't have enough free RAM quota "
255                               "to turn on this machine.")
256        bootMachine(machine, cdrom)
257    elif action == 'Power off':
258        out, err = remctl('control', machine.name, 'destroy', err=True)
259        if err:
260            if re.match("Error: Domain '.*' does not exist.", err):
261                raise InvalidInput("action", "Power off", 
262                                   "Machine is not on.")
263            else:
264                print >> sys.stderr, 'Error on power off:'
265                print >> sys.stderr, err
266                raise CodeError('ERROR on remctl')
267    elif action == 'Shutdown':
268        out, err = remctl('control', machine.name, 'shutdown', err=True)
269        if err:
270            if re.match("Error: Domain '.*' does not exist.", err):
271                raise InvalidInput("action", "Shutdown", 
272                                   "Machine is not on.")
273            else:
274                print >> sys.stderr, 'Error on Shutdown:'
275                print >> sys.stderr, err
276                raise CodeError('ERROR on remctl')
277    elif action == 'Delete VM':
278        deleteVM(machine)
279
280    d = dict(user=user,
281             command=action,
282             machine=machine)
283    return d
284
285def resizeDisk(machine_name, disk_name, new_size):
286    remctl("web", "lvresize", machine_name, disk_name, new_size)
287
288def renameMachine(machine, old_name, new_name):
289    for disk in machine.disks:
290        remctl("web", "lvrename", old_name, 
291               disk.guest_device_name, new_name)
292    remctl("web", "moveregister", old_name, new_name)
293   
Note: See TracBrowser for help on using the repository browser.