16 print 'Content-Type: text/html\n'
17 sys.stderr = sys.stdout
18 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
20 from Cheetah.Template import Template
21 from sipb_xen_database import *
24 class MyException(Exception):
27 # ... and stolen from xend/uuid.py
29 """Generate a random UUID."""
31 return [ random.randint(0, 255) for _ in range(0, 16) ]
34 return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
35 "%02x" * 6]) % tuple(u)
37 def maxMemory(user, machine=None):
40 def maxDisk(user, machine=None):
43 def haveAccess(user, machine):
44 if user.username == 'quentin':
46 return machine.owner == user.username
48 def error(op, user, fields, err):
49 d = dict(op=op, user=user, errorMessage=str(err))
50 print Template(file='error.tmpl', searchList=d);
52 def validMachineName(name):
53 """Check that name is valid for a machine name"""
56 charset = string.ascii_letters + string.digits + '-_'
57 if name[0] in '-_' or len(name) > 22:
59 return all(x in charset for x in name)
61 def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
62 """Kinit with a given username and keytab"""
64 p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
65 stderr=subprocess.PIPE)
68 raise MyException("Error %s in kinit: %s" % (e, p.stderr.read()))
71 """If we lack tickets, kinit."""
72 p = subprocess.Popen(['klist', '-s'])
76 def remctl(*args, **kws):
77 """Perform a remctl and return the output.
79 kinits if necessary, and outputs errors to stderr.
82 p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
84 stdout=subprocess.PIPE,
85 stderr=subprocess.PIPE)
87 return p.stdout.read(), p.stderr.read()
89 print >> sys.stderr, 'ERROR on remctl ', args
90 print >> sys.stderr, p.stderr.read()
91 return p.stdout.read()
94 """Update the lvm partitions to include all disks in the database."""
95 remctl('web', 'lvcreate')
97 def bootMachine(machine, cdtype):
98 """Boot a machine with a given boot CD.
100 If cdtype is None, give no boot cd. Otherwise, it is the string
101 id of the CD (e.g. 'gutsy_i386')
103 if cdtype is not None:
104 remctl('web', 'vmboot', machine.name,
107 remctl('web', 'vmboot', machine.name)
109 def registerMachine(machine):
110 """Register a machine to be controlled by the web interface"""
111 remctl('web', 'register', machine.name)
113 def unregisterMachine(machine):
114 """Unregister a machine to not be controlled by the web interface"""
115 remctl('web', 'unregister', machine.name)
118 """Parse a status string into nested tuples of strings.
120 s = output of xm list --long <machine_name>
122 values = re.split('([()])', s)
124 for v in values[2:-2]: #remove initial and final '()'
131 if len(stack[-1]) == 1:
133 stack[-2].append(stack[-1])
138 stack[-1].extend(v.split())
141 def getUptimes(machines):
142 """Return a dictionary mapping machine names to uptime strings"""
143 value_string = remctl('web', 'listvms')
144 lines = value_string.splitlines()
146 for line in lines[1:]:
149 uptime = ' '.join(lst[2:])
153 def statusInfo(machine):
154 """Return the status list for a given machine.
156 Gets and parses xm list --long
158 value_string, err_string = remctl('list-long', machine.name, err=True)
159 if 'Unknown command' in err_string:
160 raise MyException("ERROR in remctl list-long %s is not registered" % (machine.name,))
161 elif 'does not exist' in err_string:
164 raise MyException("ERROR in remctl list-long %s: %s" % (machine.name, err_string))
165 status = parseStatus(value_string)
169 """Does the machine with a given status list support VNC?"""
173 if l[0] == 'device' and l[1][0] == 'vfb':
175 return 'location' in d
178 def createVm(user, name, memory, disk, is_hvm, cdrom):
179 """Create a VM and put it in the database"""
180 # put stuff in the table
181 transaction = ctx.current.create_transaction()
183 res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
184 id = res.fetchone()[0]
186 machine.machine_id = id
188 machine.memory = memory
189 machine.owner = user.username
190 machine.contact = user.email
191 machine.uuid = uuidToString(randomUUID())
192 machine.boot_off_cd = True
193 machine_type = Type.get_by(hvm=is_hvm)
194 machine.type_id = machine_type.type_id
195 ctx.current.save(machine)
196 disk = Disk(machine.machine_id,
198 open = NIC.select_by(machine_id=None)
199 if not open: #No IPs left!
200 return "No IP addresses left! Contact sipb-xen-dev@mit.edu"
202 nic.machine_id = machine.machine_id
204 ctx.current.save(nic)
205 ctx.current.save(disk)
208 transaction.rollback()
211 registerMachine(machine)
212 # tell it to boot with cdrom
213 bootMachine(machine, cdrom)
217 def validMemory(user, memory, machine=None):
223 raise MyException("Invalid memory amount")
224 if memory > maxMemory(user, machine):
225 raise MyException("Too much memory requested")
228 def validDisk(user, disk, machine=None):
231 if disk > maxDisk(user, machine):
232 raise MyException("Too much disk requested")
233 disk = int(disk * 1024)
237 raise MyException("Invalid disk amount")
240 def create(user, fields):
241 name = fields.getfirst('name')
242 if not validMachineName(name):
243 raise MyException("Invalid name '%s'" % name)
244 name = user.username + '_' + name.lower()
246 if Machine.get_by(name=name):
247 raise MyException("A machine named '%s' already exists" % name)
249 memory = fields.getfirst('memory')
250 memory = validMemory(user, memory)
252 disk = fields.getfirst('disk')
253 disk = validDisk(user, disk)
255 vm_type = fields.getfirst('vmtype')
256 if vm_type not in ('hvm', 'paravm'):
257 raise MyException("Invalid vm type '%s'" % vm_type)
258 is_hvm = (vm_type == 'hvm')
260 cdrom = fields.getfirst('cdrom')
261 if cdrom is not None and not CDROM.get(cdrom):
262 raise MyException("Invalid cdrom type '%s'" % cdrom)
264 machine = createVm(user, name, memory, disk, is_hvm, cdrom)
265 if isinstance(machine, basestring):
266 raise MyException(machine)
269 print Template(file='create.tmpl',
272 def listVms(user, fields):
273 machines = [m for m in Machine.select() if haveAccess(user, m)]
276 uptimes = getUptimes(machines)
277 on = has_vnc = uptimes
279 # status = statusInfo(m)
280 # on[m.name] = status is not None
281 # has_vnc[m.name] = hasVnc(status)
283 maxmem=maxMemory(user),
284 maxdisk=maxDisk(user),
288 cdroms=CDROM.select())
289 print Template(file='list.tmpl', searchList=d)
291 def testMachineId(user, machineId, exists=True):
292 if machineId is None:
293 raise MyException("No machine ID specified")
295 machineId = int(machineId)
297 raise MyException("Invalid machine ID '%s'" % machineId)
298 machine = Machine.get(machineId)
299 if exists and machine is None:
300 raise MyException("No such machine ID '%s'" % machineId)
301 if not haveAccess(user, machine):
302 raise MyException("No access to machine ID '%s'" % machineId)
305 def vnc(user, fields):
308 Note that due to same-domain restrictions, the applet connects to
309 the webserver, which needs to forward those requests to the xen
310 server. The Xen server runs another proxy that (1) authenticates
311 and (2) finds the correct port for the VM.
313 You might want iptables like:
315 -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
316 -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp --dport 10003 -j SNAT --to-source 18.187.7.142
317 -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
319 machine = testMachineId(user, fields.getfirst('machine_id'))
322 TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
325 data["user"] = user.username
326 data["machine"]=machine.name
327 data["expires"]=time.time()+(5*60)
328 pickledData = cPickle.dumps(data)
329 m = hmac.new(TOKEN_KEY, digestmod=sha)
330 m.update(pickledData)
331 token = {'data': pickledData, 'digest': m.digest()}
332 token = cPickle.dumps(token)
333 token = base64.urlsafe_b64encode(token)
337 hostname=os.environ.get('SERVER_NAME', 'localhost'),
339 print Template(file='vnc.tmpl',
342 def getNicInfo(data_dict, machine):
343 data_dict['num_nics'] = len(machine.nics)
344 nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
345 ('nic%s_mac', 'NIC %s MAC Addr'),
346 ('nic%s_ip', 'NIC %s IP'),
349 for i in range(len(machine.nics)):
350 nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
351 data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
352 data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
353 data_dict['nic%s_ip' % i] = machine.nics[i].ip
354 if len(machine.nics) == 1:
355 nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
358 def getDiskInfo(data_dict, machine):
359 data_dict['num_disks'] = len(machine.disks)
360 disk_fields_template = [('%s_size', '%s size')]
362 for disk in machine.disks:
363 name = disk.guest_device_name
364 disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
365 data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
368 def deleteVM(machine):
369 transaction = ctx.current.create_transaction()
370 delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
372 for nic in machine.nics:
373 nic.machine_id = None
375 ctx.current.save(nic)
376 for disk in machine.disks:
377 ctx.current.delete(disk)
378 ctx.current.delete(machine)
381 transaction.rollback()
383 for mname, dname in delete_disk_pairs:
384 remctl('web', 'lvremove', mname, dname)
385 unregisterMachine(machine)
387 def command(user, fields):
388 print time.time()-start_time
389 machine = testMachineId(user, fields.getfirst('machine_id'))
390 action = fields.getfirst('action')
391 cdrom = fields.getfirst('cdrom')
392 print time.time()-start_time
393 if cdrom is not None and not CDROM.get(cdrom):
394 raise MyException("Invalid cdrom type '%s'" % cdrom)
395 if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
396 raise MyException("Invalid action '%s'" % action)
397 if action == 'Reboot':
398 if cdrom is not None:
399 remctl('reboot', machine.name, cdrom)
401 remctl('reboot', machine.name)
402 elif action == 'Power on':
403 bootMachine(machine, cdrom)
404 elif action == 'Power off':
405 remctl('destroy', machine.name)
406 elif action == 'Shutdown':
407 remctl('shutdown', machine.name)
408 elif action == 'Delete VM':
410 print time.time()-start_time
415 print Template(file="command.tmpl", searchList=d)
417 def modify(user, fields):
418 machine = testMachineId(user, fields.getfirst('machine_id'))
421 def info(user, fields):
422 machine = testMachineId(user, fields.getfirst('machine_id'))
423 status = statusInfo(machine)
424 has_vnc = hasVnc(status)
426 main_status = dict(name=machine.name,
427 memory=str(machine.memory))
429 main_status = dict(status[1:])
430 start_time = float(main_status.get('start_time', 0))
431 uptime = datetime.timedelta(seconds=int(time.time()-start_time))
432 cpu_time_float = float(main_status.get('cpu_time', 0))
433 cputime = datetime.timedelta(seconds=int(cpu_time_float))
434 display_fields = """name uptime memory state cpu_weight on_reboot
435 on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
436 display_fields = [('name', 'Name'),
438 ('contact', 'Contact'),
440 ('uptime', 'uptime'),
441 ('cputime', 'CPU usage'),
444 ('state', 'state (xen format)'),
445 ('cpu_weight', 'CPU weight'),
446 ('on_reboot', 'Action on VM reboot'),
447 ('on_poweroff', 'Action on VM poweroff'),
448 ('on_crash', 'Action on VM crash'),
449 ('on_xend_start', 'Action on Xen start'),
450 ('on_xend_stop', 'Action on Xen stop'),
451 ('bootloader', 'Bootloader options'),
455 machine_info['owner'] = machine.owner
456 machine_info['contact'] = machine.contact
458 nic_fields = getNicInfo(machine_info, machine)
459 nic_point = display_fields.index('NIC_INFO')
460 display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
462 disk_fields = getDiskInfo(machine_info, machine)
463 disk_point = display_fields.index('DISK_INFO')
464 display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
466 main_status['memory'] += ' MB'
467 for field, disp in display_fields:
468 if field in ('uptime', 'cputime'):
469 fields.append((disp, locals()[field]))
470 elif field in main_status:
471 fields.append((disp, main_status[field]))
472 elif field in machine_info:
473 fields.append((disp, machine_info[field]))
476 #fields.append((disp, None))
479 cdroms=CDROM.select(),
480 on=status is not None,
485 maxmem=maxMemory(user, machine),
486 maxdisk=maxDisk(user, machine),
488 print Template(file='info.tmpl',
491 mapping = dict(list=listVms,
498 if __name__ == '__main__':
499 start_time = time.time()
500 fields = cgi.FieldStorage()
503 email = 'moo@cow.com'
505 connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
506 operation = os.environ.get('PATH_INFO', '')
511 if operation.startswith('/'):
512 operation = operation[1:]
516 fun = mapping.get(operation,
518 error(operation, u, e,
519 "Invalid operation '%s'" % operation))
522 except MyException, err:
523 error(operation, u, fields, err)