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):
46 def error(op, user, fields, err):
47 d = dict(op=op, user=user, errorMessage=str(err))
48 print Template(file='error.tmpl', searchList=d);
50 def validMachineName(name):
51 """Check that name is valid for a machine name"""
54 charset = string.ascii_letters + string.digits + '-_'
55 if name[0] in '-_' or len(name) > 22:
57 return all(x in charset for x in name)
59 def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
60 """Kinit with a given username and keytab"""
62 p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
63 stderr=subprocess.PIPE)
66 raise MyException("Error %s in kinit: %s" % (e, p.stderr.read()))
69 """If we lack tickets, kinit."""
70 p = subprocess.Popen(['klist', '-s'])
74 def remctl(*args, **kws):
75 """Perform a remctl and return the output.
77 kinits if necessary, and outputs errors to stderr.
80 p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
82 stdout=subprocess.PIPE,
83 stderr=subprocess.PIPE)
85 return p.stdout.read(), p.stderr.read()
87 print >> sys.stderr, 'ERROR on remctl ', args
88 print >> sys.stderr, p.stderr.read()
89 return p.stdout.read()
92 """Update the lvm partitions to include all disks in the database."""
93 remctl('web', 'lvcreate')
95 def bootMachine(machine, cdtype):
96 """Boot a machine with a given boot CD.
98 If cdtype is None, give no boot cd. Otherwise, it is the string
99 id of the CD (e.g. 'gutsy_i386')
101 if cdtype is not None:
102 remctl('web', 'vmboot', machine.name,
105 remctl('web', 'vmboot', machine.name)
107 def registerMachine(machine):
108 """Register a machine to be controlled by the web interface"""
109 remctl('web', 'register', machine.name)
111 def unregisterMachine(machine):
112 """Unregister a machine to not be controlled by the web interface"""
113 remctl('web', 'unregister', machine.name)
116 """Parse a status string into nested tuples of strings.
118 s = output of xm list --long <machine_name>
120 values = re.split('([()])', s)
122 for v in values[2:-2]: #remove initial and final '()'
129 if len(stack[-1]) == 1:
131 stack[-2].append(stack[-1])
136 stack[-1].extend(v.split())
139 def getUptimes(machines):
140 """Return a dictionary mapping machine names to uptime strings"""
141 value_string = remctl('web', 'listvms')
142 lines = value_string.splitlines()
144 for line in lines[1:]:
147 uptime = ' '.join(lst[2:])
151 def statusInfo(machine):
152 """Return the status list for a given machine.
154 Gets and parses xm list --long
156 value_string, err_string = remctl('list-long', machine.name, err=True)
157 if 'Unknown command' in err_string:
158 raise MyException("ERROR in remctl list-long %s is not registered" % (machine.name,))
159 elif 'does not exist' in err_string:
162 raise MyException("ERROR in remctl list-long %s: %s" % (machine.name, err_string))
163 status = parseStatus(value_string)
167 """Does the machine with a given status list support VNC?"""
171 if l[0] == 'device' and l[1][0] == 'vfb':
173 return 'location' in d
176 def createVm(user, name, memory, disk, is_hvm, cdrom):
177 """Create a VM and put it in the database"""
178 # put stuff in the table
179 transaction = ctx.current.create_transaction()
181 res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
182 id = res.fetchone()[0]
184 machine.machine_id = id
186 machine.memory = memory
187 machine.owner = user.username
188 machine.contact = user.email
189 machine.uuid = uuidToString(randomUUID())
190 machine.boot_off_cd = True
191 machine_type = Type.get_by(hvm=is_hvm)
192 machine.type_id = machine_type.type_id
193 ctx.current.save(machine)
194 disk = Disk(machine.machine_id,
196 open = NIC.select_by(machine_id=None)
197 if not open: #No IPs left!
198 return "No IP addresses left! Contact sipb-xen-dev@mit.edu"
200 nic.machine_id = machine.machine_id
202 ctx.current.save(nic)
203 ctx.current.save(disk)
206 transaction.rollback()
209 registerMachine(machine)
210 # tell it to boot with cdrom
211 bootMachine(machine, cdrom)
215 def create(user, fields):
216 name = fields.getfirst('name')
217 if not validMachineName(name):
218 raise MyException("Invalid name '%s'" % name)
219 name = user.username + '_' + name.lower()
221 if Machine.get_by(name=name):
222 raise MyException("A machine named '%s' already exists" % name)
224 memory = fields.getfirst('memory')
230 raise MyException("Invalid memory amount")
231 if memory > maxMemory(user):
232 raise MyException("Too much memory requested")
234 disk = fields.getfirst('disk')
237 if disk > maxDisk(user):
238 raise MyException("Too much disk requested")
239 disk = int(disk * 1024)
243 raise MyException("Invalid disk amount")
245 vm_type = fields.getfirst('vmtype')
246 if vm_type not in ('hvm', 'paravm'):
247 raise MyException("Invalid vm type '%s'" % vm_type)
248 is_hvm = (vm_type == 'hvm')
250 cdrom = fields.getfirst('cdrom')
251 if cdrom is not None and not CDROM.get(cdrom):
252 raise MyException("Invalid cdrom type '%s'" % cdrom)
254 machine = createVm(user, name, memory, disk, is_hvm, cdrom)
255 if isinstance(machine, basestring):
256 raise MyException(machine)
259 print Template(file='create.tmpl',
262 def listVms(user, fields):
263 machines = Machine.select()
266 uptimes = getUptimes(machines)
267 on = has_vnc = uptimes
269 # status = statusInfo(m)
270 # on[m.name] = status is not None
271 # has_vnc[m.name] = hasVnc(status)
273 maxmem=maxMemory(user),
274 maxdisk=maxDisk(user),
278 cdroms=CDROM.select())
279 print Template(file='list.tmpl', searchList=d)
281 def testMachineId(user, machineId, exists=True):
282 if machineId is None:
283 raise MyException("No machine ID specified")
285 machineId = int(machineId)
287 raise MyException("Invalid machine ID '%s'" % machineId)
288 machine = Machine.get(machineId)
289 if exists and machine is None:
290 raise MyException("No such machine ID '%s'" % machineId)
291 if not haveAccess(user, machine):
292 raise MyException("No access to machine ID '%s'" % machineId)
295 def vnc(user, fields):
298 Note that due to same-domain restrictions, the applet connects to
299 the webserver, which needs to forward those requests to the xen
300 server. The Xen server runs another proxy that (1) authenticates
301 and (2) finds the correct port for the VM.
303 You might want iptables like:
305 -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
306 -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
307 -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
309 machine = testMachineId(user, fields.getfirst('machine_id'))
312 TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
315 data["user"] = user.username
316 data["machine"]=machine.name
317 data["expires"]=time.time()+(5*60)
318 pickledData = cPickle.dumps(data)
319 m = hmac.new(TOKEN_KEY, digestmod=sha)
320 m.update(pickledData)
321 token = {'data': pickledData, 'digest': m.digest()}
322 token = cPickle.dumps(token)
323 token = base64.urlsafe_b64encode(token)
327 hostname=os.environ.get('SERVER_NAME', 'localhost'),
329 print Template(file='vnc.tmpl',
332 def getNicInfo(data_dict, machine):
333 data_dict['num_nics'] = len(machine.nics)
334 nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
335 ('nic%s_mac', 'NIC %s MAC Addr'),
336 ('nic%s_ip', 'NIC %s IP'),
339 for i in range(len(machine.nics)):
340 nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
341 data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
342 data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
343 data_dict['nic%s_ip' % i] = machine.nics[i].ip
344 if len(machine.nics) == 1:
345 nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
348 def getDiskInfo(data_dict, machine):
349 data_dict['num_disks'] = len(machine.disks)
350 disk_fields_template = [('%s_size', '%s size')]
352 for disk in machine.disks:
353 name = disk.guest_device_name
354 disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
355 data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
358 def deleteVM(machine):
359 transaction = ctx.current.create_transaction()
360 delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
362 for nic in machine.nics:
363 nic.machine_id = None
365 ctx.current.save(nic)
366 for disk in machine.disks:
367 ctx.current.delete(disk)
368 ctx.current.delete(machine)
371 transaction.rollback()
373 for mname, dname in delete_disk_pairs:
374 remctl('web', 'lvremove', mname, dname)
375 unregisterMachine(machine)
377 def command(user, fields):
378 print time.time()-start_time
379 machine = testMachineId(user, fields.getfirst('machine_id'))
380 action = fields.getfirst('action')
381 cdrom = fields.getfirst('cdrom')
382 print time.time()-start_time
383 if cdrom is not None and not CDROM.get(cdrom):
384 raise MyException("Invalid cdrom type '%s'" % cdrom)
385 if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
386 raise MyException("Invalid action '%s'" % action)
387 if action == 'Reboot':
388 if cdrom is not None:
389 remctl('reboot', machine.name, cdrom)
391 remctl('reboot', machine.name)
392 elif action == 'Power on':
393 bootMachine(machine, cdrom)
394 elif action == 'Power off':
395 remctl('destroy', machine.name)
396 elif action == 'Shutdown':
397 remctl('shutdown', machine.name)
398 elif action == 'Delete VM':
400 print time.time()-start_time
405 print Template(file="command.tmpl", searchList=d)
407 def modify(user, fields):
408 machine = testMachineId(user, fields.getfirst('machine_id'))
411 def info(user, fields):
412 machine = testMachineId(user, fields.getfirst('machine_id'))
413 status = statusInfo(machine)
414 has_vnc = hasVnc(status)
416 main_status = dict(name=machine.name,
417 memory=str(machine.memory))
419 main_status = dict(status[1:])
420 start_time = float(main_status.get('start_time', 0))
421 uptime = datetime.timedelta(seconds=int(time.time()-start_time))
422 cpu_time_float = float(main_status.get('cpu_time', 0))
423 cputime = datetime.timedelta(seconds=int(cpu_time_float))
424 display_fields = """name uptime memory state cpu_weight on_reboot
425 on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
426 display_fields = [('name', 'Name'),
428 ('contact', 'Contact'),
430 ('uptime', 'uptime'),
431 ('cputime', 'CPU usage'),
434 ('state', 'state (xen format)'),
435 ('cpu_weight', 'CPU weight'),
436 ('on_reboot', 'Action on VM reboot'),
437 ('on_poweroff', 'Action on VM poweroff'),
438 ('on_crash', 'Action on VM crash'),
439 ('on_xend_start', 'Action on Xen start'),
440 ('on_xend_stop', 'Action on Xen stop'),
441 ('bootloader', 'Bootloader options'),
445 machine_info['owner'] = machine.owner
446 machine_info['contact'] = machine.contact
448 nic_fields = getNicInfo(machine_info, machine)
449 nic_point = display_fields.index('NIC_INFO')
450 display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
452 disk_fields = getDiskInfo(machine_info, machine)
453 disk_point = display_fields.index('DISK_INFO')
454 display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
456 main_status['memory'] += ' MB'
457 for field, disp in display_fields:
458 if field in ('uptime', 'cputime'):
459 fields.append((disp, locals()[field]))
460 elif field in main_status:
461 fields.append((disp, main_status[field]))
462 elif field in machine_info:
463 fields.append((disp, machine_info[field]))
466 #fields.append((disp, None))
469 cdroms=CDROM.select(),
470 on=status is not None,
475 maxmem=maxMemory(user, machine),
476 maxdisk=maxDisk(user, machine),
478 print Template(file='info.tmpl',
481 mapping = dict(list=listVms,
488 if __name__ == '__main__':
489 start_time = time.time()
490 fields = cgi.FieldStorage()
493 email = 'moo@cow.com'
495 connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
496 operation = os.environ.get('PATH_INFO', '')
501 if operation.startswith('/'):
502 operation = operation[1:]
506 fun = mapping.get(operation,
508 error(operation, u, e,
509 "Invalid operation '%s'" % operation))
512 except MyException, err:
513 error(operation, u, fields, err)