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

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

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

File size: 9.4 KB
Line 
1import validation
2from invirt.common import CodeError, InvalidInput
3import random
4import sys
5import time
6import re
7import cache_acls
8import yaml
9
10from invirt.config import structs as config
11from invirt.database import Machine, Disk, Type, NIC, CDROM, session, meta
12from invirt.remctl import remctl as gen_remctl
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 remctl(*args, **kwargs):
27    return gen_remctl(config.remote.hostname,
28                      principal='daemon/'+config.web.hostname,
29                      *args, **kwargs)
30
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
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
48    swapsize = getswap(disksize, memsize)
49    imagesize = disksize - swapsize
50    ip = machine.nics[0].ip
51    remctl('control', machine.name, 'install', 
52           'dist=%s' % autoinstall.distribution,
53           'mirror=%s' % autoinstall.mirror,
54           'arch=%s' % autoinstall.arch,
55           'imagesize=%s' % imagesize)
56
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
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    """
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:
77        raise CodeError('"%s" on "control %s create %s' 
78                        % (err, machine.name, cdtype))
79
80def createVm(username, state, owner, contact, name, description, memory, disksize, machine_type, cdrom, autoinstall):
81    """Create a VM and put it in the database"""
82    # put stuff in the table
83    session.begin()
84    try:
85        validation.Validate(username, state, name=name, description=description, owner=owner, memory=memory, disksize=disksize/1024.)
86        machine = Machine()
87        machine.name = name
88        machine.description = description
89        machine.memory = memory
90        machine.owner = owner
91        machine.administrator = None
92        machine.contact = contact
93        machine.uuid = uuidToString(randomUUID())
94        machine.boot_off_cd = True
95        machine.type = machine_type
96        session.save_or_update(machine)
97        disk = Disk(machine=machine,
98                    guest_device_name='hda', size=disksize)
99        nic = NIC.query().filter_by(machine_id=None).filter_by(reusable=True).first()
100        if not nic: #No IPs left!
101            raise CodeError("No IP addresses left!  "
102                            "Contact %s." % config.web.errormail)
103        nic.machine = machine
104        nic.hostname = name
105        session.save_or_update(nic)
106        session.save_or_update(disk)
107        cache_acls.refreshMachine(machine)
108        session.commit()
109    except:
110        session.rollback()
111        raise
112    makeDisks(machine)
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
122    return machine
123
124def getList():
125    """Return a dictionary mapping machine names to dicts."""
126    value_string = remctl('web', 'listvms')
127    value_dict = yaml.load(value_string, yaml.CSafeLoader)
128    return value_dict
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    """
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))
169    status = parseStatus(value_string)
170    return status
171
172def listHost(machine):
173    """Return the host a machine is running on"""
174    out, err = remctl('control', machine.name, 'listhost', err=True)
175    if err:
176        return None
177    return out.strip()
178
179def vnctoken(machine):
180    """Return a time-stamped VNC token"""
181    out, err = remctl('control', machine.name, 'vnctoken', err=True)
182    if err:
183        return None
184    return out.strip()
185
186def deleteVM(machine):
187    """Delete a VM."""
188    remctl('control', machine.name, 'destroy', err=True)
189    session.begin()
190    delete_disk_pairs = [(machine.name, d.guest_device_name) 
191                         for d in machine.disks]
192    try:
193        for mname, dname in delete_disk_pairs:
194            remctl('web', 'lvremove', mname, dname)
195        for nic in machine.nics:
196            nic.machine_id = None
197            nic.hostname = None
198            session.save_or_update(nic)
199        for disk in machine.disks:
200            session.delete(disk)
201        session.delete(machine)
202        session.commit()
203    except:
204        session.rollback()
205        raise
206
207def commandResult(username, state, fields):
208    start_time = 0
209    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
210    action = fields.getfirst('action')
211    cdrom = fields.getfirst('cdrom')
212    if cdrom is not None and not CDROM.query().filter_by(cdrom_id=cdrom).one():
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':
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):
226                raise InvalidInput("action", "reboot", 
227                                   "Machine is not on")
228            else:
229                print >> sys.stderr, 'Error on reboot:'
230                print >> sys.stderr, err
231                raise CodeError('ERROR on remctl')
232               
233    elif action == 'Power on':
234        if validation.maxMemory(username, state, machine) < machine.memory:
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':
240        out, err = remctl('control', machine.name, 'destroy', err=True)
241        if err:
242            if re.match("machine '.*' is not on", err):
243                raise InvalidInput("action", "Power off", 
244                                   "Machine is not on.")
245            else:
246                print >> sys.stderr, 'Error on power off:'
247                print >> sys.stderr, err
248                raise CodeError('ERROR on remctl')
249    elif action == 'Shutdown':
250        out, err = remctl('control', machine.name, 'shutdown', err=True)
251        if err:
252            if re.match("machine '.*' is not on", err):
253                raise InvalidInput("action", "Shutdown", 
254                                   "Machine is not on.")
255            else:
256                print >> sys.stderr, 'Error on Shutdown:'
257                print >> sys.stderr, err
258                raise CodeError('ERROR on remctl')
259    elif action == 'Delete VM':
260        deleteVM(machine)
261
262    d = dict(user=username,
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.