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

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