emulabserver.py.in 176 KB
Newer Older
1 2 3
#! /usr/bin/env python
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2004-2007 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
# Well known directories
46 47 48 49 50
PROJROOT = "@PROJROOT_DIR@"
GROUPROOT = "@GROUPSROOT_DIR@"
SCRATCHROOT = "@SCRATCHROOT_DIR@"
SHAREROOT = "@SHAREROOT_DIR@"
USERSROOT = "@USERSROOT_DIR@"
Timothy Stack's avatar
Timothy Stack committed
51 52 53 54 55 56 57

# List of directories exported to nodes via NFS.
NFS_EXPORTS = [
    PROJROOT,
    GROUPROOT,
    SHAREROOT,
    USERSROOT,
58
    SCRATCHROOT,
Timothy Stack's avatar
Timothy Stack committed
59 60
    ]

61 62 63 64 65 66 67 68 69 70 71 72 73 74
#
# 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" ]},
75 76 77
    "virt_lan_lans"             : { "rows"  : None, 
                                    "tag"   : "lan_lans",
                                    "attrs" : [ "vname" ]},
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    "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" ]},
93 94 95
    "virt_node_startloc"        : { "rows"  : None,
                                    "tag"   : "node_startlocs",
                                    "attrs" : [ "vname", "building" ]},
96 97 98 99 100 101 102 103 104
    "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" ]},
105 106 107
    "virt_user_environment"	: { "rows"  : None, 
                                    "tag"   : "user_environment",
                                    "attrs" : [ "name", "value" ]},
108 109 110 111 112
    "nseconfigs"		: { "rows"  : None, 
                                    "tag"   : "nseconfigs",
                                    "attrs" : [ "vname" ]},
    "eventlist"                 : { "rows"  : None, 
                                    "tag"   : "events",
113
                                    "attrs" : [ "vname" ]},
114 115 116 117 118 119 120 121 122
    "event_groups"              : { "rows"  : None,
                                    "tag"   : "event_groups",
                                    "attrs" : [ "group_name", "agent-name" ]},
    "virt_firewalls"            : { "rows"  : None,
                                    "tag"   : "virt_firewalls",
                                    "attrs" : [ "fwname", "type", "style" ]},
    "firewall_rules"            : { "rows"  : None,
                                    "tag"   : "firewall_rules",
                                    "attrs" : [ "fwname", "ruleno", "rule" ]},
123 124 125
    "virt_tiptunnels"           : { "rows"  : None,
                                    "tag"   : "tiptunnels",
                                    "attrs" : [ "host", "vnode" ]},
126 127
    }
    
Timothy Stack's avatar
Timothy Stack committed
128 129 130 131 132 133 134 135 136 137 138 139
# 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

140 141 142 143 144 145 146
# Exception thrown when a timer expires.
class TimedOutError(EmulabError):
    pass

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

147
#
148 149
# 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
150 151
# pretty much any language should be able to talk to this class.
#
152

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
#
# 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

176 177 178 179 180 181 182 183 184
#
# 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 "
185
                       "WHERE uid=%s and pid=%s and gid=%s and trust!='none'",
186 187 188 189 190 191 192 193 194
                       (uid, pid, pid))

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

195 196 197 198 199 200 201 202
def GetProjects(uid):
    res = DBQueryFatal("SELECT distinct pid FROM group_membership "
                       "WHERE uid=%s",
                       (uid,))

    return [x[0] for x in res]


203 204 205
#
# Check user permission to access an experiment.
# 
206
def CheckExptPermission(uid, pid, eid):
207 208 209 210 211
    if not (re.match("^[-\w]*$", pid) and
            re.match("^[-\w]*$", eid)):
        return EmulabResponse(RESPONSE_BADARGS,
                  output="Illegal characters in project and/or experiment IDs!")
    
212 213 214 215 216 217 218 219 220 221
    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]
222
    
223 224
    res = DBQueryFatal("SELECT trust FROM group_membership "
                       "WHERE uid=%s and pid=%s and gid=%s",
225
                       (uid, pid, gid))
226 227 228 229 230 231 232

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

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
#
# 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])

