6 #from invirt.database import Machine, NIC, Type, Disk, CDROM, Autoinstall, Owner
7 #from invirt.config import structs as config
8 #from invirt.common import InvalidInput, CodeError
10 MIN_MEMORY_SINGLE = 16
14 validate takes a Machine with values for every
15 property you want to change and None for everything else
17 username is the user makeing the changes for authorization
19 Returns: list of changes to make
20 Errors: when something is invalid it throws an error
22 ToDo: create EmptyMachine Class
23 ToDo: redo all the other methods. They were stolen from web code
27 def validate(username,machineValid):
29 if machineValid.name is None:
30 raise InvalidInput('name', name, "You must provide a machine name.")
32 machine = Machine.query()
34 authorize(username,machine)
38 if machineValid.machine_id is not None:
39 machineValid.machine = testMachineId(state, machineValid.machine_id)
41 if machineValid.owner is not None:
42 machineValid.owner = testOwner(username, machineValid.owner, machine)
45 if machineValid.admin is not None:
46 machineValid.admin = testAdmin(username, machineValid.admin, machine)
49 if contact is not None:
50 machineValid.contact = testContact(machineValid.contact, machine)
53 machineValid.name = testName(machineValid.name, machine)
55 if description is not None:
56 machineValid.description = testDescription(machineValid.description, machine)
58 if machineValid.memory is not None:
59 machineValid.memory = validMemory(machineValid.memory, machine)
62 if machineValid.disksize is not None:
63 machineValid.disksize = validDisk(machineValid.disksize, machine)
66 if machineValid.vmtype is not None:
67 machineValid.vmtype = validVmType(machineValid.vmtype)
69 if machineValid.cdrom is not None:
70 if not CDROM.query().get(machineValid.cdrom):
71 raise CodeError("Invalid cdrom type '%s'" % machineValid.cdrom)
73 if machineValid.autoinstall is not None:
74 #raise InvalidInput('autoinstall', 'install',
75 # "The autoinstaller has been temporarily disabled")
76 machineValid.autoinstall = Autoinstall.query().get(machineValid.autoinstall)
79 validQuota(username, machineValid.owner, machine)
84 def getMachinesByOwner(owner, machine=None):
85 """Return the machines owned by the same as a machine.
87 If the machine is None, return the machines owned by the same
92 return Machine.query().filter_by(owner=owner)
94 def maxMemory(owner, g, machine=None, on=True):
95 """Return the maximum memory for a machine or a user.
97 If machine is None, return the memory available for a new
98 machine. Else, return the maximum that machine can have.
100 on is whether the machine should be turned on. If false, the max
101 memory for the machine to change to, if it is left off, is
104 (quota_total, quota_single) = Owner.getMemoryQuotas(machine.owner if machine else owner)
108 machines = getMachinesByOwner(owner, machine)
109 active_machines = [m for m in machines if m.name in g.xmlist_raw]
110 mem_usage = sum([x.memory for x in active_machines if x != machine])
111 return min(quota_single, quota_total-mem_usage)
113 def maxDisk(owner, machine=None):
114 """Return the maximum disk that a machine can reach.
116 If machine is None, the maximum disk for a new machine. Otherwise,
117 return the maximum that a given machine can be changed to.
119 (quota_total, quota_single) = Owner.getDiskQuotas(machine.owner if machine else owner)
121 if machine is not None:
122 machine_id = machine.machine_id
125 disk_usage = Disk.query().filter(Disk.c.machine_id != machine_id).\
127 filter_by(owner=owner).sum(Disk.c.size) or 0
128 return min(quota_single, quota_total-disk_usage/1024.)
130 def cantAddVm(owner, g):
131 machines = getMachinesByOwner(owner)
132 active_machines = [m for m in machines if m.name in g.xmlist_raw]
133 (quota_total, quota_active) = Owner.getVMQuotas(owner)
134 if machines.count() >= quota_total:
135 return 'You have too many VMs to create a new one.'
136 if len(active_machines) >= quota_active:
137 return ('You already have the maximum number of VMs turned on. '
138 'To create more, turn one off.')
141 def haveAccess(user, state, machine):
142 """Return whether a user has administrative access to a machine"""
143 return (user in cache_acls.accessList(machine)
144 or (machine.adminable and state.isadmin))
146 def owns(user, machine):
147 """Return whether a user owns a machine"""
148 return user in expandLocker(machine.owner)
150 def validMachineName(name):
151 """Check that name is valid for a machine name"""
154 charset = string.lowercase + string.digits + '-'
155 if '-' in (name[0], name[-1]) or len(name) > 63:
162 def validMemory(owner, g, memory, machine=None, on=True):
163 """Parse and validate limits for memory for a given owner and machine.
165 on is whether the memory must be valid after the machine is
170 if memory < MIN_MEMORY_SINGLE:
173 raise InvalidInput('memory', memory,
174 "Minimum %s MiB" % MIN_MEMORY_SINGLE)
175 max_val = maxMemory(owner, g, machine, on)
176 if not g.isadmin and memory > max_val:
177 raise InvalidInput('memory', memory,
178 'Maximum %s MiB for %s' % (max_val, owner))
181 def validDisk(owner, g, disk, machine=None):
182 """Parse and validate limits for disk for a given owner and machine."""
185 if not g.isadmin and disk > maxDisk(owner, machine):
186 raise InvalidInput('disk', disk,
187 "Maximum %s G" % maxDisk(owner, machine))
188 disk = int(disk * 1024)
189 if disk < MIN_DISK_SINGLE * 1024:
192 raise InvalidInput('disk', disk,
193 "Minimum %s GiB" % MIN_DISK_SINGLE)
196 def validVmType(vm_type):
199 t = Type.query().get(vm_type)
201 raise CodeError("Invalid vm type '%s'" % vm_type)
204 def testMachineId(user, state, machine_id, exists=True):
205 """Parse, validate and check authorization for a given user and machine.
207 If exists is False, don't check that it exists.
209 if machine_id is None:
210 raise InvalidInput('machine_id', machine_id,
211 "Must specify a machine ID.")
213 machine_id = int(machine_id)
215 raise InvalidInput('machine_id', machine_id, "Must be an integer.")
216 machine = Machine.query().get(machine_id)
217 if exists and machine is None:
218 raise InvalidInput('machine_id', machine_id, "Does not exist.")
219 if machine is not None and not haveAccess(user, state, machine):
220 raise InvalidInput('machine_id', machine_id,
221 "You do not have access to this machine.")
224 def testAdmin(user, admin, machine):
225 """Determine whether a user can set the admin of a machine to this value.
227 Return the value to set the admin field to (possibly 'system:' +
228 admin). XXX is modifying this a good idea?
232 if machine is not None and admin == machine.administrator:
237 if cache_acls.isUser(admin):
239 admin = 'system:' + admin
241 if user in getafsgroups.getAfsGroupMembers(admin, config.authz.afs.cells[0].cell):
243 except getafsgroups.AfsProcessError, e:
245 if errmsg.startswith("pts: User or group doesn't exist"):
246 errmsg = 'The group "%s" does not exist.' % admin
247 raise InvalidInput('administrator', admin, errmsg)
248 #XXX Should we require that user is in the admin group?
251 def testOwner(user, owner, machine=None):
252 """Determine whether a user can set the owner of a machine to this value.
254 If machine is None, this is the owner of a new machine.
256 if machine is not None and owner in (machine.owner, None):
259 raise InvalidInput('owner', owner, "Owner must be specified")
261 raise InvalidInput('owner', owner, "No cross-realm Hesiod lockers allowed")
263 if user not in cache_acls.expandLocker(owner):
264 raise InvalidInput('owner', owner, 'You do not have access to the '
266 except getafsgroups.AfsProcessError, e:
267 raise InvalidInput('owner', owner, str(e))
270 def testContact(user, contact, machine=None):
271 if contact is None or (machine is not None and contact == machine.contact):
273 if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
274 raise InvalidInput('contact', contact, "Not a valid email.")
277 def testName(user, name, machine=None):
281 if machine is not None and name == machine.name:
284 hostname = '%s.%s.' % (name, config.dns.domains[0])
285 resolver = dns.resolver.Resolver()
286 resolver.nameservers = ['127.0.0.1']
288 resolver.query(hostname, 'A')
289 except dns.resolver.NoAnswer, e:
290 # If we can get the TXT record, then we can verify it's
291 # reserved. If this lookup fails, let it bubble up and be
293 answer = resolver.query(hostname, 'TXT')
294 txt = answer[0].strings[0]
295 if txt.startswith('reserved'):
296 raise InvalidInput('name', name, 'The name you have requested has been %s. For more information, contact us at %s' % (txt, config.dns.contact))
298 # If the hostname didn't exist, it would have thrown an
299 # exception by now - error out
300 raise InvalidInput('name', name, 'Name is already taken.')
301 except dns.resolver.NXDOMAIN, e:
302 if not validMachineName(name):
303 raise InvalidInput('name', name, 'You must provide a machine name. Max 63 chars, alnum plus \'-\', does not begin or end with \'-\'.')
308 # Any other error is a validation failure
309 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)
311 def testDescription(user, description, machine=None):
312 if description is None or description.strip() == '':
314 return description.strip()
316 def testHostname(user, hostname, machine):
317 for nic in machine.nics:
318 if hostname == nic.hostname:
320 # check if doesn't already exist
321 if NIC.select_by(hostname=hostname):
322 raise InvalidInput('hostname', hostname,
324 if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
325 raise InvalidInput('hostname', hostname, "Not a valid hostname; "
326 "must only use number, letters, and dashes.")