source: trunk/packages/sipb-xen-www/code/controls.py @ 1167

Last change on this file since 1167 was 1140, checked in by broder, 16 years ago

Don't try to boot up the machine if we're running the autoinstaller

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