249 250 251 252 253 254 255 256 257 258 259 260 261 262
#
# Check if user is an admin person
# 
def CheckIsAdmin(uid):
    res = DBQueryFatal("SELECT admin FROM users "
                       "WHERE uid=%s",
                       (uid,))
    
    if len(res) == 0:
        return EmulabResponse(RESPONSE_ERROR,
                              output="No such user: " + uid)

    return res[0][0];

263 264 265 266 267 268 269 270 271 272 273 274 275 276
#
# Template lookup, by exptidx of an experiment.
#
def TemplateLookup(exptidx):
    res = DBQueryFatal("select parent_guid,parent_vers from "
                       "   experiment_template_instances "
                       "where exptidx=%s",
                       (exptidx,))
    
    if len(res) == 0:
        return None

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

277 278 279 280 281 282 283 284 285 286 287 288 289
#
# Get an experiment index.
#
def ExperimentIndex(pid, eid):
    res = DBQueryFatal("select idx from experiments "
                       "where pid=%s and eid=%s",
                       (pid, eid))
    
    if len(res) == 0:
        return None
    
    return res[0][0];

290 291 292 293 294
#
# 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
295
    def __init__(self, readonly=0, clientip=None):
296
        self.readonly  = readonly;
Leigh Stoller's avatar
Leigh Stoller committed
297
        self.clientip  = clientip;
298
        self.instances = {};
299
        self.uid       = pwd.getpwuid(os.getuid())[0]
300 301

        self.instances["experiment"] = experiment(readonly=self.readonly);
302
        self.instances["template"]   = template(readonly=self.readonly);
303 304 305 306 307 308 309 310 311
        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
312 313
        self.instances["elabinelab"] = elabinelab(clientip=self.clientip,
                                                  readonly=self.readonly);
314 315 316 317 318 319 320
        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]):
321 322
            raise AttributeError("unknown method '%s' (readonly=%d)" %
                                 (name, self.readonly))
323 324 325 326
        
        return getattr(self.instances[dotted[0]], dotted[1]);
    pass

Timothy Stack's avatar
Timothy Stack committed
327 328 329 330 331
#
# This class implements the server side of the XMLRPC interface to emulab as a
# whole.
#
class emulab:
332 333 334
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
335
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
        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)
        elif starting:
            comparison = "> %s"
385
            pass
Timothy Stack's avatar
Timothy Stack committed
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
            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(
411
                time.strptime(str(res[2]), "%Y-%m-%d %H:%M:%S"))
Timothy Stack's avatar
Timothy Stack committed
412
            tmp["msgid"] = res[3]
413
            
Timothy Stack's avatar
Timothy Stack committed
414 415 416 417 418 419
            result.append(tmp)
            pass

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

421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
    def getareas(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")
        
        # Get the listing that is accessible to this user and
        res = DBQueryFatal("SELECT distinct building FROM obstacles")

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

        result = {}
        for area in res:
            result[area[0]] = {
                "name" : area[0]
                }
            pass

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

444 445 446 447 448 449 450 451 452
    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'!")

453 454 455
        result = DBQuery("select * from cameras where building=%s",
                        (argdict["area"],),
                        asDict=True)
456
        
457
        if len(result) == 0:
458 459 460
            return EmulabResponse(RESPONSE_ERROR,
                                  output="Unknown area " + argdict["area"])

461 462 463
        result = [scrubdict(x, defaultvals={ "loc_x" : 0.0, "loc_y" : 0.0 })
                  for x in result]

464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
        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
504
            scrubdict(ob)
505 506 507 508 509
            pass
        
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=res,
                              output=str(res))
Timothy Stack's avatar
Timothy Stack committed
510 511 512 513 514 515 516 517 518
    
    pass


#
# This class implements the server side of the XMLRPC interface to user
# specific information.
#
class user:
519 520 521
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
522
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
        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'",
547
            (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 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

        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",
598
                           (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617

        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))

618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
    #
    # Return collab password,
    #
    def collabpassword(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 mailman_password from users where uid=%s",
                           (self.uid,))

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

        passwd = res[0][0]
640
        return EmulabResponse(RESPONSE_SUCCESS, value=passwd, output=passwd)
641

