keep help page at a readable width
[invirt/packages/invirt-web.git] / code / validation.py
1 #!/usr/bin/python
2
3 import cache_acls
4 import getafsgroups
5 import re
6 import string
7 from sipb_xen_database import Machine, NIC, Type
8 from webcommon import InvalidInput, g
9
10 MAX_MEMORY_TOTAL = 512
11 MAX_MEMORY_SINGLE = 256
12 MIN_MEMORY_SINGLE = 16
13 MAX_DISK_TOTAL = 50
14 MAX_DISK_SINGLE = 50
15 MIN_DISK_SINGLE = 0.1
16 MAX_VMS_TOTAL = 10
17 MAX_VMS_ACTIVE = 4
18
19 def 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
31 def 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
51 def maxDisk(user, machine=None):
52     """Return the maximum disk that a machine can reach.
53
54     If machine is None, the maximum disk for a new machine. Otherwise,
55     return the maximum that a given machine can be changed to.
56     """
57     machines = getMachinesByOwner(user, machine)
58     disk_usage = sum([sum([y.size for y in x.disks])
59                       for x in machines if x != machine])
60     return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
61
62 def cantAddVm(user):
63     machines = getMachinesByOwner(user)
64     active_machines = [x for x in machines if g.uptimes.get(x)]
65     if len(machines) >= MAX_VMS_TOTAL:
66         return 'You have too many VMs to create a new one.'
67     if len(active_machines) >= MAX_VMS_ACTIVE:
68         return ('You already have the maximum number of VMs turned on.  '
69                 'To create more, turn one off.')
70     return False
71
72 def validAddVm(user):
73     reason = cantAddVm(user)
74     if reason:
75         raise InvalidInput('create', True, reason)
76     return True
77
78 def haveAccess(user, machine):
79     """Return whether a user has administrative access to a machine"""
80     return user in cache_acls.accessList(machine)
81
82 def owns(user, machine):
83     """Return whether a user owns a machine"""
84     return user in expandLocker(machine.owner)
85
86 def validMachineName(name):
87     """Check that name is valid for a machine name"""
88     if not name:
89         return False
90     charset = string.ascii_letters + string.digits + '-_'
91     if name[0] in '-_' or len(name) > 22:
92         return False
93     for x in name:
94         if x not in charset:
95             return False
96     return True
97
98 def validMemory(user, memory, machine=None, on=True):
99     """Parse and validate limits for memory for a given user and machine.
100
101     on is whether the memory must be valid after the machine is
102     switched on.
103     """
104     try:
105         memory = int(memory)
106         if memory < MIN_MEMORY_SINGLE:
107             raise ValueError
108     except ValueError:
109         raise InvalidInput('memory', memory,
110                            "Minimum %s MiB" % MIN_MEMORY_SINGLE)
111     if memory > maxMemory(user, machine, on):
112         raise InvalidInput('memory', memory,
113                            'Maximum %s MiB for %s' % (maxMemory(user, machine),
114                                                       user))
115     return memory
116
117 def validDisk(user, disk, machine=None):
118     """Parse and validate limits for disk for a given user and machine."""
119     try:
120         disk = float(disk)
121         if disk > maxDisk(user, machine):
122             raise InvalidInput('disk', disk,
123                                "Maximum %s G" % maxDisk(user, machine))
124         disk = int(disk * 1024)
125         if disk < MIN_DISK_SINGLE * 1024:
126             raise ValueError
127     except ValueError:
128         raise InvalidInput('disk', disk,
129                            "Minimum %s GiB" % MIN_DISK_SINGLE)
130     return disk
131
132 def validVmType(vm_type):
133     if vm_type is None:
134         return None
135     t = Type.get(vm_type)
136     if t is None:
137         raise CodeError("Invalid vm type '%s'"  % vm_type)
138     return t
139
140 def testMachineId(user, machine_id, exists=True):
141     """Parse, validate and check authorization for a given user and machine.
142
143     If exists is False, don't check that it exists.
144     """
145     if machine_id is None:
146         raise InvalidInput('machine_id', machine_id,
147                            "Must specify a machine ID.")
148     try:
149         machine_id = int(machine_id)
150     except ValueError:
151         raise InvalidInput('machine_id', machine_id, "Must be an integer.")
152     machine = Machine.get(machine_id)
153     if exists and machine is None:
154         raise InvalidInput('machine_id', machine_id, "Does not exist.")
155     if machine is not None and not haveAccess(user, machine):
156         raise InvalidInput('machine_id', machine_id,
157                            "You do not have access to this machine.")
158     return machine
159
160 def testAdmin(user, admin, machine):
161     """Determine whether a user can set the admin of a machine to this value.
162
163     Return the value to set the admin field to (possibly 'system:' +
164     admin).  XXX is modifying this a good idea?
165     """
166     if admin in (None, machine.administrator):
167         return None
168     if admin == user:
169         return admin
170     if ':' not in admin:
171         if cache_acls.isUser(admin):
172             return admin
173         admin = 'system:' + admin
174     try:
175         if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
176             return admin
177     except getafsgroups.AfsProcessError, e:
178         errmsg = str(e)
179         if errmsg.startswith("pts: User or group doesn't exist"):
180             errmsg = 'The group "%s" does not exist.' % admin
181         raise InvalidInput('administrator', admin, errmsg)
182     #XXX Should we require that user is in the admin group?
183     return admin
184
185 def testOwner(user, owner, machine=None):
186     """Determine whether a user can set the owner of a machine to this value.
187
188     If machine is None, this is the owner of a new machine.
189     """
190     if owner == user or machine is not None and owner == machine.owner:
191         return owner
192     if owner is None:
193         raise InvalidInput('owner', owner, "Owner must be specified")
194     try:
195         if user not in cache_acls.expandLocker(owner):
196             raise InvalidInput('owner', owner, 'You do not have access to the '
197                                + owner + ' locker')
198     except getafsgroups.AfsProcessError, e:
199         raise InvalidInput('owner', owner, str(e))
200     return owner
201
202 def testContact(user, contact, machine=None):
203     if contact in (None, machine.contact):
204         return None
205     if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
206         raise InvalidInput('contact', contact, "Not a valid email.")
207     return contact
208
209 def testDisk(user, disksize, machine=None):
210     return disksize
211
212 def testName(user, name, machine=None):
213     if name in (None, machine.name):
214         return None
215     if not Machine.select_by(name=name):
216         return name
217     raise InvalidInput('name', name, "Name is already taken.")
218
219 def testHostname(user, hostname, machine):
220     for nic in machine.nics:
221         if hostname == nic.hostname:
222             return hostname
223     # check if doesn't already exist
224     if NIC.select_by(hostname=hostname):
225         raise InvalidInput('hostname', hostname,
226                            "Already exists")
227     if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
228         raise InvalidInput('hostname', hostname, "Not a valid hostname; "
229                            "must only use number, letters, and dashes.")
230     return hostname