emulabserver.py.in 127 KB
Newer Older
1 2 3
#! /usr/bin/env python
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2004, 2005 University of Utah and the Flux Group.
5 6 7 8 9
# All rights reserved.
#
import sys
import socket
import os
Timothy Stack's avatar
Timothy Stack committed
10 11
import os.path
import stat
12 13 14
import popen2
import tempfile
import time
15 16
import re
import string
Timothy Stack's avatar
Timothy Stack committed
17 18 19 20
import pwd
import grp
import errno
import exceptions
21
import xmlrpclib
22
import signal
23
import types
24
import datetime
25 26 27

# Configure variables
TBDIR = "@prefix@"
28
BOSSNODE = "@BOSSNODE@"
29
BOSSEVENTPORT = "@BOSSEVENTPORT@"
30 31 32 33 34 35 36 37 38 39 40
OURDOMAIN = "@OURDOMAIN@"
USERNODE = "@USERNODE@"

TBPATH = os.path.join(TBDIR, "lib")
if TBPATH not in sys.path:
    sys.path.append(TBPATH)
    pass

from libdb        import *
from libtestbed   import SENDMAIL, TBOPS
from emulabclient import *
41

42 43 44
# Version
VERSION = 0.1

Timothy Stack's avatar
Timothy Stack committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58
# Well known directories
PROJROOT = "/proj"
GROUPROOT = "/groups"
SHAREROOT = "/share"
USERSROOT = "/users"

# List of directories exported to nodes via NFS.
NFS_EXPORTS = [
    PROJROOT,
    GROUPROOT,
    SHAREROOT,
    USERSROOT,
    ]

59 60 61 62 63 64 65 66 67 68 69 70 71 72
#
# XXX
# This mirrors db/xmlconvert. Be sure to keep this table in sync with that table.
#
virtual_tables = {
    "experiments"		: { "rows"  : None, 
                                    "tag"   : "experiment",
                                    "attrs" : [ ] },
    "virt_nodes"		: { "rows"  : None, 
                                    "tag"   : "nodes",
                                    "attrs" : [ "vname" ]},
    "virt_lans"                 : { "rows"  : None, 
                                    "tag"   : "lans",
                                    "attrs" : [ "vname" ]},
73 74 75
    "virt_lan_lans"             : { "rows"  : None, 
                                    "tag"   : "lan_lans",
                                    "attrs" : [ "vname" ]},
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    "virt_lan_settings"         : { "rows"  : None, 
                                    "tag"   : "lan_settings",
                                    "attrs" : [ "vname", "capkey" ]},
    "virt_lan_member_settings"  : { "rows"  : None, 
                                    "tag"   : "lan_member_settings",
                                    "attrs" : [ "vname", "member", "capkey" ]},
    "virt_trafgens"		: { "rows"  : None, 
                                    "tag"   : "trafgens",
                                    "attrs" : [ "vname", "vnode" ]},
    "virt_agents"		: { "rows"  : None, 
                                    "tag"   : "agents",
                                    "attrs" : [ "vname", "vnode" ]},
    "virt_node_desires"         : { "rows"  : None, 
                                    "tag"   : "node_desires",
                                    "attrs" : [ "vname", "desire" ]},
91 92 93
    "virt_node_startloc"        : { "rows"  : None,
                                    "tag"   : "node_startlocs",
                                    "attrs" : [ "vname", "building" ]},
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
    "virt_routes"		: { "rows"  : None, 
                                    "tag"   : "routes",
                                    "attrs" : [ "vname", "src", "dst" ]},
    "virt_vtypes"		: { "rows"  : None, 
                                    "tag"   : "vtypes",
                                    "attrs" : [ "name" ]},
    "virt_programs"		: { "rows"  : None, 
                                    "tag"   : "programs",
                                    "attrs" : [ "vname", "vnode" ]},
    "nseconfigs"		: { "rows"  : None, 
                                    "tag"   : "nseconfigs",
                                    "attrs" : [ "vname" ]},
    "eventlist"                 : { "rows"  : None, 
                                    "tag"   : "events",
                                    "attrs" : [ "vname" ]}
    }
    
Timothy Stack's avatar
Timothy Stack committed
111 112 113 114 115 116 117 118 119 120 121 122
# Base class for emulab specific exceptions.
class EmulabError(exceptions.Exception):
    pass

# Exception thrown when logins are not allowed.
class NoLoginsError(EmulabError):
    pass

# Exception thrown an unknown user tries to import this module.
class UnknownUserError(EmulabError):
    pass

123 124 125 126 127 128 129
# Exception thrown when a timer expires.
class TimedOutError(EmulabError):
    pass

def TimeoutHandler(signum, frame):
    raise TimedOutError, 'Timer Expired'

130
#
131 132
# Arguments to methods are passed as a Dictionary. This converts to a XML
# "struct" which in Perl/PHP/Ruby would be a hash. So, a client written in
133 134
# pretty much any language should be able to talk to this class.
#
135

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
#
# A helper function for checking required arguments.
#
def CheckRequiredArgs(argdict, arglist):
    # proj,group,exp are aliases for pid,gid,eid
    if (argdict.has_key("pid") and not argdict.has_key("proj")):
        argdict["proj"] = argdict["pid"]
        pass
    if (argdict.has_key("gid") and not argdict.has_key("group")):
        argdict["group"] = argdict["gid"]
        pass
    if (argdict.has_key("eid") and not argdict.has_key("exp")):
        argdict["exp"] = argdict["eid"]
        pass

    # Okay, now check.
    for arg in arglist:
        if not argdict.has_key(arg):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Must supply '" + arg + "'")
        pass
    return None

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
#
# Check user permission to access a project.
#
def CheckProjPermission(uid, pid):
    if not re.match("^[-\w]*$", pid):
        return EmulabResponse(RESPONSE_BADARGS,
                              output="Illegal characters in project ID!")

    res = DBQueryFatal("SELECT trust FROM group_membership "
                       "WHERE uid=%s and pid=%s and gid=%s",
                       (uid, pid, pid))

    if len(res) == 0:
        return EmulabResponse(RESPONSE_FORBIDDEN,
                              output=("You do not have permission to " +
                                      "access project: " + pid))
    
    return None

