Cleaner HVM/paravm validation
[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     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
57 def 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
67 def validAddVm(user):
68     reason = cantAddVm(user)
69     if reason:
70         raise InvalidInput('create', True, reason)
71     return True
72
73 def haveAccess(user, machine):
74     """Return whether a user has administrative access to a machine"""
75     return user in cache_acls.accessList(machine)
76
77 def owns(user, machine):
78     """Return whether a user owns a machine"""
79     return user in expandLocker(machine.owner)
80
81 def 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
93 def 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
112 def 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
127 def validVmType(vm_type):
128     if vm_type == 'hvm':
129         return Type.get('linux-hvm')
130     elif vm_type == 'paravm':
131         return Type.get('linux')
132     else:
133         raise CodeError("Invalid vm type '%s'"  % vm_type)
134
135 def testMachineId(user, machine_id, exists=True):
136     """Parse, validate and check authorization for a given user and machine.
137
138     If exists is False, don't check that it exists.
139     """
140     if machine_id is None:
141         raise InvalidInput('machine_id', machine_id, 
142                            "Must specify a machine ID.")
143     try:
144         machine_id = int(machine_id)
145     except ValueError:
146         raise InvalidInput('machine_id', machine_id, "Must be an integer.")
147     machine = Machine.get(machine_id)
148     if exists and machine is None:
149         raise InvalidInput('machine_id', machine_id, "Does not exist.")
150     if machine is not None and not haveAccess(user, machine):
151         raise InvalidInput('machine_id', machine_id,
152                            "You do not have access to this machine.")
153     return machine
154
155 def testAdmin(user, admin, machine):
156     """Determine whether a user can set the admin of a machine to this value.
157
158     Return the value to set the admin field to (possibly 'system:' +
159     admin).  XXX is modifying this a good idea?
160     """
161     if admin in (None, machine.administrator):
162         return None
163     if admin == user:
164         return admin
165     if ':' not in admin:
166         if cache_acls.isUser(admin):
167             return admin
168         admin = 'system:' + admin
169     try:
170         if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
171             return admin
172     except getafsgroups.AfsProcessError, e:
173         errmsg = str(e)
174         if errmsg.startswith("pts: User or group doesn't exist"):
175             errmsg = 'The group "%s" does not exist.' % admin
176         raise InvalidInput('administrator', admin, errmsg)
177     #XXX Should we require that user is in the admin group?
178     return admin
179     
180 def testOwner(user, owner, machine=None):
181     """Determine whether a user can set the owner of a machine to this value.
182
183     If machine is None, this is the owner of a new machine.
184     """
185     if owner == user or machine is not None and owner == machine.owner:
186         return owner
187     if owner is None:
188         raise InvalidInput('owner', owner, "Owner must be specified")
189     try:
190         if user not in cache_acls.expandLocker(owner):
191             raise InvalidInput('owner', owner, 'You do not have access to the '
192                                + owner + ' locker')
193     except getafsgroups.AfsProcessError, e:
194         raise InvalidInput('owner', owner, str(e))
195     return owner
196
197 def testContact(user, contact, machine=None):
198     if contact in (None, machine.contact):
199         return None
200     if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
201         raise InvalidInput('contact', contact, "Not a valid email.")
202     return contact
203
204 def testDisk(user, disksize, machine=None):
205     return disksize
206
207 def testName(user, name, machine=None):
208     if name in (None, machine.name):
209         return None
210     if not Machine.select_by(name=name):
211         return name
212     raise InvalidInput('name', name, "Name is already taken.")
213
214 def testHostname(user, hostname, machine):
215     for nic in machine.nics:
216         if hostname == nic.hostname:
217             return hostname
218     # check if doesn't already exist
219     if NIC.select_by(hostname=hostname):
220         raise InvalidInput('hostname', hostname,
221                            "Already exists")
222     if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
223         raise InvalidInput('hostname', hostname, "Not a valid hostname; "
224                            "must only use number, letters, and dashes.")
225     return hostname