libplab.py.in 74.5 KB
Newer Older
1
# -*- python -*-
Kirk Webb's avatar
Kirk Webb committed
2 3
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2004, 2006, 2007 University of Utah and the Flux Group.
Kirk Webb's avatar
Kirk Webb committed
5 6 7
# All rights reserved.
#

8
"""
Kirk Webb's avatar
Kirk Webb committed
9 10 11 12 13 14 15 16 17
Library for interfacing with Plab.  This abstracts out the concepts of
Plab central, slices, and nodes.  All data (except static things like
certificates) is kept in the Emulab DB.  Unlike the regular dslice
svm, this one supports dynamically changing which nodes are in a
slice.

This requires an already obtained dslice certficate and key.  By
default it expects to find these in the @prefix@/etc/plab/
subdirectory.
18 19 20
"""

import sys
21 22 23
sys.path.append("@prefix@/lib")

import os, time
24
import string
Kirk Webb's avatar
Kirk Webb committed
25
import traceback
26
import signal
27
import socket
Kirk Webb's avatar
Kirk Webb committed
28 29 30
import httplib
import xml.parsers.expat
import re
31
import calendar
32
import shlex
33

34
from popen2 import Popen4
35
from warnings import warn
36

37 38 39 40 41 42 43 44 45 46 47
#
# Testbed and DB access libs
#
from libtestbed import *
from libdb import *

#
# Plab modules to import
#
from mod_PLC import mod_PLC
from mod_dslice import mod_dslice
48
from mod_PLCNM import mod_PLCNM
49
from mod_PLC4 import mod_PLC4
50 51

agents = {'PLC'    : mod_PLC,
52
          'dslice' : mod_dslice,
53 54
          'PLCNM'  : mod_PLCNM,
          'PLC4'   : mod_PLC4}
55

Kirk Webb's avatar
Kirk Webb committed
56 57 58 59 60 61 62
#
# Initialize the AOP stuff
#
from aspects import wrap_around
from timer_advisories import initTimeAdvice, timeAdvice
initTimeAdvice("plabtiming")

Kirk Webb's avatar
Kirk Webb committed
63 64 65 66 67 68
#
# output control vars
#
verbose = 0
debug = 0

69 70 71
#
# Constants
#
72
DEF_AGENT = "PLC4";
73 74 75 76
#
# Add a compatibility agent --- libplab will make all PLC calls on this agent
# in addition to the default agent.
#
77
COMPAT_AGENT = "PLCNM";
78
# if this is set, do all PLC calls on COMPAT_AGENT as well as DEF_AGENT
79
compat_mode = 0
80

81
RENEW_TIME = 2*24*60*60  # Renew two days before lease expires
82 83 84

RENEW_TIMEOUT = 1*60     # give the node manager a minute to respond to renew
FREE_TIMEOUT  = 1*60     # give the node manager a minute to respond to free
85
NODEPROBEINT  = 30
86

87 88
USERNODE = "@USERNODE@"
TBOPS = "@TBOPSEMAIL_NOSLASH@"
89
MAILTAG = "@THISHOMEBASE@"
90
SLICE_ALIAS_DIR = "/etc/mail/plab-slice-addrs"
91

92
RESERVED_PID = "emulab-ops"
93
RESERVED_EID = "hwdown"       # start life in hwdown
94 95
MONITOR_PID  = "emulab-ops"
MONITOR_EID  = "plab-monitor"
96

Kirk Webb's avatar
Kirk Webb committed
97 98
MAGIC_INET2_GATEWAYS = ("205.124.237.10",  "205.124.244.18",
                        "205.124.244.178", )
99 100 101
MAGIC_INET_GATEWAYS =  ("205.124.244.150", "205.124.239.185",
                        "205.124.244.154", "205.124.244.138",
                        "205.124.244.130", )
102
LOCAL_PLAB_DOMAIN = ".flux.utah.edu"
103
LOCAL_PLAB_LINKTYPE = "pcplabinet2"
104 105 106

# allowed nil/unknown values (sentinels).
ATTR_NIL_VALUES = ('None',)
107

108 109 110 111
# 'critical' node identifiers - those that are actually used to uniquely
# identify a planetlab node
ATTR_CRIT_KEYS = ('HNAME', 'IP', 'PLABID', 'MAC',)

112 113 114 115
# The amount by which latitude and longitude are allowed to differ before we
# classify them ask changed
LATLONG_DELTA = 0.001

116
PLABNODE = "@prefix@/sbin/plabnode"
117
SSH = "@prefix@/bin/sshtb"
118
NAMED_SETUP = "@prefix@/sbin/named_setup"
119
PELAB_PUSH  = "@prefix@/sbin/pelab_opspush"
120

Kirk Webb's avatar
Kirk Webb committed
121 122 123 124
ROOTBALL_URL = "http://localhost:1492/" # ensure this ends in a slash

DEF_PLAB_URL = "www.planet-lab.org"
DEF_SITE_XML = "/xml/sites.xml"
125 126
IGNORED_NODES_FILE = "@prefix@/etc/plab/IGNOREDNODES"
ALLOWED_NODES_FILE = "@prefix@/etc/plab/ALLOWEDNODES"
127

128
DEF_ROOTBALL_NAME = "@PLAB_ROOTBALL@"
129
SLICEPREFIX = "@PLAB_SLICEPREFIX@"
Kirk Webb's avatar
Kirk Webb committed
130 131 132 133
NODEPREFIX  = "plab"

BADSITECHARS = re.compile(r"\W+")
PLABBASEPRIO = 20000
134
PLAB_SVC_SLICENAME = "utah_svc_slice"
135 136 137 138
PLAB_SVC_SLICEDESC = "Emulab management service slice. Performs periodic " \
                     "checkins with Emulab central, and routes events for " \
                     "other Emulab slices. Slivers in this slice should " \
                     "only interact with other PlanetLab machines, and Emulab."
139 140
PLABMON_PID = "emulab-ops"
PLABMON_EID = "plab-monitor"
141
DEF_SLICE_DESC = "Slice created by Emulab"
142

