source: trunk/packages/sipb-xen-www/code/validation.py @ 482

Last change on this file since 482 was 440, checked in by ecprice, 17 years ago

Support setting paravm/hvm for off, but already created, VMs.

File size: 7.9 KB
Line 
1#!/usr/bin/python
2
3import cache_acls
4import getafsgroups
5import re
6import string
7from sipb_xen_database import Machine, NIC, Type
8from webcommon import InvalidInput, g
9
10MAX_MEMORY_TOTAL = 512
11MAX_MEMORY_SINGLE = 256
12MIN_MEMORY_SINGLE = 16
13MAX_DISK_TOTAL = 50
14MAX_DISK_SINGLE = 50
15MIN_DISK_SINGLE = 0.1
16MAX_VMS_TOTAL = 10
17MAX_VMS_ACTIVE = 4
18
19def 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
31def 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
51def 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
62def 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
72def validAddVm(user):
73    reason = cantAddVm(user)
74    if reason:
75        raise InvalidInput('create', True, reason)
76    return True
77
78def haveAccess(user, machine):
79    """Return whether a user has administrative access to a machine"""
80    return user in cache_acls.accessList(machine)
81
82def owns(user, machine):
83    """Return whether a user owns a machine"""
84    return user in expandLocker(machine.owner)
85
86def 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
98def 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
117def 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
132def 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
140def 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
160def 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
185def 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
202def 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
209def testDisk(user, disksize, machine=None):
210    return disksize
211
212def 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
219def 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
Note: See TracBrowser for help on using the repository browser.