15 print 'Content-Type: text/html\n'
16 sys.stderr = sys.stdout
17 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
19 from Cheetah.Template import Template
20 from sipb_xen_database import *
23 class MyException(Exception):
26 # ... and stolen from xend/uuid.py
28 """Generate a random UUID."""
30 return [ random.randint(0, 255) for _ in range(0, 16) ]
33 return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
34 "%02x" * 6]) % tuple(u)
42 def haveAccess(user, machine):
45 def error(op, user, fields, err):
46 d = dict(op=op, user=user, errorMessage=str(err))
47 print Template(file='error.tmpl', searchList=d);
49 def validMachineName(name):
50 """Check that name is valid for a machine name"""
53 charset = string.ascii_letters + string.digits + '-_'
54 if name[0] in '-_' or len(name) > 22:
56 return all(x in charset for x in name)
58 def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
59 """Kinit with a given username and keytab"""
61 p = subprocess.Popen(['kinit', "-k", "-t", keytab, username])
64 raise MyException("Error %s in kinit" % e)
67 """If we lack tickets, kinit."""
68 p = subprocess.Popen(['klist', '-s'])
72 def remctl(*args, **kws):
73 """Perform a remctl and return the output.
75 kinits if necessary, and outputs errors to stderr.
78 p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
80 stdout=subprocess.PIPE,
81 stderr=subprocess.PIPE)
83 return p.stdout.read(), p.stderr.read()
85 print >> sys.stderr, 'ERROR on remctl ', args
86 print >> sys.stderr, p.stderr.read()
87 return p.stdout.read()
90 """Update the lvm partitions to include all disks in the database."""
91 remctl('web', 'lvcreate')
93 def bootMachine(machine, cdtype):
94 """Boot a machine with a given boot CD.
96 If cdtype is None, give no boot cd. Otherwise, it is the string
97 id of the CD (e.g. 'gutsy_i386')
99 if cdtype is not None:
100 remctl('web', 'vmboot', machine.name,
103 remctl('web', 'vmboot', machine.name)
105 def registerMachine(machine):
106 """Register a machine to be controlled by the web interface"""
107 remctl('web', 'register', machine.name)
110 """Parse a status string into nested tuples of strings.
112 s = output of xm list --long <machine_name>
114 values = re.split('([()])', s)
116 for v in values[2:-2]: #remove initial and final '()'
123 stack[-2].append(stack[-1])
128 stack[-1].extend(v.split())
131 def statusInfo(machine):
132 value_string, err_string = remctl('list-long', machine.name, err=True)
133 if 'Unknown command' in err_string:
134 raise MyException("ERROR in remctl list-long %s is not registered" % (machine.name,))
135 elif 'does not exist' in err_string:
138 raise MyException("ERROR in remctl list-long %s: %s" % (machine.name, err_string))
139 status = parseStatus(value_string)
146 if l[0] == 'device' and l[1][0] == 'vfb':
148 return 'location' in d
151 def createVm(user, name, memory, disk, is_hvm, cdrom):
152 # put stuff in the table
153 transaction = ctx.current.create_transaction()
155 res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
156 id = res.fetchone()[0]
158 machine.machine_id = id
160 machine.memory = memory
161 machine.owner = user.username
162 machine.contact = user.email
163 machine.uuid = uuidToString(randomUUID())
164 machine.boot_off_cd = True
165 machine_type = Type.get_by(hvm=is_hvm)
166 machine.type_id = machine_type.type_id
167 ctx.current.save(machine)
168 disk = Disk(machine.machine_id,
170 open = NIC.select_by(machine_id=None)
171 if not open: #No IPs left!
172 return "No IP addresses left! Contact sipb-xen-dev@mit.edu"
174 nic.machine_id = machine.machine_id
176 ctx.current.save(nic)
177 ctx.current.save(disk)
180 transaction.rollback()
183 registerMachine(machine)
184 # tell it to boot with cdrom
185 bootMachine(machine, cdrom)
189 def create(user, fields):
190 name = fields.getfirst('name')
191 if not validMachineName(name):
192 raise MyException("Invalid name '%s'" % name)
195 if Machine.get_by(name=name):
196 raise MyException("A machine named '%s' already exists" % name)
198 memory = fields.getfirst('memory')
204 raise MyException("Invalid memory amount")
205 if memory > maxMemory(user):
206 raise MyException("Too much memory requested")
208 disk = fields.getfirst('disk')
211 disk = int(disk * 1024)
215 raise MyException("Invalid disk amount")
216 if disk > maxDisk(user):
217 raise MyException("Too much disk requested")
219 vm_type = fields.getfirst('vmtype')
220 if vm_type not in ('hvm', 'paravm'):
221 raise MyException("Invalid vm type '%s'" % vm_type)
222 is_hvm = (vm_type == 'hvm')
224 cdrom = fields.getfirst('cdrom')
225 if cdrom is not None and not CDROM.get(cdrom):
226 raise MyException("Invalid cdrom type '%s'" % cdrom)
228 machine = createVm(user, name, memory, disk, is_hvm, cdrom)
229 if isinstance(machine, basestring):
230 raise MyException(machine)
233 print Template(file='create.tmpl',
236 def listVms(user, fields):
237 machines = Machine.select()
238 status = statusInfo(machines)
241 on[m.name] = status[m.name] is not None
242 has_vnc[m.name] = hasVnc(status[m.name])
244 maxmem=maxMemory(user),
245 maxdisk=maxDisk(user),
249 cdroms=CDROM.select())
250 print Template(file='list.tmpl', searchList=d)
252 def testMachineId(user, machineId, exists=True):
253 if machineId is None:
254 raise MyException("No machine ID specified")
256 machineId = int(machineId)
258 raise MyException("Invalid machine ID '%s'" % machineId)
259 machine = Machine.get(machineId)
260 if exists and machine is None:
261 raise MyException("No such machine ID '%s'" % machineId)
262 if not haveAccess(user, machine):
263 raise MyException("No access to machine ID '%s'" % machineId)
266 def vnc(user, fields):
269 Note that due to same-domain restrictions, the applet connects to
270 the webserver, which needs to forward those requests to the xen
271 server. The Xen server runs another proxy that (1) authenticates
272 and (2) finds the correct port for the VM.
274 You might want iptables like:
276 -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
277 -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
278 -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
280 machine = testMachineId(user, fields.getfirst('machine_id'))
283 TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
287 data["machine"]=machine
288 data["expires"]=time.time()+(5*60)
289 pickledData = cPickle.dumps(data)
290 m = hmac.new(TOKEN_KEY, digestmod=sha)
291 m.update(pickledData)
292 token = {'data': pickledData, 'digest': m.digest()}
293 token = cPickle.dumps(token)
294 token = base64.urlsafe_b64encode(token)
298 hostname=os.environ.get('SERVER_NAME', 'localhost'),
300 print Template(file='vnc.tmpl',
303 def info(user, fields):
304 machine = testMachineId(user, fields.getfirst('machine_id'))
307 print Template(file='info.tmpl',
310 mapping = dict(list=listVms,
315 if __name__ == '__main__':
316 fields = cgi.FieldStorage()
319 email = 'moo@cow.com'
321 connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
322 operation = os.environ.get('PATH_INFO', '')
327 if operation.startswith('/'):
328 operation = operation[1:]
332 fun = mapping.get(operation,
334 error(operation, u, e,
335 "Invalid operation '%'" % operation))
338 except MyException, err:
339 error(operation, u, fields, err)