143 144
PLABEXPIREWARN = 1*WEEK        # one week advance warning for slice expiration.
NODEEXPIREWARN = 2*WEEK+2*DAY  # about two weeks advance warning for slivers.
145

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
#
# This is a trigger table for dist'ing out multiple rootballs depending on
# which NM is running on the node.  Since we don't know which version is
# running on a node until we call into (DEF|COMPAT)_AGENT and actually connect
# to the node, it's based off classnames.
#
# The table specifies a prefix to the default rootball name; this rootball
# should be placed in the normal location.
#
# If the plcagent class and nodeagent class are both not in the trigger table,
# we simply push the default rootball.
#
# Note that we could store version info in the database, but that doesn't
# really have long-term value.  Plus, we have to be checking sites.xml all the
# time to catch version changes during rollout.  This way, plabmonitord and
# the web interface know exactly what is the version whenever a node is
# contacted for setup.
#
# For now, only NM4agent has a custom tarball, since v4 nodes are in the
# distinct minority right now.
#
from mod_PLC4 import NM4agent

rootball_triggers = { mod_PLC4 : { NM4agent : 'NM4-' } }

171 172 173 174 175
#
# var to track failed renewals
#
failedrenew = []

176 177 178 179 180
#
# Disable line buffering
#
sys.stdout = os.fdopen(sys.stdout.fileno(), sys.stdout.mode, 0)

181 182 183 184 185
#
# Ensure SIGPIPE doesn't bite us:
#
signal.signal(signal.SIGPIPE, signal.SIG_IGN)

186

187 188 189
#
# Plab abstraction
#
Kirk Webb's avatar
Kirk Webb committed
190

191 192 193 194 195 196 197 198 199 200
#
# Multiple attribute change exception
#
class MultiChangeError(Exception):
    def __init__(self, nodeid, chattrs={}):
        self.nodeid = nodeid
        self.chattrs = chattrs
        pass
    pass

Kirk Webb's avatar
Kirk Webb committed
201

Kirk Webb's avatar
Kirk Webb committed
202 203 204
#
# Class responsible for parsing planetlab sites file
#
Kirk Webb's avatar
Kirk Webb committed
205 206 207 208 209 210 211 212
class siteParser:

    def __init__(self):
        self.parser = xml.parsers.expat.ParserCreate()
        self.parser.StartElementHandler = self.__site_start_elt
        self.parser.EndElementHandler = self.__site_end_elt
        self.__hosts = []
        self.__sitename = ""
213 214
        self.__latitude = 0
        self.__longitude = 0
Kirk Webb's avatar
Kirk Webb committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
        
    def getPlabNodeInfo(self):
                
        conn = httplib.HTTPSConnection(DEF_PLAB_URL)
        conn.request("GET", DEF_SITE_XML)
        res = conn.getresponse()
        if res.status != 200:
            raise RuntimeError, "HTTP Error getting site list:\n" \
                  "Code: %d Reason: %s" % \
                  (res.status, res.reason)
        try:
            self.parser.ParseFile(res)
            pass
        except xml.parsers.expat.ExpatError, e:
            print "Error parsing XML file, lineno: %d, offset: %d:\n%s" % \
                  (e.lineno, e.offset, xml.parsers.expat.ErrorString(e.code))
            raise

        return self.__hosts

    def __site_start_elt(self, name, attrs):
        
        if name == "PLANETLAB_SITES":
            pass
        
        elif name == "SITE":
            self.__sitename = attrs['SHORT_SITE_NAME']
242 243 244
            if attrs.has_key('LATITUDE'):
                self.__latitude = attrs['LATITUDE']
            else:
245
                self.__latitude = 0
246 247 248 249
            if attrs.has_key('LONGITUDE'):
                self.__longitude = attrs['LONGITUDE']
            else:
                self.__longitude = 0
Kirk Webb's avatar
Kirk Webb committed
250 251 252
            pass
        
        elif name == "HOST":
253 254 255
            if not attrs.has_key('MAC'):
                attrs['MAC'] = "None"
                pass
256 257 258
            if not attrs.has_key('BWLIMIT'):
                attrs['BWLIMIT'] = "-1"
                pass
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
            if not attrs.has_key('IP'):
                print "node %s did not have IP!" % attrs['NAME']
                pass
            else:
                self.__hosts.append({
                    'HNAME'     : attrs['NAME'],
                    'IP'        : attrs['IP'],
                    'PLABID'    : attrs['NODE_ID'],
                    'MAC'       : attrs['MAC'],
                    'BWLIMIT'   : attrs['BWLIMIT'],
                    'SITE'      : self.__sitename,
                    'LATITUDE'  : self.__latitude,
                    'LONGITUDE' : self.__longitude,
                    'STATUS'    : attrs['STATUS']
                    })
                pass
Kirk Webb's avatar
Kirk Webb committed
275 276 277 278 279 280 281 282 283 284 285 286
            pass
        
        else:
            print "Unknown element in site file: %s: %s" % (name, attrs)
            pass
        
        return

    def __site_end_elt(self, name):
        
        if name == "SITE":
            self.__sitename = "Unknown"
287 288
            self.__latitude = 0
            self.__longitude = 0
Kirk Webb's avatar
Kirk Webb committed
289 290 291
            pass
        return

Kirk Webb's avatar
Kirk Webb committed
292

293
class Plab:
Kirk Webb's avatar
Kirk Webb committed
294
    def __init__(self, agent=None):
295 296 297
        if not agent:
            self.agent = agents[DEF_AGENT]()
            pass
298 299
        if compat_mode:
            self.compat_agent = agents[COMPAT_AGENT]()
Kirk Webb's avatar
Kirk Webb committed
300
        if debug:
301
            print "Using module: %s" % self.agent.modname
302 303
            if compat_mode:
                print "COMPAT: Using module: %s" % self.compat_agent.modname
304 305
            pass
        pass
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322

    def createSlice(self, pid, eid):
        """
        Slice factory function
        """
        slice = Slice(self, pid, eid)
        slice._create()
        return slice

    def loadSlice(self, pid, eid):
        """
        Slice factory function
        """
        slice = Slice(self, pid, eid)
        slice._load()
        return slice

Kirk Webb's avatar
Kirk Webb committed
323
    def updateNodeEntries(self, ignorenew = False):
324
        """
Kirk Webb's avatar
Kirk Webb committed
325
        Finds out which Plab nodes are available, and
326 327 328 329 330
        update the DB accordingly.  If ignorenew is True, this will only
        make sure that the data in the DB is correct, and not complete.
        If ignorenew is False (the default), this will do a complete
        update of the DB.  However, this can take some time, as
        information about new nodes (such as link type) must be
Kirk Webb's avatar
Kirk Webb committed
331
        discovered.
332 333 334 335 336 337

        Note that this seemingly innocent funciton actually does a lot of
        magic.  This is the main/only way that Plab nodes get into the
        nodes DB, and this list is updated dynamically.  It also gathers
        static data about new nodes.
        """
