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

Last change on this file since 413 was 413, checked in by ecprice, 17 years ago

Resolve #69, by checking that administrators are either users or
groups in the athena cell.

File size: 7.4 KB
Line 
1#!/usr/bin/python
2
3import cache_acls
4import getafsgroups
5import re
6import string
7from sipb_xen_database import Machine, NIC
8from webcommon import InvalidInput, g
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
19def getMachinesByOwner(user, machine=None):
20    """Return the machines owned by the same as a machine.
21   
22    If the machine is None, return the machines owned by the same
23    user.
24    """
25    if machine:
26        owner = machine.owner
27    else:
28        owner = user
29    return Machine.select_by(owner=owner)
30
31def maxMemory(user, machine=None, on=True):
32    """Return the maximum memory for a machine or a user.
33
34    If machine is None, return the memory available for a new
35    machine.  Else, return the maximum that machine can have.
36
37    on is whether the machine should be turned on.  If false, the max
38    memory for the machine to change to, if it is left off, is
39    returned.
40    """
41    if machine is not None and machine.memory > MAX_MEMORY_SINGLE:
42        # If they've been blessed, let them have it
43        return machine.memory
44    if not on:
45        return MAX_MEMORY_SINGLE
46    machines = getMachinesByOwner(user, machine)
47    active_machines = [x for x in machines if g.uptimes.get(x)]
48    mem_usage = sum([x.memory for x in active_machines if x != machine])
49    return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
50
51def maxDisk(user, machine=None):
52    machines = getMachinesByOwner(user, machine)
53    disk_usage = sum([sum([y.size for y in x.disks])
54                      for x in machines if x != machine])
55    return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
56
57def cantAddVm(user):
58    machines = getMachinesByOwner(user)
59    active_machines = [x for x in machines if g.uptimes.get(x)]
60    if len(machines) >= MAX_VMS_TOTAL:
61        return 'You have too many VMs to create a new one.'
62    if len(active_machines) >= MAX_VMS_ACTIVE:
63        return ('You already have the maximum number of VMs turned on.  '
64                'To create more, turn one off.')
65    return False
66
67def validAddVm(user):
68    reason = cantAddVm(user)
69    if reason:
70        raise InvalidInput('create', True, reason)
71    return True
72
73def haveAccess(user, machine):
74    """Return whether a user has administrative access to a machine"""
75    return user in cache_acls.accessList(machine)
76
77def owns(user, machine):
78    """Return whether a user owns a machine"""
79    return user in expandLocker(machine.owner)
80
81def validMachineName(name):
82    """Check that name is valid for a machine name"""
83    if not name:
84        return False
85    charset = string.ascii_letters + string.digits + '-_'
86    if name[0] in '-_' or len(name) > 22:
87        return False
88    for x in name:
89        if x not in charset:
90            return False
91    return True
92
93def validMemory(user, memory, machine=None, on=True):
94    """Parse and validate limits for memory for a given user and machine.
95
96    on is whether the memory must be valid after the machine is
97    switched on.
98    """
99    try:
100        memory = int(memory)
101        if memory < MIN_MEMORY_SINGLE:
102            raise ValueError
103    except ValueError:
104        raise InvalidInput('memory', memory, 
105                           "Minimum %s MiB" % MIN_MEMORY_SINGLE)
106    if memory > maxMemory(user, machine, on):
107        raise InvalidInput('memory', memory,
108                           'Maximum %s MiB for %s' % (maxMemory(user, machine),
109                                                      user))
110    return memory
111
112def validDisk(user, disk, machine=None):
113    """Parse and validate limits for disk for a given user and machine."""
114    try:
115        disk = float(disk)
116        if disk > maxDisk(user, machine):
117            raise InvalidInput('disk', disk,
118                               "Maximum %s G" % maxDisk(user, machine))
119        disk = int(disk * 1024)
120        if disk < MIN_DISK_SINGLE * 1024:
121            raise ValueError
122    except ValueError:
123        raise InvalidInput('disk', disk,
124                           "Minimum %s GiB" % MIN_DISK_SINGLE)
125    return disk
126           
127def testMachineId(user, machine_id, exists=True):
128    """Parse, validate and check authorization for a given user and machine.
129
130    If exists is False, don't check that it exists.
131    """
132    if machine_id is None:
133        raise InvalidInput('machine_id', machine_id, 
134                           "Must specify a machine ID.")
135    try:
136        machine_id = int(machine_id)
137    except ValueError:
138        raise InvalidInput('machine_id', machine_id, "Must be an integer.")
139    machine = Machine.get(machine_id)
140    if exists and machine is None:
141        raise InvalidInput('machine_id', machine_id, "Does not exist.")
142    if machine is not None and not haveAccess(user, machine):
143        raise InvalidInput('machine_id', machine_id,
144                           "You do not have access to this machine.")
145    return machine
146
147def testAdmin(user, admin, machine):
148    """Determine whether a user can set the admin of a machine to this value.
149
150    Return the value to set the admin field to (possibly 'system:' +
151    admin).  XXX is modifying this a good idea?
152    """
153    if admin in (None, machine.administrator):
154        return None
155    if admin == user:
156        return admin
157    if ':' not in admin:
158        if cache_acls.isUser(admin):
159            return admin
160        admin = 'system:' + admin
161    try:
162        if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
163            return admin
164    except getafsgroups.AfsProcessError, e:
165        raise InvalidInput('administrator', admin, str(e))
166    #XXX Should we require that user is in the admin group?
167    return admin
168   
169def testOwner(user, owner, machine=None):
170    """Determine whether a user can set the owner of a machine to this value.
171
172    If machine is None, this is the owner of a new machine.
173    """
174    if owner == user or machine is not None and owner == machine.owner:
175        return owner
176    if owner is None:
177        raise InvalidInput('owner', owner, "Owner must be specified")
178    try:
179        if user not in cache_acls.expandLocker(owner):
180            raise InvalidInput('owner', owner, 'You do not have access to the '
181                               + owner + ' locker')
182    except getafsgroups.AfsProcessError, e:
183        raise InvalidInput('owner', owner, str(e))
184    return owner
185
186def testContact(user, contact, machine=None):
187    if contact in (None, machine.contact):
188        return None
189    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
190        raise InvalidInput('contact', contact, "Not a valid email.")
191    return contact
192
193def testDisk(user, disksize, machine=None):
194    return disksize
195
196def testName(user, name, machine=None):
197    if name in (None, machine.name):
198        return None
199    if not Machine.select_by(name=name):
200        return name
201    raise InvalidInput('name', name, "Name is already taken.")
202
203def testHostname(user, hostname, machine):
204    for nic in machine.nics:
205        if hostname == nic.hostname:
206            return hostname
207    # check if doesn't already exist
208    if NIC.select_by(hostname=hostname):
209        raise InvalidInput('hostname', hostname,
210                           "Already exists")
211    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
212        raise InvalidInput('hostname', hostname, "Not a valid hostname; "
213                           "must only use number, letters, and dashes.")
214    return hostname
Note: See TracBrowser for help on using the repository browser.