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 validMemory(user, memory, machine=None):
221 raise MyException("Invalid memory amount")
222 if memory > maxMemory(user, machine):
223 raise MyException("Too much memory requested")
226 def validDisk(user, disk, machine=None):
229 if disk > maxDisk(user, machine):
230 raise MyException("Too much disk requested")
231 disk = int(disk * 1024)
235 raise MyException("Invalid disk amount")
238 def create(user, fields):
239 name = fields.getfirst('name')
240 if not validMachineName(name):
241 raise MyException("Invalid name '%s'" % name)
242 name = user.username + '_' + name.lower()
244 if Machine.get_by(name=name):
245 raise MyException("A machine named '%s' already exists" % name)
247 memory = fields.getfirst('memory')
248 memory = validMemory(user, memory)
250 disk = fields.getfirst('disk')
251 disk = validDisk(user, disk)
253 vm_type = fields.getfirst('vmtype')
254 if vm_type not in ('hvm', 'paravm'):
255 raise MyException("Invalid vm type '%s'" % vm_type)
256 is_hvm = (vm_type == 'hvm')
258 cdrom = fields.getfirst('cdrom')
259 if cdrom is not None and not CDROM.get(cdrom):
260 raise MyException("Invalid cdrom type '%s'" % cdrom)
262 machine = createVm(user, name, memory, disk, is_hvm, cdrom)
263 if isinstance(machine, basestring):
264 raise MyException(machine)
267 print Template(file='create.tmpl',
270 def listVms(user, fields):
271 machines = Machine.select()
274 uptimes = getUptimes(machines)
275 on = has_vnc = uptimes
277 # status = statusInfo(m)
278 # on[m.name] = status is not None
279 # has_vnc[m.name] = hasVnc(status)
281 maxmem=maxMemory(user),
282 maxdisk=maxDisk(user),
286 cdroms=CDROM.select())
287 print Template(file='list.tmpl', searchList=d)
289 def testMachineId(user, machineId, exists=True):
290 if machineId is None:
291 raise MyException("No machine ID specified")
293 machineId = int(machineId)
295 raise MyException("Invalid machine ID '%s'" % machineId)
296 machine = Machine.get(machineId)
297 if exists and machine is None:
298 raise MyException("No such machine ID '%s'" % machineId)
299 if not haveAccess(user, machine):
300 raise MyException("No access to machine ID '%s'" % machineId)
303 def vnc(user, fields):
306 Note that due to same-domain restrictions, the applet connects to
307 the webserver, which needs to forward those requests to the xen
308 server. The Xen server runs another proxy that (1) authenticates
309 and (2) finds the correct port for the VM.
311 You might want iptables like:
313 -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
314 -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
315 -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
317 machine = testMachineId(user, fields.getfirst('machine_id'))
320 TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
323 data["user"] = user.username
324 data["machine"]=machine.name
325 data["expires"]=time.time()+(5*60)
326 pickledData = cPickle.dumps(data)
327 m = hmac.new(TOKEN_KEY, digestmod=sha)
328 m.update(pickledData)
329 token = {'data': pickledData, 'digest': m.digest()}
330 token = cPickle.dumps(token)
331 token = base64.urlsafe_b64encode(token)
335 hostname=os.environ.get('SERVER_NAME', 'localhost'),
337 print Template(file='vnc.tmpl',
340 def getNicInfo(data_dict, machine):
341 data_dict['num_nics'] = len(machine.nics)
342 nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
343 ('nic%s_mac', 'NIC %s MAC Addr'),
344 ('nic%s_ip', 'NIC %s IP'),
347 for i in range(len(machine.nics)):
348 nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
349 data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
350 data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
351 data_dict['nic%s_ip' % i] = machine.nics[i].ip
352 if len(machine.nics) == 1:
353 nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
356 def getDiskInfo(data_dict, machine):
357 data_dict['num_disks'] = len(machine.disks)
358 disk_fields_template = [('%s_size', '%s size')]
360 for disk in machine.disks:
361 name = disk.guest_device_name
362 disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
363 data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
366 def deleteVM(machine):
367 transaction = ctx.current.create_transaction()
368 delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
370 for nic in machine.nics:
371 nic.machine_id = None
373 ctx.current.save(nic)
374 for disk in machine.disks:
375 ctx.current.delete(disk)
376 ctx.current.delete(machine)
379 transaction.rollback()
381 for mname, dname in delete_disk_pairs:
382 remctl('web', 'lvremove', mname, dname)
383 unregisterMachine(machine)
385 def command(user, fields):
386 print time.time()-start_time
387 machine = testMachineId(user, fields.getfirst('machine_id'))
388 action = fields.getfirst('action')
389 cdrom = fields.getfirst('cdrom')
390 print time.time()-start_time
391 if cdrom is not None and not CDROM.get(cdrom):
392 raise MyException("Invalid cdrom type '%s'" % cdrom)
393 if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
394 raise MyException("Invalid action '%s'" % action)
395 if action == 'Reboot':
396 if cdrom is not None:
397 remctl('reboot', machine.name, cdrom)
399 remctl('reboot', machine.name)
400 elif action == 'Power on':
401 bootMachine(machine, cdrom)
402 elif action == 'Power off':
403 remctl('destroy', machine.name)
404 elif action == 'Shutdown':
405 remctl('shutdown', machine.name)
406 elif action == 'Delete VM':
408 print time.time()-start_time
413 print Template(file="command.tmpl", searchList=d)
415 def modify(user, fields):
416 machine = testMachineId(user, fields.getfirst('machine_id'))
419 def info(user, fields):
420 machine = testMachineId(user, fields.getfirst('machine_id'))
421 status = statusInfo(machine)
422 has_vnc = hasVnc(status)
424 main_status = dict(name=machine.name,
425 memory=str(machine.memory))
427 main_status = dict(status[1:])
428 start_time = float(main_status.get('start_time', 0))
429 uptime = datetime.timedelta(seconds=int(time.time()-start_time))
430 cpu_time_float = float(main_status.get('cpu_time', 0))
431 cputime = datetime.timedelta(seconds=int(cpu_time_float))
432 display_fields = """name uptime memory state cpu_weight on_reboot
433 on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
434 display_fields = [('name', 'Name'),
436 ('contact', 'Contact'),
438 ('uptime', 'uptime'),
439 ('cputime', 'CPU usage'),
442 ('state', 'state (xen format)'),
443 ('cpu_weight', 'CPU weight'),
444 ('on_reboot', 'Action on VM reboot'),
445 ('on_poweroff', 'Action on VM poweroff'),
446 ('on_crash', 'Action on VM crash'),
447 ('on_xend_start', 'Action on Xen start'),
448 ('on_xend_stop', 'Action on Xen stop'),
449 ('bootloader', 'Bootloader options'),
453 machine_info['owner'] = machine.owner
454 machine_info['contact'] = machine.contact
456 nic_fields = getNicInfo(machine_info, machine)
457 nic_point = display_fields.index('NIC_INFO')
458 display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
460 disk_fields = getDiskInfo(machine_info, machine)
461 disk_point = display_fields.index('DISK_INFO')
462 display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
464 main_status['memory'] += ' MB'
465 for field, disp in display_fields:
466 if field in ('uptime', 'cputime'):
467 fields.append((disp, locals()[field]))
468 elif field in main_status:
469 fields.append((disp, main_status[field]))
470 elif field in machine_info:
471 fields.append((disp, machine_info[field]))
474 #fields.append((disp, None))
477 cdroms=CDROM.select(),
478 on=status is not None,
483 maxmem=maxMemory(user, machine),
484 maxdisk=maxDisk(user, machine),
486 print Template(file='info.tmpl',
489 mapping = dict(list=listVms,
496 if __name__ == '__main__':
497 start_time = time.time()
498 fields = cgi.FieldStorage()
501 email = 'moo@cow.com'
503 connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
504 operation = os.environ.get('PATH_INFO', '')
509 if operation.startswith('/'):
510 operation = operation[1:]
514 fun = mapping.get(operation,
516 error(operation, u, e,
517 "Invalid operation '%s'" % operation))
520 except MyException, err:
521 error(operation, u, fields, err)