178 179 180
#
# Check user permission to access an experiment.
# 
181
def CheckExptPermission(uid, pid, eid):
182 183 184 185 186
    if not (re.match("^[-\w]*$", pid) and
            re.match("^[-\w]*$", eid)):
        return EmulabResponse(RESPONSE_BADARGS,
                  output="Illegal characters in project and/or experiment IDs!")
    
187 188 189 190 191 192 193 194 195 196
    res = DBQueryFatal("SELECT gid FROM experiments "
                       "WHERE pid=%s and eid=%s",
                       (pid, eid))

    if len(res) == 0:
        return EmulabResponse(RESPONSE_ERROR,
                              output="No such experiment: " +
                              pid + "/" + eid)

    gid = res[0][0]
197
    
198 199
    res = DBQueryFatal("SELECT trust FROM group_membership "
                       "WHERE uid=%s and pid=%s and gid=%s",
200
                       (uid, pid, gid))
201 202 203 204 205 206 207

    if len(res) == 0:
        return EmulabResponse(RESPONSE_FORBIDDEN,
                              output=("You do not have permission to " +
                                      "access experiment: " + pid + "/" + eid))
    return None

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
#
# Check user permission to access a node.
# 
def CheckNodePermission(uid, node):
    res = DBQueryFatal("SELECT e.pid,e.eid FROM reserved AS r "
                       "left join experiments as e on "
                       "     e.pid=r.pid and e.eid=r.eid "
                       "WHERE r.node_id=%s",
                       (node,))
    
    if len(res) == 0:
        return EmulabResponse(RESPONSE_ERROR,
                              output="No such node: " + node)

    return CheckExptPermission(uid, res[0][0], res[0][1])

224 225 226 227 228
#
# This is a wrapper class so that you can invoke methods in dotted form.
# For example experiment.swapexp(...).
#
class EmulabServer:
Leigh Stoller's avatar
Leigh Stoller committed
229
    def __init__(self, readonly=0, clientip=None):
230
        self.readonly  = readonly;
Leigh Stoller's avatar
Leigh Stoller committed
231
        self.clientip  = clientip;
232
        self.instances = {};
233
        self.uid       = pwd.getpwuid(os.getuid())[0]
234 235 236 237 238 239 240 241 242 243 244

        self.instances["experiment"] = experiment(readonly=self.readonly);
        if readonly:
            return
        
        self.instances["emulab"]     = emulab(readonly=self.readonly);
        self.instances["user"]       = user(readonly=self.readonly);
        self.instances["fs"]         = fs(readonly=self.readonly);
        self.instances["imageid"]    = imageid(readonly=self.readonly);
        self.instances["osid"]       = osid(readonly=self.readonly);
        self.instances["node"]       = node(readonly=self.readonly);
Leigh Stoller's avatar
Leigh Stoller committed
245 246
        self.instances["elabinelab"] = elabinelab(clientip=self.clientip,
                                                  readonly=self.readonly);
247 248 249 250 251 252 253
        return

    def __getattr__(self, name):
        dotted = name.split(".");
        if len(dotted) != 2:
            raise AttributeError("Bad name '%s'" % name)
        if not self.instances.has_key(dotted[0]):
254 255
            raise AttributeError("unknown method '%s' (readonly=%d)" %
                                 (name, self.readonly))
256 257 258 259
        
        return getattr(self.instances[dotted[0]], dotted[1]);
    pass