Kirk Webb's avatar
Kirk Webb committed
338 339 340
        
        print "Getting available Plab nodes ..."

341
        avail = []
342
        try:
Kirk Webb's avatar
Kirk Webb committed
343 344 345
            parser = siteParser()
            avail = parser.getPlabNodeInfo()
            pass
346
        # XXX: rewrite to use more elegant exception info gathering.
347 348
        except:
            extype, exval, extrace = sys.exc_info()
349
            print "Error talking to agent: %s: %s" % (extype, exval)
Kirk Webb's avatar
Kirk Webb committed
350
            if debug:
351 352 353 354
                #print extrace
                traceback.print_exc()
                pass
            
355 356
            print "Going back to sleep until next scheduled poll"
            return
357

Kirk Webb's avatar
Kirk Webb committed
358
        if debug:
359 360
            print "Got advertisement list:"
            print avail
Kirk Webb's avatar
Kirk Webb committed
361
            pass
362

363 364 365
        ignored_nodes = self.__readNodeFile(IGNORED_NODES_FILE)
        allowed_nodes = self.__readNodeFile(ALLOWED_NODES_FILE)

366 367 368 369 370 371
        # Enforce node limitations, if any.
        # XXX: This is ugly - maybe move to a separate function
        #      that takes a list of filter functions.  I know!!
        #      Create a generator out of a set of filter functions
        #      and the initial node list! :-)  Python geek points to me if
        #      I ever get around to it...  KRW
372
        if len(allowed_nodes) or len(ignored_nodes):
373
            allowed = []
Kirk Webb's avatar
Kirk Webb committed
374
            for nodeent in avail:
375
                if nodeent['PLABID'] in ignored_nodes:
376
                    continue
377 378
                elif len(allowed_nodes):
                    if nodeent['IP'] in allowed_nodes:
379 380 381 382 383
                        allowed.append(nodeent)
                        pass
                    pass
                else:
                    allowed.append(nodeent)
Kirk Webb's avatar
Kirk Webb committed
384 385
                    pass
                pass
386 387 388 389
            if verbose:
                print "Advertisements in allowed nodes list:\n%s" % allowed
                pass
            avail = allowed
Kirk Webb's avatar
Kirk Webb committed
390
            pass
391

392 393 394 395 396 397 398 399 400 401 402
        # Check for duplicate node attributes (sanity check)
        availdups = self.__findDuplicateAttrs(avail)
        if len(availdups):
            SENDMAIL(TBOPS, "Duplicates in plab advertised node list",
                     "Duplicate attributes:\n"
                     "%s\n\n"
                     "Let plab support know!" % availdups,
                     TBOPS)
            raise RuntimeError, \
                  "Duplicate attributes in plab node listing:\n%s" % availdups

403
        # Get node info we already have.
404
        known = self.__getKnownPnodes()
Kirk Webb's avatar
Kirk Webb committed
405
        if debug:
406 407
            print "Got known pnodes:"
            print known
Kirk Webb's avatar
Kirk Webb committed
408
            pass
409

Kirk Webb's avatar
Kirk Webb committed
410
        # Create list of nodes to add or update
411 412
        toadd    = []  # List of node entries to add to DB
        toupdate = []  # List of node entries to update in the DB
Kirk Webb's avatar
Kirk Webb committed
413
        for nodeent in avail:
Kirk Webb's avatar
Kirk Webb committed
414 415
            # Replace sequences of bad chars in the site entity with
            # a single "-".
Kirk Webb's avatar
Kirk Webb committed
416
            nodeent['SITE'] = BADSITECHARS.sub("-", nodeent['SITE'])
417 418 419
            # Determine if we already know about this node.
            matchres = self.__matchPlabNode(nodeent, known)
            if not matchres:
420
                toadd.append(nodeent)
Kirk Webb's avatar
Kirk Webb committed
421
                pass
422 423
            elif len(matchres[1]):
                toupdate.append((nodeent,matchres))
Kirk Webb's avatar
Kirk Webb committed
424
                pass
425
            pass
Kirk Webb's avatar
Kirk Webb committed
426

427 428
        # Process the list of nodes to add
        addstr = ""
429
        if len(toadd):
Kirk Webb's avatar
Kirk Webb committed
430
            # Are we ignoring new entries?
431
            if ignorenew:
Kirk Webb's avatar
Kirk Webb committed
432
                if verbose:
433
                    print "%d new Plab nodes, but ignored for now" % len(toadd)
Kirk Webb's avatar
Kirk Webb committed
434 435
                    pass
                pass
Kirk Webb's avatar
Kirk Webb committed
436
            # If not ignoring, do the addition/update.
437
            else:
438 439
                print "There are %d new Plab nodes." % len(toadd)
                for nodeent in toadd:
Kirk Webb's avatar
Kirk Webb committed
440
                    # Get the linktype here so we can report it in email.
Kirk Webb's avatar
Kirk Webb committed
441
                    self.__findLinkType(nodeent)
Kirk Webb's avatar
Kirk Webb committed
442
                    if debug:
Kirk Webb's avatar
Kirk Webb committed
443 444
                        print "Found linktype %s for node %s" % \
                              (nodeent['LINKTYPE'], nodeent['IP'])
Kirk Webb's avatar
Kirk Webb committed
445
                        pass
446 447 448
                    # Add the node.
                    self.__addNode(nodeent)
                    # Add a line for the add/update message.
Kirk Webb's avatar
Kirk Webb committed
449
                    nodestr = "%s\t\t%s\t\t%s\t\t%s\t\t%s\n" % \
450
                              (nodeent['PLABID'],
Kirk Webb's avatar
Kirk Webb committed
451 452 453 454
                               nodeent['IP'],
                               nodeent['HNAME'],
                               nodeent['SITE'],
                               nodeent['LINKTYPE'])
455
                    addstr += nodestr
Kirk Webb's avatar
Kirk Webb committed
456
                    pass
457 458
                pass
            pass
459

460
        # Process node updates.
461
        updstr = ""
462
        chgerrstr = ""