Timothy Stack's avatar
Timothy Stack committed
642 643 644 645 646 647 648 649
    pass


#
# This class implements the server side of the XMLRPC interface to the emulab
# NFS exports.
#
class fs:
650 651 652
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
653
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
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 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 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 789 790 791 792 793 794 795 796 797 798 799
        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 = [
800
            USERSROOT + "/" + self.uid, # XXX Use getpwuid() and handle admin
Timothy Stack's avatar
Timothy Stack committed
801 802 803 804 805 806
            SHAREROOT,
            ]

        # ... add the project/group listings.
        projs = DBQueryFatal("SELECT distinct pid,gid FROM group_membership "
                             "WHERE uid=%s and trust!='none' ORDER BY pid",
807
                             (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
808 809 810 811

        for proj in projs:
            if proj[0] == proj[1]:
                res.append(PROJROOT + "/" + proj[0])
812
		res.append(SCRATCHROOT + "/" + proj[0])
Timothy Stack's avatar
Timothy Stack committed
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
                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:
830 831 832
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
833
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
        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",
852
            (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878

        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:
879 880 881
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
882
        self.uid      = pwd.getpwuid(os.getuid())[0]
Timothy Stack's avatar
Timothy Stack committed
883 884 885 886 887 888 889 890 891 892 893 894 895 896
        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
897
        res = DBQueryFatal("SELECT distinct "
898
                           "o.* FROM "
Timothy Stack's avatar
Timothy Stack committed
899 900 901
                           "os_info as o "
                           "left join group_membership as g on g.pid=o.pid "
                           "where g.uid=%s or o.shared=1",
902 903
                           (self.uid,),
                           True)
Timothy Stack's avatar
Timothy Stack committed
904 905 906 907 908 909 910 911

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

        # ... package it up.
        result = {}
        for osid in res:
912 913 914
            # XXX Legacy stuff...
            osid["fullosid"] = osid["osid"]
            osid["osid"] = osid["osname"]
Timothy Stack's avatar
Timothy Stack committed
915

916 917 918 919 920 921 922
            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
923 924
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
925
                              output="")
926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941
    
    def info(self, version, argdict):
        # Check for valid arguments.
        argerror = CheckRequiredArgs(argdict, ("osid",))
        if (argerror):
            return argerror

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

        # Get the listing that is accessible to this user and
        res = DBQueryFatal("SELECT distinct "
                           "o.* FROM "
                           "os_info as o "
                           "left join group_membership as g on g.pid=o.pid "
942
                           "where (g.uid=%s or o.shared=1) and "
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957
                           "(o.osname=%s or o.osid=%s)",
                           (self.uid, argdict["osid"], argdict["osid"]),
                           True)

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

        osid = res[0]
        osid["fullosid"] = osid["osid"]
        osid["osid"] = osid["osname"]
        
        if not osid["OS"] or len(osid["OS"]) == 0:
            osid["OS"] = "(None)"
            pass
958

959 960 961
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=scrubdict(osid),
                              output=str(osid))
Timothy Stack's avatar
Timothy Stack committed
962 963 964

    pass

965

966 967 968 969
#
# This class implements the server side of the XMLRPC interface to experiments.
#
class experiment:
970 971 972
    ##
    # Initialize the object.  Currently only sets the objects 'VERSION' value.
    #
973 974 975
    def __init__(self, readonly=0):
        self.readonly = readonly;
        self.VERSION  = VERSION
976
        self.uid      = pwd.getpwuid(os.getuid())[0]
977 978 979 980 981 982 983 984 985
        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):
986
        if not argdict.has_key("str"):
987
            return EmulabResponse(RESPONSE_BADARGS,
Timothy Stack's avatar
Timothy Stack committed
988
                                  output="Must supply a string to echo!")
989
        
990
        return EmulabResponse(RESPONSE_SUCCESS, 0,
991 992
                             socket.gethostname() + ": " + str(version)
                             + " " + argdict["str"])
993

Timothy Stack's avatar
Timothy Stack committed
994 995 996 997 998 999 1000 1001
    #
    # 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!")

1002 1003 1004 1005
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
        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!")
        
1029 1030 1031 1032
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
1033 1034 1035 1036 1037 1038
        try:
            checknologins()
            pass
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))

