7 from sipb_xen_database import Machine, NIC, Type, Disk
8 from webcommon import InvalidInput
10 MAX_MEMORY_TOTAL = 512
11 MAX_MEMORY_SINGLE = 256
12 MIN_MEMORY_SINGLE = 16
20 def __init__(self, username, state, machine_id=None, name=None, owner=None,
21 admin=None, contact=None, memory=None, disksize=None,
22 vmtype=None, cdrom=None, clone_from=None, strict=False):
23 # XXX Successive quota checks aren't a good idea, since you
24 # can't necessarily change the locker and disk size at the
26 created_new = (machine_id is None)
30 raise InvalidInput('name', name, "You must provide a machine name.")
32 raise InvalidInput('memory', memory, "You must provide a memory size.")
34 raise InvalidInput('disk', disksize, "You must provide a disk size.")
36 if machine_id is not None:
37 self.machine = testMachineId(username, machine_id)
38 machine = getattr(self, 'machine', None)
40 owner = testOwner(username, owner, machine)
43 admin = testAdmin(username, admin, machine)
46 contact = testContact(username, contact, machine)
47 if contact is not None:
48 self.contact = contact
49 name = testName(username, name, machine)
52 if memory is not None:
53 self.memory = validMemory(self.owner, state, memory, machine,
55 if disksize is not None:
56 self.disksize = validDisk(self.owner, disksize, machine)
57 if vmtype is not None:
58 self.vmtype = validVmType(vmtype)
60 if not CDROM.get(cdrom):
61 raise CodeError("Invalid cdrom type '%s'" % cdrom)
63 if clone_from is not None:
64 if clone_from not in ('ice3', ):
65 raise CodeError("Invalid clone image '%s'" % clone_from)
66 self.clone_from = clone_from
69 def getMachinesByOwner(owner, machine=None):
70 """Return the machines owned by the same as a machine.
72 If the machine is None, return the machines owned by the same
77 return Machine.select_by(owner=owner)
79 def maxMemory(owner, g, machine=None, on=True):
80 """Return the maximum memory for a machine or a user.
82 If machine is None, return the memory available for a new
83 machine. Else, return the maximum that machine can have.
85 on is whether the machine should be turned on. If false, the max
86 memory for the machine to change to, if it is left off, is
89 if machine is not None and machine.memory > MAX_MEMORY_SINGLE:
90 # If they've been blessed, let them have it
93 return MAX_MEMORY_SINGLE
94 machines = getMachinesByOwner(owner, machine)
95 active_machines = [m for m in machines if m.name in g.xmlist_raw]
96 mem_usage = sum([x.memory for x in active_machines if x != machine])
97 return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
99 def maxDisk(owner, machine=None):
100 """Return the maximum disk that a machine can reach.
102 If machine is None, the maximum disk for a new machine. Otherwise,
103 return the maximum that a given machine can be changed to.
105 if machine is not None:
106 machine_id = machine.machine_id
109 disk_usage = Disk.query().filter_by(Disk.c.machine_id != machine_id,
110 owner=owner).sum(Disk.c.size) or 0
111 return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
113 def cantAddVm(owner, g):
114 machines = getMachinesByOwner(owner)
115 active_machines = [m for m in machines if m.name in g.xmlist_raw]
116 if len(machines) >= MAX_VMS_TOTAL:
117 return 'You have too many VMs to create a new one.'
118 if len(active_machines) >= MAX_VMS_ACTIVE:
119 return ('You already have the maximum number of VMs turned on. '
120 'To create more, turn one off.')
123 def haveAccess(user, machine):
124 """Return whether a user has administrative access to a machine"""
125 return user in cache_acls.accessList(machine)
127 def owns(user, machine):
128 """Return whether a user owns a machine"""
129 return user in expandLocker(machine.owner)
131 def validMachineName(name):
132 """Check that name is valid for a machine name"""
135 charset = string.ascii_letters + string.digits + '-_'
136 if name[0] in '-_' or len(name) > 22:
143 def validMemory(owner, g, memory, machine=None, on=True):
144 """Parse and validate limits for memory for a given owner and machine.
146 on is whether the memory must be valid after the machine is
151 if memory < MIN_MEMORY_SINGLE:
154 raise InvalidInput('memory', memory,
155 "Minimum %s MiB" % MIN_MEMORY_SINGLE)
156 max_val = maxMemory(owner, g, machine, on)
158 raise InvalidInput('memory', memory,
159 'Maximum %s MiB for %s' % (max_val, owner))
162 def validDisk(owner, disk, machine=None):
163 """Parse and validate limits for disk for a given owner and machine."""
166 if disk > maxDisk(owner, machine):
167 raise InvalidInput('disk', disk,
168 "Maximum %s G" % maxDisk(owner, machine))
169 disk = int(disk * 1024)
170 if disk < MIN_DISK_SINGLE * 1024:
173 raise InvalidInput('disk', disk,
174 "Minimum %s GiB" % MIN_DISK_SINGLE)
177 def validVmType(vm_type):
180 t = Type.get(vm_type)
182 raise CodeError("Invalid vm type '%s'" % vm_type)
185 def testMachineId(user, machine_id, exists=True):
186 """Parse, validate and check authorization for a given user and machine.
188 If exists is False, don't check that it exists.
190 if machine_id is None:
191 raise InvalidInput('machine_id', machine_id,
192 "Must specify a machine ID.")
194 machine_id = int(machine_id)
196 raise InvalidInput('machine_id', machine_id, "Must be an integer.")
197 machine = Machine.get(machine_id)
198 if exists and machine is None:
199 raise InvalidInput('machine_id', machine_id, "Does not exist.")
200 if machine is not None and not haveAccess(user, machine):
201 raise InvalidInput('machine_id', machine_id,
202 "You do not have access to this machine.")
205 def testAdmin(user, admin, machine):
206 """Determine whether a user can set the admin of a machine to this value.
208 Return the value to set the admin field to (possibly 'system:' +
209 admin). XXX is modifying this a good idea?
213 if machine is not None and admin == machine.administrator:
218 if cache_acls.isUser(admin):
220 admin = 'system:' + admin
222 if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
224 except getafsgroups.AfsProcessError, e:
226 if errmsg.startswith("pts: User or group doesn't exist"):
227 errmsg = 'The group "%s" does not exist.' % admin
228 raise InvalidInput('administrator', admin, errmsg)
229 #XXX Should we require that user is in the admin group?
232 def testOwner(user, owner, machine=None):
233 """Determine whether a user can set the owner of a machine to this value.
235 If machine is None, this is the owner of a new machine.
239 if machine is not None and owner in (machine.owner, None):
242 raise InvalidInput('owner', owner, "Owner must be specified")
244 if user not in cache_acls.expandLocker(owner):
245 raise InvalidInput('owner', owner, 'You do not have access to the '
247 except getafsgroups.AfsProcessError, e:
248 raise InvalidInput('owner', owner, str(e))
251 def testContact(user, contact, machine=None):
252 if contact is None or (machine is not None and contact == machine.contact):
254 if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
255 raise InvalidInput('contact', contact, "Not a valid email.")
258 def testDisk(user, disksize, machine=None):
261 def testName(user, name, machine=None):
264 if machine is not None and name == machine.name:
266 if not Machine.select_by(name=name):
267 if not validMachineName(name):
268 raise InvalidInput('name', name, 'You must provide a machine name. Max 22 chars, alnum plus \'-\' and \'_\'.')
270 raise InvalidInput('name', name, "Name is already taken.")
272 def testHostname(user, hostname, machine):
273 for nic in machine.nics:
274 if hostname == nic.hostname:
276 # check if doesn't already exist
277 if NIC.select_by(hostname=hostname):
278 raise InvalidInput('hostname', hostname,
280 if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
281 raise InvalidInput('hostname', hostname, "Not a valid hostname; "
282 "must only use number, letters, and dashes.")