463 464
        if len(toupdate):
            print "There are %d plab node updates." % len(toupdate)
465
            for (nodeent,(nodeid,diffattrs)) in toupdate:
466
                try:
467
                    self.__updateNodeMapping(nodeid, diffattrs)
468 469 470 471 472
                    pass
                except MultiChangeError, e:
                    print "%s not updated: Too many attribute changes." % \
                          e.nodeid
                    chgerrstr += "%s:\n" % e.nodeid
473 474
                    for (attr,(old,new)) in e.chattrs.items():
                        chgerrstr += "\t%s:\t%s => %s\n" % (attr,old,new)
475 476 477
                        pass
                    chgerrstr += "\n"
                    continue
478
                self.__updateNode(nodeid, nodeent)
479
                # Add a line for the add/update message.
480 481 482
                nodestr = nodeid + "\n"
                for (attr,(old,new)) in diffattrs.items():
                    nodestr += "\t%s:\t%s => %s\n" % (attr,old,new)
483 484
                    pass
                updstr += nodestr + "\n"
Kirk Webb's avatar
Kirk Webb committed
485 486
                pass
            pass
487

488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
        # Do node features updates separately since very few nodes are usually
        # updated, whereas we must do status separately from other fields.
        # XXX: munge this in with other fields later.
        upfeatures = []
        for nodeent in avail:
            # Determine if we already know about this node.
            try:
                matchres = self.__matchPlabNode(nodeent, known)
                if matchres:
                    upfeatures.append((nodeent,matchres))
                    pass
                pass
            except:
                pass
            pass
        
        for (nodeent,(nodeid,other)) in upfeatures:
            self.__updateNodeFeatures(nodeid,nodeent)
            pass
        
508 509 510 511 512 513 514 515
        if chgerrstr:
            SENDMAIL(TBOPS,
                     "Two or more changes detected for some plab nodes",
                     "Two or more distinguishing attributes have changed "
                     "on the following planetlab nodes:\n\n%s\n" % chgerrstr,
                     TBOPS)
            pass

516 517 518 519
        if len(toadd) or len(toupdate):
            # We need to update DNS since we've added hosts..
            print "Forcing a named map update ..."
            os.spawnl(os.P_WAIT, NAMED_SETUP, NAMED_SETUP)
520 521
            print "Pushing out site_mapping ..."
            os.spawnl(os.P_WAIT, PELAB_PUSH, PELAB_PUSH)
522 523 524 525 526 527 528 529 530 531 532 533
            # Now announce that we've added/updated nodes.
            SENDMAIL(TBOPS,
                     "Plab nodes have been added/updated in the DB.",
                     "The following plab nodes have been added to the DB:\n"
                     "PlabID\t\tIP\t\tHostname\t\tSite\t\tLinktype\n\n"
                     "%s\n\n"
                     "The following plab nodes have been updated in the DB:\n"
                     "\n%s\n\n" % \
                     (addstr, updstr),
                     TBOPS)
            print "Done adding new Plab nodes."
            pass
Kirk Webb's avatar
Kirk Webb committed
534
        return
535

536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
    def __matchPlabNode(self, plabent, knownents):
        """
        Helper function.  Returns a two-element tuple or null.
        Null is returned when the node does not match any in the
        knownents list (none of it's attributes match those of any
        in the list).  If a match (or partial match) is found, a two
        element tuple is returned.  The first element is the emulab
        node id that matched, and the second is a dictionary containing
        thos elements that differed between the two (in the case of a
        partial match).
        """
        for nid in knownents:
            ent = knownents[nid]
            same = {}
            diff = {}
            for attr in ent:
                if ent[attr] in ATTR_NIL_VALUES:
                    continue
554 555 556
                elif (attr == "LATITUDE") or (attr == "LONGITUDE"):
                    # Special rules for latitude and longitude to avoid
                    # FP errors
557 558 559 560 561 562 563 564 565
                    nasty = False
                    try:
                        x = float(ent[attr])
                        x = float(plabent[attr])
                        pass
                    except:
                        nasty = True
                        pass
                    if (not nasty and ent[attr] != None and plabent[attr] != None) \
566 567 568 569 570
                           and (ent[attr] != "" and plabent[attr] != "") \
                           and ((float(ent[attr]) > \
                                 (float(plabent[attr]) + LATLONG_DELTA)) \
                                or (float(ent[attr]) < \
                                    (float(plabent[attr]) - LATLONG_DELTA))):
571 572 573 574
                        diff[attr] = (ent[attr], plabent[attr])
                    else:
                        same[attr] = ent[attr]
                        pass
575 576 577 578 579 580 581
                elif ent[attr] == plabent[attr]:
                    same[attr] = ent[attr]
                    pass
                else:
                    diff[attr] = (ent[attr], plabent[attr])
                    pass
                pass
582 583
            # Only consider these to be the same if at least one 'critical'
            # attr is the same
584
            if len(same):
585 586 587
                for attr in same:
                    if attr in ATTR_CRIT_KEYS:
                        return (nid, diff)
588 589 590
            pass
        return ()

591 592 593 594 595
    def __getKnownPnodes(self):
        """
        getFree helper function.  Returns a dict of IP:node_id pairs
        for the Plab nodes that currently exist in the DB.
        """
596 597
        res = DBQueryFatal("select plab_mapping.node_id,plab_id,"
                           "plab_mapping.hostname,IP,mac,site,latitude,"
598
                           "longitude,bwlimit"
599 600 601 602
                           " from plab_mapping"
                           " left join widearea_nodeinfo on"
                           "    plab_mapping.node_id = "
                           "    widearea_nodeinfo.node_id")
603
        
604
        ret = {}
605 606
        for (nodeid, plabid, hostname, ip, mac, site,
             latitude, longitude, bwlimit) in res:
607 608 609 610 611 612
            ret[nodeid] = {'PLABID'    : plabid,
                           'HNAME'     : hostname,
                           'IP'        : ip,
                           'MAC'       : mac,
                           'SITE'      : site,
                           'LATITUDE'  : latitude,
613 614
                           'LONGITUDE' : longitude,
                           'BWLIMIT'   : bwlimit}
Kirk Webb's avatar
Kirk Webb committed
615
            pass
