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)
279 if on.get(m.name) and m.type.hvm:
280 has_vnc[m.name] = True
282 has_vnc[m.name] = False
284 # status = statusInfo(m)
285 # on[m.name] = status is not None
286 # has_vnc[m.name] = hasVnc(status)
288 maxmem=maxMemory(user),
289 maxdisk=maxDisk(user),
293 cdroms=CDROM.select())
294 print Template(file='list.tmpl', searchList=d)
296 def testMachineId(user, machineId, exists=True):
297 if machineId is None:
298 raise MyException("No machine ID specified")
300 machineId = int(machineId)
302 raise MyException("Invalid machine ID '%s'" % machineId)
303 machine = Machine.get(machineId)
304 if exists and machine is None:
305 raise MyException("No such machine ID '%s'" % machineId)
306 if not haveAccess(user, machine):
307 raise MyException("No access to machine ID '%s'" % machineId)
310 def vnc(user, fields):
313 Note that due to same-domain restrictions, the applet connects to
314 the webserver, which needs to forward those requests to the xen
315 server. The Xen server runs another proxy that (1) authenticates
316 and (2) finds the correct port for the VM.
318 You might want iptables like:
320 -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
321 -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
322 -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
324 machine = testMachineId(user, fields.getfirst('machine_id'))
327 TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
330 data["user"] = user.username
331 data["machine"]=machine.name
332 data["expires"]=time.time()+(5*60)
333 pickledData = cPickle.dumps(data)
334 m = hmac.new(TOKEN_KEY, digestmod=sha)
335 m.update(pickledData)
336 token = {'data': pickledData, 'digest': m.digest()}
337 token = cPickle.dumps(token)
338 token = base64.urlsafe_b64encode(token)
342 hostname=os.environ.get('SERVER_NAME', 'localhost'),
344 print Template(file='vnc.tmpl',
347 def getNicInfo(data_dict, machine):
348 data_dict['num_nics'] = len(machine.nics)
349 nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
350 ('nic%s_mac', 'NIC %s MAC Addr'),
351 ('nic%s_ip', 'NIC %s IP'),
354 for i in range(len(machine.nics)):
355 nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
356 data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
357 data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
358 data_dict['nic%s_ip' % i] = machine.nics[i].ip
359 if len(machine.nics) == 1:
360 nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
363 def getDiskInfo(data_dict, machine):
364 data_dict['num_disks'] = len(machine.disks)
365 disk_fields_template = [('%s_size', '%s size')]
367 for disk in machine.disks:
368 name = disk.guest_device_name
369 disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
370 data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
373 def deleteVM(machine):
374 transaction = ctx.current.create_transaction()
375 delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
377 for nic in machine.nics:
378 nic.machine_id = None
380 ctx.current.save(nic)
381 for disk in machine.disks:
382 ctx.current.delete(disk)
383 ctx.current.delete(machine)
386 transaction.rollback()
388 for mname, dname in delete_disk_pairs:
389 remctl('web', 'lvremove', mname, dname)
390 unregisterMachine(machine)
392 def command(user, fields):
393 print time.time()-start_time
394 machine = testMachineId(user, fields.getfirst('machine_id'))
395 action = fields.getfirst('action')
396 cdrom = fields.getfirst('cdrom')
397 print time.time()-start_time
398 if cdrom is not None and not CDROM.get(cdrom):
399 raise MyException("Invalid cdrom type '%s'" % cdrom)
400 if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
401 raise MyException("Invalid action '%s'" % action)
402 if action == 'Reboot':
403 if cdrom is not None:
404 remctl('reboot', machine.name, cdrom)
406 remctl('reboot', machine.name)
407 elif action == 'Power on':
408 bootMachine(machine, cdrom)
409 elif action == 'Power off':
410 remctl('destroy', machine.name)
411 elif action == 'Shutdown':
412 remctl('shutdown', machine.name)
413 elif action == 'Delete VM':
415 print time.time()-start_time
420 print Template(file="command.tmpl", searchList=d)
422 def modify(user, fields):
423 machine = testMachineId(user, fields.getfirst('machine_id'))
426 def info(user, fields):
427 machine = testMachineId(user, fields.getfirst('machine_id'))
428 status = statusInfo(machine)
429 has_vnc = hasVnc(status)
431 main_status = dict(name=machine.name,
432 memory=str(machine.memory))
434 main_status = dict(status[1:])
435 start_time = float(main_status.get('start_time', 0))
436 uptime = datetime.timedelta(seconds=int(time.time()-start_time))
437 cpu_time_float = float(main_status.get('cpu_time', 0))
438 cputime = datetime.timedelta(seconds=int(cpu_time_float))
439 display_fields = """name uptime memory state cpu_weight on_reboot
440 on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
441 display_fields = [('name', 'Name'),
443 ('contact', 'Contact'),
446 ('uptime', 'uptime'),
447 ('cputime', 'CPU usage'),
450 ('state', 'state (xen format)'),
451 ('cpu_weight', 'CPU weight'),
452 ('on_reboot', 'Action on VM reboot'),
453 ('on_poweroff', 'Action on VM poweroff'),
454 ('on_crash', 'Action on VM crash'),
455 ('on_xend_start', 'Action on Xen start'),
456 ('on_xend_stop', 'Action on Xen stop'),
457 ('bootloader', 'Bootloader options'),
461 machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
462 machine_info['owner'] = machine.owner
463 machine_info['contact'] = machine.contact
465 nic_fields = getNicInfo(machine_info, machine)
466 nic_point = display_fields.index('NIC_INFO')
467 display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
469 disk_fields = getDiskInfo(machine_info, machine)
470 disk_point = display_fields.index('DISK_INFO')
471 display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
473 main_status['memory'] += ' MB'
474 for field, disp in display_fields:
475 if field in ('uptime', 'cputime'):
476 fields.append((disp, locals()[field]))
477 elif field in main_status:
478 fields.append((disp, main_status[field]))
479 elif field in machine_info:
480 fields.append((disp, machine_info[field]))
483 #fields.append((disp, None))
486 cdroms=CDROM.select(),
487 on=status is not None,
492 maxmem=maxMemory(user, machine),
493 maxdisk=maxDisk(user, machine),
495 print Template(file='info.tmpl',
498 mapping = dict(list=listVms,
505 if __name__ == '__main__':
506 start_time = time.time()
507 fields = cgi.FieldStorage()
510 email = 'moo@cow.com'
512 connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
513 operation = os.environ.get('PATH_INFO', '')
518 if operation.startswith('/'):
519 operation = operation[1:]
523 fun = mapping.get(operation,
525 error(operation, u, e,
526 "Invalid operation '%s'" % operation))
529 except MyException, err:
530 error(operation, u, fields, err)