1039 1040 1041 1042 1043 1044 1045 1046
        # 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
1047
        # XXX Let the user specify the pid/gid
1048
        dbres = DBQueryFatal("SELECT e.pid,e.gid,e.eid,e.expt_name,e.state,"
1049 1050
                             "e.expt_head_uid,e.minimum_nodes,e.maximum_nodes,"
                             "count(r.node_id) as actual_nodes "
1051
                             "  FROM experiments AS e "
1052 1053 1054 1055
                             "left join reserved as r on e.pid=r.pid and "
                             "  e.eid=r.eid "
                             "WHERE e.expt_head_uid=%s "
                             "GROUP BY e.pid,e.eid",
1056
                             (self.uid,))
Timothy Stack's avatar
Timothy Stack committed
1057 1058 1059 1060 1061

        # 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:
1062 1063 1064 1065 1066 1067 1068
            # 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]
1069 1070 1071
            minimum_nodes = res[6]
            maximum_nodes = res[7]
            actual_nodes = res[8]
1072 1073 1074 1075
            # ... make sure 'result' has the proper slots,
            if not result.has_key(pid):
                result[pid] = {
                    gid : list()
Timothy Stack's avatar
Timothy Stack committed
1076 1077
                    }
                pass
1078 1079
            elif not result[pid].has_key(gid):
                result[pid][gid] = list()
Timothy Stack's avatar
Timothy Stack committed
1080
                pass
1081 1082 1083 1084 1085 1086 1087

            # ... drop the data into place, and
            expdata = None
            if format == "brief":
                expdata = eid;
                pass
            elif format == "full":
1088
                expdata = scrubdict({
1089 1090 1091 1092 1093 1094
                    "pid" : pid,
                    "gid" : gid,
                    "name" : eid,
                    "description" : desc,
                    "state" : state,
                    "expt_head" : expt_head,
1095 1096 1097
                    "minimum_nodes" : minimum_nodes,
                    "maximum_nodes" : maximum_nodes,
                    "actual_nodes" : actual_nodes,
1098
                    })
Timothy Stack's avatar
Timothy Stack committed
1099
                pass
1100 1101

            # ... append it to the group list.
1102
            result[pid][gid].append(expdata)
Timothy Stack's avatar
Timothy Stack committed
1103 1104 1105 1106 1107 1108
            pass
        
        return EmulabResponse(RESPONSE_SUCCESS,
                              value=result,
                              output=str(result))

1109 1110 1111 1112 1113 1114
    #
    # 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:
1115
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
1116 1117
                                  output="Client version mismatch!")

1118 1119 1120 1121
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
1122 1123
        try:
            checknologins()
1124
            pass
Timothy Stack's avatar
Timothy Stack committed
1125 1126
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1127

1128 1129 1130
        argerror = CheckRequiredArgs(argdict, ("proj", "exp"))
        if (argerror):
            return argerror
1131

1132
        nsfilename = None
1133
        argstr     = "-q"
1134 1135
        
        for opt, val in argdict.items():
1136
            if opt == "batch":
Timothy Stack's avatar
Timothy Stack committed
1137
                if not xbool(val):
1138 1139 1140
                    argstr += " -i"
                    pass
                pass
1141
            elif opt == "description":
1142
                argstr += " -E "
1143
                argstr += escapeshellarg(val)
1144
                pass
1145
            elif opt == "group":
1146
                argstr += " -g "
1147
                argstr += escapeshellarg(val)
1148
                pass
1149
            elif opt == "exp":
1150
                argstr += " -e "
1151
                argstr += escapeshellarg(val)
1152
                pass
1153
            elif opt == "proj":
1154
                argstr += " -p "
1155
                argstr += escapeshellarg(val)
1156
                pass
1157
            elif opt == "swappable":
Timothy Stack's avatar
Timothy Stack committed
1158
                if not xbool(val):
1159 1160 1161
                    if not argdict.has_key("noswap_reason"):
                        return EmulabResponse(RESPONSE_BADARGS,
                                       output="Must supply noswap reason!");
1162
                    argstr += " -S "
1163
                    argstr += escapeshellarg(argdict["noswap_reason"])
