source: trunk/packages/sipb-xen-www/code/validation.py @ 576

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

Fix a couple more bugs

File size: 9.8 KB
Line 
1#!/usr/bin/python
2
3import cache_acls
4import getafsgroups
5import re
6import string
7from sipb_xen_database import Machine, NIC, Type, Disk
8from webcommon import InvalidInput
9
10MAX_MEMORY_TOTAL = 512
11MAX_MEMORY_SINGLE = 256
12MIN_MEMORY_SINGLE = 16
13MAX_DISK_TOTAL = 50
14MAX_DISK_SINGLE = 50
15MIN_DISK_SINGLE = 0.1
16MAX_VMS_TOTAL = 10
17MAX_VMS_ACTIVE = 4
18
19class Validate:
20    def __init__(self, username, state, machine_id=None, name=None, owner=None,
21                 admin=None, contact=None, memory=None, disksize=None,
22                 vmtype=None, cdrom=None, clone_from=None):
23        # XXX Successive quota checks aren't a good idea, since you
24        # can't necessarily change the locker and disk size at the
25        # same time.
26        created_new = (machine_id is None)
27
28        if machine_id is not None:
29            self.machine = testMachineId(username, machine_id)
30        machine = getattr(self, 'machine', None)
31
32        owner = testOwner(username, owner, machine)
33        if owner is not None:
34            self.owner = owner
35        admin = testAdmin(username, admin, machine)
36        if admin is not None:
37            self.admin = admin
38        contact = testContact(username, contact, machine)
39        if contact is not None:
40            self.contact = contact
41        name = testName(username, name, machine)
42        if name is not None:
43            self.name = name
44        if memory is not None:
45            self.memory = validMemory(self.owner, state, memory, machine,
46                                      on=not created_new)
47        if disksize is not None:
48            self.disksize = validDisk(self.owner, disksize, machine)
49        if vmtype is not None:
50            self.vmtype = validVmType(vmtype)
51        if cdrom is not None:
52            if not CDROM.get(cdrom):
53                raise CodeError("Invalid cdrom type '%s'" % cdrom)
54            self.cdrom = cdrom
55        if clone_from is not None:
56            if clone_from not in ('ice3', ):
57                raise CodeError("Invalid clone image '%s'" % clone_from)
58            self.clone_from = clone_from
59
60
61def getMachinesByOwner(owner, machine=None):
62    """Return the machines owned by the same as a machine.
63
64    If the machine is None, return the machines owned by the same
65    user.
66    """
67    if machine:
68        owner = machine.owner
69    return Machine.select_by(owner=owner)
70
71def maxMemory(owner, g, machine=None, on=True):
72    """Return the maximum memory for a machine or a user.
73
74    If machine is None, return the memory available for a new
75    machine.  Else, return the maximum that machine can have.
76
77    on is whether the machine should be turned on.  If false, the max
78    memory for the machine to change to, if it is left off, is
79    returned.
80    """
81    if machine is not None and machine.memory > MAX_MEMORY_SINGLE:
82        # If they've been blessed, let them have it
83        return machine.memory
84    if not on:
85        return MAX_MEMORY_SINGLE
86    machines = getMachinesByOwner(owner, machine)
87    active_machines = [m for m in machines if m.name in g.xmlist_raw]
88    mem_usage = sum([x.memory for x in active_machines if x != machine])
89    return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
90
91def maxDisk(owner, machine=None):
92    """Return the maximum disk that a machine can reach.
93
94    If machine is None, the maximum disk for a new machine. Otherwise,
95    return the maximum that a given machine can be changed to.
96    """
97    if machine is not None:
98        machine_id = machine.machine_id
99    else:
100        machine_id = None
101    disk_usage = Disk.query().filter_by(Disk.c.machine_id != machine_id,
102                                        owner=owner).sum(Disk.c.size) or 0
103    return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
104
105def cantAddVm(owner, g):
106    machines = getMachinesByOwner(owner)
107    active_machines = [m for m in machines if m.name in g.xmlist_raw]
108    if len(machines) >= MAX_VMS_TOTAL:
109        return 'You have too many VMs to create a new one.'
110    if len(active_machines) >= MAX_VMS_ACTIVE:
111        return ('You already have the maximum number of VMs turned on.  '
112                'To create more, turn one off.')
113    return False
114
115def haveAccess(user, machine):
116    """Return whether a user has administrative access to a machine"""
117    return user in cache_acls.accessList(machine)
118
119def owns(user, machine):
120    """Return whether a user owns a machine"""
121    return user in expandLocker(machine.owner)
122
123def validMachineName(name):
124    """Check that name is valid for a machine name"""
125    if not name:
126        return False
127    charset = string.ascii_letters + string.digits + '-_'
128    if name[0] in '-_' or len(name) > 22:
129        return False
130    for x in name:
131        if x not in charset:
132            return False
133    return True
134
135def validMemory(owner, g, memory, machine=None, on=True):
136    """Parse and validate limits for memory for a given owner and machine.
137
138    on is whether the memory must be valid after the machine is
139    switched on.
140    """
141    try:
142        memory = int(memory)
143        if memory < MIN_MEMORY_SINGLE:
144            raise ValueError
145    except ValueError:
146        raise InvalidInput('memory', memory,
147                           "Minimum %s MiB" % MIN_MEMORY_SINGLE)
148    max_val = maxMemory(owner, g, machine, on)
149    if memory > max_val:
150        raise InvalidInput('memory', memory,
151                           'Maximum %s MiB for %s' % (max_val, owner))
152    return memory
153
154def validDisk(owner, disk, machine=None):
155    """Parse and validate limits for disk for a given owner and machine."""
156    try:
157        disk = float(disk)
158        if disk > maxDisk(owner, machine):
159            raise InvalidInput('disk', disk,
160                               "Maximum %s G" % maxDisk(owner, machine))
161        disk = int(disk * 1024)
162        if disk < MIN_DISK_SINGLE * 1024:
163            raise ValueError
164    except ValueError:
165        raise InvalidInput('disk', disk,
166                           "Minimum %s GiB" % MIN_DISK_SINGLE)
167    return disk
168
169def validVmType(vm_type):
170    if vm_type is None:
171        return None
172    t = Type.get(vm_type)
173    if t is None:
174        raise CodeError("Invalid vm type '%s'"  % vm_type)
175    return t
176
177def testMachineId(user, machine_id, exists=True):
178    """Parse, validate and check authorization for a given user and machine.
179
180    If exists is False, don't check that it exists.
181    """
182    if machine_id is None:
183        raise InvalidInput('machine_id', machine_id,
184                           "Must specify a machine ID.")
185    try:
186        machine_id = int(machine_id)
187    except ValueError:
188        raise InvalidInput('machine_id', machine_id, "Must be an integer.")
189    machine = Machine.get(machine_id)
190    if exists and machine is None:
191        raise InvalidInput('machine_id', machine_id, "Does not exist.")
192    if machine is not None and not haveAccess(user, machine):
193        raise InvalidInput('machine_id', machine_id,
194                           "You do not have access to this machine.")
195    return machine
196
197def testAdmin(user, admin, machine):
198    """Determine whether a user can set the admin of a machine to this value.
199
200    Return the value to set the admin field to (possibly 'system:' +
201    admin).  XXX is modifying this a good idea?
202    """
203    if admin is None:
204        return None
205    if machine is not None and admin == machine.administrator:
206        return None
207    if admin == user:
208        return admin
209    if ':' not in admin:
210        if cache_acls.isUser(admin):
211            return admin
212        admin = 'system:' + admin
213    try:
214        if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
215            return admin
216    except getafsgroups.AfsProcessError, e:
217        errmsg = str(e)
218        if errmsg.startswith("pts: User or group doesn't exist"):
219            errmsg = 'The group "%s" does not exist.' % admin
220        raise InvalidInput('administrator', admin, errmsg)
221    #XXX Should we require that user is in the admin group?
222    return admin
223
224def testOwner(user, owner, machine=None):
225    """Determine whether a user can set the owner of a machine to this value.
226
227    If machine is None, this is the owner of a new machine.
228    """
229    if owner == user:
230        return owner
231    if machine is not None and owner in (machine.owner, None):
232        return machine.owner
233    if owner is None:
234        raise InvalidInput('owner', owner, "Owner must be specified")
235    try:
236        if user not in cache_acls.expandLocker(owner):
237            raise InvalidInput('owner', owner, 'You do not have access to the '
238                               + owner + ' locker')
239    except getafsgroups.AfsProcessError, e:
240        raise InvalidInput('owner', owner, str(e))
241    return owner
242
243def testContact(user, contact, machine=None):
244    if contact is None or (machine is not None and contact == machine.contact):
245        return None
246    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
247        raise InvalidInput('contact', contact, "Not a valid email.")
248    return contact
249
250def testDisk(user, disksize, machine=None):
251    return disksize
252
253def testName(user, name, machine=None):
254    if name is None:
255        return None
256    if machine is not None and name == machine.name:
257        return None
258    if not Machine.select_by(name=name):
259        if not validMachineName(name):
260            raise InvalidInput('name', name, 'You must provide a machine name.  Max 22 chars, alnum plus \'-\' and \'_\'.')
261        return name
262    raise InvalidInput('name', name, "Name is already taken.")
263
264def testHostname(user, hostname, machine):
265    for nic in machine.nics:
266        if hostname == nic.hostname:
267            return hostname
268    # check if doesn't already exist
269    if NIC.select_by(hostname=hostname):
270        raise InvalidInput('hostname', hostname,
271                           "Already exists")
272    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
273        raise InvalidInput('hostname', hostname, "Not a valid hostname; "
274                           "must only use number, letters, and dashes.")
275    return hostname
Note: See TracBrowser for help on using the repository browser.