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

Last change on this file since 599 was 584, checked in by broder, 16 years ago

More bugs

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