1164 1165
                    pass
                pass
1166
            elif opt == "noswap_reason":
1167
                pass
1168
            elif opt == "idleswap":
1169
                if val == 0:
1170 1171 1172
                    if not argdict.has_key("noidleswap_reason"):
                        return EmulabResponse(RESPONSE_BADARGS,
                                      output="Must supply noidleswap reason!");
1173
                    argstr += " -L "
1174
                    argstr += escapeshellarg(argdict["noidleswap_reason"])
1175 1176 1177
                    pass
                else:
                    argstr += " -l "
Timothy Stack's avatar
Timothy Stack committed
1178
                    argstr += escapeshellarg(str(val))
1179 1180
                    pass
                pass
1181
            elif opt == "noidleswap_reason":
1182
                pass
1183
            elif opt == "autoswap" or opt == "max_duration":
1184
                argstr += " -a "
Timothy Stack's avatar
Timothy Stack committed
1185
                argstr += escapeshellarg(str(val))
1186
                pass
1187
            elif opt == "noswapin":
Timothy Stack's avatar
Timothy Stack committed
1188
                if xbool(val):
1189 1190
                    argstr += " -f "
                    pass
1191
                pass
1192
            elif opt == "wait":
Timothy Stack's avatar
Timothy Stack committed
1193
                if xbool(val):
1194 1195
                    argstr += " -w "
                    pass
1196
                pass
1197
            elif opt == "nsfilepath":
1198
                # Backend script will verify this local path. 
1199
                nsfilename = escapeshellarg(val)
1200
                pass
1201
            elif opt == "nsfilestr":
1202 1203 1204
                nsfilestr = val
            
                if len(nsfilestr) > (1024 * 512):
1205
                    return EmulabResponse(RESPONSE_TOOBIG,
1206
                                         output="NS File way too big!");
1207 1208 1209
        
                (nsfp, nsfilename) = writensfile(nsfilestr)
                if not nsfilename:
1210
                    return EmulabResponse(RESPONSE_SERVERERROR,
1211
                                         output="Server Error")
1212 1213 1214 1215
                pass
            pass

        if nsfilename:
1216
            argstr += " " + nsfilename
1217 1218 1219
            pass

        (exitval, output) = runcommand(TBDIR + "/bin/batchexp " + argstr)
1220
        if exitval:
1221
            return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
1222
        
1223
        return EmulabResponse(RESPONSE_SUCCESS, output=output)
1224 1225 1226 1227 1228 1229

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

1231 1232 1233 1234 1235 1236
    #
    # 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
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", "direction"))
        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 1261 1262 1263 1264 1265
        if (permerror):
            return permerror

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

1266
        argstr = "-q"
1267
        for opt, val in argdict.items():
1268
            if opt == "wait":
Timothy Stack's avatar
Timothy Stack committed
1269
                if xbool(val):
1270 1271 1272 1273 1274 1275
                    argstr += " -w "
                    pass
                pass
            pass

        argstr += " -s " + escapeshellarg(argdict["direction"])
1276 1277
        argstr += " " + escapeshellarg(argdict["proj"])
        argstr += " " + escapeshellarg(argdict["exp"])
1278 1279 1280 1281 1282 1283 1284

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

1285
    #
Timothy Stack's avatar
Timothy Stack committed
1286
    # modify an experiment using swapexp. We get the NS file inline, which
1287 1288
    # we have to write to a temp file first. 
    #
1289
    def modify(self, version, argdict):
1290
        if version != self.VERSION:
1291
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
1292 1293
                                  output="Client version mismatch!")

1294 1295 1296 1297
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
1298 1299
        try:
            checknologins()
1300
            pass
Timothy Stack's avatar
Timothy Stack committed
1301 1302
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1303

1304 1305 1306 1307
        argerror = CheckRequiredArgs(argdict, ("proj", "exp"))
        if (argerror):
            return argerror

1308 1309 1310
        #
        # Check permission. This will check proj/exp for illegal chars.
        #
1311 1312
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1313 1314 1315
        if (permerror):
            return permerror

1316
        nsfilename = None
1317
        argstr     = "-q"
1318 1319
        
        for opt, val in argdict.items():