Timothy Stack's avatar
Timothy Stack committed
260 261 262 263 264
#
# This class implements the server side of the XMLRPC interface to emulab as a
# whole.
#
class emulab:
265 266 267
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
268
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
        return

    #
    # Get the global 'notice' message that is usually printed under the menu
    # on the web site.
    #
    def message(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        msg = TBGetSiteVar("web/message")

        return EmulabResponse(RESPONSE_SUCCESS,
                              value=msg,
                              output=msg)

    #
    # Get the news items as a list of {subject,author,date,msgid} items for
    # dates between an option start and ending.
    #
    def news(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        # Process optional arguments.
        starting = None;
        if argdict.has_key("starting"):
            if not re.match("^[-:\w]*$", str(argdict["starting"])):
                return EmulabResponse(RESPONSE_BADARGS,
                                      output="Improperly formed 'starting'!")
            starting = sqldate(argdict["starting"])
            pass

        ending = None
        if argdict.has_key("ending"):
            if not re.match("^[-:\w]*$", str(argdict["ending"])):
                return EmulabResponse(RESPONSE_BADARGS,
                                      output="Improperly formed 'ending'!")
            ending = sqldate(argdict["ending"])
            pass

        # Construct the SQL date comparison
        if starting and ending:
            comparison = "BETWEEN %s and %s"
            sub = (starting, ending)
            pass
        elif starting:
            comparison = "> %s"
            sub = (starting,)
            pass
        elif ending:
            comparison = "< %s"
            sub = (ending,)
            pass
        else:
            comparison = ""
            sub = ()
            pass

        # Get the headlines and
        dbres = DBQueryFatal("SELECT subject,author,date,msgid FROM webnews "
                             "WHERE date "
                             + comparison
                             + " ORDER BY date DESC",
                             sub)

        # ... package them up
        result = []
        for res in dbres:
            tmp = {}
            tmp["subject"] = res[0]
            tmp["author"] = res[1]
            tmp["date"] = xmlrpclib.DateTime(
344
                time.strptime(str(res[2]), "%Y-%m-%d %H:%M:%S"))
Timothy Stack's avatar
Timothy Stack committed
345
            tmp["msgid"] = res[3]
346
            
Timothy Stack's avatar
Timothy Stack committed
347 348 349 350 351 352
            result.append(tmp)
            pass

        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
                              output=str(result))
353 354 355 356 357 358 359 360 361 362 363 364 365

    def vision_config(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        if not re.match("^[-:\w]*$", str(argdict["area"])):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Improperly formed 'area'!")

        table = {
            "MEB-ROBOTS" : [ { "name" : "camera0",
                               "host" : "junk.flux.utah.edu",
Timothy Stack's avatar
Timothy Stack committed
366 367 368 369 370
                               "port" : 6100,
                               "x" : 0.25,
                               "y" : 1.80,
                               "width" : 1.45,
                               "height" : 2.20 },
371 372
                             { "name" : "camera1",
                               "host" : "junk.flux.utah.edu",
Timothy Stack's avatar
Timothy Stack committed
373 374 375 376 377
                               "port" : 6101,
                               "x" : 1.7,
                               "y" : 1.5,
                               "width" : 1.45,
                               "height" : 1.6 },
378 379
                             { "name" : "camera2",
                               "host" : "junk.flux.utah.edu",
Timothy Stack's avatar
Timothy Stack committed
380 381 382 383 384
                               "port" : 6102,
                               "x" : 3.1,
                               "y" : 1.5,
                               "width" : 1.45,
                               "height" : 1.6 },
385 386
                             { "name" : "camera3",
                               "host" : "junk.flux.utah.edu",
Timothy Stack's avatar
Timothy Stack committed
387 388 389 390 391
                               "port" : 6103,
                               "x" : 4.50,
                               "y" : 1.5,
                               "width" : 1.4,
                               "height" : 1.6 } ]
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
            }
        
        if not table.has_key(argdict["area"]):
            return EmulabResponse(RESPONSE_ERROR,
                                  output="Unknown area " + argdict["area"])

        result = table[argdict["area"]]
        
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
                              output=str(result))

    def obstacle_config(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        if not re.match("^[-:\w]*$", str(argdict["area"])):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Improperly formed 'area'!")

        if not argdict.has_key("units"):
            argdict["units"] = "pixels"
            pass
        
        res = DBQuery("select * from obstacles where building=%s",
                      (argdict["area"],),
                      asDict=True)

        ppm = DBQueryFatal("select pixels_per_meter from floorimages "
                           "where building=%s",
                           (argdict["area"],))
        
        if len(res) == 0 or len(ppm) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No such area " + argdict["area"])

        ppm = ppm[0][0]

        for ob in res:
            if argdict["units"] == "meters":
                ob["x1"] = ob["x1"] / ppm
                ob["y1"] = ob["y1"] / ppm
                ob["z1"] = ob["z1"] / ppm
                ob["x2"] = ob["x2"] / ppm
                ob["y2"] = ob["y2"] / ppm
                ob["z2"] = ob["z2"] / ppm
                pass
            pass
        
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=res,
                              output=str(res))
Timothy Stack's avatar
Timothy Stack committed
445 446 447 448 449 450 451 452 453
    
    pass


#
# This class implements the server side of the XMLRPC interface to user
# specific information.
#
class user:
454 455 456
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
457
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
        return

    #
    # Get the number of nodes this user is has allocated.
    #
    def nodecount(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        res = DBQueryFatal(
            "SELECT a.node_id FROM nodes AS a "
            "left join reserved as b on a.node_id=b.node_id "
            "left join node_types as nt on a.type=nt.type "
            "left join experiments as e on b.pid=e.pid and "
            " b.eid=e.eid "
            "WHERE e.expt_head_uid=%s and e.pid!='emulab-ops' "
            "  and a.role='testnode' and nt.class = 'pc'",
482
            (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532

        return EmulabResponse(RESPONSE_SUCCESS,
                              value=len(res),
                              output=str(len(res)))

    #
    # Get the listing of projects/groups that this user is a member of and,
    # optionally, has the permission to perform some task.
    #
    def membership(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        permission = "readinfo"
        if argdict.has_key("permission"):
            permission = argdict["permission"]
            pass

        # Convert the permission to a SQL condition.
        if permission == "readinfo":
            trust_clause = "trust!='none'"
            pass
        elif permission == "makegroup":
            trust_clause = "trust='project_root'"
            pass
        elif permission == "createexpt":
            trust_clause = ("(trust='project_root' or trust='group_root' or "
                            " trust='local_root')")
            pass
        elif permission == "makeosid" or permission == "makeimageid":
            # XXX Handle admin
            trust_clause = ("(trust='project_root' or trust='group_root' or "
                            " trust='local_root')")
            pass
        else:
            return EmulabResponse(RESPONSE_BADARGS,
                                  output=("Bad permission value: "
                                          + permission))

        res = DBQueryFatal("SELECT distinct pid,gid FROM group_membership "
                           "WHERE uid=%s and "
                           + trust_clause
                           + " ORDER BY pid",
533
                           (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560

        result = {}
        for proj in res:
            if result.has_key(proj[0]):
                # Add group to existing project list
                tmp = result[proj[0]]
                tmp.append(proj[1])
                pass
            else:
                # Add new project to root list
                tmp = [proj[1],]
                result[proj[0]] = tmp
                pass
            pass

        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
                              output=str(result))

    pass


#
# This class implements the server side of the XMLRPC interface to the emulab
# NFS exports.
#
class fs:
561 562 563
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
564
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
        return

    #
    # Check the accessibility of a path.
    #
    def access(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!");

        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        argerror = CheckRequiredArgs(argdict, ("permission", "path"))
        if (argerror):
            return argerror

        try:
            path = nfspath(argdict["path"]) # Scrub the path
            
            permission = argdict["permission"]

            # Convert the permission to a python compatible value.
            if permission == "read" or permission == "r":
                accessmode = os.R_OK
                pass
            elif permission == "write" or permission == "w":
                accessmode = os.W_OK
                pass
            elif permission == "execute" or permission == "x":
                accessmode = os.X_OK
                pass
            elif permission == "exists" or permission == "e":
                accessmode = os.F_OK
                pass
            else:
                return EmulabResponse(RESPONSE_BADARGS,
                                      output=("Bad permission value: "
                                              + permission))
            
            res = os.access(path, accessmode)

            return EmulabResponse(RESPONSE_SUCCESS,
                                  value=res,
                                  output=str(res))
        except OSError, e:
            return EmulabResponse(RESPONSE_ERROR,
                                  value=e,
                                  output=(e.strerror + ": " + e.filename))

        # Never reached...
        assert False
        pass

    #
    # Get a directory listing for a given path.
    #
    def listdir(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        argerror = CheckRequiredArgs(argdict, ("path",))
        if (argerror):
            return argerror

        try:
            path = nfspath(argdict["path"]) # Scrub the path

            # Make sure the path is accessible,
            if not os.access(path, os.X_OK):
                raise OSError(errno.EPERM, "Path is not accessible", path)

            # ... get the directory listing, and
            res = os.listdir(path)

            # ... package it up into a platform independent from.
            result = []
            for entry in res:
                try:
                    st = os.stat(os.path.join(path, entry))
                    # The UID/GID will be meaningless to the other side,
                    # resolve them before sending it back.
                    try:
                        uname = pwd.getpwuid(st[stat.ST_UID])[0]
                        pass
                    except:
                        # Unknown UID, just send the number as a string
                        uname = str(st[stat.ST_UID])
                        pass
                    try:
                        gname = grp.getgrgid(st[stat.ST_GID])[0]
                        pass
                    except:
                        # Unknown GID, just send the number as a string
                        gname = str(st[stat.ST_GID])
                        pass
                    result.append((entry,
                                   filetype(st[stat.ST_MODE]),
                                   stat.S_IMODE(st[stat.ST_MODE]),
                                   uname,
                                   gname,
                                   st[stat.ST_SIZE],
                                   st[stat.ST_ATIME],
                                   st[stat.ST_MTIME],
                                   st[stat.ST_CTIME]))
                except OSError:
                    pass
                pass
            retval = EmulabResponse(RESPONSE_SUCCESS,
                                    value=result,
                                    output=str(result))
            pass
        except OSError, e:
            retval = EmulabResponse(RESPONSE_ERROR,
                                    value=e,
                                    output=(e.strerror + ": " + e.filename))
            pass

        return retval

    #
    # Get the list of potential NFS exports for an experiment.
    #
    def exports(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        # Start with the default set of exports, then
        res = [
711
            USERSROOT + "/" + self.uid, # XXX Use getpwuid() and handle admin
Timothy Stack's avatar
Timothy Stack committed
712 713 714 715 716 717
            SHAREROOT,
            ]

        # ... add the project/group listings.
        projs = DBQueryFatal("SELECT distinct pid,gid FROM group_membership "
                             "WHERE uid=%s and trust!='none' ORDER BY pid",
718
                             (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739

        for proj in projs:
            if proj[0] == proj[1]:
                res.append(PROJROOT + "/" + proj[0])
                pass
            else:
                res.append(GROUPROOT + "/" + proj[0] + "/" + proj[1])
                pass
            pass
        
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=res,
                              output=str(res))

    pass


#
# This class implements the server side of the XMLRPC interface to image IDs.
#
class imageid:
740 741 742
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
743
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
        return

    def getlist(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        # Get the listing that is accessible to this user and
        res = DBQueryFatal(
            "SELECT distinct i.imagename,i.description FROM images as i "
            "left join group_membership as g on g.pid=i.pid "
            "WHERE g.uid=%s or i.global",
762
            (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788

        if len(res) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No image ids?")

        # ... package it up.
        result = {}
        for image in res:
            tmp = {
                "imageid" : image[0],
                "description" : image[1],
                }
            result[image[0]] = tmp
            pass

        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
                              output=str(result))

    pass


#
# This class implements the server side of the XMLRPC interface to OS IDs.
#
class osid:
789 790 791
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
792
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
793 794 795 796 797 798 799 800 801 802 803 804 805 806
        return

    def getlist(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        # Get the listing that is accessible to this user and
807
        res = DBQueryFatal("SELECT distinct "
808
                           "o.* FROM "
Timothy Stack's avatar
Timothy Stack committed
809 810 811
                           "os_info as o "
                           "left join group_membership as g on g.pid=o.pid "
                           "where g.uid=%s or o.shared=1",
812 813
                           (self.uid,),
                           True)
Timothy Stack's avatar
Timothy Stack committed
814 815 816 817 818 819 820 821

        if len(res) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No OS IDs?")

        # ... package it up.
        result = {}
        for osid in res:
822 823 824
            # XXX Legacy stuff...
            osid["fullosid"] = osid["osid"]
            osid["osid"] = osid["osname"]
Timothy Stack's avatar
Timothy Stack committed
825

826 827 828 829 830 831 832
            if not osid["OS"] or len(osid["OS"]) == 0:
                osid["OS"] = "(None)"
                pass

            result[osid["osid"]] = scrubdict(osid)
            pass
        
Timothy Stack's avatar
Timothy Stack committed
833 834 835 836 837 838
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
                              output=str(result))

    pass

839

840 841 842 843
#
# This class implements the server side of the XMLRPC interface to experiments.
#
class experiment:
844 845 846
    ##
    # Initialize the object.  Currently only sets the objects 'VERSION' value.
    #
847 848 849
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
850
        self.uid      = pwd.getpwuid(os.getuid())[0]
851 852 853 854 855 856 857 858 859
        return

    ##
    # Echo a message, basically, prepend the host name to the parameter list.
    #
    # @param args The argument list to echo back.
    # @return The 'msg' value with this machine's name prepended.
    #
    def echo(self, version, argdict):
860
        if not argdict.has_key("str"):
861
            return EmulabResponse(RESPONSE_BADARGS,
Timothy Stack's avatar
Timothy Stack committed
862
                                  output="Must supply a string to echo!")
863
        
864
        return EmulabResponse(RESPONSE_SUCCESS, 0,
865 866
                             socket.gethostname() + ": " + str(version)
                             + " " + argdict["str"])
867

Timothy Stack's avatar
Timothy Stack committed
868 869 870 871 872 873 874 875
    #
    # Get the physical/policy constraints for experiment parameters.
    #
    def constraints(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

876 877 878 879
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        result = {
            "idle/threshold" : TBGetSiteVar("idle/threshold"),
            # XXX Add more...
            }
        
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
                              output=str(result))

    #
    # Get the list of experiments where the user is the head.
    #
    def getlist(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")
        
903 904 905 906
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
907 908 909 910 911 912
        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

913 914 915 916 917 918 919 920
        # Figure out the format for the returned data.
        if argdict.has_key("format"):
            format = argdict["format"]
            pass
        else:
            format = "brief"
            pass

Timothy Stack's avatar
Timothy Stack committed
921
        # XXX Let the user specify the pid/gid
922 923 924 925
        dbres = DBQueryFatal("SELECT e.pid,e.gid,e.eid,e.expt_name,e.state,"
                             "e.expt_head_uid "
                             "  FROM experiments AS e "
                             "WHERE e.expt_head_uid=%s",
926
                             (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
927 928 929 930 931

        # Build a dictionary of projects that refer to a dictionary of groups
        # that refer to a list of experiments in that group.
        result = {}
        for res in dbres:
932 933 934 935 936 937 938 939 940 941 942
            # Get everything from the DB result,
            pid = res[0]
            gid = res[1]
            eid = res[2]
            desc = res[3]
            state = res[4]
            expt_head = res[5]
            # ... make sure 'result' has the proper slots,
            if not result.has_key(pid):
                result[pid] = {
                    gid : list()
Timothy Stack's avatar
Timothy Stack committed
943 944
                    }
                pass
945 946
            elif not result[pid].has_key(gid):
                result[pid][gid] = list()
Timothy Stack's avatar
Timothy Stack committed
947
                pass
948 949 950 951 952 953 954 955 956 957 958 959 960 961 962

            # ... drop the data into place, and
            expdata = None
            if format == "brief":
                expdata = eid;
                pass
            elif format == "full":
                expdata = {
                    "pid" : pid,
                    "gid" : gid,
                    "name" : eid,
                    "description" : desc,
                    "state" : state,
                    "expt_head" : expt_head,
                    }
Timothy Stack's avatar
Timothy Stack committed
963
                pass
964 965 966

            # ... append it to the group list.
            result[pid][gid].append(expdata)
Timothy Stack's avatar
Timothy Stack committed
967 968 969 970 971 972
            pass
        
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
                              output=str(result))

973 974 975 976 977 978
    #
    # Start an experiment using batchexp. We get the NS file inline, which
    # we have to write to a temp file first. 
    #
    def batchexp(self, version, argdict):
        if version != self.VERSION:
979
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
980 981
                                  output="Client version mismatch!")

982 983 984 985
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
986 987
        try:
            checknologins()
988
            pass
Timothy Stack's avatar
Timothy Stack committed
989 990
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
991

992 993 994
        argerror = CheckRequiredArgs(argdict, ("proj", "exp"))
        if (argerror):
            return argerror
995

996
        nsfilename = None
997
        argstr     = "-q"
998 999
        
        for opt, val in argdict.items():
1000
            if opt == "batch":
Timothy Stack's avatar
Timothy Stack committed
1001
                if not xbool(val):
1002 1003 1004
                    argstr += " -i"
                    pass
                pass
1005
            elif opt == "description":
1006
                argstr += " -E "
1007
                argstr += escapeshellarg(val)
1008
                pass
1009
            elif opt == "group":
1010
                argstr += " -g "
1011
                argstr += escapeshellarg(val)
1012
                pass
1013
            elif opt == "exp":
1014
                argstr += " -e "
1015
                argstr += escapeshellarg(val)
1016
                pass
1017
            elif opt == "proj":
1018
                argstr += " -p "
1019
                argstr += escapeshellarg(val)
1020
                pass
1021
            elif opt == "swappable":
Timothy Stack's avatar
Timothy Stack committed
1022
                if not xbool(val):
1023 1024 1025
                    if not argdict.has_key("noswap_reason"):
                        return EmulabResponse(RESPONSE_BADARGS,
                                       output="Must supply noswap reason!");
1026
                    argstr += " -S "
1027
                    argstr += escapeshellarg(argdict["noswap_reason"])
1028 1029
                    pass
                pass
1030
            elif opt == "noswap_reason":
1031
                pass
1032
            elif opt == "idleswap":
1033
                if val == 0:
1034 1035 1036
                    if not argdict.has_key("noidleswap_reason"):
                        return EmulabResponse(RESPONSE_BADARGS,
                                      output="Must supply noidleswap reason!");
1037
                    argstr += " -L "
1038
                    argstr += escapeshellarg(argdict["noidleswap_reason"])
1039 1040 1041
                    pass
                else:
                    argstr += " -l "
Timothy Stack's avatar
Timothy Stack committed
1042
                    argstr += escapeshellarg(str(val))
1043 1044
                    pass
                pass
1045
            elif opt == "noidleswap_reason":
1046
                pass
1047
            elif opt == "autoswap" or opt == "max_duration":
1048
                argstr += " -a "
Timothy Stack's avatar
Timothy Stack committed
1049
                argstr += escapeshellarg(str(val))
1050
                pass
1051
            elif opt == "noswapin":
Timothy Stack's avatar
Timothy Stack committed
1052
                if xbool(val):
1053 1054
                    argstr += " -f "
                    pass
1055
                pass
1056
            elif opt == "wait":
Timothy Stack's avatar
Timothy Stack committed
1057
                if xbool(val):
1058 1059
                    argstr += " -w "
                    pass
1060
                pass
1061
            elif opt == "nsfilepath":
1062
                # Backend script will verify this local path. 
1063
                nsfilename = escapeshellarg(val)
1064
                pass
1065
            elif opt == "nsfilestr":
1066 1067 1068
                nsfilestr = val
            
                if len(nsfilestr) > (1024 * 512):
1069
                    return EmulabResponse(RESPONSE_TOOBIG,
1070
                                         output="NS File way too big!");
1071 1072 1073
        
                (nsfp, nsfilename) = writensfile(nsfilestr)
                if not nsfilename:
1074
                    return EmulabResponse(RESPONSE_SERVERERROR,
1075
                                         output="Server Error")
1076 1077 1078 1079
                pass
            pass

        if nsfilename:
1080
            argstr += " " + nsfilename
1081 1082 1083
            pass

        (exitval, output) = runcommand(TBDIR + "/bin/batchexp " + argstr)
1084
        if exitval:
1085
            return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
1086
        
1087
        return EmulabResponse(RESPONSE_SUCCESS, output=output)
1088 1089 1090 1091 1092 1093

    #
    # startexp is an alias for batchexp.
    # 
    def startexp(self, version, argdict):
        return self.batchexp(version, argdict)
1094

1095 1096 1097 1098 1099 1100
    #
    # swap an experiment using swapexp. 
    #
    def swapexp(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
1101 1102
                                  output="Client version mismatch!")

1103 1104 1105 1106
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
1107 1108
        try:
            checknologins()
1109
            pass
Timothy Stack's avatar
Timothy Stack committed
1110 1111
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1112

1113 1114 1115 1116
        argerror = CheckRequiredArgs(argdict, ("proj", "exp", "direction"))
        if (argerror):
            return argerror

1117 1118 1119
        #
        # Check permission. This will check proj/exp for illegal chars.
        #
1120 1121
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1122 1123 1124 1125 1126 1127 1128 1129
        if (permerror):
            return permerror

        if not (argdict["direction"] == "in" or
                argdict["direction"] == "out"):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="direction must be 'in' or 'out'");

1130
        argstr = "-q"
1131
        for opt, val in argdict.items():
1132
            if opt == "wait":
Timothy Stack's avatar
Timothy Stack committed
1133
                if xbool(val):
1134 1135 1136 1137 1138 1139
                    argstr += " -w "
                    pass
                pass
            pass

        argstr += " -s " + escapeshellarg(argdict["direction"])
1140 1141
        argstr += " " + escapeshellarg(argdict["proj"])
        argstr += " " + escapeshellarg(argdict["exp"])
1142 1143 1144 1145 1146 1147 1148

        (exitval, output) = runcommand(TBDIR + "/bin/swapexp " + argstr)
        if exitval:
            return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
        
        return EmulabResponse(RESPONSE_SUCCESS, output=output)

1149
    #
Timothy Stack's avatar
Timothy Stack committed
1150
    # modify an experiment using swapexp. We get the NS file inline, which
1151 1152
    # we have to write to a temp file first. 
    #
1153
    def modify(self, version, argdict):
1154
        if version != self.VERSION:
1155
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
1156 1157
                                  output="Client version mismatch!")

1158 1159 1160 1161
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
1162 1163
        try:
            checknologins()
1164
            pass
Timothy Stack's avatar
Timothy Stack committed
1165 1166
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1167

1168 1169 1170 1171
        argerror = CheckRequiredArgs(argdict, ("proj", "exp"))
        if (argerror):
            return argerror

1172 1173 1174
        #
        # Check permission. This will check proj/exp for illegal chars.
        #
1175 1176
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1177 1178 1179
        if (permerror):
            return permerror

1180
        nsfilename = None
1181
        argstr     = "-q"
1182 1183
        
        for opt, val in argdict.items():
1184
            if opt == "wait":
Timothy Stack's avatar
Timothy Stack committed
1185
                if xbool(val):
1186 1187
                    argstr += " -w "
                    pass
1188
                pass
1189
            elif opt == "reboot":
Timothy Stack's avatar
Timothy Stack committed
1190
                if xbool(val):
1191 1192
                    argstr += " -r "
                    pass
1193
                pass
1194
            elif opt == "restart_eventsys":
Timothy Stack's avatar
Timothy Stack committed
1195
                if xbool(val):
1196 1197
                    argstr += " -e "
                    pass
1198
                pass
1199
            elif opt == "nsfilepath":
1200
                # Backend script will verify this local path. 
1201
                nsfilename = escapeshellarg(val)
1202
                pass
1203
            elif opt == "nsfilestr":
1204 1205 1206
                nsfilestr = val
            
                if len(nsfilestr) > (1024 * 512):
1207
                    return EmulabResponse(RESPONSE_TOOBIG,
1208
                                         output="NS File way too big!");
1209 1210 1211
        
                (nsfp, nsfilename) = writensfile(nsfilestr)
                if not nsfilename:
1212
                    return EmulabResponse(RESPONSE_SERVERERROR,
1213
                                         output="Server Error")
1214 1215 1216
                pass
            pass

1217
        argstr += " -s modify"
1218 1219
        argstr += " " + escapeshellarg(argdict["proj"])
        argstr += " " + escapeshellarg(argdict["exp"])
1220

1221
        if nsfilename:
1222
            argstr += " " + nsfilename
1223 1224 1225
            pass

        (exitval, output) = runcommand(TBDIR + "/bin/swapexp " + argstr)
1226
        if exitval:
1227
            return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
1228
        
1229
        return EmulabResponse(RESPONSE_SUCCESS, output=output)
1230 1231 1232 1233 1234 1235

    #
    # end an experiment using endexp.
    #
    def endexp(self, version, argdict):
        if version != self.VERSION:
1236
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
1237 1238
                                  output="Client version mismatch!")
        
1239 1240 1241 1242
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
1243 1244
        try:
            checknologins()
1245
            pass
Timothy Stack's avatar
Timothy Stack committed
1246 1247
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1248

1249 1250 1251 1252
        argerror = CheckRequiredArgs(argdict, ("proj", "exp"))
        if (argerror):
            return argerror

1253 1254 1255
        #
        # Check permission. This will check proj/exp for illegal chars.
        #
1256 1257
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1258 1259 1260
        if (permerror):
            return permerror

1261
        argstr = "-q"
1262
        for opt, val in argdict.items():
1263
            if opt == "wait":
Timothy Stack's avatar
Timothy Stack committed
1264
                if xbool(val):
1265 1266
                    argstr += " -w "
                    pass
1267 1268 1269
                pass
            pass

1270 1271
        argstr += " " + escapeshellarg(argdict["proj"])
        argstr += " " + escapeshellarg(argdict["exp"])
1272 1273

        (exitval, output) = runcommand(TBDIR + "/bin/endexp " + argstr)
1274
        if exitval:
1275
            return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
1276
        
1277
        return EmulabResponse(RESPONSE_SUCCESS, output=output)
1278

1279 1280 1281 1282 1283 1284 1285 1286
    #
    # Get textual info from tbreport and send back as string
    #
    def expinfo(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")
        
1287 1288 1289 1290
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303
        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        argerror = CheckRequiredArgs(argdict, ("proj", "exp", "show"))
        if (argerror):
            return argerror

        #
        # Check permission. This will check proj/exp for illegal chars.
        #
1304 1305
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334
        if (permerror):
            return permerror

        argstr = ""
        tokens = argdict["show"].split(",")
        for show in tokens:
            if show == "nodeinfo":
                argstr += " -n"
                pass
            elif show == "mapping":
                argstr += " -m"
                pass
            elif show == "linkinfo":
                argstr += " -l"
                pass
            elif show == "shaping":
                argstr += " -d"
                pass
            pass

        argstr += " " + escapeshellarg(argdict["proj"])
        argstr += " " + escapeshellarg(argdict["exp"])

        (exitval, output) = runcommand(TBDIR + "/bin/tbreport " + argstr)
        if exitval:
            return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
        
        return EmulabResponse(RESPONSE_SUCCESS, output=output)

1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378
    def metadata(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")
        
        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

        argerror = CheckRequiredArgs(argdict, ("proj", "exp"))
        if (argerror):
            return argerror

        #
        # Check permission. This will check proj/exp for illegal chars.
        #
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
        if (permerror):
            return permerror

        res = DBQueryFatal("SELECT * FROM experiments WHERE pid=%s and eid=%s",
                           (argdict["proj"], argdict["exp"]),
                           True)

        if len(res) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No such experiment?")

        res = scrubdict(res[0], [
            "expt_locked",
            "testdb",
            "logfile_open",
            "prerender_pid",
            "keyhash",
            "eventkey",
            "sim_reswap_count",
            "elab_in_elab"
            ])
        
        return EmulabResponse(RESPONSE_SUCCESS, value=res, output=str(res))

1379 1380 1381 1382 1383 1384
    #
    # Return the state of an experiment.
    #
    def state(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
1385 1386
                                  output="Client version mismatch!")

1387 1388 1389 1390
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
1391 1392 1393 1394 1395
        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1396 1397 1398 1399 1400 1401 1402 1403 1404

        argerror = CheckRequiredArgs(argdict, ("proj", "exp"))
        if (argerror):
            return argerror

        if not (re.match("^[-\w]*$", argdict["proj"]) and
                re.match("^[-\w]*$", argdict["exp"])):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Improperly formed proj/exp!")
1405 1406 1407 1408

        #
        # Check permission.
        #
1409 1410
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1411 1412 1413
        if (permerror):
            return permerror

1414 1415 1416 1417 1418 1419 1420
        res = DBQueryFatal("select state from experiments "
                           "where pid=%s and eid=%s",
                           (argdict["proj"], argdict["exp"]))

        if len(res) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No such experiment!")
Timothy Stack's avatar
Timothy Stack committed
1421

1422 1423 1424
        state = res[0][0]
        return EmulabResponse(RESPONSE_SUCCESS, value=state, output=state)

1425 1426 1427 1428 1429 1430 1431 1432 1433 1434
    #
    # Wait for an experiment to reach a state; this is especially useful
    # with batch experiments. There are probably race conditions inherent
    # in this stuff, but typical usage should not encounter them, I hope.
    #
    def statewait(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")

1435 1436 1437 1438 1439
        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1440 1441 1442 1443 1444

        argerror = CheckRequiredArgs(argdict, ("proj", "exp", "state"))
        if (argerror):
            return argerror

1445
        # Check for well formed proj/exp and
1446 1447 1448 1449 1450
        if not (re.match("^[-\w]*$", argdict["proj"]) and
                re.match("^[-\w]*$", argdict["exp"])):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Improperly formed proj/exp!")

1451
        # ... timeout arguments.
1452
        if (argdict.has_key("timeout") and
1453
            isinstance(argdict["timeout"], types.StringType) and
1454 1455 1456
            not re.match("^[\d]*$", argdict["timeout"])):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Improperly formed timeout!")
1457 1458 1459 1460 1461

        # Make sure the state argument is a list.
        if (not isinstance(argdict["state"], types.ListType)):
            argdict["state"] = [argdict["state"],]
            pass
1462
        
1463
        res = DBQueryFatal("select state from experiments "
1464 1465 1466 1467 1468 1469 1470 1471 1472
                           "where pid=%s and eid=%s",
                           (argdict["proj"], argdict["exp"]))

        if len(res) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No such experiment: " +
                                  argdict["proj"] + "/" + argdict["exp"])

        #
1473
        # Check permission.
1474
        #
1475 1476
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1477 1478
        if (permerror):
            return permerror
1479 1480

        #
1481
        # First, see if the experiment is already in the desired state,
1482
        #
1483
        state = res[0][0]
1484
        if (state in argdict["state"]):
1485 1486
            return EmulabResponse(RESPONSE_SUCCESS, value=state, output=state)

1487
        # ... subscribe to the event, and then
1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500
        try:
            import tbevent
            pass
        except ImportError, e:
            return EmulabResponse(RESPONSE_ERROR, output="System Error")

        at = tbevent.address_tuple()
        at.objtype = "TBEXPTSTATE"
        at.objname = argdict["proj"] + "/" + argdict["exp"]
        at.expt    = argdict["proj"] + "/" + argdict["exp"]
        at.host    = BOSSNODE

        try:
1501
            mc = tbevent.EventClient(server="localhost", port=BOSSEVENTPORT)
1502
            mc.subscribe(at)
1503
            pass
1504 1505 1506 1507
        except:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="Could not connect to Event System")

1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522
        # ... check the state again in case it changed between the first
        # check and the subscription.
        res = DBQueryFatal("select state from experiments "
                           "where pid=%s and eid=%s",
                           (argdict["proj"], argdict["exp"]))

        if len(res) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No such experiment: " +
                                  argdict["proj"] + "/" + argdict["exp"])

        state = res[0][0]
        if (state in argdict["state"]):
            return EmulabResponse(RESPONSE_SUCCESS, value=state, output=state)

1523 1524 1525 1526 1527
        if (argdict.has_key("timeout")):
            signal.signal(signal.SIGALRM, TimeoutHandler)
            signal.alarm(int(argdict["timeout"]))
            pass

1528
        # Need to wait for an event.
1529 1530 1531 1532 1533
        try:
            while True:
                ev = mc.poll()

                if ev == None:
1534
                    time.sleep(1) # Slow down the polling.
1535 1536
                    continue

1537 1538 1539 1540
                # ... check if it is one the user cares about.
                if ((argdict["state"] == []) or
                    (ev.getEventType() in argdict["state"])):
                    retval = ev.getEventType()
1541
                    break
1542

1543
                pass
1544
            pass
1545 1546
        except TimedOutError, e:
            return EmulabResponse(RESPONSE_TIMEDOUT,
1547 1548
                                  output=("Timed out waiting for states: "
                                          + `argdict["state"]`))
1549 1550 1551 1552

        if (argdict.has_key("timeout")):
            signal.alarm(0)
            pass
1553 1554

        del(mc)
1555
        
1556
        return EmulabResponse(RESPONSE_SUCCESS, value=retval, output=retval)
1557 1558 1559 1560 1561 1562 1563 1564

    #
    # Wrap up above for a simple "waitforactive" to avoid leaking more
    # goo out then needed (eventstates).
    #
    def waitforactive(self, version, argdict):
        argdict["state"] = "active";
        return self.statewait(version, argdict);
1565
        
1566
    #
Timothy Stack's avatar
Timothy Stack committed
1567
    # Return the node/link mappings for an experiment.
1568 1569 1570 1571
    #
    def info(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
1572 1573 1574 1575
                                  output="Client version mismatch!")

        try:
            checknologins()
1576
            pass
Timothy Stack's avatar
Timothy Stack committed
1577 1578
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1579 1580 1581 1582 1583 1584 1585 1586 1587 1588

        argerror = CheckRequiredArgs(argdict, ("proj", "exp", "aspect"))
        if (argerror):
            return argerror

        if not (re.match("^[-\w]*$", argdict["proj"]) and
                re.match("^[-\w]*$", argdict["exp"])):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Illegal characters in arguments!")

1589 1590 1591
        #
        # Check permission.
        #
1592 1593
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1594 1595 1596
        if (permerror):
            return permerror

1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608
        res = DBQueryFatal("select state from experiments "
                           "where pid=%s and eid=%s",
                           (argdict["proj"], argdict["exp"]))

        if len(res) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No such experiment!")
        state   = res[0][0]
        result  = {}
        mapping = None

        if state == "active":
Timothy Stack's avatar
Timothy Stack committed
1609 1610 1611 1612 1613
            dbres = DBQuery(
                "select r.vname,r.node_id,n.type,n.def_boot_osid,ns.status,"
                "n.eventstate,"
                "(unix_timestamp(now()) - unix_timestamp( "
                "greatest(na.last_tty_act,na.last_net_act,na.last_cpu_act,"
1614
                "na.last_ext_act))),ni.load_1min,ni.load_5min,ni.load_15min,"
1615
                "n.phys_nodeid,r.erole "
Timothy Stack's avatar
Timothy Stack committed
1616 1617 1618 1619 1620 1621 1622 1623 1624
                "  from reserved as r "
                "left join nodes as n on r.node_id=n.node_id "
                "left join node_status as ns on ns.node_id=n.node_id "
                "left join node_activity as na on na.node_id=n.node_id "
                "left join node_idlestats as ni on ni.node_id=n.node_id "
                "where r.pid=%s and r.eid=%s "
                "order by r.vname",
                (argdict["proj"], argdict["exp"]))
            osmappings = {}
1625 1626 1627 1628 1629 1630
            mapping = {}
            for res in dbres:
                tmp = {}
                tmp["name"] = res[0]
                tmp["node"] = res[1]
                tmp["type"] = res[2]
1631
                tmp["pnode"] = res[10]
Timothy Stack's avatar
Timothy Stack committed
1632 1633 1634 1635 1636 1637 1638 1639 1640 1641
                if not osmappings.has_key(res[3]):
                    osres = DBQuery(
                        "SELECT osname from os_info where osid=%s", (res[3],))
                    osmappings[res[3]] = osres[0][0]
                    pass
                tmp["osid"] = osmappings[res[3]]
                tmp["status"] = res[4]
                if res[5]:
                    tmp["eventstatus"] = res[5]
                    pass
1642 1643 1644
                if res[6]:
                    tmp["idle"] = res[6]
                    pass
Timothy Stack's avatar
Timothy Stack committed
1645 1646 1647 1648 1649 1650 1651 1652 1653
                if res[7]:
                    tmp["load_1min"] = res[7]
                    pass
                if res[8]:
                    tmp["load_5min"] = res[8]
                    pass
                if res[9]:
                    tmp["load_15min"] = res[9]
                    pass
1654
                tmp["erole"] = res[11]
1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672
                mapping[res[0]] = tmp
                pass
            pass

        if argdict["aspect"] == "mapping":
            if state != "active":
                return EmulabResponse(RESPONSE_ERROR,
                                      output="Experiment is not active!")
            # Just return the mapping above
            result = mapping
            pass
        elif argdict["aspect"] == "links":
            dbres = DBQueryFatal("SELECT vname,ips from virt_nodes "
                                 "where pid=%s and eid=%s",
                                 (argdict["proj"], argdict["exp"]))
            ipmap = {}
            for res in dbres:
                for ipinfo in string.split(res[1], " "):
1673 1674 1675 1676
                    if len(ipinfo) > 0:
                        port, ip = string.split(ipinfo, ":")
                        ipmap[res[0] + ":" + port] = ip
                        pass
1677 1678 1679 1680 1681 1682 1683 1684 1685
                    pass
                pass
            
            dbres = DBQuery("select vname,member,mask,delay,bandwidth, "
                            "       lossrate,rdelay,rbandwidth,rlossrate "
                            "from virt_lans where pid=%s and eid=%s "
                            "order by vname,member",
                            (argdict["proj"], argdict["exp"]))

1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700
            if len(dbres) > 0:
                for res in dbres:
                    tmp = {}
                    tmp["name"]        = res[0]
                    tmp["member"]      = res[1]
                    tmp["ipaddr"]      = ipmap[res[1]]
                    tmp["mask"]        = res[2]
                    tmp["delay"]       = res[3]
                    tmp["bandwidth"]   = int(res[4])
                    tmp["plr"]         = res[5]
                    tmp["r_delay"]     = res[6]
                    tmp["r_bandwidth"] = int(res[7])
                    tmp<