8 from invirt.database import Machine, NIC, Type, Disk, CDROM, Autoinstall
9 from invirt.config import structs as config
10 from invirt.common import InvalidInput
12 MAX_MEMORY_TOTAL = 512
13 MAX_MEMORY_SINGLE = 512
14 MIN_MEMORY_SINGLE = 16
22 def __init__(self, username, state, machine_id=None, name=None, description=None, owner=None,
23 admin=None, contact=None, memory=None, disksize=None,
24 vmtype=None, cdrom=None, autoinstall=None, strict=False):
25 # XXX Successive quota checks aren't a good idea, since you
26 # can't necessarily change the locker and disk size at the
28 created_new = (machine_id is None)
32 raise InvalidInput('name', name, "You must provide a machine name.")
33 if description is None:
34 raise InvalidInput('description', description, "You must provide a description.")
36 raise InvalidInput('memory', memory, "You must provide a memory size.")
38 raise InvalidInput('disk', disksize, "You must provide a disk size.")
40 if machine_id is not None:
41 self.machine = testMachineId(username, state, machine_id)
42 machine = getattr(self, 'machine', None)
44 owner = testOwner(username, owner, machine)
47 self.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, state, disksize, machine)
62 if vmtype is not None:
63 self.vmtype = validVmType(vmtype)
65 if not CDROM.query().get(cdrom):
66 raise CodeError("Invalid cdrom type '%s'" % cdrom)
68 if autoinstall is not None:
69 #raise InvalidInput('autoinstall', 'install',
70 # "The autoinstaller has been temporarily disabled")
71 self.autoinstall = Autoinstall.query().get(autoinstall)
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.query().filter_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(Disk.c.machine_id != machine_id).\
116 filter_by(owner=owner).sum(Disk.c.size) or 0
117 return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
119 def cantAddVm(owner, g):
120 machines = getMachinesByOwner(owner)
121 active_machines = [m for m in machines if m.name in g.xmlist_raw]
122 if machines.count() >= MAX_VMS_TOTAL:
123 return 'You have too many VMs to create a new one.'
124 if len(active_machines) >= MAX_VMS_ACTIVE:
125 return ('You already have the maximum number of VMs turned on. '
126 'To create more, turn one off.')
129 def haveAccess(user, state, machine):
130 """Return whether a user has administrative access to a machine"""
131 return (user in cache_acls.accessList(machine)
132 or (machine.adminable and state.isadmin))
134 def owns(user, machine):
135 """Return whether a user owns a machine"""
136 return user in expandLocker(machine.owner)
138 def validMachineName(name):
139 """Check that name is valid for a machine name"""
142 charset = string.lowercase + string.digits + '-'
143 if '-' in (name[0], name[-1]) or len(name) > 63:
150 def validMemory(owner, g, memory, machine=None, on=True):
151 """Parse and validate limits for memory for a given owner and machine.
153 on is whether the memory must be valid after the machine is
158 if memory < MIN_MEMORY_SINGLE:
161 raise InvalidInput('memory', memory,
162 "Minimum %s MiB" % MIN_MEMORY_SINGLE)
163 max_val = maxMemory(owner, g, machine, on)
164 if not g.isadmin and memory > max_val:
165 raise InvalidInput('memory', memory,
166 'Maximum %s MiB for %s' % (max_val, owner))
169 def validDisk(owner, g, disk, machine=None):
170 """Parse and validate limits for disk for a given owner and machine."""
173 if not g.isadmin and disk > maxDisk(owner, machine):
174 raise InvalidInput('disk', disk,
175 "Maximum %s G" % maxDisk(owner, machine))
176 disk = int(disk * 1024)
177 if disk < MIN_DISK_SINGLE * 1024:
180 raise InvalidInput('disk', disk,
181 "Minimum %s GiB" % MIN_DISK_SINGLE)
184 def validVmType(vm_type):
187 t = Type.query().get(vm_type)
189 raise CodeError("Invalid vm type '%s'" % vm_type)
192 def testMachineId(user, state, machine_id, exists=True):
193 """Parse, validate and check authorization for a given user and machine.
195 If exists is False, don't check that it exists.
197 if machine_id is None:
198 raise InvalidInput('machine_id', machine_id,
199 "Must specify a machine ID.")
201 machine_id = int(machine_id)
203 raise InvalidInput('machine_id', machine_id, "Must be an integer.")
204 machine = Machine.query().get(machine_id)
205 if exists and machine is None:
206 raise InvalidInput('machine_id', machine_id, "Does not exist.")
207 if machine is not None and not haveAccess(user, state, machine):
208 raise InvalidInput('machine_id', machine_id,
209 "You do not have access to this machine.")
212 def testAdmin(user, admin, machine):
213 """Determine whether a user can set the admin of a machine to this value.
215 Return the value to set the admin field to (possibly 'system:' +
216 admin). XXX is modifying this a good idea?
220 if machine is not None and admin == machine.administrator:
225 if cache_acls.isUser(admin):
227 admin = 'system:' + admin
229 if user in getafsgroups.getAfsGroupMembers(admin, config.authz[0].cell):
231 except getafsgroups.AfsProcessError, e:
233 if errmsg.startswith("pts: User or group doesn't exist"):
234 errmsg = 'The group "%s" does not exist.' % admin
235 raise InvalidInput('administrator', admin, errmsg)
236 #XXX Should we require that user is in the admin group?
239 def testOwner(user, owner, machine=None):
240 """Determine whether a user can set the owner of a machine to this value.
242 If machine is None, this is the owner of a new machine.
246 if machine is not None and owner in (machine.owner, None):
249 raise InvalidInput('owner', owner, "Owner must be specified")
251 if user not in cache_acls.expandLocker(owner):
252 raise InvalidInput('owner', owner, 'You do not have access to the '
254 except getafsgroups.AfsProcessError, e:
255 raise InvalidInput('owner', owner, str(e))
258 def testContact(user, contact, machine=None):
259 if contact is None or (machine is not None and contact == machine.contact):
261 if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
262 raise InvalidInput('contact', contact, "Not a valid email.")
265 def testDisk(user, disksize, machine=None):
268 def testName(user, name, machine=None):
272 if machine is not None and name == machine.name:
275 hostname = '%s.%s.' % (name, config.dns.domains[0])
276 resolver = dns.resolver.Resolver()
277 resolver.nameservers = ['127.0.0.1']
279 resolver.query(hostname, 'A')
280 except dns.resolver.NoAnswer, e:
281 # If we can get the TXT record, then we can verify it's
282 # reserved. If this lookup fails, let it bubble up and be
284 answer = resolver.query(hostname, 'TXT')
285 txt = answer[0].strings[0]
286 if txt.startswith('reserved'):
287 raise InvalidInput('name', name, 'The name you have requested has been %s. For more information, contact us at %s' % (txt, config.dns.contact))
289 # If the hostname didn't exist, it would have thrown an
290 # exception by now - error out
291 raise InvalidInput('name', name, 'Name is already taken.')
292 except dns.resolver.NXDOMAIN, e:
293 if not validMachineName(name):
294 raise InvalidInput('name', name, 'You must provide a machine name. Max 63 chars, alnum plus \'-\', does not begin or end with \'-\'.')
299 # Any other error is a validation failure
300 raise InvalidInput('name', name, 'We were unable to verify that this name is available. If you believe this is in error, please contact us at %s' % config.dns.contact)
302 def testDescription(user, description, machine=None):
303 if description is None or description.strip() == '':
305 return description.strip()
307 def testHostname(user, hostname, machine):
308 for nic in machine.nics:
309 if hostname == nic.hostname:
311 # check if doesn't already exist
312 if NIC.select_by(hostname=hostname):
313 raise InvalidInput('hostname', hostname,
315 if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
316 raise InvalidInput('hostname', hostname, "Not a valid hostname; "
317 "must only use number, letters, and dashes.")