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

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

Nicer error message on admin not being a group.

File size: 7.5 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        errmsg = str(e)
166        if errmsg.startswith("pts: User or group doesn't exist"):
167            errmsg = 'The group "%s" does not exist.' % admin
168        raise InvalidInput('administrator', admin, errmsg)
169    #XXX Should we require that user is in the admin group?
170    return admin
171   
172def testOwner(user, owner, machine=None):
173    """Determine whether a user can set the owner of a machine to this value.
174
175    If machine is None, this is the owner of a new machine.
176    """
177    if owner == user or machine is not None and owner == machine.owner:
178        return owner
179    if owner is None:
180        raise InvalidInput('owner', owner, "Owner must be specified")
181    try:
182        if user not in cache_acls.expandLocker(owner):
183            raise InvalidInput('owner', owner, 'You do not have access to the '
184                               + owner + ' locker')
185    except getafsgroups.AfsProcessError, e:
186        raise InvalidInput('owner', owner, str(e))
187    return owner
188
189def testContact(user, contact, machine=None):
190    if contact in (None, machine.contact):
191        return None
192    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
193        raise InvalidInput('contact', contact, "Not a valid email.")
194    return contact
195
196def testDisk(user, disksize, machine=None):
197    return disksize
198
199def testName(user, name, machine=None):
200    if name in (None, machine.name):
201        return None
202    if not Machine.select_by(name=name):
203        return name
204    raise InvalidInput('name', name, "Name is already taken.")
205
206def testHostname(user, hostname, machine):
207    for nic in machine.nics:
208        if hostname == nic.hostname:
209            return hostname
210    # check if doesn't already exist
211    if NIC.select_by(hostname=hostname):
212        raise InvalidInput('hostname', hostname,
213                           "Already exists")
214    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
215        raise InvalidInput('hostname', hostname, "Not a valid hostname; "
216                           "must only use number, letters, and dashes.")
217    return hostname
Note: See TracBrowser for help on using the repository browser.