Allow service maintainers to bump memory
[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     if machine.memory > MAX_MEMORY_SINGLE:
43         # If they've been blessed, let them have it
44         return machine.memory
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 == 'moo':
75         return True
76     if user in (machine.administrator, machine.owner):
77         return True
78     if getafsgroups.checkAfsGroup(user, machine.administrator, 
79                                   'athena.mit.edu'): #XXX Cell?
80         return True
81     if not getafsgroups.notLockerOwner(user, machine.owner):
82         return True
83     return owns(user, machine)
84
85 def owns(user, machine):
86     """Return whether a user owns a machine"""
87     if user == 'moo':
88         return True
89     return not getafsgroups.notLockerOwner(user, machine.owner)
90
91 def validMachineName(name):
92     """Check that name is valid for a machine name"""
93     if not name:
94         return False
95     charset = string.ascii_letters + string.digits + '-_'
96     if name[0] in '-_' or len(name) > 22:
97         return False
98     for x in name:
99         if x not in charset:
100             return False
101     return True
102
103 def validMemory(user, memory, machine=None, on=True):
104     """Parse and validate limits for memory for a given user and machine.
105
106     on is whether the memory must be valid after the machine is
107     switched on.
108     """
109     try:
110         memory = int(memory)
111         if memory < MIN_MEMORY_SINGLE:
112             raise ValueError
113     except ValueError:
114         raise InvalidInput('memory', memory, 
115                            "Minimum %s MiB" % MIN_MEMORY_SINGLE)
116     if memory > maxMemory(user, machine, on):
117         raise InvalidInput('memory', memory,
118                            'Maximum %s MiB for %s' % (maxMemory(user, machine),
119                                                       user))
120     return memory
121
122 def validDisk(user, disk, machine=None):
123     """Parse and validate limits for disk for a given user and machine."""
124     try:
125         disk = float(disk)
126         if disk > maxDisk(user, machine):
127             raise InvalidInput('disk', disk,
128                                "Maximum %s G" % maxDisk(user, machine))
129         disk = int(disk * 1024)
130         if disk < MIN_DISK_SINGLE * 1024:
131             raise ValueError
132     except ValueError:
133         raise InvalidInput('disk', disk,
134                            "Minimum %s GiB" % MIN_DISK_SINGLE)
135     return disk
136             
137 def testMachineId(user, machine_id, exists=True):
138     """Parse, validate and check authorization for a given user and machine.
139
140     If exists is False, don't check that it exists.
141     """
142     if machine_id is None:
143         raise InvalidInput('machine_id', machine_id, 
144                            "Must specify a machine ID.")
145     try:
146         machine_id = int(machine_id)
147     except ValueError:
148         raise InvalidInput('machine_id', machine_id, "Must be an integer.")
149     machine = Machine.get(machine_id)
150     if exists and machine is None:
151         raise InvalidInput('machine_id', machine_id, "Does not exist.")
152     if machine is not None and not haveAccess(user, machine):
153         raise InvalidInput('machine_id', machine_id,
154                            "You do not have access to this machine.")
155     return machine
156
157 def testAdmin(user, admin, machine):
158     if admin in (None, machine.administrator):
159         return None
160     if admin == user:
161         return admin
162     if getafsgroups.checkAfsGroup(user, admin, 'athena.mit.edu'):
163         return admin
164     if getafsgroups.checkAfsGroup(user, 'system:'+admin,
165                                   'athena.mit.edu'):
166         return 'system:'+admin
167     return admin
168     
169 def testOwner(user, owner, machine=None):
170     if owner == user or machine is not None and owner == machine.owner:
171         return owner
172     if owner is None:
173         raise InvalidInput('owner', owner, "Owner must be specified")
174     value = getafsgroups.notLockerOwner(user, owner)
175     if not value:
176         return owner
177     raise InvalidInput('owner', owner, value)
178
179 def testContact(user, contact, machine=None):
180     if contact in (None, machine.contact):
181         return None
182     if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
183         raise InvalidInput('contact', contact, "Not a valid email.")
184     return contact
185
186 def testDisk(user, disksize, machine=None):
187     return disksize
188
189 def testName(user, name, machine=None):
190     if name in (None, machine.name):
191         return None
192     if not Machine.select_by(name=name):
193         return name
194     raise InvalidInput('name', name, "Name is already taken.")
195
196 def testHostname(user, hostname, machine):
197     for nic in machine.nics:
198         if hostname == nic.hostname:
199             return hostname
200     # check if doesn't already exist
201     if NIC.select_by(hostname=hostname):
202         raise InvalidInput('hostname', hostname,
203                            "Already exists")
204     if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
205         raise InvalidInput('hostname', hostname, "Not a valid hostname; "
206                            "must only use number, letters, and dashes.")
207     return hostname