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