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

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

Put validation behind more abstraction.

File size: 9.7 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 = [x for x in machines if g.xmlist.get(x)]
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 = [x for x in machines if g.xmlist.get(x)]
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 in (None, machine.administrator):
204        return None
205    if admin == user:
206        return admin
207    if ':' not in admin:
208        if cache_acls.isUser(admin):
209            return admin
210        admin = 'system:' + admin
211    try:
212        if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
213            return admin
214    except getafsgroups.AfsProcessError, e:
215        errmsg = str(e)
216        if errmsg.startswith("pts: User or group doesn't exist"):
217            errmsg = 'The group "%s" does not exist.' % admin
218        raise InvalidInput('administrator', admin, errmsg)
219    #XXX Should we require that user is in the admin group?
220    return admin
221
222def testOwner(user, owner, machine=None):
223    """Determine whether a user can set the owner of a machine to this value.
224
225    If machine is None, this is the owner of a new machine.
226    """
227    if owner == user:
228        return owner
229    if machine is not None and owner in (machine.owner, None):
230        return None
231    if owner is None:
232        raise InvalidInput('owner', owner, "Owner must be specified")
233    try:
234        if user not in cache_acls.expandLocker(owner):
235            raise InvalidInput('owner', owner, 'You do not have access to the '
236                               + owner + ' locker')
237    except getafsgroups.AfsProcessError, e:
238        raise InvalidInput('owner', owner, str(e))
239    return owner
240
241def testContact(user, contact, machine=None):
242    if contact in (None, machine.contact):
243        return None
244    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
245        raise InvalidInput('contact', contact, "Not a valid email.")
246    return contact
247
248def testDisk(user, disksize, machine=None):
249    return disksize
250
251def testName(user, name, machine=None):
252    if name is None:
253        return None
254    if machine is not None and name == machine.name:
255        return None
256    if not Machine.select_by(name=name):
257        if not validMachineName(name):
258            raise InvalidInput('name', name, 'You must provide a machine name.  Max 22 chars, alnum plus \'-\' and \'_\'.')
259        return name
260    raise InvalidInput('name', name, "Name is already taken.")
261
262def testHostname(user, hostname, machine):
263    for nic in machine.nics:
264        if hostname == nic.hostname:
265            return hostname
266    # check if doesn't already exist
267    if NIC.select_by(hostname=hostname):
268        raise InvalidInput('hostname', hostname,
269                           "Already exists")
270    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
271        raise InvalidInput('hostname', hostname, "Not a valid hostname; "
272                           "must only use number, letters, and dashes.")
273    return hostname
Note: See TracBrowser for help on using the repository browser.