616 617 618 619 620 621 622 623 624 625
        # Check for duplicate node attributes: report any that are found.
        dups = self.__findDuplicateAttrs(ret.values())
        if len(dups):
            SENDMAIL(TBOPS, "Duplicate plab node attributes in the DB!",
                     "Duplicate node attrs:\n"
                     "%s\n\n"
                     "Fix up please!" % dups,
                     TBOPS)
            raise RuntimeError, \
                  "Duplicate node attributes in DB:\n%s" % dups            
626
        return ret
627 628 629 630 631 632 633 634 635

    def __findDuplicateAttrs(self, nodelist):
        """
        Find duplicate node attributes in the node list passed in.
        """
        attrs = {}
        dups = {}
        
        for ent in nodelist:
636
            for attr in ATTR_CRIT_KEYS:
637 638 639 640 641 642 643 644 645 646 647 648 649 650
                entry = "%s:%s" % (attr, ent[attr])
                if attrs.has_key(entry) and \
                   ent[attr] not in ATTR_NIL_VALUES:
                    print "Duplicate node attribute: %s" % entry
                    if not dups.has_key(entry):
                        dups[entry] = [attrs[entry],]
                        pass
                    dups[entry].append(ent['PLABID'])
                else:
                    attrs[entry] = ent['PLABID']
                    pass
                pass
            pass
        return dups
651
        
Kirk Webb's avatar
Kirk Webb committed
652
    def __findLinkType(self, nodeent):
653 654 655 656 657 658 659
        """
        getFree helper function.  Figures out the link type of the given
        host.  This first performs a traceroute and checks for the U of
        U's I2 gateway to classify Internet2 hosts.  If this test fails,
        it checks if the hostname is international.  If this test fails,
        this simply specifies an inet link type.

Kirk Webb's avatar
Kirk Webb committed
660
        This can't detect DSL links..
661
        """
662
        # Is host international (or flux/emulab local)?
663
        from socket import gethostbyaddr, getfqdn, herror
Kirk Webb's avatar
Kirk Webb committed
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
        
        if not nodeent.has_key('HNAME'):
            try:
                (hname, ) = gethostbyaddr(ip)
                nodeent['HNAME'] = getfqdn(hname)
                pass
            except herror:
                nodeent['HNAME'] = nodeent['IP']
                print "WARNING: Failed to get hostname for %s" % nodeent['IP']
                pass
            pass
        
        tld = nodeent['HNAME'].split(".")[-1].lower()
        if not tld in ("edu", "org", "net", "com", "gov", "us", "ca"):
            nodeent['LINKTYPE'] = "pcplabintl"
            return
        
        # Is it us?
        if nodeent['HNAME'].endswith(LOCAL_PLAB_DOMAIN):
            nodeent['LINKTYPE'] = LOCAL_PLAB_LINKTYPE
            return
        
686
        # Is host on I2?
Kirk Webb's avatar
Kirk Webb committed
687
        traceroute = os.popen("traceroute -nm 10 -q 1 %s" % nodeent['IP'])
688 689 690 691 692
        trace = traceroute.read()
        traceroute.close()

        for gw in MAGIC_INET2_GATEWAYS:
            if trace.find(gw) != -1:
Kirk Webb's avatar
Kirk Webb committed
693 694
                nodeent['LINKTYPE'] = "pcplabinet2"
                return
695

696 697
        for gw in MAGIC_INET_GATEWAYS:
            if trace.find(gw) != -1:
Kirk Webb's avatar
Kirk Webb committed
698 699
                nodeent['LINKTYPE'] = "pcplabinet"
                return
700
        else:
Kirk Webb's avatar
Kirk Webb committed
701
            print "WARNING: Unknown gateway for host %s" % nodeent['IP']
702

Kirk Webb's avatar
Kirk Webb committed
703 704
        # We don't know - must manually classify.
        nodeent['LINKTYPE'] = "*Unknown*"
Kirk Webb's avatar
Kirk Webb committed
705
        return
706

707
    def __addNode(self, nodeent):
708
        """
709 710 711
        updateNodeEntries() helper function.  Adds a new Plab pnode and
        associated vnode to the DB.  The argument is a dictionary containing
        the new node's attributes.
712
        """
713
        # Generate/grab variables to be used when creating the node.
714
        defosid, controliface = self.__getNodetypeInfo()
Kirk Webb's avatar
Kirk Webb committed
715
        hostonly = nodeent['HNAME'].replace(".", "-")
716 717 718 719 720 721 722 723 724 725 726 727
        nidnum, priority = self.__nextFreeNodeid()
        nodeid = "%s%d" % (NODEPREFIX, nidnum)
        vnodeprefix = "%svm%d" % (NODEPREFIX, nidnum)
        print "Creating pnode %s as %s, priority %d." % \
              (nodeent['IP'], nodeid, priority)

        # Do the stuff common to both node addition and update first
        # Note that if this fails, we want the exception generated to
        # percolate up to the caller immediately, so don't catch it.
        self.__updateNode(nodeid, nodeent)

        # Now perform stuff specific to node addition
728
        try:
729 730 731
            res_exptidx = TBExptIDX(RESERVED_PID, RESERVED_EID)
            mon_exptidx = TBExptIDX(MONITOR_PID, MONITOR_EID)
            
Kirk Webb's avatar
Kirk Webb committed
732
            DBQueryFatal("replace into nodes"
733
                         " (node_id, type, phys_nodeid, role, priority,"
734
                         "  op_mode, def_boot_osid,"
735
                         "  allocstate, allocstate_timestamp,"
736
                         "  eventstate, state_timestamp)"
737
                         " values (%s, %s, %s, %s, %s,"
738 739 740 741
                         "  %s, %s, %s, now(), %s, now())",
                         (nodeid, 'pcplabphys', nodeid,
                          'testnode', priority*100,
                          'ALWAYSUP', defosid,
742
                          'FREE_CLEAN',
743
                          'ISUP'))
744

745 746 747
            DBQueryFatal("replace into node_hostkeys"
                         " (node_id)"
                         " values (%s)",
Kirk Webb's avatar
Kirk Webb committed
748
                         (nodeid))
749

Kirk Webb's avatar
Kirk Webb committed
750
            DBQueryFatal("replace into reserved"
751
                         " (node_id, exptidx, pid, eid, rsrv_time, vname)"
752
                         " values (%s, %s, %s, %s, now(), %s)",
753 754
                         (nodeid, res_exptidx,
                          RESERVED_PID, RESERVED_EID, hostonly))
755

