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 == 'moo':
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)
279 if not on.get(m.name):
280 has_vnc[m.name] = 'Off'
282 has_vnc[m.name] = True
284 help_name = 'paravm_console'
285 has_vnc[m.name] = 'ParaVM <span class="helplink"><a href="help?subject=%s&simple=true" target="_blank" onclick="return helppopup(\'%s\')">(?)</a></span>' % (help_name, help_name)
287 # status = statusInfo(m)
288 # on[m.name] = status is not None
289 # has_vnc[m.name] = hasVnc(status)
291 maxmem=maxMemory(user),
292 maxdisk=maxDisk(user),
296 cdroms=CDROM.select())
297 print Template(file='list.tmpl', searchList=d)
299 def testMachineId(user, machineId, exists=True):
300 if machineId is None:
301 raise MyException("No machine ID specified")
303 machineId = int(machineId)
305 raise MyException("Invalid machine ID '%s'" % machineId)
306 machine = Machine.get(machineId)
307 if exists and machine is None:
308 raise MyException("No such machine ID '%s'" % machineId)
309 if not haveAccess(user, machine):
310 raise MyException("No access to machine ID '%s'" % machineId)
313 def vnc(user, fields):
316 Note that due to same-domain restrictions, the applet connects to
317 the webserver, which needs to forward those requests to the xen
318 server. The Xen server runs another proxy that (1) authenticates
319 and (2) finds the correct port for the VM.
321 You might want iptables like:
323 -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
324 -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
325 -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
327 machine = testMachineId(user, fields.getfirst('machine_id'))
330 TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
333 data["user"] = user.username
334 data["machine"]=machine.name
335 data["expires"]=time.time()+(5*60)
336 pickledData = cPickle.dumps(data)
337 m = hmac.new(TOKEN_KEY, digestmod=sha)
338 m.update(pickledData)
339 token = {'data': pickledData, 'digest': m.digest()}
340 token = cPickle.dumps(token)
341 token = base64.urlsafe_b64encode(token)
345 hostname=os.environ.get('SERVER_NAME', 'localhost'),
347 print Template(file='vnc.tmpl',
350 def getNicInfo(data_dict, machine):
351 data_dict['num_nics'] = len(machine.nics)
352 nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
353 ('nic%s_mac', 'NIC %s MAC Addr'),
354 ('nic%s_ip', 'NIC %s IP'),
357 for i in range(len(machine.nics)):
358 nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
359 data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
360 data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
361 data_dict['nic%s_ip' % i] = machine.nics[i].ip
362 if len(machine.nics) == 1:
363 nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
366 def getDiskInfo(data_dict, machine):
367 data_dict['num_disks'] = len(machine.disks)
368 disk_fields_template = [('%s_size', '%s size')]
370 for disk in machine.disks:
371 name = disk.guest_device_name
372 disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
373 data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
376 def deleteVM(machine):
377 transaction = ctx.current.create_transaction()
378 delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
380 for nic in machine.nics:
381 nic.machine_id = None
383 ctx.current.save(nic)
384 for disk in machine.disks:
385 ctx.current.delete(disk)
386 ctx.current.delete(machine)
389 transaction.rollback()
391 for mname, dname in delete_disk_pairs:
392 remctl('web', 'lvremove', mname, dname)
393 unregisterMachine(machine)
395 def command(user, fields):
396 print time.time()-start_time
397 machine = testMachineId(user, fields.getfirst('machine_id'))
398 action = fields.getfirst('action')
399 cdrom = fields.getfirst('cdrom')
400 print time.time()-start_time
401 if cdrom is not None and not CDROM.get(cdrom):
402 raise MyException("Invalid cdrom type '%s'" % cdrom)
403 if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
404 raise MyException("Invalid action '%s'" % action)
405 if action == 'Reboot':
406 if cdrom is not None:
407 remctl('reboot', machine.name, cdrom)
409 remctl('reboot', machine.name)
410 elif action == 'Power on':
411 bootMachine(machine, cdrom)
412 elif action == 'Power off':
413 remctl('destroy', machine.name)
414 elif action == 'Shutdown':
415 remctl('shutdown', machine.name)
416 elif action == 'Delete VM':
418 print time.time()-start_time
423 print Template(file="command.tmpl", searchList=d)
425 def modify(user, fields):
426 machine = testMachineId(user, fields.getfirst('machine_id'))
429 def info(user, fields):
430 machine = testMachineId(user, fields.getfirst('machine_id'))
431 status = statusInfo(machine)
432 has_vnc = hasVnc(status)
434 main_status = dict(name=machine.name,
435 memory=str(machine.memory))
437 main_status = dict(status[1:])
438 start_time = float(main_status.get('start_time', 0))
439 uptime = datetime.timedelta(seconds=int(time.time()-start_time))
440 cpu_time_float = float(main_status.get('cpu_time', 0))
441 cputime = datetime.timedelta(seconds=int(cpu_time_float))
442 display_fields = """name uptime memory state cpu_weight on_reboot
443 on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
444 display_fields = [('name', 'Name'),
446 ('contact', 'Contact'),
449 ('uptime', 'uptime'),
450 ('cputime', 'CPU usage'),
453 ('state', 'state (xen format)'),
454 ('cpu_weight', 'CPU weight'),
455 ('on_reboot', 'Action on VM reboot'),
456 ('on_poweroff', 'Action on VM poweroff'),
457 ('on_crash', 'Action on VM crash'),
458 ('on_xend_start', 'Action on Xen start'),
459 ('on_xend_stop', 'Action on Xen stop'),
460 ('bootloader', 'Bootloader options'),
464 machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
465 machine_info['owner'] = machine.owner
466 machine_info['contact'] = machine.contact
468 nic_fields = getNicInfo(machine_info, machine)
469 nic_point = display_fields.index('NIC_INFO')
470 display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
472 disk_fields = getDiskInfo(machine_info, machine)
473 disk_point = display_fields.index('DISK_INFO')
474 display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
476 main_status['memory'] += ' MB'
477 for field, disp in display_fields:
478 if field in ('uptime', 'cputime'):
479 fields.append((disp, locals()[field]))
480 elif field in main_status:
481 fields.append((disp, main_status[field]))
482 elif field in machine_info:
483 fields.append((disp, machine_info[field]))
486 #fields.append((disp, None))
489 cdroms=CDROM.select(),
490 on=status is not None,
495 maxmem=maxMemory(user, machine),
496 maxdisk=maxDisk(user, machine),
498 print Template(file='info.tmpl',
501 mapping = dict(list=listVms,
508 if __name__ == '__main__':
509 start_time = time.time()
510 fields = cgi.FieldStorage()
513 email = 'moo@cow.com'
515 connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
516 operation = os.environ.get('PATH_INFO', '')
521 if operation.startswith('/'):
522 operation = operation[1:]
526 fun = mapping.get(operation,
528 error(operation, u, e,
529 "Invalid operation '%s'" % operation))
532 except MyException, err:
533 error(operation, u, fields, err)