8 #from invirt.database import Machine, NIC, Type, Disk, CDROM, Autoinstall, Owner
9 #from invirt.config import structs as config
10 #from invirt.common import InvalidInput, CodeError
12 MIN_MEMORY_SINGLE = 16
16 validate takes a Machine with values for every
17 property you want to change and None for everything else
19 username is the user makeing the changes for authorization
21 Returns: list of changes to make
22 Errors: when something is invalid it throws an error
24 ToDo: create EmptyMachine Class
25 ToDo: redo all the other methods. They were stolen from web code
29 def validate(username,machineValid):
31 if machineValid.name is None:
32 raise InvalidInput('name', name, "You must provide a machine name.")
34 machine = Machine.query()
36 authorize(username,machine)
40 if machineValid.machine_id is not None:
41 machineValid.machine = testMachineId(state, machineValid.machine_id)
43 if machineValid.owner is not None:
44 machineValid.owner = testOwner(username, machineValid.owner, machine)
47 if machineValid.admin is not None:
48 machineValid.admin = testAdmin(username, machineValid.admin, machine)
51 if contact is not None:
52 machineValid.contact = testContact(machineValid.contact, machine)
55 machineValid.name = testName(machineValid.name, machine)
57 if description is not None:
58 machineValid.description = testDescription(machineValid.description, machine)
60 if machineValid.memory is not None:
61 machineValid.memory = validMemory(machineValid.memory, machine)
64 if machineValid.disksize is not None:
65 machineValid.disksize = validDisk(machineValid.disksize, machine)
68 if machineValid.vmtype is not None:
69 machineValid.vmtype = validVmType(machineValid.vmtype)
71 if machineValid.cdrom is not None:
72 if not CDROM.query().get(machineValid.cdrom):
73 raise CodeError("Invalid cdrom type '%s'" % machineValid.cdrom)
75 if machineValid.autoinstall is not None:
76 #raise InvalidInput('autoinstall', 'install',
77 # "The autoinstaller has been temporarily disabled")
78 machineValid.autoinstall = Autoinstall.query().get(machineValid.autoinstall)
81 validQuota(username, machineValid.owner, machine)
86 def getMachinesByOwner(owner, machine=None):
87 """Return the machines owned by the same as a machine.
89 If the machine is None, return the machines owned by the same
94 return Machine.query().filter_by(owner=owner)
96 def maxMemory(owner, g, machine=None, on=True):
97 """Return the maximum memory for a machine or a user.
99 If machine is None, return the memory available for a new
100 machine. Else, return the maximum that machine can have.
102 on is whether the machine should be turned on. If false, the max
103 memory for the machine to change to, if it is left off, is
106 (quota_total, quota_single) = Owner.getMemoryQuotas(machine.owner if machine else owner)
110 machines = getMachinesByOwner(owner, machine)
111 active_machines = [m for m in machines if m.name in g.xmlist_raw]
112 mem_usage = sum([x.memory for x in active_machines if x != machine])
113 return min(quota_single, quota_total-mem_usage)
115 def maxDisk(owner, machine=None):
116 """Return the maximum disk that a machine can reach.
118 If machine is None, the maximum disk for a new machine. Otherwise,
119 return the maximum that a given machine can be changed to.
121 (quota_total, quota_single) = Owner.getDiskQuotas(machine.owner if machine else owner)
123 if machine is not None:
124 machine_id = machine.machine_id
127 disk_usage = Disk.query().filter(Disk.c.machine_id != machine_id).\
129 filter_by(owner=owner).sum(Disk.c.size) or 0
130 return min(quota_single, quota_total-disk_usage/1024.)
132 def cantAddVm(owner, g):
133 machines = getMachinesByOwner(owner)
134 active_machines = [m for m in machines if m.name in g.xmlist_raw]
135 (quota_total, quota_active) = Owner.getVMQuotas(owner)
136 if machines.count() >= quota_total:
137 return 'You have too many VMs to create a new one.'
138 if len(active_machines) >= quota_active:
139 return ('You already have the maximum number of VMs turned on. '
140 'To create more, turn one off.')
143 def haveAccess(user, state, machine):
144 """Return whether a user has administrative access to a machine"""
145 return (user in cache_acls.accessList(machine)
146 or (machine.adminable and state.isadmin))
148 def owns(user, machine):
149 """Return whether a user owns a machine"""
150 return user in expandLocker(machine.owner)
152 def validMachineName(name):
153 """Check that name is valid for a machine name"""
156 charset = string.lowercase + string.digits + '-'
157 if '-' in (name[0], name[-1]) or len(name) > 63:
164 def validMemory(owner, g, memory, machine=None, on=True):
165 """Parse and validate limits for memory for a given owner and machine.
167 on is whether the memory must be valid after the machine is
172 if memory < MIN_MEMORY_SINGLE:
175 raise InvalidInput('memory', memory,
176 "Minimum %s MiB" % MIN_MEMORY_SINGLE)
177 max_val = maxMemory(owner, g, machine, on)
178 if not g.isadmin and memory > max_val:
179 raise InvalidInput('memory', memory,
180 'Maximum %s MiB for %s' % (max_val, owner))
183 def validDisk(owner, g, disk, machine=None):
184 """Parse and validate limits for disk for a given owner and machine."""
187 if not g.isadmin and disk > maxDisk(owner, machine):
188 raise InvalidInput('disk', disk,
189 "Maximum %s G" % maxDisk(owner, machine))
190 disk = int(disk * 1024)
191 if disk < MIN_DISK_SINGLE * 1024:
194 raise InvalidInput('disk', disk,
195 "Minimum %s GiB" % MIN_DISK_SINGLE)
198 def validVmType(vm_type):
201 t = Type.query().get(vm_type)
203 raise CodeError("Invalid vm type '%s'" % vm_type)
206 def testMachineId(user, state, machine_id, exists=True):
207 """Parse, validate and check authorization for a given user and machine.
209 If exists is False, don't check that it exists.
211 if machine_id is None:
212 raise InvalidInput('machine_id', machine_id,
213 "Must specify a machine ID.")
215 machine_id = int(machine_id)
217 raise InvalidInput('machine_id', machine_id, "Must be an integer.")
218 machine = Machine.query().get(machine_id)
219 if exists and machine is None:
220 raise InvalidInput('machine_id', machine_id, "Does not exist.")
221 if machine is not None and not haveAccess(user, state, machine):
222 raise InvalidInput('machine_id', machine_id,
223 "You do not have access to this machine.")
226 def testAdmin(user, admin, machine):
227 """Determine whether a user can set the admin of a machine to this value.
229 Return the value to set the admin field to (possibly 'system:' +
230 admin). XXX is modifying this a good idea?
234 if machine is not None and admin == machine.administrator:
239 if cache_acls.isUser(admin):
241 admin = 'system:' + admin
243 if user in getafsgroups.getAfsGroupMembers(admin, config.authz.afs.cells[0].cell):
245 except getafsgroups.AfsProcessError, e:
247 if errmsg.startswith("pts: User or group doesn't exist"):
248 errmsg = 'The group "%s" does not exist.' % admin
249 raise InvalidInput('administrator', admin, errmsg)
250 #XXX Should we require that user is in the admin group?
253 def testOwner(user, owner, machine=None):
254 """Determine whether a user can set the owner of a machine to this value.
256 If machine is None, this is the owner of a new machine.
258 if machine is not None and owner in (machine.owner, None):
261 raise InvalidInput('owner', owner, "Owner must be specified")
263 raise InvalidInput('owner', owner, "No cross-realm Hesiod lockers allowed")
265 if user not in cache_acls.expandLocker(owner):
266 raise InvalidInput('owner', owner, 'You do not have access to the '
268 except getafsgroups.AfsProcessError, e:
269 raise InvalidInput('owner', owner, str(e))
272 def testContact(user, contact, machine=None):
273 if contact is None or (machine is not None and contact == machine.contact):
275 if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
276 raise InvalidInput('contact', contact, "Not a valid email.")
279 def testName(user, name, machine=None):
283 if machine is not None and name == machine.name:
286 hostname = '%s.%s.' % (name, config.dns.domains[0])
287 resolver = dns.resolver.Resolver()
288 resolver.nameservers = ['127.0.0.1']
290 resolver.query(hostname, 'A')
291 except dns.resolver.NoAnswer, e:
292 # If we can get the TXT record, then we can verify it's
293 # reserved. If this lookup fails, let it bubble up and be
295 answer = resolver.query(hostname, 'TXT')
296 txt = answer[0].strings[0]
297 if txt.startswith('reserved'):
298 raise InvalidInput('name', name, 'The name you have requested has been %s. For more information, contact us at %s' % (txt, config.dns.contact))
300 # If the hostname didn't exist, it would have thrown an
301 # exception by now - error out
302 raise InvalidInput('name', name, 'Name is already taken.')
303 except dns.resolver.NXDOMAIN, e:
304 if not validMachineName(name):
305 raise InvalidInput('name', name, 'You must provide a machine name. Max 63 chars, alnum plus \'-\', does not begin or end with \'-\'.')
310 # Any other error is a validation failure
311 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)
313 def testDescription(user, description, machine=None):
314 if description is None or description.strip() == '':
316 return description.strip()
318 def testHostname(user, hostname, machine):
319 for nic in machine.nics:
320 if hostname == nic.hostname:
322 # check if doesn't already exist
323 if NIC.select_by(hostname=hostname):
324 raise InvalidInput('hostname', hostname,
326 if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
327 raise InvalidInput('hostname', hostname, "Not a valid hostname; "
328 "must only use number, letters, and dashes.")