756 757
            # XXX: This should probably be checked and updated if necessary
            #      when updating.
Kirk Webb's avatar
Kirk Webb committed
758
            DBQueryFatal("replace into node_auxtypes"
759 760
                         " (node_id, type, count)"
                         " values (%s, %s, %s)",
Kirk Webb's avatar
Kirk Webb committed
761
                         (nodeid, nodeent['LINKTYPE'], 1))
762
            
Kirk Webb's avatar
Kirk Webb committed
763
            DBQueryFatal("replace into node_auxtypes"
764 765 766 767
                         " (node_id, type, count)"
                         " values (%s, %s, %s)",
                         (nodeid, 'pcplab', 1))
            
Kirk Webb's avatar
Kirk Webb committed
768
            DBQueryFatal("replace into node_status"
769 770
                         " (node_id, status, status_timestamp)"
                         " values (%s, %s, now())",
Kirk Webb's avatar
Kirk Webb committed
771
                         (nodeid, 'down'))
772

773 774 775 776 777
            DBQueryFatal("insert into plab_mapping"
                         " (node_id, plab_id, hostname, IP, mac, create_time)"
                         " values (%s, %s, %s, %s, %s, now())",
                         (nodeid, nodeent['PLABID'], nodeent['HNAME'],
                          nodeent['IP'], nodeent['MAC']))
778

779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
            #
            # NowAdd the site_mapping entry for this node.
            #
            
            # See if we know about the associated site - grab idx if so
            siteidx = 0
            nodeidx = 1
            siteres = DBQueryFatal("select site_idx, node_idx from "
                                   " plab_site_mapping where site_name=%s",
                                   nodeent['SITE']);
            if len(siteres):
                # There are already nodes listed for this site, so get
                # the next node id.
                siteidx = siteres[0][0]
                for (foo, idx) in siteres:
                    if idx > nodeidx: nodeidx = idx
                    pass
                nodeidx += 1
                pass
            else:
                # No nodes listed for site, so get the largest site_idx
                # in the DB so far, and increment cuz we're going to add
                # a new one.
                maxres = DBQueryFatal("select MAX(site_idx) from "
                                      " plab_site_mapping")
                try:
                    siteidx = int(maxres[0][0]) + 1
                    pass
                except ValueError:
                    siteidx = 1
                    pass
                pass
            # Create site_mapping entry, optionally creating new site idx
            # via not specifying the site_idx field (field is auto_increment)
            DBQueryFatal("insert into plab_site_mapping "
                         " values (%s, %s, %s, %s)",
                         (nodeent['SITE'], siteidx, nodeid, nodeidx))

817
            # Create a single reserved plab vnode for the managment sliver.
818 819 820 821 822
            n = 1
            vprio = (priority * 100) + n
            sshdport = 38000 + n
            vnodeid = "%s-%d" % (vnodeprefix, n)
            vnodetype = "pcplab"
823 824
            if verbose:
                print "Creating vnode %s, priority %d" % (vnodeid, vprio)
Kirk Webb's avatar
Kirk Webb committed
825
                pass
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
                    
            DBQueryFatal("insert into nodes"
                         " (node_id, type, phys_nodeid, role, priority,"
                         "  op_mode, def_boot_osid, update_accounts,"
                         "  allocstate, allocstate_timestamp,"
                         "  eventstate, state_timestamp, sshdport)"
                         " values (%s, %s, %s, %s, %s,"
                         "  %s, %s, %s, %s, now(), %s, now(), %s)",
                         (vnodeid, vnodetype, nodeid, 'virtnode', vprio,
                          'PCVM', defosid, 1,
                          'FREE_CLEAN',
                          'SHUTDOWN', sshdport))

            DBQueryFatal("insert into node_hostkeys"
                         " (node_id)"
                         " values (%s)",
                         (vnodeid))
            
            DBQueryFatal("insert into node_status"
                         " (node_id, status, status_timestamp)"
                         " values (%s, %s, now())",
                         (vnodeid, 'up'))
            
Kirk Webb's avatar
Kirk Webb committed
849
            # Put the last vnode created into the special monitoring expt.
850
            DBQueryFatal("insert into reserved"
851
                         " (node_id, exptidx, pid, eid, rsrv_time, vname)"
852
                         " values (%s, %s, %s, %s, now(), %s)",
853 854
                         (vnodeid, mon_exptidx,
                          MONITOR_PID, MONITOR_EID, vnodeid))
Kirk Webb's avatar
Kirk Webb committed
855 856
            pass
        
857 858 859 860 861 862
        except:
            print "Error adding PLAB node to DB: someone needs to clean up!"
            tbmsg = "".join(traceback.format_exception(*sys.exc_info()))
            SENDMAIL(TBOPS, "Error adding new plab node to DB: %s\n" %
                     nodeid, "Some operation failed while trying to add a"
                     " newly discovered plab node to the DB:\n %s"
863
                     "\n Please clean up!\n" % tbmsg, TBOPS)
864
            raise
865 866
        return

867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
    def __updateNodeFeatures(self,nodeid,nodeent):
        """
        Record the status of this node in the node_features
        table.
        """
        # XXX Make this atomic
        #
        try:
            # Note that we have to pass '%' as an arg to DBQuery, sigh
            DBQueryFatal("delete from node_features where node_id=%s" \
                         " and feature like %s",
                         (nodeid,'plabstatus-%'))
            
            if nodeent.has_key('STATUS'):
                # Kind of a hack - we assume most people will want Production
                # nodes
                if nodeent['STATUS'] == "Production" :
                    weight = 0.0
                    pass
                else:
                    weight = 1.0
                    pass
                DBQueryFatal("insert into node_features" \
                             " (node_id, feature, weight)" \
                             " values (%s,%s,%s)",
                             (nodeid,
                              'plabstatus-%s' % nodeent['STATUS'],
                              weight))
                pass
            pass
        except:
            print "Error updating plab node STATUS feature " \
                  "for node %s!" % nodeid
            traceback.print_exc()
            
        
        return None
    
