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

Last change on this file since 1069 was 1013, checked in by broder, 16 years ago

Update web code to for SQLAlchemy 0.4

File size: 10.9 KB
Line 
1#!/usr/bin/python
2
3import cache_acls
4import getafsgroups
5import re
6import string
7from invirt.database import Machine, NIC, Type, Disk, CDROM, Autoinstall
8from invirt.config import structs as config
9from webcommon import InvalidInput
10
11MAX_MEMORY_TOTAL = 512
12MAX_MEMORY_SINGLE = 256
13MIN_MEMORY_SINGLE = 16
14MAX_DISK_TOTAL = 50
15MAX_DISK_SINGLE = 50
16MIN_DISK_SINGLE = 0.1
17MAX_VMS_TOTAL = 10
18MAX_VMS_ACTIVE = 4
19
20class Validate:
21    def __init__(self, username, state, machine_id=None, name=None, description=None, owner=None,
22                 admin=None, contact=None, memory=None, disksize=None,
23                 vmtype=None, cdrom=None, autoinstall=None, strict=False):
24        # XXX Successive quota checks aren't a good idea, since you
25        # can't necessarily change the locker and disk size at the
26        # same time.
27        created_new = (machine_id is None)
28
29        if strict:
30            if name is None:
31                raise InvalidInput('name', name, "You must provide a machine name.")
32            if description is None:
33                raise InvalidInput('description', description, "You must provide a description.")
34            if memory is None:
35                raise InvalidInput('memory', memory, "You must provide a memory size.")
36            if disksize is None:
37                raise InvalidInput('disk', disksize, "You must provide a disk size.")
38
39        if machine_id is not None:
40            self.machine = testMachineId(username, state, machine_id)
41        machine = getattr(self, 'machine', None)
42
43        owner = testOwner(username, owner, machine)
44        if owner is not None:
45            self.owner = owner
46        admin = testAdmin(username, admin, machine)
47        if admin is not None:
48            self.admin = admin
49        contact = testContact(username, contact, machine)
50        if contact is not None:
51            self.contact = contact
52        name = testName(username, name, machine)
53        if name is not None:
54            self.name = name
55        description = testDescription(username, description, machine)
56        if description is not None:
57            self.description = description
58        if memory is not None:
59            self.memory = validMemory(self.owner, state, memory, machine,
60                                      on=not created_new)
61        if disksize is not None:
62            self.disksize = validDisk(self.owner, state, disksize, machine)
63        if vmtype is not None:
64            self.vmtype = validVmType(vmtype)
65        if cdrom is not None:
66            if not CDROM.query().get(cdrom):
67                raise CodeError("Invalid cdrom type '%s'" % cdrom)
68            self.cdrom = cdrom
69        if autoinstall is not None:
70            self.autoinstall = Autoinstall.query().get(autoinstall)
71
72
73def getMachinesByOwner(owner, machine=None):
74    """Return the machines owned by the same as a machine.
75
76    If the machine is None, return the machines owned by the same
77    user.
78    """
79    if machine:
80        owner = machine.owner
81    return Machine.query().filter_by(owner=owner)
82
83def maxMemory(owner, g, machine=None, on=True):
84    """Return the maximum memory for a machine or a user.
85
86    If machine is None, return the memory available for a new
87    machine.  Else, return the maximum that machine can have.
88
89    on is whether the machine should be turned on.  If false, the max
90    memory for the machine to change to, if it is left off, is
91    returned.
92    """
93    if machine is not None and machine.memory > MAX_MEMORY_SINGLE:
94        # If they've been blessed, let them have it
95        return machine.memory
96    if not on:
97        return MAX_MEMORY_SINGLE
98    machines = getMachinesByOwner(owner, machine)
99    active_machines = [m for m in machines if m.name in g.xmlist_raw]
100    mem_usage = sum([x.memory for x in active_machines if x != machine])
101    return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
102
103def maxDisk(owner, machine=None):
104    """Return the maximum disk that a machine can reach.
105
106    If machine is None, the maximum disk for a new machine. Otherwise,
107    return the maximum that a given machine can be changed to.
108    """
109    if machine is not None:
110        machine_id = machine.machine_id
111    else:
112        machine_id = None
113    disk_usage = Disk.query().filter(Disk.c.machine_id != machine_id).\
114                     join('machine').\
115                     filter_by(owner=owner).sum(Disk.c.size) or 0
116    return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
117
118def cantAddVm(owner, g):
119    machines = getMachinesByOwner(owner)
120    active_machines = [m for m in machines if m.name in g.xmlist_raw]
121    if machines.count() >= MAX_VMS_TOTAL:
122        return 'You have too many VMs to create a new one.'
123    if len(active_machines) >= MAX_VMS_ACTIVE:
124        return ('You already have the maximum number of VMs turned on.  '
125                'To create more, turn one off.')
126    return False
127
128def haveAccess(user, state, machine):
129    """Return whether a user has administrative access to a machine"""
130    return (user in cache_acls.accessList(machine)
131            or (machine.adminable and state.isadmin))
132
133def owns(user, machine):
134    """Return whether a user owns a machine"""
135    return user in expandLocker(machine.owner)
136
137def validMachineName(name):
138    """Check that name is valid for a machine name"""
139    if not name:
140        return False
141    charset = string.lowercase + string.digits + '-'
142    if '-' in (name[0], name[-1]) or len(name) > 63:
143        return False
144    for x in name:
145        if x not in charset:
146            return False
147    return True
148
149def validMemory(owner, g, memory, machine=None, on=True):
150    """Parse and validate limits for memory for a given owner and machine.
151
152    on is whether the memory must be valid after the machine is
153    switched on.
154    """
155    try:
156        memory = int(memory)
157        if memory < MIN_MEMORY_SINGLE:
158            raise ValueError
159    except ValueError:
160        raise InvalidInput('memory', memory,
161                           "Minimum %s MiB" % MIN_MEMORY_SINGLE)
162    max_val = maxMemory(owner, g, machine, on)
163    if not g.isadmin and memory > max_val:
164        raise InvalidInput('memory', memory,
165                           'Maximum %s MiB for %s' % (max_val, owner))
166    return memory
167
168def validDisk(owner, g, disk, machine=None):
169    """Parse and validate limits for disk for a given owner and machine."""
170    try:
171        disk = float(disk)
172        if not g.isadmin and disk > maxDisk(owner, machine):
173            raise InvalidInput('disk', disk,
174                               "Maximum %s G" % maxDisk(owner, machine))
175        disk = int(disk * 1024)
176        if disk < MIN_DISK_SINGLE * 1024:
177            raise ValueError
178    except ValueError:
179        raise InvalidInput('disk', disk,
180                           "Minimum %s GiB" % MIN_DISK_SINGLE)
181    return disk
182
183def validVmType(vm_type):
184    if vm_type is None:
185        return None
186    t = Type.query().get(vm_type)
187    if t is None:
188        raise CodeError("Invalid vm type '%s'"  % vm_type)
189    return t
190
191def testMachineId(user, state, machine_id, exists=True):
192    """Parse, validate and check authorization for a given user and machine.
193
194    If exists is False, don't check that it exists.
195    """
196    if machine_id is None:
197        raise InvalidInput('machine_id', machine_id,
198                           "Must specify a machine ID.")
199    try:
200        machine_id = int(machine_id)
201    except ValueError:
202        raise InvalidInput('machine_id', machine_id, "Must be an integer.")
203    machine = Machine.query().get(machine_id)
204    if exists and machine is None:
205        raise InvalidInput('machine_id', machine_id, "Does not exist.")
206    if machine is not None and not haveAccess(user, state, machine):
207        raise InvalidInput('machine_id', machine_id,
208                           "You do not have access to this machine.")
209    return machine
210
211def testAdmin(user, admin, machine):
212    """Determine whether a user can set the admin of a machine to this value.
213
214    Return the value to set the admin field to (possibly 'system:' +
215    admin).  XXX is modifying this a good idea?
216    """
217    if admin is None:
218        return None
219    if machine is not None and admin == machine.administrator:
220        return None
221    if admin == user:
222        return admin
223    if ':' not in admin:
224        if cache_acls.isUser(admin):
225            return admin
226        admin = 'system:' + admin
227    try:
228        if user in getafsgroups.getAfsGroupMembers(admin, config.authz[0].cell):
229            return admin
230    except getafsgroups.AfsProcessError, e:
231        errmsg = str(e)
232        if errmsg.startswith("pts: User or group doesn't exist"):
233            errmsg = 'The group "%s" does not exist.' % admin
234        raise InvalidInput('administrator', admin, errmsg)
235    #XXX Should we require that user is in the admin group?
236    return admin
237
238def testOwner(user, owner, machine=None):
239    """Determine whether a user can set the owner of a machine to this value.
240
241    If machine is None, this is the owner of a new machine.
242    """
243    if owner == user:
244        return owner
245    if machine is not None and owner in (machine.owner, None):
246        return machine.owner
247    if owner is None:
248        raise InvalidInput('owner', owner, "Owner must be specified")
249    try:
250        if user not in cache_acls.expandLocker(owner):
251            raise InvalidInput('owner', owner, 'You do not have access to the '
252                               + owner + ' locker')
253    except getafsgroups.AfsProcessError, e:
254        raise InvalidInput('owner', owner, str(e))
255    return owner
256
257def testContact(user, contact, machine=None):
258    if contact is None or (machine is not None and contact == machine.contact):
259        return None
260    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
261        raise InvalidInput('contact', contact, "Not a valid email.")
262    return contact
263
264def testDisk(user, disksize, machine=None):
265    return disksize
266
267def testName(user, name, machine=None):
268    if name is None:
269        return None
270    name = name.lower()
271    if machine is not None and name == machine.name:
272        return None
273    if not Machine.query().filter_by(name=name).count():
274        if not validMachineName(name):
275            raise InvalidInput('name', name, 'You must provide a machine name.  Max 63 chars, alnum plus \'-\', does not begin or end with \'-\'.')
276        return name
277    raise InvalidInput('name', name, "Name is already taken.")
278
279def testDescription(user, description, machine=None):
280    if description is None or description.strip() == '':
281        return None
282    return description.strip()
283
284def testHostname(user, hostname, machine):
285    for nic in machine.nics:
286        if hostname == nic.hostname:
287            return hostname
288    # check if doesn't already exist
289    if NIC.select_by(hostname=hostname):
290        raise InvalidInput('hostname', hostname,
291                           "Already exists")
292    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
293        raise InvalidInput('hostname', hostname, "Not a valid hostname; "
294                           "must only use number, letters, and dashes.")
295    return hostname
Note: See TracBrowser for help on using the repository browser.