1320
            if opt == "wait":
Timothy Stack's avatar
Timothy Stack committed
1321
                if xbool(val):
1322 1323
                    argstr += " -w "
                    pass
1324
                pass
1325
            elif opt == "reboot":
Timothy Stack's avatar
Timothy Stack committed
1326
                if xbool(val):
1327 1328
                    argstr += " -r "
                    pass
1329
                pass
1330
            elif opt == "restart_eventsys":
Timothy Stack's avatar
Timothy Stack committed
1331
                if xbool(val):
1332 1333
                    argstr += " -e "
                    pass
1334
                pass
1335
            elif opt == "nsfilepath":
1336
                # Backend script will verify this local path. 
1337
                nsfilename = escapeshellarg(val)
1338
                pass
1339
            elif opt == "nsfilestr":
1340 1341 1342
                nsfilestr = val
            
                if len(nsfilestr) > (1024 * 512):
1343
                    return EmulabResponse(RESPONSE_TOOBIG,
1344
                                         output="NS File way too big!");
1345 1346 1347
        
                (nsfp, nsfilename) = writensfile(nsfilestr)
                if not nsfilename:
1348
                    return EmulabResponse(RESPONSE_SERVERERROR,
1349
                                         output="Server Error")
1350 1351 1352
                pass
            pass

1353
        argstr += " -s modify"
1354 1355
        argstr += " " + escapeshellarg(argdict["proj"])
        argstr += " " + escapeshellarg(argdict["exp"])
1356

1357
        if nsfilename:
1358
            argstr += " " + nsfilename
1359 1360 1361
            pass

        (exitval, output) = runcommand(TBDIR + "/bin/swapexp " + argstr)
1362
        if exitval:
1363
            return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
1364
        
1365
        return EmulabResponse(RESPONSE_SUCCESS, output=output)
1366 1367 1368 1369 1370 1371

    #
    # end an experiment using endexp.
    #
    def endexp(self, version, argdict):
        if version != self.VERSION:
1372
            return EmulabResponse(RESPONSE_BADVERSION,
Timothy Stack's avatar
Timothy Stack committed
1373 1374
                                  output="Client version mismatch!")
        
1375 1376 1377 1378
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
Timothy Stack's avatar
Timothy Stack committed
1379 1380
        try:
            checknologins()
1381
            pass
Timothy Stack's avatar
Timothy Stack committed
1382 1383
        except NoLoginsError, e:
            return EmulabResponse(RESPONSE_REFUSED, output=str(e))
1384

1385 1386 1387 1388
        argerror = CheckRequiredArgs(argdict, ("proj", "exp"))
        if (argerror):
            return argerror

1389 1390 1391
        #
        # Check permission. This will check proj/exp for illegal chars.
        #
1392 1393
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1394 1395 1396
        if (permerror):
            return permerror

1397
        argstr = "-q"
1398
        for opt, val in argdict.items():
1399
            if opt == "wait":
Timothy Stack's avatar
Timothy Stack committed
1400
                if xbool(val):
1401 1402
                    argstr += " -w "
                    pass
1403 1404 1405
                pass
            pass

1406 1407
        argstr += " " + escapeshellarg(argdict["proj"])
        argstr += " " + escapeshellarg(argdict["exp"])
1408 1409

        (exitval, output) = runcommand(TBDIR + "/bin/endexp " + argstr)
1410
        if exitval:
1411
            return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
1412
        
1413
        return EmulabResponse(RESPONSE_SUCCESS, output=output)
1414

1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471
    #
    # Get textual info from tbreport and send back as string
    #
    def history(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")
        
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
        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 = {}
        dbres = DBQuery(
            "SELECT * FROM experiment_stats "
            "WHERE pid=%s and eid=%s ORDER by exptidx desc",
            (argdict["proj"], argdict["exp"]),
            asDict=True)

        if len(dbres) == 0:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="No such experiment!")
        
        res["stats"] = scrubdict(dbres[0])
        dbres = DBQuery(
            "SELECT er.*,ts.start_time,ts.end_time,ts.action,ts.idx as tidx "
            "FROM experiments as e "
            "LEFT JOIN experiment_resources as er on e.idx=er.exptidx "
            "LEFT JOIN testbed_stats as ts on ts.rsrcidx=er.idx "
            "WHERE e.pid=%s and e.eid=%s",
            (argdict["proj"], argdict["exp"]),
            asDict=True)
        for er in dbres:
            er["thumbnail"] = xmlrpclib.Binary(er["thumbnail"])
            pass
        res["resources"] = [scrubdict(x) for x in dbres]

        return EmulabResponse(RESPONSE_SUCCESS, value=res)
        
