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