7 from invirt.database import Machine, NIC, Type, Disk, CDROM, Autoinstall
8 from invirt.config import structs as config
9 from webcommon import InvalidInput
11 MAX_MEMORY_TOTAL = 512
12 MAX_MEMORY_SINGLE = 256
13 MIN_MEMORY_SINGLE = 16
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
27 created_new = (machine_id 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.")
35 raise InvalidInput('memory', memory, "You must provide a memory size.")
37 raise InvalidInput('disk', disksize, "You must provide a disk size.")
39 if machine_id is not None:
40 self.machine = testMachineId(username, state, machine_id)
41 machine = getattr(self, 'machine', None)
43 owner = testOwner(username, owner, machine)
46 admin = testAdmin(username, admin, machine)
49 contact = testContact(username, contact, machine)
50 if contact is not None:
51 self.contact = contact
52 name = testName(username, name, machine)
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,
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)
66 if not CDROM.get(cdrom):
67 raise CodeError("Invalid cdrom type '%s'" % cdrom)
69 if autoinstall is not None:
70 self.autoinstall = Autoinstall.get(autoinstall)
73 def getMachinesByOwner(owner, machine=None):
74 """Return the machines owned by the same as a machine.
76 If the machine is None, return the machines owned by the same
81 return Machine.query().filter_by(owner=owner)
83 def maxMemory(owner, g, machine=None, on=True):
84 """Return the maximum memory for a machine or a user.
86 If machine is None, return the memory available for a new
87 machine. Else, return the maximum that machine can have.
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
93 if machine is not None and machine.memory > MAX_MEMORY_SINGLE:
94 # If they've been blessed, let them have it
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)
103 def maxDisk(owner, machine=None):
104 """Return the maximum disk that a machine can reach.
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.
109 if machine is not None:
110 machine_id = machine.machine_id
113 disk_usage = Disk.query().filter(Disk.c.machine_id != machine_id).\
115 filter_by(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 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.')
128 def 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))
133 def owns(user, machine):
134 """Return whether a user owns a machine"""
135 return user in expandLocker(machine.owner)
137 def validMachineName(name):
138 """Check that name is valid for a machine name"""
141 charset = string.lowercase + string.digits + '-'
142 if '-' in (name[0], name[-1]) or len(name) > 63:
149 def validMemory(owner, g, memory, machine=None, on=True):
150 """Parse and validate limits for memory for a given owner and machine.
152 on is whether the memory must be valid after the machine is
157 if memory < MIN_MEMORY_SINGLE:
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))
168 def validDisk(owner, g, disk, machine=None):
169 """Parse and validate limits for disk for a given owner and machine."""
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:
179 raise InvalidInput('disk', disk,
180 "Minimum %s GiB" % MIN_DISK_SINGLE)
183 def validVmType(vm_type):
186 t = Type.get(vm_type)
188 raise CodeError("Invalid vm type '%s'" % vm_type)
191 def testMachineId(user, state, machine_id, exists=True):
192 """Parse, validate and check authorization for a given user and machine.
194 If exists is False, don't check that it exists.
196 if machine_id is None:
197 raise InvalidInput('machine_id', machine_id,
198 "Must specify a machine ID.")
200 machine_id = int(machine_id)
202 raise InvalidInput('machine_id', machine_id, "Must be an integer.")
203 machine = Machine.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.")
211 def testAdmin(user, admin, machine):
212 """Determine whether a user can set the admin of a machine to this value.
214 Return the value to set the admin field to (possibly 'system:' +
215 admin). XXX is modifying this a good idea?
219 if machine is not None and admin == machine.administrator:
224 if cache_acls.isUser(admin):
226 admin = 'system:' + admin
228 if user in getafsgroups.getAfsGroupMembers(admin, config.authz[0].cell):
230 except getafsgroups.AfsProcessError, 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?
238 def testOwner(user, owner, machine=None):
239 """Determine whether a user can set the owner of a machine to this value.
241 If machine is None, this is the owner of a new machine.
245 if machine is not None and owner in (machine.owner, None):
248 raise InvalidInput('owner', owner, "Owner must be specified")
250 if user not in cache_acls.expandLocker(owner):
251 raise InvalidInput('owner', owner, 'You do not have access to the '
253 except getafsgroups.AfsProcessError, e:
254 raise InvalidInput('owner', owner, str(e))
257 def testContact(user, contact, machine=None):
258 if contact is None or (machine is not None and contact == machine.contact):
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.")
264 def testDisk(user, disksize, machine=None):
267 def testName(user, name, machine=None):
271 if machine is not None and name == machine.name:
273 if not Machine.query().filter_by(name=name):
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 \'-\'.')
277 raise InvalidInput('name', name, "Name is already taken.")
279 def testDescription(user, description, machine=None):
280 if description is None or description.strip() == '':
282 return description.strip()
284 def testHostname(user, hostname, machine):
285 for nic in machine.nics:
286 if hostname == nic.hostname:
288 # check if doesn't already exist
289 if NIC.select_by(hostname=hostname):
290 raise InvalidInput('hostname', hostname,
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.")