905 906 907 908 909 910 911 912 913 914 915 916
    def __updateNode(self, nodeid, nodeent):
        """
        updateNodeEntries() helper function.  Updates attributes for plab
        nodes passed in via the nodeent argument.
        """
        # Get the name of the control interface for plab nodes.
        defosid, controliface = self.__getNodetypeInfo()

        haslatlong = (('LATITUDE' in nodeent and 'LONGITUDE' in nodeent) and
            (nodeent['LATITUDE'] != 0 or nodeent['LONGITUDE'] != 0))
        try:
            DBQueryFatal("replace into widearea_nodeinfo"
917 918 919 920 921
                         " (node_id, contact_uid, contact_idx, hostname, site,"
                         "  latitude, longitude, bwlimit)"
                         " values (%s, %s, %s, %s, %s, %s, %s, %s)",
                         (nodeid, 'nobody', '0', nodeent['HNAME'],
                          nodeent['SITE'],
922 923 924 925
                          # Poor man's ternary operator
                          haslatlong and nodeent['LATITUDE'] or "NULL",
                          haslatlong and nodeent['LONGITUDE'] or "NULL",
                          nodeent['BWLIMIT']))
926

927 928 929 930 931 932
            DBQueryFatal("replace into interfaces"
                         " (node_id, card, port, IP, interface_type,"
                         " iface, role)"
                         " values (%s, %s, %s, %s, %s, %s, %s)",
                         (nodeid, 0, 1, nodeent['IP'], 'plab_fake',
                          controliface, 'ctrl'))
933

934 935 936 937 938 939 940 941 942
            pass
        except:
            print "Error updating PLAB node in DB: someone needs to clean up!"
            tbmsg = "".join(traceback.format_exception(*sys.exc_info()))
            SENDMAIL(TBOPS, "Error updating plab node in DB: %s\n" % nodeid,
                     "Some operation failed while trying to update"
                     " plab node %s in the DB:\n\n%s"
                     "\nPlease clean up!\n" % (nodeid, tbmsg), TBOPS)
            raise
Kirk Webb's avatar
Kirk Webb committed
943
        return
944

945

946
    def __updateNodeMapping(self, nodeid, chattrs):
947
        """
948
        Updates changed node attributes in the plab mapping table.
949 950 951
        """
        uid = os.getuid()
        dbuid = uid == 0 and "root" or UNIX2DBUID(uid)
952

953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
        # mapping from attrs to column names
        attrmap = {'PLABID' : 'plab_id',
                   'HNAME'  : 'hostname',
                   'IP'     : 'IP',
                   'MAC'    : 'mac'}

        # Get the intersection of mapping (critical) keys with those that
        # have changed.
        changedcritkeys = set(ATTR_CRIT_KEYS) & set(chattrs.keys())
        # nothing to do if none of the mapping attributes have changed.
        if not changedcritkeys:
            return
        # If the node has more than two critical attrs that have changed,
        # then move it to hwdown and raise an exception.
        if len(changedcritkeys) > 2:
            crattrs = {}
            for chkey in changedcritkeys:
                crattrs[chkey] = chattrs[chkey]
971 972
                pass
            errmsg = "More than 2 plab node attrs have changed!\n\n%s\n\n" \
973
                     "%s has been moved to hwdown." % (crattrs, nodeid)
974 975
            MarkPhysNodeDown(nodeid)
            TBSetNodeLogEntry(nodeid, dbuid, TB_NODELOGTYPE_MISC, errmsg)
976 977 978 979 980 981 982
            raise MultiChangeError(nodeid, crattrs)

        # Update mapping table entry.
        updstr = ",".join(map(lambda x: "%s='%s'" %
                              (attrmap[x],chattrs[x][1]), changedcritkeys))
        DBQueryFatal("update plab_mapping set %s where node_id='%s'" %
                     (updstr, nodeid))
983 984
        updmsg = "Plab node %s attributes updated:\n\n%s" % (nodeid, chattrs)
        TBSetNodeLogEntry(nodeid, dbuid, TB_NODELOGTYPE_MISC, updmsg)
985

986 987 988 989 990
        # updateNodeEtries() already sends mail.
        #SENDMAIL(TBOPS,
        #         "Plab node %s attributes updated." % nodeid, updmsg, TBOPS)
        return

991 992
    def __getNodetypeInfo(self):
        """
993 994
        addNode helper function.  Returns a (defosid, controliface) 
        tuple for the Plab pnode type.  Caches the result since
995
        it doesn't change.
996 997
        """
        if not hasattr(self, "__getNodetypeInfoCache"):
Kirk Webb's avatar
Kirk Webb committed
998
            if debug:
999
                print "Getting node type info"
Kirk Webb's avatar
Kirk Webb committed
1000
                pass
1001 1002 1003 1004 1005 1006 1007 1008 1009

            dbres = DBQueryFatal("select attrkey,attrvalue "
                                 " from node_type_attributes as a "
                                 " where type = 'pcplabphys' and "
                                 "       (a.attrkey='default_osid' or "
                                 "        a.attrkey='control_interface') "
                                 " order by attrkey")
            
            assert (len(dbres) == 2), "Failed to get node type info"
Kirk Webb's avatar
Kirk Webb committed
1010 1011
            attrdict = {}
            for attrkey, attrvalue in dbres:
Kirk Webb's avatar
Kirk Webb committed
1012
                attrdict[attrkey] = attrvalue;
Kirk Webb's avatar
Kirk Webb committed
1013 1014
                pass
            self.__getNodetypeInfoCache = \
Kirk Webb's avatar
Kirk Webb committed
1015 1016
                                        (attrdict["default_osid"],
                                         attrdict["control_interface"])
Kirk Webb's avatar
Kirk Webb committed
1017 1018
            pass
        
1019 1020 1021 1022 1023 1024 1025
        return self.__getNodetypeInfoCache

    def __nextFreeNodeid(self):
        """
        addNode helper function.  Returns a (nodeid, priority) tuple of
        the next free nodeid and priority for Plab nodes.
        """
Kirk Webb's avatar
Kirk Webb committed
1026
        if debug:
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036
            print "Getting next free nodeid"
        DBQueryFatal("lock tables nextfreenode write")
        try:
            res = DBQueryFatal("select nextid, nextpri from nextfreenode"
                               " where nodetype = 'pcplab'")
            assert (len(res) == 1), "Unable to find next free nodeid"
            DBQueryFatal("update nextfreenode"
                         " set nextid = nextid + 1, nextpri = nextpri + 1"
                         " where nodetype = 'pcplab'")
            ((nodeid, priority), ) = res
Kirk Webb's avatar
Kirk Webb committed
1037
            pass
1038 1039
        finally:
            DBQueryFatal("unlock tables")