1472 1473 1474 1475 1476 1477 1478 1479
    #
    # 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!")
        
1480 1481 1482 1483
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496
        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.
        #
1497 1498
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527
        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)

1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560
    def metadata(self, version, argdict):
        if version != self.VERSION:
            return EmulabResponse(RESPONSE_BADVERSION,
                                  output="Client version mismatch!")
        
        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",
1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572
            "elab_in_elab",
            "locpiper_port",
            "locpiper_pid",
            "usemodelnet",
            "priority",
            "modelnet_edges",
            "modelnet_cores",
            "elabinelab_nosetup",
            "event_sched_pid",
            "wa_bw_solverweight",
            "wa_plr_solverweight",
            "wa_delay_solverweight",
1573 1574
            ])
        
1575 1576 1577 1578 1579 1580 1581
        vue = DBQueryFatal("SELECT name,value FROM virt_user_environment "
                           "WHERE pid=%s and eid=%s order by idx",
                           (argdict["proj"], argdict["exp"]),
                           True)

        res["user_environment"] = vue

1582 1583
        return EmulabResponse(RESPONSE_SUCCESS, value=res, output=str(res))

1584 1585 1586 1587 1588 1589
    #
    # 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
1590 1591
                                  output="Client version mismatch!")

1592 1593 1594 1595
        if self.readonly:
            return EmulabResponse(RESPONSE_FORBIDDEN,
                              output="Insufficient privledge to invoke method")
        
1596 1597 1598 1599 1600 1601 1602 1603
        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!")
1604 1605 1606 1607

        #
        # Check permission.
        #
1608 1609
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1610 1611 1612
        if (permerror):
            return permerror

1613 1614 1615 1616 1617 1618 1619
        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
1620

1621 1622 1623
        state = res[0][0]
        return EmulabResponse(RESPONSE_SUCCESS, value=state, output=state)

1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637
    #
    # 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!")

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

1638
        # Check for well formed proj/exp and
1639 1640 1641 1642 1643
        if not (re.match("^[-\w]*$", argdict["proj"]) and
                re.match("^[-\w]*$", argdict["exp"])):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Improperly formed proj/exp!")

1644
        # ... timeout arguments.
1645
        if (argdict.has_key("timeout") and
1646
            isinstance(argdict["timeout"], types.StringType) and
1647 1648 1649
            not re.match("^[\d]*$", argdict["timeout"])):
            return EmulabResponse(RESPONSE_BADARGS,
                                  output="Improperly formed timeout!")
1650 1651 1652 1653 1654

        # Make sure the state argument is a list.
        if (not isinstance(argdict["state"], types.ListType)):
            argdict["state"] = [argdict["state"],]
            pass
1655
        
1656
        res = DBQueryFatal("select state from experiments "
1657 1658 1659 1660 1661 1662 1663 1664 1665
                           "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"])

        #
1666
        # Check permission.
1667
        #
1668 1669
        permerror = CheckExptPermission(self.uid,
                                        argdict["proj"], argdict["exp"])
1670 1671
        if (permerror):
            return permerror
1672 1673

        #
1674
        # First, see if the experiment is already in the desired state,
1675
        #
1676
        state = res[0][0]
1677
        if (state in argdict["state"]):
1678 1679
            return EmulabResponse(RESPONSE_SUCCESS, value=state, output=state)

1680
        # ... subscribe to the event, and then
1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693
        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:
1694
            mc = tbevent.EventClient(server="localhost", port=BOSSEVENTPORT)
1695
            mc.subscribe(at)
1696
            pass
1697 1698 1699 1700
        except:
            return EmulabResponse(RESPONSE_ERROR,
                                  output="Could not connect to Event System")