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):
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)
28 if machine_id is not None:
29 self.machine = testMachineId(username, machine_id)
30 machine = getattr(self, 'machine', None)
32 owner = testOwner(username, owner, machine)
35 admin = testAdmin(username, admin, machine)
38 contact = testContact(username, contact, machine)
39 if contact is not None:
40 self.contact = contact
41 name = testName(username, name, machine)
44 if memory is not None:
45 self.memory = validMemory(self.owner, state, memory, machine,
47 if disksize is not None:
48 self.disksize = validDisk(self.owner, disksize, machine)
49 if vmtype is not None:
50 self.vmtype = validVmType(vmtype)
52 if not CDROM.get(cdrom):
53 raise CodeError("Invalid cdrom type '%s'" % cdrom)
55 if clone_from is not None:
56 if clone_from not in ('ice3', ):
57 raise CodeError("Invalid clone image '%s'" % clone_from)
58 self.clone_from = clone_from
61 def getMachinesByOwner(owner, machine=None):
62 """Return the machines owned by the same as a machine.
64 If the machine is None, return the machines owned by the same
69 return Machine.select_by(owner=owner)
71 def maxMemory(owner, g, machine=None, on=True):
72 """Return the maximum memory for a machine or a user.
74 If machine is None, return the memory available for a new
75 machine. Else, return the maximum that machine can have.
77 on is whether the machine should be turned on. If false, the max
78 memory for the machine to change to, if it is left off, is
81 if machine is not None and machine.memory > MAX_MEMORY_SINGLE:
82 # If they've been blessed, let them have it
85 return MAX_MEMORY_SINGLE
86 machines = getMachinesByOwner(owner, machine)
87 active_machines = [m for m in machines if m.name in g.xmlist_raw]
88 mem_usage = sum([x.memory for x in active_machines if x != machine])
89 return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
91 def maxDisk(owner, machine=None):
92 """Return the maximum disk that a machine can reach.
94 If machine is None, the maximum disk for a new machine. Otherwise,
95 return the maximum that a given machine can be changed to.
97 if machine is not None:
98 machine_id = machine.machine_id
101 disk_usage = Disk.query().filter_by(Disk.c.machine_id != machine_id,
102 owner=owner).sum(Disk.c.size) or 0
103 return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
105 def cantAddVm(owner, g):
106 machines = getMachinesByOwner(owner)
107 active_machines = [m for m in machines if m.name in g.xmlist_raw]
108 if len(machines) >= MAX_VMS_TOTAL:
109 return 'You have too many VMs to create a new one.'
110 if len(active_machines) >= MAX_VMS_ACTIVE:
111 return ('You already have the maximum number of VMs turned on. '
112 'To create more, turn one off.')
115 def haveAccess(user, machine):
116 """Return whether a user has administrative access to a machine"""
117 return user in cache_acls.accessList(machine)
119 def owns(user, machine):
120 """Return whether a user owns a machine"""
121 return user in expandLocker(machine.owner)
123 def validMachineName(name):
124 """Check that name is valid for a machine name"""
127 charset = string.ascii_letters + string.digits + '-_'
128 if name[0] in '-_' or len(name) > 22:
135 def validMemory(owner, g, memory, machine=None, on=True):
136 """Parse and validate limits for memory for a given owner and machine.
138 on is whether the memory must be valid after the machine is
143 if memory < MIN_MEMORY_SINGLE:
146 raise InvalidInput('memory', memory,
147 "Minimum %s MiB" % MIN_MEMORY_SINGLE)
148 max_val = maxMemory(owner, g, machine, on)
150 raise InvalidInput('memory', memory,
151 'Maximum %s MiB for %s' % (max_val, owner))
154 def validDisk(owner, disk, machine=None):
155 """Parse and validate limits for disk for a given owner and machine."""
158 if disk > maxDisk(owner, machine):
159 raise InvalidInput('disk', disk,
160 "Maximum %s G" % maxDisk(owner, machine))
161 disk = int(disk * 1024)
162 if disk < MIN_DISK_SINGLE * 1024:
165 raise InvalidInput('disk', disk,
166 "Minimum %s GiB" % MIN_DISK_SINGLE)
169 def validVmType(vm_type):
172 t = Type.get(vm_type)
174 raise CodeError("Invalid vm type '%s'" % vm_type)
177 def testMachineId(user, machine_id, exists=True):
178 """Parse, validate and check authorization for a given user and machine.
180 If exists is False, don't check that it exists.
182 if machine_id is None:
183 raise InvalidInput('machine_id', machine_id,
184 "Must specify a machine ID.")
186 machine_id = int(machine_id)
188 raise InvalidInput('machine_id', machine_id, "Must be an integer.")
189 machine = Machine.get(machine_id)
190 if exists and machine is None:
191 raise InvalidInput('machine_id', machine_id, "Does not exist.")
192 if machine is not None and not haveAccess(user, machine):
193 raise InvalidInput('machine_id', machine_id,
194 "You do not have access to this machine.")
197 def testAdmin(user, admin, machine):
198 """Determine whether a user can set the admin of a machine to this value.
200 Return the value to set the admin field to (possibly 'system:' +
201 admin). XXX is modifying this a good idea?
205 if machine is not None and admin == machine.administrator:
210 if cache_acls.isUser(admin):
212 admin = 'system:' + admin
214 if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
216 except getafsgroups.AfsProcessError, e:
218 if errmsg.startswith("pts: User or group doesn't exist"):
219 errmsg = 'The group "%s" does not exist.' % admin
220 raise InvalidInput('administrator', admin, errmsg)
221 #XXX Should we require that user is in the admin group?
224 def testOwner(user, owner, machine=None):
225 """Determine whether a user can set the owner of a machine to this value.
227 If machine is None, this is the owner of a new machine.
231 if machine is not None and owner in (machine.owner, None):
234 raise InvalidInput('owner', owner, "Owner must be specified")
236 if user not in cache_acls.expandLocker(owner):
237 raise InvalidInput('owner', owner, 'You do not have access to the '
239 except getafsgroups.AfsProcessError, e:
240 raise InvalidInput('owner', owner, str(e))
243 def testContact(user, contact, machine=None):
244 if contact in (None, machine.contact):
246 if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
247 raise InvalidInput('contact', contact, "Not a valid email.")
250 def testDisk(user, disksize, machine=None):
253 def testName(user, name, machine=None):
256 if machine is not None and name == machine.name:
258 if not Machine.select_by(name=name):
259 if not validMachineName(name):
260 raise InvalidInput('name', name, 'You must provide a machine name. Max 22 chars, alnum plus \'-\' and \'_\'.')
262 raise InvalidInput('name', name, "Name is already taken.")
264 def testHostname(user, hostname, machine):
265 for nic in machine.nics:
266 if hostname == nic.hostname:
268 # check if doesn't already exist
269 if NIC.select_by(hostname=hostname):
270 raise InvalidInput('hostname', hostname,
272 if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
273 raise InvalidInput('hostname', hostname, "Not a valid hostname; "
274 "must only use number, letters, and dashes.")