source: trunk/packages/invirt-web/code/controls.py @ 2279

Last change on this file since 2279 was 2211, checked in by quentin, 16 years ago

Add "reusable" column for nics, to avoid reusing addresses that are special in some way.

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