Kirk Webb's avatar
Kirk Webb committed
1040 1041
            pass
        
1042 1043
        return nodeid, priority

1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
    def __readNodeFile(self, filename):
        """
        Helper function - read in list of nodes from a file, seperated
        by arbitrary amounts of whitespace.  No comments allowed.
        """
        nodelist = []
        if os.access(filename, os.F_OK):
            nodefile = open(filename, "r+")
            nodelist = nodefile.read().split()
            nodefile.close()
            pass
        return nodelist

1057
    def renew(self, inpid = None, ineid = None, force = False):
1058
        """
1059 1060 1061
        Renews all of the Plab leases regardless of when they expire.  Note
        that all times are handled in the UTC time zone.  We don't trust
        MySQL to do the right thing with times (yet).
1062
        """
1063

1064 1065
        global failedrenew # XXX
        
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
        now = int(time.time())
        
        if not inpid:
            res = DBQueryFatal("select pid, eid from plab_slices");
            pass
        else:
            if not ineid:
                raise RuntimeError, "renew: Must provide eid with pid."
            res = ((inpid, ineid),)
            pass
Kirk Webb's avatar
Kirk Webb committed
1076
        
1077
        loadedSlices = {}
1078 1079
        newfail = []
        failsoon = []
Kirk Webb's avatar
Kirk Webb committed
1080 1081
        ret = 0

1082
        print "Renewing Plab leases at %s ..." % time.ctime()
Kirk Webb's avatar
Kirk Webb committed
1083

1084
        for (pid, eid) in res:
Kirk Webb's avatar
Kirk Webb committed
1085

1086 1087
            try:
                slice = loadedSlices[(pid, eid)]
Kirk Webb's avatar
Kirk Webb committed
1088
                pass
1089 1090 1091
            except KeyError:
                slice = self.loadSlice(pid, eid)
                loadedSlices[(pid, eid)] = slice
Kirk Webb's avatar
Kirk Webb committed
1092
                pass
1093
            
1094
            res = slice.renew(force)
1095 1096 1097 1098 1099
            entry = (pid, eid, slice.leaseend)
            
            if not res:
                print "Failed to renew lease for %s/%s" % \
                      entry[:2]
1100 1101
                if entry not in failedrenew:
                    newfail.append(entry)
Kirk Webb's avatar
Kirk Webb committed
1102
                    pass
1103
                if (slice.leaseend - now) < PLABEXPIREWARN:
1104
                    failsoon.append(entry)
Kirk Webb's avatar
Kirk Webb committed
1105 1106
                    pass
                pass
1107 1108 1109
            else:
                if entry in failedrenew:
                    failedrenew.remove(entry)
1110
                    pass
Kirk Webb's avatar
Kirk Webb committed
1111
                    
1112 1113
        if newfail:
            failedrenew += newfail
1114
            failstr = ""
1115
            for n in newfail:
1116
                failstr += "%s/%s (expires: %s UTC)\n" % \
1117
                           (n[:2] + (time.asctime(time.gmtime(n[2])),))
Kirk Webb's avatar
Kirk Webb committed
1118
                pass
1119
            
1120
            SENDMAIL(TBOPS, "Lease renewal(s) failed",
1121
                     "Failed to renew the following leases:\n%s" %
1122
                     failstr + "\n\nPlease check the plabrenew log", TBOPS)
Kirk Webb's avatar
Kirk Webb committed
1123
            pass
1124

1125 1126 1127
        if failsoon:
            failstr = ""
            for n in failsoon:
1128
                failstr += "%s/%s: (expires: %s UTC)\n" % \
1129
                           (n[:2] + (time.asctime(time.gmtime(n[2])),))
1130 1131 1132 1133
                pass
            SENDMAIL(TBOPS, "WARNING: PLAB leases have expired, or will soon",
                     "The following plab leases have expired, or will soon:\n"
                     + failstr + "\n\nPlease look into it!", TBOPS)
Kirk Webb's avatar
Kirk Webb committed
1134
            pass
1135

Kirk Webb's avatar
Kirk Webb committed
1136 1137 1138
        return
    
    pass # end class Plab
Kirk Webb's avatar
Kirk Webb committed
1139 1140
# AOP wrappers for class Plab
wrap_around(Plab.createSlice, timeAdvice)
1141 1142 1143 1144 1145

#
# Slice abstraction
#
class Slice:
1146 1147

    def __init__(self, plab, pid, eid, slicename = None):
1148 1149
        self.plab = plab
        self.pid, self.eid = pid, eid
1150
        self.slicemeta = None
1151
        self.slicemeta_legacy = None
1152
        self.exptidx = None
1153
        self.slicename = slicename
1154
        self.description = DEF_SLICE_DESC
1155
        return
1156 1157 1158 1159 1160 1161
    
    def _create(self):
        """
        Creates a new slice that initially contains no nodes.  Don't call
        this directly, use Plab.createSlice instead.
        """
1162 1163 1164

        adminbit = 0
        if self.pid == PLABMON_PID and self.eid == PLABMON_EID:
1165 1166
            self.slicename   = PLAB_SVC_SLICENAME
            self.description = PLAB_SVC_SLICEDESC
1167 1168
            adminbit = 1
            pass
1169 1170 1171 1172 1173 1174 1175 1176 1177 1178

        res = DBQueryFatal("select idx, expt_name from experiments "
                           "where pid=%s "
                           "and eid=%s",
                           (self.pid, self.eid))
        if not len(res):
            raise RuntimeError, \
                  "Didn't get any results while looking up info on " \
                  "experiment %s/%s" % (self.pid, self.eid)
        (eindex, descr) = res[0]
1179
        
1180 1181 1182
        if not self.slicename:
            self.slicename = "%s_%s" % (SLICEPREFIX, eindex)
            pass
1183

1184 1185 1186
        self.description = descr
        self.exptidx = eindex

1187
        print "Creating Plab slice %s." % self.slicename
1188

1189
        try:
1190
            res, tmpslicemeta, self.leaseend = \
Kirk Webb's avatar
Kirk Webb committed
1191
                 self.plab.agent.createSlice(self)
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
            
            # Do compat calls right after main agent calls.
            if compat_mode:
                try:
                    self.plab.compat_agent.createSlice(self)
                except:
                    print "WARNING: compat agent failed in createSlice; " \
                          "\n  watch for inconsistent DB state!"
                    pass
                pass