| [113] | 1 | #!/usr/bin/python | 
|---|
| [205] | 2 | """Main CGI script for web interface""" | 
|---|
| [113] | 3 |  | 
|---|
| [205] | 4 | import base64 | 
|---|
|  | 5 | import cPickle | 
|---|
| [113] | 6 | import cgi | 
|---|
| [205] | 7 | import datetime | 
|---|
|  | 8 | import hmac | 
|---|
| [113] | 9 | import os | 
|---|
| [205] | 10 | import sha | 
|---|
|  | 11 | import simplejson | 
|---|
|  | 12 | import sys | 
|---|
| [118] | 13 | import time | 
|---|
| [205] | 14 | from StringIO import StringIO | 
|---|
| [113] | 15 |  | 
|---|
| [205] | 16 |  | 
|---|
|  | 17 | def revertStandardError(): | 
|---|
|  | 18 | """Move stderr to stdout, and return the contents of the old stderr.""" | 
|---|
|  | 19 | errio = sys.stderr | 
|---|
|  | 20 | if not isinstance(errio, StringIO): | 
|---|
|  | 21 | return None | 
|---|
|  | 22 | sys.stderr = sys.stdout | 
|---|
|  | 23 | errio.seek(0) | 
|---|
|  | 24 | return errio.read() | 
|---|
|  | 25 |  | 
|---|
|  | 26 | def printError(): | 
|---|
|  | 27 | """Revert stderr to stdout, and print the contents of stderr""" | 
|---|
|  | 28 | if isinstance(sys.stderr, StringIO): | 
|---|
|  | 29 | print revertStandardError() | 
|---|
|  | 30 |  | 
|---|
|  | 31 | if __name__ == '__main__': | 
|---|
|  | 32 | import atexit | 
|---|
|  | 33 | atexit.register(printError) | 
|---|
|  | 34 | sys.stderr = StringIO() | 
|---|
|  | 35 |  | 
|---|
| [113] | 36 | sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages') | 
|---|
|  | 37 |  | 
|---|
| [235] | 38 | import templates | 
|---|
| [113] | 39 | from Cheetah.Template import Template | 
|---|
| [209] | 40 | from sipb_xen_database import Machine, CDROM, ctx, connect | 
|---|
|  | 41 | import validation | 
|---|
|  | 42 | from webcommon import InvalidInput, CodeError, g | 
|---|
|  | 43 | import controls | 
|---|
| [113] | 44 |  | 
|---|
| [235] | 45 | class Checkpoint: | 
|---|
|  | 46 | def __init__(self): | 
|---|
|  | 47 | self.start_time = time.time() | 
|---|
|  | 48 | self.checkpoints = [] | 
|---|
|  | 49 |  | 
|---|
|  | 50 | def checkpoint(self, s): | 
|---|
|  | 51 | self.checkpoints.append((s, time.time())) | 
|---|
|  | 52 |  | 
|---|
|  | 53 | def __str__(self): | 
|---|
|  | 54 | return ('Timing info:\n%s\n' % | 
|---|
|  | 55 | '\n'.join(['%s: %s' % (d, t - self.start_time) for | 
|---|
|  | 56 | (d, t) in self.checkpoints])) | 
|---|
|  | 57 |  | 
|---|
|  | 58 | checkpoint = Checkpoint() | 
|---|
|  | 59 |  | 
|---|
|  | 60 |  | 
|---|
| [205] | 61 | def helppopup(subj): | 
|---|
|  | 62 | """Return HTML code for a (?) link to a specified help topic""" | 
|---|
|  | 63 | return ('<span class="helplink"><a href="help?subject=' + subj + | 
|---|
|  | 64 | '&simple=true" target="_blank" ' + | 
|---|
|  | 65 | 'onclick="return helppopup(\'' + subj + '\')">(?)</a></span>') | 
|---|
|  | 66 |  | 
|---|
|  | 67 | def makeErrorPre(old, addition): | 
|---|
|  | 68 | if addition is None: | 
|---|
|  | 69 | return | 
|---|
|  | 70 | if old: | 
|---|
|  | 71 | return old[:-6]  + '\n----\n' + str(addition) + '</pre>' | 
|---|
|  | 72 | else: | 
|---|
|  | 73 | return '<p>STDERR:</p><pre>' + str(addition) + '</pre>' | 
|---|
| [139] | 74 |  | 
|---|
| [205] | 75 | Template.helppopup = staticmethod(helppopup) | 
|---|
|  | 76 | Template.err = None | 
|---|
| [139] | 77 |  | 
|---|
| [205] | 78 | class JsonDict: | 
|---|
|  | 79 | """Class to store a dictionary that will be converted to JSON""" | 
|---|
|  | 80 | def __init__(self, **kws): | 
|---|
|  | 81 | self.data = kws | 
|---|
|  | 82 | if 'err' in kws: | 
|---|
|  | 83 | err = kws['err'] | 
|---|
|  | 84 | del kws['err'] | 
|---|
|  | 85 | self.addError(err) | 
|---|
| [139] | 86 |  | 
|---|
| [205] | 87 | def __str__(self): | 
|---|
|  | 88 | return simplejson.dumps(self.data) | 
|---|
|  | 89 |  | 
|---|
|  | 90 | def addError(self, text): | 
|---|
|  | 91 | """Add stderr text to be displayed on the website.""" | 
|---|
|  | 92 | self.data['err'] = \ | 
|---|
|  | 93 | makeErrorPre(self.data.get('err'), text) | 
|---|
|  | 94 |  | 
|---|
|  | 95 | class Defaults: | 
|---|
|  | 96 | """Class to store default values for fields.""" | 
|---|
|  | 97 | memory = 256 | 
|---|
|  | 98 | disk = 4.0 | 
|---|
|  | 99 | cdrom = '' | 
|---|
|  | 100 | name = '' | 
|---|
|  | 101 | vmtype = 'hvm' | 
|---|
|  | 102 | def __init__(self, max_memory=None, max_disk=None, **kws): | 
|---|
|  | 103 | if max_memory is not None: | 
|---|
|  | 104 | self.memory = min(self.memory, max_memory) | 
|---|
|  | 105 | if max_disk is not None: | 
|---|
|  | 106 | self.max_disk = min(self.disk, max_disk) | 
|---|
|  | 107 | for key in kws: | 
|---|
|  | 108 | setattr(self, key, kws[key]) | 
|---|
|  | 109 |  | 
|---|
|  | 110 |  | 
|---|
|  | 111 |  | 
|---|
| [209] | 112 | DEFAULT_HEADERS = {'Content-Type': 'text/html'} | 
|---|
| [205] | 113 |  | 
|---|
| [153] | 114 | def error(op, user, fields, err, emsg): | 
|---|
| [145] | 115 | """Print an error page when a CodeError occurs""" | 
|---|
| [153] | 116 | d = dict(op=op, user=user, errorMessage=str(err), | 
|---|
|  | 117 | stderr=emsg) | 
|---|
| [235] | 118 | return templates.error(searchList=[d]) | 
|---|
| [113] | 119 |  | 
|---|
| [153] | 120 | def invalidInput(op, user, fields, err, emsg): | 
|---|
|  | 121 | """Print an error page when an InvalidInput exception occurs""" | 
|---|
|  | 122 | d = dict(op=op, user=user, err_field=err.err_field, | 
|---|
|  | 123 | err_value=str(err.err_value), stderr=emsg, | 
|---|
|  | 124 | errorMessage=str(err)) | 
|---|
| [235] | 125 | return templates.invalid(searchList=[d]) | 
|---|
| [153] | 126 |  | 
|---|
| [119] | 127 | def hasVnc(status): | 
|---|
| [133] | 128 | """Does the machine with a given status list support VNC?""" | 
|---|
| [119] | 129 | if status is None: | 
|---|
|  | 130 | return False | 
|---|
|  | 131 | for l in status: | 
|---|
|  | 132 | if l[0] == 'device' and l[1][0] == 'vfb': | 
|---|
|  | 133 | d = dict(l[1][1:]) | 
|---|
|  | 134 | return 'location' in d | 
|---|
|  | 135 | return False | 
|---|
|  | 136 |  | 
|---|
| [205] | 137 | def parseCreate(user, fields): | 
|---|
| [134] | 138 | name = fields.getfirst('name') | 
|---|
| [209] | 139 | if not validation.validMachineName(name): | 
|---|
| [205] | 140 | raise InvalidInput('name', name, 'You must provide a machine name.') | 
|---|
| [162] | 141 | name = name.lower() | 
|---|
| [134] | 142 |  | 
|---|
|  | 143 | if Machine.get_by(name=name): | 
|---|
| [153] | 144 | raise InvalidInput('name', name, | 
|---|
| [205] | 145 | "Name already exists.") | 
|---|
| [113] | 146 |  | 
|---|
| [228] | 147 | owner = validation.testOwner(user, fields.getfirst('owner')) | 
|---|
|  | 148 |  | 
|---|
| [134] | 149 | memory = fields.getfirst('memory') | 
|---|
| [209] | 150 | memory = validation.validMemory(user, memory, on=True) | 
|---|
| [134] | 151 |  | 
|---|
| [243] | 152 | disk_size = fields.getfirst('disk') | 
|---|
|  | 153 | disk_size = validation.validDisk(user, disk_size) | 
|---|
| [134] | 154 |  | 
|---|
| [113] | 155 | vm_type = fields.getfirst('vmtype') | 
|---|
|  | 156 | if vm_type not in ('hvm', 'paravm'): | 
|---|
| [145] | 157 | raise CodeError("Invalid vm type '%s'"  % vm_type) | 
|---|
| [113] | 158 | is_hvm = (vm_type == 'hvm') | 
|---|
|  | 159 |  | 
|---|
|  | 160 | cdrom = fields.getfirst('cdrom') | 
|---|
|  | 161 | if cdrom is not None and not CDROM.get(cdrom): | 
|---|
| [205] | 162 | raise CodeError("Invalid cdrom type '%s'" % cdrom) | 
|---|
| [243] | 163 | return dict(contact=user, name=name, memory=memory, disk_size=disk_size, | 
|---|
| [228] | 164 | owner=owner, is_hvm=is_hvm, cdrom=cdrom) | 
|---|
| [113] | 165 |  | 
|---|
| [205] | 166 | def create(user, fields): | 
|---|
|  | 167 | """Handler for create requests.""" | 
|---|
|  | 168 | try: | 
|---|
|  | 169 | parsed_fields = parseCreate(user, fields) | 
|---|
| [209] | 170 | machine = controls.createVm(**parsed_fields) | 
|---|
| [205] | 171 | except InvalidInput, err: | 
|---|
| [207] | 172 | pass | 
|---|
| [205] | 173 | else: | 
|---|
|  | 174 | err = None | 
|---|
|  | 175 | g.clear() #Changed global state | 
|---|
|  | 176 | d = getListDict(user) | 
|---|
|  | 177 | d['err'] = err | 
|---|
|  | 178 | if err: | 
|---|
|  | 179 | for field in fields.keys(): | 
|---|
|  | 180 | setattr(d['defaults'], field, fields.getfirst(field)) | 
|---|
|  | 181 | else: | 
|---|
|  | 182 | d['new_machine'] = parsed_fields['name'] | 
|---|
| [235] | 183 | return templates.list(searchList=[d]) | 
|---|
| [205] | 184 |  | 
|---|
|  | 185 |  | 
|---|
|  | 186 | def getListDict(user): | 
|---|
| [209] | 187 | machines = [m for m in Machine.select() | 
|---|
|  | 188 | if validation.haveAccess(user, m)] | 
|---|
| [235] | 189 | checkpoint.checkpoint('Got my machines') | 
|---|
| [133] | 190 | on = {} | 
|---|
| [119] | 191 | has_vnc = {} | 
|---|
| [152] | 192 | on = g.uptimes | 
|---|
| [235] | 193 | checkpoint.checkpoint('Got uptimes') | 
|---|
| [136] | 194 | for m in machines: | 
|---|
| [205] | 195 | m.uptime = g.uptimes.get(m) | 
|---|
| [144] | 196 | if not on[m]: | 
|---|
|  | 197 | has_vnc[m] = 'Off' | 
|---|
| [138] | 198 | elif m.type.hvm: | 
|---|
| [144] | 199 | has_vnc[m] = True | 
|---|
| [136] | 200 | else: | 
|---|
| [144] | 201 | has_vnc[m] = "ParaVM"+helppopup("paravm_console") | 
|---|
| [209] | 202 | max_memory = validation.maxMemory(user) | 
|---|
|  | 203 | max_disk = validation.maxDisk(user) | 
|---|
| [235] | 204 | checkpoint.checkpoint('Got max mem/disk') | 
|---|
| [205] | 205 | defaults = Defaults(max_memory=max_memory, | 
|---|
|  | 206 | max_disk=max_disk, | 
|---|
| [228] | 207 | owner=user, | 
|---|
| [205] | 208 | cdrom='gutsy-i386') | 
|---|
| [235] | 209 | checkpoint.checkpoint('Got defaults') | 
|---|
| [113] | 210 | d = dict(user=user, | 
|---|
| [209] | 211 | cant_add_vm=validation.cantAddVm(user), | 
|---|
| [205] | 212 | max_memory=max_memory, | 
|---|
| [144] | 213 | max_disk=max_disk, | 
|---|
| [205] | 214 | defaults=defaults, | 
|---|
| [113] | 215 | machines=machines, | 
|---|
| [119] | 216 | has_vnc=has_vnc, | 
|---|
| [157] | 217 | uptimes=g.uptimes, | 
|---|
| [113] | 218 | cdroms=CDROM.select()) | 
|---|
| [205] | 219 | return d | 
|---|
| [113] | 220 |  | 
|---|
| [205] | 221 | def listVms(user, fields): | 
|---|
|  | 222 | """Handler for list requests.""" | 
|---|
| [235] | 223 | checkpoint.checkpoint('Getting list dict') | 
|---|
| [205] | 224 | d = getListDict(user) | 
|---|
| [235] | 225 | checkpoint.checkpoint('Got list dict') | 
|---|
|  | 226 | return templates.list(searchList=[d]) | 
|---|
| [205] | 227 |  | 
|---|
| [113] | 228 | def vnc(user, fields): | 
|---|
| [119] | 229 | """VNC applet page. | 
|---|
|  | 230 |  | 
|---|
|  | 231 | Note that due to same-domain restrictions, the applet connects to | 
|---|
|  | 232 | the webserver, which needs to forward those requests to the xen | 
|---|
|  | 233 | server.  The Xen server runs another proxy that (1) authenticates | 
|---|
|  | 234 | and (2) finds the correct port for the VM. | 
|---|
|  | 235 |  | 
|---|
|  | 236 | You might want iptables like: | 
|---|
|  | 237 |  | 
|---|
| [205] | 238 | -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \ | 
|---|
|  | 239 | --dport 10003 -j DNAT --to-destination 18.181.0.60:10003 | 
|---|
|  | 240 | -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \ | 
|---|
|  | 241 | --dport 10003 -j SNAT --to-source 18.187.7.142 | 
|---|
|  | 242 | -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \ | 
|---|
|  | 243 | --dport 10003 -j ACCEPT | 
|---|
| [145] | 244 |  | 
|---|
|  | 245 | Remember to enable iptables! | 
|---|
|  | 246 | echo 1 > /proc/sys/net/ipv4/ip_forward | 
|---|
| [119] | 247 | """ | 
|---|
| [209] | 248 | machine = validation.testMachineId(user, fields.getfirst('machine_id')) | 
|---|
| [118] | 249 |  | 
|---|
|  | 250 | TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN" | 
|---|
|  | 251 |  | 
|---|
|  | 252 | data = {} | 
|---|
| [228] | 253 | data["user"] = user | 
|---|
| [205] | 254 | data["machine"] = machine.name | 
|---|
|  | 255 | data["expires"] = time.time()+(5*60) | 
|---|
|  | 256 | pickled_data = cPickle.dumps(data) | 
|---|
| [118] | 257 | m = hmac.new(TOKEN_KEY, digestmod=sha) | 
|---|
| [205] | 258 | m.update(pickled_data) | 
|---|
|  | 259 | token = {'data': pickled_data, 'digest': m.digest()} | 
|---|
| [118] | 260 | token = cPickle.dumps(token) | 
|---|
|  | 261 | token = base64.urlsafe_b64encode(token) | 
|---|
|  | 262 |  | 
|---|
| [209] | 263 | status = controls.statusInfo(machine) | 
|---|
| [152] | 264 | has_vnc = hasVnc(status) | 
|---|
|  | 265 |  | 
|---|
| [113] | 266 | d = dict(user=user, | 
|---|
| [152] | 267 | on=status, | 
|---|
|  | 268 | has_vnc=has_vnc, | 
|---|
| [113] | 269 | machine=machine, | 
|---|
| [119] | 270 | hostname=os.environ.get('SERVER_NAME', 'localhost'), | 
|---|
| [113] | 271 | authtoken=token) | 
|---|
| [235] | 272 | return templates.vnc(searchList=[d]) | 
|---|
| [113] | 273 |  | 
|---|
| [133] | 274 | def getNicInfo(data_dict, machine): | 
|---|
| [145] | 275 | """Helper function for info, get data on nics for a machine. | 
|---|
|  | 276 |  | 
|---|
|  | 277 | Modifies data_dict to include the relevant data, and returns a list | 
|---|
|  | 278 | of (key, name) pairs to display "name: data_dict[key]" to the user. | 
|---|
|  | 279 | """ | 
|---|
| [133] | 280 | data_dict['num_nics'] = len(machine.nics) | 
|---|
| [227] | 281 | nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'), | 
|---|
| [133] | 282 | ('nic%s_mac', 'NIC %s MAC Addr'), | 
|---|
|  | 283 | ('nic%s_ip', 'NIC %s IP'), | 
|---|
|  | 284 | ] | 
|---|
|  | 285 | nic_fields = [] | 
|---|
|  | 286 | for i in range(len(machine.nics)): | 
|---|
|  | 287 | nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template]) | 
|---|
| [227] | 288 | if not i: | 
|---|
|  | 289 | data_dict['nic%s_hostname' % i] = (machine.name + | 
|---|
|  | 290 | '.servers.csail.mit.edu') | 
|---|
| [133] | 291 | data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr | 
|---|
|  | 292 | data_dict['nic%s_ip' % i] = machine.nics[i].ip | 
|---|
|  | 293 | if len(machine.nics) == 1: | 
|---|
|  | 294 | nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields] | 
|---|
|  | 295 | return nic_fields | 
|---|
|  | 296 |  | 
|---|
|  | 297 | def getDiskInfo(data_dict, machine): | 
|---|
| [145] | 298 | """Helper function for info, get data on disks for a machine. | 
|---|
|  | 299 |  | 
|---|
|  | 300 | Modifies data_dict to include the relevant data, and returns a list | 
|---|
|  | 301 | of (key, name) pairs to display "name: data_dict[key]" to the user. | 
|---|
|  | 302 | """ | 
|---|
| [133] | 303 | data_dict['num_disks'] = len(machine.disks) | 
|---|
|  | 304 | disk_fields_template = [('%s_size', '%s size')] | 
|---|
|  | 305 | disk_fields = [] | 
|---|
|  | 306 | for disk in machine.disks: | 
|---|
|  | 307 | name = disk.guest_device_name | 
|---|
| [205] | 308 | disk_fields.extend([(x % name, y % name) for x, y in | 
|---|
|  | 309 | disk_fields_template]) | 
|---|
| [211] | 310 | data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.) | 
|---|
| [133] | 311 | return disk_fields | 
|---|
|  | 312 |  | 
|---|
| [205] | 313 | def command(user, fields): | 
|---|
|  | 314 | """Handler for running commands like boot and delete on a VM.""" | 
|---|
| [207] | 315 | back = fields.getfirst('back') | 
|---|
| [205] | 316 | try: | 
|---|
| [209] | 317 | d = controls.commandResult(user, fields) | 
|---|
| [207] | 318 | if d['command'] == 'Delete VM': | 
|---|
|  | 319 | back = 'list' | 
|---|
| [205] | 320 | except InvalidInput, err: | 
|---|
| [207] | 321 | if not back: | 
|---|
| [205] | 322 | raise | 
|---|
| [207] | 323 | print >> sys.stderr, err | 
|---|
| [205] | 324 | result = None | 
|---|
|  | 325 | else: | 
|---|
|  | 326 | result = 'Success!' | 
|---|
| [207] | 327 | if not back: | 
|---|
| [235] | 328 | return templates.command(searchList=[d]) | 
|---|
| [207] | 329 | if back == 'list': | 
|---|
| [205] | 330 | g.clear() #Changed global state | 
|---|
|  | 331 | d = getListDict(user) | 
|---|
| [207] | 332 | d['result'] = result | 
|---|
| [235] | 333 | return templates.list(searchList=[d]) | 
|---|
| [207] | 334 | elif back == 'info': | 
|---|
| [209] | 335 | machine = validation.testMachineId(user, fields.getfirst('machine_id')) | 
|---|
| [205] | 336 | d = infoDict(user, machine) | 
|---|
| [207] | 337 | d['result'] = result | 
|---|
| [235] | 338 | return templates.info(searchList=[d]) | 
|---|
| [205] | 339 | else: | 
|---|
| [235] | 340 | raise InvalidInput | 
|---|
|  | 341 | ('back', back, 'Not a known back page.') | 
|---|
| [205] | 342 |  | 
|---|
|  | 343 | def modifyDict(user, fields): | 
|---|
| [177] | 344 | olddisk = {} | 
|---|
| [161] | 345 | transaction = ctx.current.create_transaction() | 
|---|
|  | 346 | try: | 
|---|
| [209] | 347 | machine = validation.testMachineId(user, fields.getfirst('machine_id')) | 
|---|
|  | 348 | owner = validation.testOwner(user, fields.getfirst('owner'), machine) | 
|---|
|  | 349 | admin = validation.testAdmin(user, fields.getfirst('administrator'), | 
|---|
|  | 350 | machine) | 
|---|
|  | 351 | contact = validation.testContact(user, fields.getfirst('contact'), | 
|---|
|  | 352 | machine) | 
|---|
|  | 353 | name = validation.testName(user, fields.getfirst('name'), machine) | 
|---|
| [161] | 354 | oldname = machine.name | 
|---|
| [205] | 355 | command = "modify" | 
|---|
| [153] | 356 |  | 
|---|
| [161] | 357 | memory = fields.getfirst('memory') | 
|---|
|  | 358 | if memory is not None: | 
|---|
| [209] | 359 | memory = validation.validMemory(user, memory, machine, on=False) | 
|---|
| [161] | 360 | machine.memory = memory | 
|---|
| [177] | 361 |  | 
|---|
| [209] | 362 | disksize = validation.testDisk(user, fields.getfirst('disk')) | 
|---|
| [161] | 363 | if disksize is not None: | 
|---|
| [209] | 364 | disksize = validation.validDisk(user, disksize, machine) | 
|---|
| [177] | 365 | disk = machine.disks[0] | 
|---|
|  | 366 | if disk.size != disksize: | 
|---|
|  | 367 | olddisk[disk.guest_device_name] = disksize | 
|---|
|  | 368 | disk.size = disksize | 
|---|
|  | 369 | ctx.current.save(disk) | 
|---|
| [161] | 370 |  | 
|---|
| [187] | 371 | if owner is not None: | 
|---|
| [161] | 372 | machine.owner = owner | 
|---|
| [187] | 373 | if name is not None: | 
|---|
| [161] | 374 | machine.name = name | 
|---|
| [187] | 375 | if admin is not None: | 
|---|
|  | 376 | machine.administrator = admin | 
|---|
|  | 377 | if contact is not None: | 
|---|
|  | 378 | machine.contact = contact | 
|---|
| [161] | 379 |  | 
|---|
|  | 380 | ctx.current.save(machine) | 
|---|
|  | 381 | transaction.commit() | 
|---|
|  | 382 | except: | 
|---|
|  | 383 | transaction.rollback() | 
|---|
| [163] | 384 | raise | 
|---|
| [177] | 385 | for diskname in olddisk: | 
|---|
| [209] | 386 | controls.resizeDisk(oldname, diskname, str(olddisk[diskname])) | 
|---|
| [187] | 387 | if name is not None: | 
|---|
| [209] | 388 | controls.renameMachine(machine, oldname, name) | 
|---|
| [205] | 389 | return dict(user=user, | 
|---|
|  | 390 | command=command, | 
|---|
|  | 391 | machine=machine) | 
|---|
|  | 392 |  | 
|---|
|  | 393 | def modify(user, fields): | 
|---|
|  | 394 | """Handler for modifying attributes of a machine.""" | 
|---|
|  | 395 | try: | 
|---|
|  | 396 | modify_dict = modifyDict(user, fields) | 
|---|
|  | 397 | except InvalidInput, err: | 
|---|
| [207] | 398 | result = None | 
|---|
| [209] | 399 | machine = validation.testMachineId(user, fields.getfirst('machine_id')) | 
|---|
| [205] | 400 | else: | 
|---|
|  | 401 | machine = modify_dict['machine'] | 
|---|
| [209] | 402 | result = 'Success!' | 
|---|
| [205] | 403 | err = None | 
|---|
|  | 404 | info_dict = infoDict(user, machine) | 
|---|
|  | 405 | info_dict['err'] = err | 
|---|
|  | 406 | if err: | 
|---|
|  | 407 | for field in fields.keys(): | 
|---|
|  | 408 | setattr(info_dict['defaults'], field, fields.getfirst(field)) | 
|---|
| [207] | 409 | info_dict['result'] = result | 
|---|
| [235] | 410 | return templates.info(searchList=[info_dict]) | 
|---|
| [205] | 411 |  | 
|---|
| [161] | 412 |  | 
|---|
| [205] | 413 | def helpHandler(user, fields): | 
|---|
| [145] | 414 | """Handler for help messages.""" | 
|---|
| [139] | 415 | simple = fields.getfirst('simple') | 
|---|
|  | 416 | subjects = fields.getlist('subject') | 
|---|
|  | 417 |  | 
|---|
| [205] | 418 | help_mapping = dict(paravm_console=""" | 
|---|
| [139] | 419 | ParaVM machines do not support console access over VNC.  To access | 
|---|
|  | 420 | these machines, you either need to boot with a liveCD and ssh in or | 
|---|
|  | 421 | hope that the sipb-xen maintainers add support for serial consoles.""", | 
|---|
| [205] | 422 | hvm_paravm=""" | 
|---|
| [139] | 423 | HVM machines use the virtualization features of the processor, while | 
|---|
|  | 424 | ParaVM machines use Xen's emulation of virtualization features.  You | 
|---|
|  | 425 | want an HVM virtualized machine.""", | 
|---|
| [205] | 426 | cpu_weight=""" | 
|---|
|  | 427 | Don't ask us!  We're as mystified as you are.""", | 
|---|
|  | 428 | owner=""" | 
|---|
|  | 429 | The owner field is used to determine <a | 
|---|
|  | 430 | href="help?subject=quotas">quotas</a>.  It must be the name of a | 
|---|
|  | 431 | locker that you are an AFS administrator of.  In particular, you or an | 
|---|
|  | 432 | AFS group you are a member of must have AFS rlidwka bits on the | 
|---|
| [187] | 433 | locker.  You can check see who administers the LOCKER locker using the | 
|---|
| [205] | 434 | command 'fs la /mit/LOCKER' on Athena.)  See also <a | 
|---|
|  | 435 | href="help?subject=administrator">administrator</a>.""", | 
|---|
|  | 436 | administrator=""" | 
|---|
|  | 437 | The administrator field determines who can access the console and | 
|---|
|  | 438 | power on and off the machine.  This can be either a user or a moira | 
|---|
|  | 439 | group.""", | 
|---|
|  | 440 | quotas=""" | 
|---|
|  | 441 | Quotas are determined on a per-locker basis.  Each quota may have a | 
|---|
|  | 442 | maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4 | 
|---|
|  | 443 | active machines.""" | 
|---|
| [187] | 444 | ) | 
|---|
| [139] | 445 |  | 
|---|
| [187] | 446 | if not subjects: | 
|---|
| [205] | 447 | subjects = sorted(help_mapping.keys()) | 
|---|
| [187] | 448 |  | 
|---|
| [139] | 449 | d = dict(user=user, | 
|---|
|  | 450 | simple=simple, | 
|---|
|  | 451 | subjects=subjects, | 
|---|
| [205] | 452 | mapping=help_mapping) | 
|---|
| [139] | 453 |  | 
|---|
| [235] | 454 | return templates.help(searchList=[d]) | 
|---|
| [139] | 455 |  | 
|---|
| [133] | 456 |  | 
|---|
| [205] | 457 | def badOperation(u, e): | 
|---|
|  | 458 | raise CodeError("Unknown operation") | 
|---|
|  | 459 |  | 
|---|
|  | 460 | def infoDict(user, machine): | 
|---|
| [209] | 461 | status = controls.statusInfo(machine) | 
|---|
| [235] | 462 | checkpoint.checkpoint('Getting status info') | 
|---|
| [133] | 463 | has_vnc = hasVnc(status) | 
|---|
|  | 464 | if status is None: | 
|---|
|  | 465 | main_status = dict(name=machine.name, | 
|---|
|  | 466 | memory=str(machine.memory)) | 
|---|
| [205] | 467 | uptime = None | 
|---|
|  | 468 | cputime = None | 
|---|
| [133] | 469 | else: | 
|---|
|  | 470 | main_status = dict(status[1:]) | 
|---|
| [167] | 471 | start_time = float(main_status.get('start_time', 0)) | 
|---|
|  | 472 | uptime = datetime.timedelta(seconds=int(time.time()-start_time)) | 
|---|
|  | 473 | cpu_time_float = float(main_status.get('cpu_time', 0)) | 
|---|
|  | 474 | cputime = datetime.timedelta(seconds=int(cpu_time_float)) | 
|---|
| [235] | 475 | checkpoint.checkpoint('Status') | 
|---|
| [133] | 476 | display_fields = """name uptime memory state cpu_weight on_reboot | 
|---|
|  | 477 | on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split() | 
|---|
|  | 478 | display_fields = [('name', 'Name'), | 
|---|
|  | 479 | ('owner', 'Owner'), | 
|---|
| [187] | 480 | ('administrator', 'Administrator'), | 
|---|
| [133] | 481 | ('contact', 'Contact'), | 
|---|
| [136] | 482 | ('type', 'Type'), | 
|---|
| [133] | 483 | 'NIC_INFO', | 
|---|
|  | 484 | ('uptime', 'uptime'), | 
|---|
|  | 485 | ('cputime', 'CPU usage'), | 
|---|
|  | 486 | ('memory', 'RAM'), | 
|---|
|  | 487 | 'DISK_INFO', | 
|---|
|  | 488 | ('state', 'state (xen format)'), | 
|---|
| [139] | 489 | ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')), | 
|---|
| [133] | 490 | ('on_reboot', 'Action on VM reboot'), | 
|---|
|  | 491 | ('on_poweroff', 'Action on VM poweroff'), | 
|---|
|  | 492 | ('on_crash', 'Action on VM crash'), | 
|---|
|  | 493 | ('on_xend_start', 'Action on Xen start'), | 
|---|
|  | 494 | ('on_xend_stop', 'Action on Xen stop'), | 
|---|
|  | 495 | ('bootloader', 'Bootloader options'), | 
|---|
|  | 496 | ] | 
|---|
|  | 497 | fields = [] | 
|---|
|  | 498 | machine_info = {} | 
|---|
| [147] | 499 | machine_info['name'] = machine.name | 
|---|
| [136] | 500 | machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM' | 
|---|
| [133] | 501 | machine_info['owner'] = machine.owner | 
|---|
| [187] | 502 | machine_info['administrator'] = machine.administrator | 
|---|
| [133] | 503 | machine_info['contact'] = machine.contact | 
|---|
|  | 504 |  | 
|---|
|  | 505 | nic_fields = getNicInfo(machine_info, machine) | 
|---|
|  | 506 | nic_point = display_fields.index('NIC_INFO') | 
|---|
| [205] | 507 | display_fields = (display_fields[:nic_point] + nic_fields + | 
|---|
|  | 508 | display_fields[nic_point+1:]) | 
|---|
| [133] | 509 |  | 
|---|
|  | 510 | disk_fields = getDiskInfo(machine_info, machine) | 
|---|
|  | 511 | disk_point = display_fields.index('DISK_INFO') | 
|---|
| [205] | 512 | display_fields = (display_fields[:disk_point] + disk_fields + | 
|---|
|  | 513 | display_fields[disk_point+1:]) | 
|---|
| [133] | 514 |  | 
|---|
| [211] | 515 | main_status['memory'] += ' MiB' | 
|---|
| [133] | 516 | for field, disp in display_fields: | 
|---|
| [167] | 517 | if field in ('uptime', 'cputime') and locals()[field] is not None: | 
|---|
| [133] | 518 | fields.append((disp, locals()[field])) | 
|---|
| [147] | 519 | elif field in machine_info: | 
|---|
|  | 520 | fields.append((disp, machine_info[field])) | 
|---|
| [133] | 521 | elif field in main_status: | 
|---|
|  | 522 | fields.append((disp, main_status[field])) | 
|---|
|  | 523 | else: | 
|---|
|  | 524 | pass | 
|---|
|  | 525 | #fields.append((disp, None)) | 
|---|
| [235] | 526 |  | 
|---|
|  | 527 | checkpoint.checkpoint('Got fields') | 
|---|
|  | 528 |  | 
|---|
|  | 529 |  | 
|---|
|  | 530 | max_mem = validation.maxMemory(user, machine, False) | 
|---|
|  | 531 | checkpoint.checkpoint('Got mem') | 
|---|
| [209] | 532 | max_disk = validation.maxDisk(user, machine) | 
|---|
|  | 533 | defaults = Defaults() | 
|---|
| [205] | 534 | for name in 'machine_id name administrator owner memory contact'.split(): | 
|---|
|  | 535 | setattr(defaults, name, getattr(machine, name)) | 
|---|
|  | 536 | defaults.disk = "%0.2f" % (machine.disks[0].size/1024.) | 
|---|
| [235] | 537 | checkpoint.checkpoint('Got defaults') | 
|---|
| [113] | 538 | d = dict(user=user, | 
|---|
| [133] | 539 | cdroms=CDROM.select(), | 
|---|
|  | 540 | on=status is not None, | 
|---|
|  | 541 | machine=machine, | 
|---|
| [205] | 542 | defaults=defaults, | 
|---|
| [133] | 543 | has_vnc=has_vnc, | 
|---|
|  | 544 | uptime=str(uptime), | 
|---|
|  | 545 | ram=machine.memory, | 
|---|
| [144] | 546 | max_mem=max_mem, | 
|---|
|  | 547 | max_disk=max_disk, | 
|---|
| [166] | 548 | owner_help=helppopup("owner"), | 
|---|
| [133] | 549 | fields = fields) | 
|---|
| [205] | 550 | return d | 
|---|
| [113] | 551 |  | 
|---|
| [205] | 552 | def info(user, fields): | 
|---|
|  | 553 | """Handler for info on a single VM.""" | 
|---|
| [209] | 554 | machine = validation.testMachineId(user, fields.getfirst('machine_id')) | 
|---|
| [205] | 555 | d = infoDict(user, machine) | 
|---|
| [235] | 556 | checkpoint.checkpoint('Got infodict') | 
|---|
|  | 557 | return templates.info(searchList=[d]) | 
|---|
| [205] | 558 |  | 
|---|
| [113] | 559 | mapping = dict(list=listVms, | 
|---|
|  | 560 | vnc=vnc, | 
|---|
| [133] | 561 | command=command, | 
|---|
|  | 562 | modify=modify, | 
|---|
| [113] | 563 | info=info, | 
|---|
| [139] | 564 | create=create, | 
|---|
| [205] | 565 | help=helpHandler) | 
|---|
| [113] | 566 |  | 
|---|
| [205] | 567 | def printHeaders(headers): | 
|---|
|  | 568 | for key, value in headers.iteritems(): | 
|---|
|  | 569 | print '%s: %s' % (key, value) | 
|---|
|  | 570 | print | 
|---|
|  | 571 |  | 
|---|
|  | 572 |  | 
|---|
|  | 573 | def getUser(): | 
|---|
|  | 574 | """Return the current user based on the SSL environment variables""" | 
|---|
|  | 575 | if 'SSL_CLIENT_S_DN_Email' in os.environ: | 
|---|
|  | 576 | username = os.environ['SSL_CLIENT_S_DN_Email'].split("@")[0] | 
|---|
| [228] | 577 | return username | 
|---|
| [205] | 578 | else: | 
|---|
| [228] | 579 | return 'moo' | 
|---|
| [205] | 580 |  | 
|---|
| [209] | 581 | def main(operation, user, fields): | 
|---|
| [235] | 582 | start_time = time.time() | 
|---|
| [153] | 583 | fun = mapping.get(operation, badOperation) | 
|---|
| [205] | 584 |  | 
|---|
|  | 585 | if fun not in (helpHandler, ): | 
|---|
|  | 586 | connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen') | 
|---|
| [119] | 587 | try: | 
|---|
| [235] | 588 | checkpoint.checkpoint('Before') | 
|---|
| [153] | 589 | output = fun(u, fields) | 
|---|
| [235] | 590 | checkpoint.checkpoint('After') | 
|---|
| [205] | 591 |  | 
|---|
| [209] | 592 | headers = dict(DEFAULT_HEADERS) | 
|---|
| [205] | 593 | if isinstance(output, tuple): | 
|---|
|  | 594 | new_headers, output = output | 
|---|
|  | 595 | headers.update(new_headers) | 
|---|
|  | 596 | e = revertStandardError() | 
|---|
| [153] | 597 | if e: | 
|---|
| [205] | 598 | output.addError(e) | 
|---|
|  | 599 | printHeaders(headers) | 
|---|
| [235] | 600 | output_string =  str(output) | 
|---|
|  | 601 | checkpoint.checkpoint('output as a string') | 
|---|
|  | 602 | print output_string | 
|---|
|  | 603 | print '<pre>%s</pre>' % checkpoint | 
|---|
| [205] | 604 | except Exception, err: | 
|---|
|  | 605 | if not fields.has_key('js'): | 
|---|
|  | 606 | if isinstance(err, CodeError): | 
|---|
|  | 607 | print 'Content-Type: text/html\n' | 
|---|
|  | 608 | e = revertStandardError() | 
|---|
|  | 609 | print error(operation, u, fields, err, e) | 
|---|
|  | 610 | sys.exit(1) | 
|---|
|  | 611 | if isinstance(err, InvalidInput): | 
|---|
|  | 612 | print 'Content-Type: text/html\n' | 
|---|
|  | 613 | e = revertStandardError() | 
|---|
|  | 614 | print invalidInput(operation, u, fields, err, e) | 
|---|
|  | 615 | sys.exit(1) | 
|---|
| [153] | 616 | print 'Content-Type: text/plain\n' | 
|---|
| [205] | 617 | print 'Uh-oh!  We experienced an error.' | 
|---|
|  | 618 | print 'Please email sipb-xen@mit.edu with the contents of this page.' | 
|---|
|  | 619 | print '----' | 
|---|
|  | 620 | e = revertStandardError() | 
|---|
| [153] | 621 | print e | 
|---|
|  | 622 | print '----' | 
|---|
|  | 623 | raise | 
|---|
| [209] | 624 |  | 
|---|
|  | 625 | if __name__ == '__main__': | 
|---|
|  | 626 | fields = cgi.FieldStorage() | 
|---|
|  | 627 | u = getUser() | 
|---|
|  | 628 | g.user = u | 
|---|
|  | 629 | operation = os.environ.get('PATH_INFO', '') | 
|---|
|  | 630 | if not operation: | 
|---|
|  | 631 | print "Status: 301 Moved Permanently" | 
|---|
|  | 632 | print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n' | 
|---|
|  | 633 | sys.exit(0) | 
|---|
|  | 634 |  | 
|---|
|  | 635 | if operation.startswith('/'): | 
|---|
|  | 636 | operation = operation[1:] | 
|---|
|  | 637 | if not operation: | 
|---|
|  | 638 | operation = 'list' | 
|---|
|  | 639 |  | 
|---|
| [235] | 640 | #main(operation, u, fields) | 
|---|
|  | 641 | import profile | 
|---|
|  | 642 | profile.run('main(operation, u, fields)', 'log-'+operation) | 
|---|
| [209] | 643 |  | 
|---|