7 from sipb_xen_database import Machine, NIC, Type, Disk, CDROM
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, description=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.")
31 if description is None:
32 raise InvalidInput('description', description, "You must provide a description.")
34 raise InvalidInput('memory', memory, "You must provide a memory size.")
36 raise InvalidInput('disk', disksize, "You must provide a disk size.")
38 if machine_id is not None:
39 self.machine = testMachineId(username, machine_id)
40 machine = getattr(self, 'machine', None)
42 owner = testOwner(username, owner, machine)
45 admin = testAdmin(username, admin, machine)
48 contact = testContact(username, contact, machine)
49 if contact is not None:
50 self.contact = contact
51 name = testName(username, name, machine)
54 description = testDescription(username, description, machine)
55 if description is not None:
56 self.description = description
57 if memory is not None:
58 self.memory = validMemory(self.owner, state, memory, machine,
60 if disksize is not None:
61 self.disksize = validDisk(self.owner, disksize, machine)
62 if vmtype is not None:
63 self.vmtype = validVmType(vmtype)
65 if not CDROM.get(cdrom):
66 raise CodeError("Invalid cdrom type '%s'" % cdrom)
68 if clone_from is not None:
69 if clone_from not in ('ice3', ):
70 raise CodeError("Invalid clone image '%s'" % clone_from)
71 self.clone_from = clone_from
74 def getMachinesByOwner(owner, machine=None):
75 """Return the machines owned by the same as a machine.
77 If the machine is None, return the machines owned by the same
82 return Machine.select_by(owner=owner)
84 def maxMemory(owner, g, machine=None, on=True):
85 """Return the maximum memory for a machine or a user.
87 If machine is None, return the memory available for a new
88 machine. Else, return the maximum that machine can have.
90 on is whether the machine should be turned on. If false, the max
91 memory for the machine to change to, if it is left off, is
94 if machine is not None and machine.memory > MAX_MEMORY_SINGLE:
95 # If they've been blessed, let them have it
98 return MAX_MEMORY_SINGLE
99 machines = getMachinesByOwner(owner, machine)
100 active_machines = [m for m in machines if m.name in g.xmlist_raw]
101 mem_usage = sum([x.memory for x in active_machines if x != machine])
102 return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
104 def maxDisk(owner, machine=None):
105 """Return the maximum disk that a machine can reach.
107 If machine is None, the maximum disk for a new machine. Otherwise,
108 return the maximum that a given machine can be changed to.
110 if machine is not None:
111 machine_id = machine.machine_id
114 disk_usage = Disk.query().filter_by(Disk.c.machine_id != machine_id,
115 owner=owner).sum(Disk.c.size) or 0
116 return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
118 def cantAddVm(owner, g):
119 machines = getMachinesByOwner(owner)
120 active_machines = [m for m in machines if m.name in g.xmlist_raw]
121 if len(machines) >= 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.')
128 def haveAccess(user, machine):
129 """Return whether a user has administrative access to a machine"""
130 return user in cache_acls.accessList(machine)
132 def owns(user, machine):
133 """Return whether a user owns a machine"""
134 return user in expandLocker(machine.owner)
136 def validMachineName(name):
137 """Check that name is valid for a machine name"""
140 charset = string.ascii_letters + string.digits + '-_'
141 if name[0] in '-_' or len(name) > 22:
148 def validMemory(owner, g, memory, machine=None, on=True):
149 """Parse and validate limits for memory for a given owner and machine.
151 on is whether the memory must be valid after the machine is
156 if memory < MIN_MEMORY_SINGLE:
159 raise InvalidInput('memory', memory,
160 "Minimum %s MiB" % MIN_MEMORY_SINGLE)
161 max_val = maxMemory(owner, g, machine, on)
163 raise InvalidInput('memory', memory,
164 'Maximum %s MiB for %s' % (max_val, owner))
167 def validDisk(owner, disk, machine=None):
168 """Parse and validate limits for disk for a given owner and machine."""
171 if disk > maxDisk(owner, machine):
172 raise InvalidInput('disk', disk,
173 "Maximum %s G" % maxDisk(owner, machine))
174 disk = int(disk * 1024)
175 if disk < MIN_DISK_SINGLE * 1024:
178 raise InvalidInput('disk', disk,
179 "Minimum %s GiB" % MIN_DISK_SINGLE)
182 def validVmType(vm_type):
185 t = Type.get(vm_type)
187 raise CodeError("Invalid vm type '%s'" % vm_type)
190 def testMachineId(user, machine_id, exists=True):
191 """Parse, validate and check authorization for a given user and machine.
193 If exists is False, don't check that it exists.
195 if machine_id is None:
196 raise InvalidInput('machine_id', machine_id,
197 "Must specify a machine ID.")
199 machine_id = int(machine_id)
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.")
205 if machine is not None and not haveAccess(user, machine):
206 raise InvalidInput('machine_id', machine_id,
207 "You do not have access to this machine.")
210 def testAdmin(user, admin, machine):
211 """Determine whether a user can set the admin of a machine to this value.
213 Return the value to set the admin field to (possibly 'system:' +
214 admin). XXX is modifying this a good idea?
218 if machine is not None and admin == machine.administrator:
223 if cache_acls.isUser(admin):
225 admin = 'system:' + admin
227 if user in getafsgroups.getAfsGroupMembers(admin, 'athena.mit.edu'):
229 except getafsgroups.AfsProcessError, 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)
234 #XXX Should we require that user is in the admin group?
237 def testOwner(user, owner, machine=None):
238 """Determine whether a user can set the owner of a machine to this value.
240 If machine is None, this is the owner of a new machine.
244 if machine is not None and owner in (machine.owner, None):
247 raise InvalidInput('owner', owner, "Owner must be specified")
249 if user not in cache_acls.expandLocker(owner):
250 raise InvalidInput('owner', owner, 'You do not have access to the '
252 except getafsgroups.AfsProcessError, e:
253 raise InvalidInput('owner', owner, str(e))
256 def testContact(user, contact, machine=None):
257 if contact is None or (machine is not None and contact == machine.contact):
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.")
263 def testDisk(user, disksize, machine=None):
266 def testName(user, name, machine=None):
269 if machine is not None and name == machine.name:
271 if not Machine.select_by(name=name):
272 if not validMachineName(name):
273 raise InvalidInput('name', name, 'You must provide a machine name. Max 22 chars, alnum plus \'-\' and \'_\'.')
275 raise InvalidInput('name', name, "Name is already taken.")
277 def testDescription(user, description, machine=None):
278 if description is None or description.strip() == '':
280 return description.strip()
282 def testHostname(user, hostname, machine):
283 for nic in machine.nics:
284 if hostname == nic.hostname:
286 # check if doesn't already exist
287 if NIC.select_by(hostname=hostname):
288 raise InvalidInput('hostname', hostname,
290 if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
291 raise InvalidInput('hostname', hostname, "Not a valid hostname; "
292 "must only use number, letters, and dashes.")