libplab.py.in 134 KB
Newer Older
1
# -*- python -*-
Kirk Webb's avatar
Kirk Webb committed
2 3
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2004, 2006-2008 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
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.
14 15 16
"""

import sys
17 18 19
sys.path.append("@prefix@/lib")

import os, time
20
import string
Kirk Webb's avatar
Kirk Webb committed
21
import traceback
22
import signal
23
import socket
Kirk Webb's avatar
Kirk Webb committed
24 25 26
import httplib
import xml.parsers.expat
import re
27
import calendar
28
import shlex
29

30
from popen2 import Popen4
31
from warnings import warn
32

33 34 35 36 37 38 39 40 41 42 43
#
# 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
44
from mod_PLCNM import mod_PLCNM
45
from mod_PLC4 import mod_PLC4
46 47

agents = {'PLC'    : mod_PLC,
48
          'dslice' : mod_dslice,
49 50
          'PLCNM'  : mod_PLCNM,
          'PLC4'   : mod_PLC4}
51

Kirk Webb's avatar
Kirk Webb committed
52 53 54 55 56 57 58
#
# Initialize the AOP stuff
#
from aspects import wrap_around
from timer_advisories import initTimeAdvice, timeAdvice
initTimeAdvice("plabtiming")

Kirk Webb's avatar
Kirk Webb committed
59 60 61 62 63 64
#
# output control vars
#
verbose = 0
debug = 0

65 66 67
#
# Constants
#
68
DEF_AGENT = "PLC4";
69

70
RENEW_TIME = 2*24*60*60  # Renew two days before lease expires
71 72 73

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
74
NODEPROBEINT  = 30
75

76 77
USERNODE = "@USERNODE@"
TBOPS = "@TBOPSEMAIL_NOSLASH@"
78
MAILTAG = "@THISHOMEBASE@"
79
SLICE_ALIAS_DIR = "/etc/mail/plab-slice-addrs"
80

81 82
DEFAULT_DATA_PATH = "@prefix@/etc/plab"

83
RESERVED_PID = "emulab-ops"
84
RESERVED_EID = "hwdown"       # start life in hwdown
85 86
MONITOR_PID  = "emulab-ops"
MONITOR_EID  = "plab-monitor"
87

88 89 90 91 92 93
BOSSNODE_IP = "@BOSSNODE_IP@"
# obviously this isn't really true, but we put plab/pgeni nodes on the .35
CONTROL_NETWORK = "155.98.35.0"
CONTROL_NETMASK = "255.255.255.0"
CONTROL_ROUTER = "@CONTROL_ROUTER_IP@"

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

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

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

109 110 111 112
# The amount by which latitude and longitude are allowed to differ before we
# classify them ask changed
LATLONG_DELTA = 0.001

113
PLABNODE = "@prefix@/sbin/plabnode"
114
SSH = "@prefix@/bin/sshtb"
115
NAMED_SETUP = "@prefix@/sbin/named_setup"
116
PELAB_PUSH  = "@prefix@/sbin/pelab_opspush"
117

Kirk Webb's avatar
Kirk Webb committed
118 119 120
ROOTBALL_URL = "http://localhost:1492/" # ensure this ends in a slash

DEF_SITE_XML = "/xml/sites.xml"
121 122
IGNORED_NODES_FILE = "@prefix@/etc/plab/IGNOREDNODES"
ALLOWED_NODES_FILE = "@prefix@/etc/plab/ALLOWEDNODES"
123

Kirk Webb's avatar
Kirk Webb committed
124 125
BADSITECHARS = re.compile(r"\W+")
PLABBASEPRIO = 20000
126
PLAB_SVC_SLICENAME = "utah_svc_slice"
127 128 129 130 131
PLAB_SVC_SLICEDESC = "@THISHOMEBASE@ management service slice. Performs " \
                     "periodic checkins with @THISHOMEBASE@ central, and " \
                     "routes events for other @THISHOMEBASE@ slices. " \
                     "Slivers in this slice should only interact with " \
                     "other PLC-based nodes, and @THISHOMEBASE@."
132 133
PLABMON_PID = "emulab-ops"
PLABMON_EID = "plab-monitor"
134 135
DEF_SLICE_DESC = "Slice created by @THISHOMEBASE@"
DEF_EMULAB_URL = "http://@WWWHOST@"
136

137 138
PLABEXPIREWARN = 1*WEEK        # one week advance warning for slice expiration.
NODEEXPIREWARN = 2*WEEK+2*DAY  # about two weeks advance warning for slivers.
139

140 141 142 143 144
#
# var to track failed renewals
#
failedrenew = []

145 146 147 148 149
#
# Disable line buffering
#
sys.stdout = os.fdopen(sys.stdout.fileno(), sys.stdout.mode, 0)

150 151 152 153 154
#
# Ensure SIGPIPE doesn't bite us:
#
signal.signal(signal.SIGPIPE, signal.SIG_IGN)

155 156 157
#
# Plab abstraction
#
Kirk Webb's avatar
Kirk Webb committed
158

159 160 161 162 163 164 165 166 167 168
#
# 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
169 170 171
#
# Class responsible for parsing planetlab sites file
#
Kirk Webb's avatar
Kirk Webb committed
172 173
class siteParser:

174 175
    def __init__(self,plc):
        self.plc = plc
Kirk Webb's avatar
Kirk Webb committed
176 177 178 179 180
        self.parser = xml.parsers.expat.ParserCreate()
        self.parser.StartElementHandler = self.__site_start_elt
        self.parser.EndElementHandler = self.__site_end_elt
        self.__hosts = []
        self.__sitename = ""
181 182
        self.__latitude = 0
        self.__longitude = 0
Kirk Webb's avatar
Kirk Webb committed
183 184
        
    def getPlabNodeInfo(self):
185
        conn = httplib.HTTPSConnection(self.plc.url)
Kirk Webb's avatar
Kirk Webb committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
        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):
203 204

        # XXX: how will this look for multiple plcs?
Kirk Webb's avatar
Kirk Webb committed
205 206 207 208 209
        if name == "PLANETLAB_SITES":
            pass
        
        elif name == "SITE":
            self.__sitename = attrs['SHORT_SITE_NAME']
210 211 212
            if attrs.has_key('LATITUDE'):
                self.__latitude = attrs['LATITUDE']
            else:
213
                self.__latitude = 0
214 215 216 217
            if attrs.has_key('LONGITUDE'):
                self.__longitude = attrs['LONGITUDE']
            else:
                self.__longitude = 0
Kirk Webb's avatar
Kirk Webb committed
218 219 220
            pass
        
        elif name == "HOST":
221 222 223
            if not attrs.has_key('MAC'):
                attrs['MAC'] = "None"
                pass
224 225 226
            if not attrs.has_key('BWLIMIT'):
                attrs['BWLIMIT'] = "-1"
                pass
227 228 229 230
            if not attrs.has_key('IP'):
                print "node %s did not have IP!" % attrs['NAME']
                pass
            else:
231 232 233 234 235 236 237 238 239 240 241 242 243 244
                adi = { '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
                        }
                if attrs.has_key('STATUS'):
                    adi['STATUS'] = attrs['STATUS']
                    pass

                self.__hosts.append(adi)
245
                pass
Kirk Webb's avatar
Kirk Webb committed
246 247 248 249 250 251 252 253 254 255 256 257
            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"
258 259
            self.__latitude = 0
            self.__longitude = 0
Kirk Webb's avatar
Kirk Webb committed
260 261 262
            pass
        return

263
    pass
Kirk Webb's avatar
Kirk Webb committed
264

265 266 267 268 269 270 271 272 273 274
#
# Class to pull node, nodenetwork, and site info from PLC via xmlrpc.  Its
# getPlabNodeInfo method returns in the same format as the original
# SiteParser.getPlabNodeInfo .
#
class XmlrpcNodeInfoFetcher:
    def __init__(self,plc):
        self.plc = plc
        # store the info
        self.__hosts = []
275
        self.__groups = dict({})
276 277 278 279

        which_agent = plc.getAttrVal("nmagent")
        if which_agent == None:
            which_agent = DEF_AGENT
280
            pass
281 282 283

        # grab a mod_PLC4 agent
        self.agent = agents[which_agent](plc)
284 285 286 287
        try:
            self.agent.setdebug(debug,verbose)
        except:
            pass
288 289 290

        pass

291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    def getPlabNodeGroupInfo(self,ignoreErrors=True):
        ngf = ['nodegroup_id','name','description','node_ids']
        ng = self.agent.getNodeGroups(outfilter=ngf)

        # build up our translation table
        pemap = dict({})
        qres = DBQueryFatal("select node_id,plab_id from plab_mapping")
        for (node_id,plab_id) in qres:
            pemap[plab_id] = node_id
            pass

        self.__groups = dict({})

        for n in ng:
            elab_nodes = []
            for plab_id in n['node_ids']:
                # we have to turn plab_id from planetlab's int into our 
                # varchars; sigh, legacy.
                elab_plab_id = str(plab_id)
                if pemap.has_key(elab_plab_id):
                    elab_nodes.append(pemap[elab_plab_id])
                    pass
                else:
                    print "%s not in pemap!" % elab_plab_id
                pass

            od = dict({ 'name':n['name'],'nodegroup_id':n['nodegroup_id'],
                        'description':n['description'],'node_ids':elab_nodes })

            self.__groups[n['nodegroup_id']] = od
            pass

        return self.__groups

325
    def getPlabNodeInfo(self,ignoreErrors=True):
326 327 328 329 330 331 332 333 334 335 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
        nif = ['nodenetwork_ids','boot_status','hostname','site_id','node_id']
        ni = self.agent.getNodes(outfilter=nif)

        # XXX: eventually will want to handle multiple interfaces
        nni_inf = dict({'is_primary' : True})
        nni_outf = ['ip','nodenetwork_id','mac','bwlimit']
        nni = self.agent.getNodeNetworks(infilter=nni_inf,outfilter=nni_outf)
        # index by nnid to make lookups easier
        nni_map = dict({})
        for n in nni:
            nni_map[n['nodenetwork_id']] = dict({ 'IP'      : n['ip'],
                                                  'MAC'     : n['mac'],
                                                  'BWLIMIT' : n['bwlimit'] })
            pass

        sif = ['site_id','longitude','latitude','abbreviated_name']
        si = self.agent.getSites(outfilter=sif)
        # index by sid
        si_map = dict({})
        for s in si:
            si_map[s['site_id']] = dict({ 'SITE' : s['abbreviated_name'],
                                          'LATITUDE' : s['latitude'],
                                          'LONGITUDE' : s['longitude'] })
            pass

        # now, munge into one list:
        for n in ni:
            # check if we have site info for this node:
            if not si_map.has_key(n['site_id']):
                errstr = "could not find site for node %s" % n['hostname']
                if not ignoreErrors:
                    raise RuntimeError, "Error: %s" % errstr
                else:
                    print "Warning: %s" % errstr
                    pass
                continue

            # check if we have the primary nodenetwork for this node:
            nnid = -1
            for i in n['nodenetwork_ids']:
                if nni_map.has_key(i):
                    nnid = i
                    break
                pass
            if nnid < 0:
                errstr = "could not find network for node %s" % n['hostname']
                if not ignoreErrors:
                    raise RuntimeError, "Error: %s" % errstr
                else:
                    print "Warning: %s" % errstr
                    pass
                continue

            # now add the node:
380 381
            # note that we force some ints to strings because the xml file
            # siteParser didn't force them to ints.
382
            adi = { 'HNAME'     : n['hostname'],
383
                    'PLABID'    : str(n['node_id']),
384 385 386 387 388 389
                    'IP'        : nni_map[nnid]['IP'],
                    'MAC'       : nni_map[nnid]['MAC'],
                    'BWLIMIT'   : nni_map[nnid]['BWLIMIT'],
                    'SITE'      : si_map[n['site_id']]['SITE'],
                    'LATITUDE'  : si_map[n['site_id']]['LATITUDE'],
                    'LONGITUDE' : si_map[n['site_id']]['LONGITUDE'] }
390 391 392
            if adi['BWLIMIT']:
                adi['BWLIMIT'] = str(adi['BWLIMIT'])
                pass
393 394 395 396 397
            if n.has_key('boot_status'):
                adi['STATUS'] = n['boot_status']
                pass
            
            self.__hosts.append(adi)
398
            pass
399 400 401 402 403 404 405 406

        return self.__hosts
    
    pass


class Plab:
    def __init__(self):
407
        pass
408

409
    def getPLCs(self):
410
        """
411
        Returns a list of PLC (name,idx) tuples currently in the Emulab db.
412
        """
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
        plcs = []
        res = DBQueryFatal("select plc_idx,plc_name from plab_plc_info")
        for (idx,name) in res:
            plcs.append((name,idx))
            pass
        return plcs

    def createSlices(self,pid,eid,stopOnFail=False):
        """
        Slice factory function that creates all slices necessary for an
        Emulab experiment.  If you want to be immediately notified if one of
        the slices cannot be created successfully, set the stopOnFail param.

        Note: if there are errors while creating and configuring some slices,
        those are returned via an exception.
        Any successfully-created slices are NOT torn down!  If you get an
        exception from this call, you should immediately call Plab.loadSlices,
        and call the destroySlice method on each slice returned by loadSlices.
        """

        # Since each experiment could have multiple slices, we figure out
        # which slices we're going to need, and we allow for the possibility
        # that there may already be an existing slice for this experiment
        # that can host some of its nodes.
        #
        # (XXX: for now, we're assuming that each experiment can have only one
        # slice per PLC (figuring out which nodes go to which slice at the
        # same PLC may not be needed ever, and is going to require lots more
        # config info.))

        slicelist = []
        failedslices = []

        # grab any existing slices and which plcs host them
        res = DBQueryFatal("select plc_idx,slicename"
                           "  from plab_slices"
                           " where pid=%s and eid=%s",
                           (pid,eid))
        existing = dict({})
        for (plcidx,slicename) in res:
            existing[plcidx] = slicename
            pass

        # grab which plcs we need a slice at, and the necessary info to create
        # a slicename
        res = DBQueryFatal("select ppi.plc_idx,ppi.def_slice_prefix"
                           " from reserved as r"
                           " left join nodes as n on r.node_id=n.node_id"
                           " left join node_types as nt on n.type=nt.type"
                           " left join plab_plc_info as ppi"
                           "   on nt.type=ppi.node_type"
                           " where r.pid=%s and r.eid=%s"
                           "   and ppi.plc_idx is not NULL"
                           " group by n.type",(pid,eid))
        for (plcidx,prefix) in res:
            plc,slicename = None,None
469 470 471 472 473 474 475 476

            # grab our plc so we can get config attrs
            try:
                plc = PLC(plcidx)
            except:
                raise

            # figure out how we're supposed to create this slice
David Johnson's avatar
Bugs.  
David Johnson committed
477
            slice_create_method = plc.getAttrVal('slice_create_method')
478 479 480 481
            if not slice_create_method or slice_create_method == '':
                slice_create_method = 'singlesite'
                pass

482 483 484 485
            if existing.has_key(plcidx):
                slicename = existing[plcidx]
                pass
            else:
486
                # grab the exptidx; may need it
487 488 489 490 491
                res = DBQueryFatal("select idx from experiments"
                                   " where pid=%s and eid=%s",
                                   (pid,eid))
                if not len(res):
                    raise RuntimeError, \
492 493
                          "Didn't get any results while looking up info" \
                          " on experiment %s/%s" % (self.pid, self.eid)
494
                (exptidx,) = res[0]
495 496 497 498 499 500
                
                if slice_create_method == 'singlesite':
                    # name the slice using the prefix of our single site
                    slicename = "%s_elab_%d" % (prefix,exptidx)
                    pass
                elif slice_create_method == 'federate':
501 502 503
                    if debug:
                        print "Using 'federate' slice create method!"
                        pass
504 505 506 507
                    # use the prefix of the site for the pid containing
                    # this eid.
                    translator = EmulabPlcObjTranslator(plc)

508 509 510
                    if debug:
                        print "Synch'ing project %s" % str(pid)
                        pass
511 512 513 514 515 516 517 518 519
                    # ensure that project (site) is created and up to date
                    translator.syncObject('project',pid)

                    # ensure that all users in this project are members of the
                    # site, and that their info is up to date
                    res = DBQueryFatal("select uid from group_membership" \
                                       " where pid=%s",(pid,))
                    for row in res:
                        (p_uid,) = row
520 521 522
                        if debug:
                            print "Synch'ing user %s" % str(p_uid)
                            pass
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
                        translator.syncObject('user',p_uid)
                        pass

                    # now grab whatever site name we need:
                    site = translator.getPlabName('project',pid)

                    # have to get rid of dashes and make things lowercase.
                    slicename = "%s_%s" % (site,eid.lower().replace("-",""))

                    # NOTE: since we're mapping from the Emulab eid superset
                    # of slice names, check to ensure we're not duplicating
                    # a slicename already in use.  If we are, no problem;
                    # we just append "_%s" % eid_idx.
                    res = DBQueryFatal("select slicename from plab_slices" \
                                       " where pid=%s and slicename=%s",
                                       (pid,slicename))
                    if res and len(res) > 0:
                        slicename = "%s_%s" % (slicename,str(exptidx))
                        pass
                    pass
543
                pass
544

545 546 547 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
            slice = EmulabSlice(plc,slicename,pid=pid,eid=eid)
            try:
                slice.create()
            except:
                print "Create of slice %s at %s failed!" % (slice.slicename,
                                                            plc.name)
                if stopOnFail:
                    raise
                failedslices.append(slice.slicename)
                if debug:
                    traceback.print_exc()
                    pass
                continue
            slicelist.append(slice)
            pass

        if not failedslices == []:
            raise RuntimeError, "Could not create some slices: %s" \
                  % ','.join(failedslices)

        return slicelist

    def createSlice(self,pid,eid,plcidx,slicename):
        """
        Create only a single slice within an experiment.
        """
        plc = None
        try:
573
            plc = PLC(plcidx)
574 575 576 577 578 579 580 581 582 583 584 585 586 587
        except:
            raise

        slice = EmulabSlice(plc,slicename,pid=pid,eid=eid)

        try:
            slice.create()
        except:
            print "Creation of slice %s failed!" % slicename
            if debug:
                traceback.print_exc()
                pass
            raise

588 589
        return slice

590 591
    def createSliceDirect(self,plcidx,slicename,description,sliceurl=None,
                          userlist=[],nodelist=[],instmethod=None):
592 593 594
        """
        Slice factory function that doesn't use the Emulab db.
        """
595 596
        plc = None
        try:
597
            plc = PLC(plcidx)
598 599 600
        except:
            raise
        slice = Slice(plc,slicename,slicedescr=description,sliceurl=sliceurl,
601 602
                      userlist=userlist,nodelist=nodelist,
                      instmethod=instmethod)
603 604 605
        slice._create()
        return slice

606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
    def loadSliceByNode(self, pid, eid, nodeid):
        """
        Slice factory function that loads the slice which contains the given
        nodeid and corresponds to the given pid/eid.
        """
        slice = None

        #
        # We want to have only one of each of monitoring and testing
        # experiments, but we want to have a slice from each PLC be "in"
        # that experiment.  Thus, we have to find out which plc the node
        # "belongs" to, and figure out which slice we care about based the
        # hosting plc.
        #
        res = DBQueryFatal("select ps.plc_idx,ps.slicename"
                           " from reserved as r"
                           " left join nodes as n on r.node_id=n.node_id"
                           " left join plab_mapping as pm"
                           "   on n.phys_nodeid=pm.node_id"
                           " left join plab_slices as ps"
                           "   on pm.plc_idx=ps.plc_idx"
                           " where r.node_id=%s and r.pid=%s and r.eid=%s"
                           "   and ps.pid=%s and ps.eid=%s",
                           (nodeid,pid,eid,pid,eid))
        if not res or len(res) <= 0:
            raise RuntimeError, "Could not find a slice for %s/%s/%s!" % \
                  (pid,eid,nodeid)
        if len(res) > 1:
            raise RuntimeError, "Found multiple slices for %s/%s/%s!" % \
                  (pid,eid,nodeid)
        (plcidx,slicename) = res[0]

        plc = None
        try:
640
            plc = PLC(plcidx)
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
        except:
            raise

        slice = EmulabSlice(plc,slicename,pid,eid)
        try:
            slice.load()
        except:
            print "Load of existing slice %s (%s) failed!" % (slice.slicename,
                                                              plc.name)
            if debug:
                traceback.print_exc()
                pass
            raise

        return slice

    def loadSlices(self, pid, eid, stopOnFail=False):
        """
        Slice factory function that loads all slices necessary for an Emulab
        experiment.
        """
        slicelist = []

        # grab any existing slices and which plcs host them
        res = DBQueryFatal("select plc_idx,slicename"
                           "  from plab_slices"
                           " where pid=%s and eid=%s",
                           (pid,eid))

        for (plcidx,slicename) in res:
            plc = None
            try:
673
                plc = PLC(plcidx)
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
            except:
                raise
            
            # need to try to load and (re)configure an existing slice:
            slice = EmulabSlice(plc,slicename,pid,eid)
            try:
                slice.load()
            except:
                print "Load of existing slice %s failed!" % slice.slicename
                if debug:
                    traceback.print_exc()
                    pass
                if stopOnFail:
                    raise
                #failedslices.append(slice.slicename)
                continue
            slicelist.append(slice)
            pass
        
        return slicelist

    def loadSlice(self,pid,eid,plcidx,slicename):
696
        """
697 698
        Slice factory function that loads all slices necessary for an Emulab
        experiment.
699
        """
700 701 702
        slice = None
        plc = None
        try:
703
            plc = PLC(plcidx)
704 705 706 707 708 709 710 711 712 713 714 715 716
        except:
            raise
        
        slice = EmulabSlice(plc,slicename,pid,eid)
        try:
            slice.load()
        except:
            print "Load of existing slice %s failed!" % slice.slicename
            if debug:
                traceback.print_exc()
                pass
            raise
        
717 718
        return slice

719 720
    def loadSliceDirect(self,plcidx,slicename,slicedescr=None,sliceurl=None,
                        userlist=[],nodelist=[],instmethod=None):
721 722 723
        """
        Slice factory function that doesn't use the Emulab db.
        """
724 725
        plc = None
        try:
726
            plc = PLC(plcidx)
727 728 729
        except:
            raise
        slice = Slice(plc,slicename,slicedescr=slicedescr,sliceurl=sliceurl,
730 731
                      userlist=userlist,nodelist=nodelist,
                      instmethod=instmethod)
732
        slice.load()
733 734
        return slice

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 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
    def updateNodeGroupEntries(self,plcid,ignorenew = False):
        # grab our plc:
        plc = PLC(plcid)

        print "Synching %s node groups..." % plc.name
        print "Starting at %s." % time.strftime("%Y-%m-%d %H:%M:%S",
                                                time.localtime())

        method = plc.getAttrVal("syncmethod")
        if method == None:
            method = "xmlrpc"
            pass

        groups = None
        parser = None
        try:
            if method == "sitesxml":
                parser = siteParser(plc)
            elif method == "xmlrpc":
                parser = XmlrpcNodeInfoFetcher(plc)
            else:
                raise RuntimeError, "Unsupported update node method %s!" % \
                      method

            if callable(getattr(parser,'getPlabNodeGroupInfo')):
                groups = parser.getPlabNodeGroupInfo()
                pass
            if not groups == None:
                print "\nGot nodegroup list:"
                print str(groups)
                pass
            else:
                print "No way to get nodegroups; done."
                pass

            pass
        except:
            extype, exval, extrace = sys.exc_info()
            print "Error talking to agent: %s: %s" % (extype, exval)
            if debug:
                traceback.print_exc()
                pass
            
            print "Going back to sleep until next scheduled poll"
            return

        qres = DBQueryFatal("select nodegroup_idx from plab_nodegroups"
                            " where plc_idx=%s",(plc.idx,))
        existing = []
        for (idx,) in qres:
            existing.append(idx)
            pass

        new = groups.keys()

        # We take the easy way out for synch'ing: synch the groups, but
        # just wipe and rebuild the members table.  So, make that set of
        # operations appear atomic to any readers.
        DBQueryFatal("lock tables plab_nodegroups write,"
                     " plab_nodegroup_members write")

        try:
            # figure out groups that no longer exist and delete them
            delete = []
            for gid in existing:
                if not gid in new:
                    delete.append(gid)
                    pass
                pass
            for gid in delete:
                print "Removing nodegroup %d." % gid
                DBQueryFatal("delete from plab_nodegroup_members" \
                             " where plc_idx=%s and nodegroup_idx=%s",
                             (plc.idx,gid))
                DBQueryFatal("delete from plab_nodegroups" \
                             " where plc_idx=%s and nodegroup_idx=%s",
                             (plc.idx,gid))
                existing.remove(gid)
                pass
            
            # update existing/new groups
            for gid in new:
                # just delete all the member entries, then replace--it's easier
                DBQueryFatal("delete from plab_nodegroup_members" \
                             " where plc_idx=%s and nodegroup_idx=%s",
                             (plc.idx,gid))
                DBQueryFatal("replace into plab_nodegroups" \
                             " (plc_idx,nodegroup_idx,name,description)" \
                             " values (%s,%s,%s,%s)",
                             (plc.idx,gid,groups[gid]['name'],
                              groups[gid]['description']))
                for node_id in groups[gid]['node_ids']:
                    DBQueryFatal("replace into plab_nodegroup_members" \
                                 " (plc_idx,nodegroup_idx,node_id)" \
                                 " values (%s,%s,%s)",
                                 (plc.idx,gid,node_id))
                    pass
                pass
            pass
        except:
            print "Exception while talking to DB; there may be\n" \
                  "  unsynch'd state to cleanup!"
            traceback.print_exc()
            pass

        DBQueryFatal("unlock tables")

        return

844
    def updateNodeEntries(self, plcid, ignorenew = False):
845
        """
Kirk Webb's avatar
Kirk Webb committed
846
        Finds out which Plab nodes are available, and
847 848 849 850 851
        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
852
        discovered.
853 854 855 856 857 858

        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
859

860 861 862
        # grab our plc:
        plc = PLC(plcid)

863 864 865 866
        print "Synching %s nodes..." % plc.name
        print "Starting at %s." % time.strftime("%Y-%m-%d %H:%M:%S",
                                                time.localtime())

867 868 869 870 871
        method = plc.getAttrVal("syncmethod")
        if method == None:
            method = "xmlrpc"
            pass

872
        avail = []
873
        parser = None
874
        try:
875 876 877 878 879 880 881 882
            if method == "sitesxml":
                parser = siteParser(plc)
            elif method == "xmlrpc":
                parser = XmlrpcNodeInfoFetcher(plc)
            else:
                raise RuntimeError, "Unsupported update node method %s!" % \
                      method

Kirk Webb's avatar
Kirk Webb committed
883
            avail = parser.getPlabNodeInfo()
884

Kirk Webb's avatar
Kirk Webb committed
885
            pass
886
        # XXX: rewrite to use more elegant exception info gathering.
887 888
        except:
            extype, exval, extrace = sys.exc_info()
889
            print "Error talking to agent: %s: %s" % (extype, exval)
Kirk Webb's avatar
Kirk Webb committed
890
            if debug:
891 892 893 894
                #print extrace
                traceback.print_exc()
                pass
            
895 896
            print "Going back to sleep until next scheduled poll"
            return
897

Kirk Webb's avatar
Kirk Webb committed
898
        if debug:
899
            print "Got advertisement list:"
900
            print str(avail)
Kirk Webb's avatar
Kirk Webb committed
901
            pass
902

903 904 905 906 907 908
        # We use nodetype because the plc name might have icky chars, or might
        # change -- but the nodetype will not.
        ignored_nodes = self.__readNodeFile("%s.%s" % (IGNORED_NODES_FILE,
                                                       plc.nodetype))
        allowed_nodes = self.__readNodeFile("%s.%s" % (ALLOWED_NODES_FILE,
                                                       plc.nodetype))
909

910 911 912 913 914 915
        # 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
916
        if len(allowed_nodes) or len(ignored_nodes):
917
            allowed = []
Kirk Webb's avatar
Kirk Webb committed
918
            for nodeent in avail:
919
                if nodeent['PLABID'] in ignored_nodes:
920
                    continue
921 922
                elif len(allowed_nodes):
                    if nodeent['IP'] in allowed_nodes:
923 924 925 926 927
                        allowed.append(nodeent)
                        pass
                    pass
                else:
                    allowed.append(nodeent)
Kirk Webb's avatar
Kirk Webb committed
928 929
                    pass
                pass
930 931 932 933
            if verbose:
                print "Advertisements in allowed nodes list:\n%s" % allowed
                pass
            avail = allowed
Kirk Webb's avatar
Kirk Webb committed
934
            pass
935

936 937 938
        # Check for duplicate node attributes (sanity check)
        availdups = self.__findDuplicateAttrs(avail)
        if len(availdups):
939 940 941 942 943 944 945 946
            #SENDMAIL(TBOPS, "Duplicates in %s advertised node list" % plc.name,
            #         "Duplicate attributes:\n"
            #         "%s\n\n"
            #         "Let plab support know!" % availdups,
            #         TBOPS)
            #raise RuntimeError, \
            print "Warning: duplicate attributes in plab node listing:\n%s" \
                  % availdups
947

948
        # Get node info we already have.
949
        known = self.__getKnownPnodes(plc)
Kirk Webb's avatar
Kirk Webb committed
950
        if debug:
951 952
            print "Got known pnodes:"
            print known
Kirk Webb's avatar
Kirk Webb committed
953
            pass
954

Kirk Webb's avatar
Kirk Webb committed
955
        # Create list of nodes to add or update
956 957
        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
958
        for nodeent in avail:
Kirk Webb's avatar
Kirk Webb committed
959 960
            # Replace sequences of bad chars in the site entity with
            # a single "-".
Kirk Webb's avatar
Kirk Webb committed
961
            nodeent['SITE'] = BADSITECHARS.sub("-", nodeent['SITE'])
962 963 964
            # Determine if we already know about this node.
            matchres = self.__matchPlabNode(nodeent, known)
            if not matchres:
965
                toadd.append(nodeent)
Kirk Webb's avatar
Kirk Webb committed
966
                pass
967 968
            elif len(matchres[1]):
                toupdate.append((nodeent,matchres))
Kirk Webb's avatar
Kirk Webb committed
969
                pass
970
            pass
Kirk Webb's avatar
Kirk Webb committed
971

972 973
        # Process the list of nodes to add
        addstr = ""
974
        if len(toadd):
Kirk Webb's avatar
Kirk Webb committed
975
            # Are we ignoring new entries?
976
            if ignorenew:
Kirk Webb's avatar
Kirk Webb committed
977
                if verbose:
978
                    print "%d new Plab nodes, but ignored for now" % len(toadd)
Kirk Webb's avatar
Kirk Webb committed
979 980
                    pass
                pass
Kirk Webb's avatar
Kirk Webb committed
981
            # If not ignoring, do the addition/update.
982
            else:
983 984
                print "There are %d new Plab nodes." % len(toadd)
                for nodeent in toadd:
Kirk Webb's avatar
Kirk Webb committed
985
                    # Get the linktype here so we can report it in email.
Kirk Webb's avatar
Kirk Webb committed
986
                    self.__findLinkType(nodeent)
Kirk Webb's avatar
Kirk Webb committed
987
                    if debug:
Kirk Webb's avatar
Kirk Webb committed
988 989
                        print "Found linktype %s for node %s" % \
                              (nodeent['LINKTYPE'], nodeent['IP'])
Kirk Webb's avatar
Kirk Webb committed
990
                        pass
991
                    # Add the node.
992
                    self.__addNode(plc,nodeent)
993
                    # Add a line for the add/update message.
Kirk Webb's avatar
Kirk Webb committed
994
                    nodestr = "%s\t\t%s\t\t%s\t\t%s\t\t%s\n" % \
995
                              (nodeent['PLABID'],
Kirk Webb's avatar
Kirk Webb committed
996 997 998 999
                               nodeent['IP'],
                               nodeent['HNAME'],
                               nodeent['SITE'],
                               nodeent['LINKTYPE'])
1000
                    addstr += nodestr
Kirk Webb's avatar
Kirk Webb committed
1001
                    pass
1002 1003
                pass
            pass
1004

1005
        # Process node updates.
1006
        updstr = ""
1007
        chgerrstr = ""
1008 1009
        if len(toupdate):
            print "There are %d plab node updates." % len(toupdate)
1010
            for (nodeent,(nodeid,diffattrs)) in toupdate:
1011 1012 1013
                if debug:
                    print "About to update %s; new info %s; diff %s" \
                          % (str(nodeid),str(nodeent),str(diffattrs))
1014
                try:
1015
                    self.__updateNodeMapping(nodeid, diffattrs)
1016 1017 1018 1019 1020
                    pass
                except MultiChangeError, e:
                    print "%s not updated: Too many attribute changes." % \
                          e.nodeid
                    chgerrstr += "%s:\n" % e.nodeid
1021 1022
                    for (attr,(old,new)) in e.chattrs.items():
                        chgerrstr += "\t%s:\t%s => %s\n" % (attr,old,new)
1023 1024 1025
                        pass
                    chgerrstr += "\n"
                    continue
1026
                self.__updateNode(plc, nodeid, nodeent)
1027
                # Add a line for the add/update message.
1028 1029 1030
                nodestr = nodeid + "\n"
                for (attr,(old,new)) in diffattrs.items():
                    nodestr += "\t%s:\t%s => %s\n" % (attr,old,new)
1031 1032
                    pass
                updstr += nodestr + "\n"
Kirk Webb's avatar
Kirk Webb committed
1033 1034
                pass
            pass
1035

1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
        # 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
        
1056
        if chgerrstr:
1057 1058 1059 1060 1061 1062 1063 1064
            #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)
            print "Warning: two or more distinguishing attributes have" \
                  " changed on the following planetlab nodes:\n\n%s\n" \
                  % chgerrstr,
1065 1066
            pass

1067 1068 1069 1070
        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)
1071 1072
            print "Pushing out site_mapping ..."
            os.spawnl(os.P_WAIT, PELAB_PUSH, PELAB_PUSH)
1073
            # Now announce that we've added/updated nodes.
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
            #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 "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),
1088 1089
            print "Done adding new Plab nodes."
            pass
1090

Kirk Webb's avatar
Kirk Webb committed
1091
        return
1092

1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110
    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
1111 1112 1113
                elif (attr == "LATITUDE") or (attr == "LONGITUDE"):
                    # Special rules for latitude and longitude to avoid
                    # FP errors
1114 1115 1116 1117 1118 1119 1120 1121 1122
                    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) \
1123 1124 1125 1126 1127
                           and (ent[attr] != "" and plabent[attr] != "") \
                           and ((float(ent[attr]) > \
                                 (float(plabent[attr]) + LATLONG_DELTA)) \
                                or (float(ent[attr]) < \
                                    (float(plabent[attr]) - LATLONG_DELTA))):
1128 1129 1130 1131
                        diff[attr] = (ent[attr], plabent[attr])
                    else:
                        same[attr] = ent[attr]
                        pass
1132 1133 1134 1135 1136 1137 1138
                elif ent[attr] == plabent[attr]:
                    same[attr] = ent[attr]
                    pass
                else:
                    diff[attr] = (ent[attr], plabent[attr])
                    pass
                pass
1139 1140
            # Only consider these to be the same if at least one 'critical'
            # attr is the same
1141
            if len(same):
1142 1143 1144
                for attr in same:
                    if attr in ATTR_CRIT_KEYS:
                        return (nid, diff)
1145 1146 1147
            pass
        return ()

1148
    def __getKnownPnodes(self,plc,deleted=0):
1149 1150 1151 1152
        """
        getFree helper function.  Returns a dict of IP:node_id pairs
        for the Plab nodes that currently exist in the DB.
        """
David Johnson's avatar
David Johnson committed
1153 1154 1155 1156 1157
        res = DBQueryFatal("select pm.node_id,pm.plab_id,pm.hostname,"
                           "pm.IP,pm.mac,wni.site,wni.latitude,"
                           "wni.longitude,wni.bwlimit"
                           " from plab_mapping as pm"
                           " left join widearea_nodeinfo as wni on"
1158
                           "    pm.node_id = wni.node_id"
1159 1160
                           " where pm.plc_idx=%s and deleted=%s",
                           (plc.idx,int(deleted)))
1161
        
1162
        ret = {}
1163 1164
        for (nodeid, plabid, hostname, ip, mac, site,
             latitude, longitude, bwlimit) in res:
1165 1166 1167 1168 1169 1170
            ret[nodeid] = {'PLABID'    : plabid,
                           'HNAME'     : hostname,
                           'IP'        : ip,
                           'MAC'       : mac,
                           'SITE'      : site,
                           'LATITUDE'  : latitude,
1171 1172
                           'LONGITUDE' : longitude,
                           'BWLIMIT'   : bwlimit}
Kirk Webb's avatar
Kirk Webb committed
1173
            pass
1174 1175 1176
        # Check for duplicate node attributes: report any that are found.
        dups = self.__findDuplicateAttrs(ret.values())
        if len(dups):
1177 1178 1179 1180 1181 1182 1183 1184
            #SENDMAIL(TBOPS,
            #         "Duplicate %s node attributes in the DB!" % plc.name,
            #         "Duplicate node attrs:\n"
            #         "%s\n\n"
            #         "Fix up please!" % str(dups),
            #         TBOPS)
            #raise RuntimeError, \
            print "Warning: duplicate node attributes in DB:\n%s" % str(dups)
1185
        return ret
1186 1187 1188 1189 1190 1191 1192 1193 1194

    def __findDuplicateAttrs(self, nodelist):
        """
        Find duplicate node attributes in the node list passed in.
        """
        attrs = {}
        dups = {}
        
        for ent in nodelist:
1195
            for attr in ATTR_CRIT_KEYS:
1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209
                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
1210
        
Kirk Webb's avatar
Kirk Webb committed
1211
    def __findLinkType(self, nodeent):
1212 1213 1214 1215 1216 1217 1218
        """
        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
1219
        This can't detect DSL links..
1220
        """
1221
        # Is host international (or flux/emulab local)?
1222
        from socket import gethostbyaddr, getfqdn, herror
Kirk Webb's avatar
Kirk Webb committed
1223 1224 1225 1226 1227 1228 1229 1230
        
        if not nodeent.has_key('HNAME'):
            try:
                (hname, ) = gethostbyaddr(ip)
                nodeent['HNAME'] = getfqdn(hname)
                pass
            except herror:
                nodeent['HNAME'] = nodeent['IP']
1231
                print "Warning: Failed to get hostname for %s" % nodeent['IP']
Kirk Webb's avatar
Kirk Webb committed
1232 1233 1234 1235 1236
                pass
            pass
        
        tld = nodeent['HNAME'].split(".")[-1].lower()
        if not tld in ("edu", "org", "net", "com", "gov", "us", "ca"):
1237
            nodeent['LINKTYPE'] = "intl"
Kirk Webb's avatar
Kirk Webb committed
1238 1239 1240 1241 1242 1243 1244
            return
        
        # Is it us?
        if nodeent['HNAME'].endswith(LOCAL_PLAB_DOMAIN):
            nodeent['LINKTYPE'] = LOCAL_PLAB_LINKTYPE
            return
        
1245
        # Is host on I2?
Kirk Webb's avatar
Kirk Webb committed
1246
        traceroute = os.popen("traceroute -nm 10 -q 1 %s" % nodeent['IP'])
1247 1248 1249 1250 1251
        trace = traceroute.read()
        traceroute.close()

        for gw in MAGIC_INET2_GATEWAYS:
            if trace.find(gw) != -1:
1252
                nodeent['LINKTYPE'] = "inet2"
Kirk Webb's avatar
Kirk Webb committed
1253
                return
1254

1255 1256
        for gw in MAGIC_INET_GATEWAYS:
            if trace.find(gw) != -1:
1257
                nodeent['LINKTYPE'] = "inet"
Kirk Webb's avatar
Kirk Webb committed
1258
                return
1259
        else:
1260
            print "Warning: Unknown gateway for host %s" % nodeent['IP']
1261

Kirk Webb's avatar
Kirk Webb committed
1262 1263
        # We don't know - must manually classify.
        nodeent['LINKTYPE'] = "*Unknown*"
Kirk Webb's avatar
Kirk Webb committed
1264
        return
1265

1266
    def __addNode(self, plc, nodeent):
1267
        """
1268 1269 1270
        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.
1271
        """
1272
        # Generate/grab variables to be used when creating the node.
1273
        defosid, controliface = self.__getNodetypeInfo(plc)
Kirk Webb's avatar
Kirk Webb committed
1274
        hostonly = nodeent['HNAME'].replace(".", "-")
1275 1276 1277
        nidnum, priority = self.__nextFreeNodeid(plc)
        nodeid = "%s%d" % (plc.nodename_prefix, nidnum)
        vnodeprefix = "%svm%d" % (plc.nodename_prefix, nidnum)
1278 1279 1280
        print "Creating pnode %s as %s, priority %d." % \
              (nodeent['IP'], nodeid, priority)

1281 1282 1283 1284 1285
        # fixup MAC so it's not null, even if plab did not give us a MAC:
        if nodeent['MAC'] == None:
            nodeent['MAC'] = ''
            pass

1286 1287 1288
        # 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.
1289
        self.__updateNode(plc, nodeid, nodeent)
1290 1291

        # Now perform stuff specific to node addition
1292
        try:
1293 1294 1295
            res_exptidx = TBExptIDX(RESERVED_PID, RESERVED_EID)
            mon_exptidx = TBExptIDX(MONITOR_PID, MONITOR_EID)
            
Kirk Webb's avatar
Kirk Webb committed
1296
            DBQueryFatal("replace into nodes"
1297
                         " (node_id, type, phys_nodeid, role, priority,"
1298
                         "  op_mode, def_boot_osid,"
1299
                         "  allocstate, allocstate_timestamp,"
1300
                         "  eventstate, state_timestamp, inception)"
1301
                         " values (%s, %s, %s, %s, %s,"
1302
                         "  %s, %s, %s, now(), %s, now(), now())",
1303
                         (nodeid, "%s%s" % (plc.nodetype,'phys'), nodeid,
1304 1305
                          'testnode', priority*100,
                          'ALWAYSUP', defosid,
1306
                          'FREE_CLEAN',
1307
                          'ISUP'))
1308

1309 1310 1311
            DBQueryFatal("replace into node_hostkeys"
                         " (node_id)"
                         " values (%s)",
Kirk Webb's avatar
Kirk Webb committed
1312
                         (nodeid))
1313

1314 1315 1316 1317 1318
            DBQueryFatal("replace into node_utilization"
                         " (node_id)"
                         " values (%s)",
                         (nodeid))

Kirk Webb's avatar
Kirk Webb committed
1319
            DBQueryFatal("replace into reserved"
1320
                         " (node_id, exptidx, pid, eid, rsrv_time, vname)"
1321
                         " values (%s, %s, %s, %s, now(), %s)",
1322 1323
                         (nodeid, res_exptidx,
                          RESERVED_PID, RESERVED_EID, hostonly))
1324

1325 1326
            # XXX: This should probably be checked and updated if necessary
            #      when updating.
Kirk Webb's avatar
Kirk Webb committed
1327
            DBQueryFatal("replace into node_auxtypes"
1328 1329
                         " (node_id, type, count)"
                         " values (%s, %s, %s)",
1330 1331
                         (nodeid,"%s%s" % (plc.nodetype,nodeent['LINKTYPE']),
                          1))
1332
            
Kirk Webb's avatar
Kirk Webb committed
1333
            DBQueryFatal("replace into node_auxtypes"
1334 1335
                         " (node_id, type, count)"
                         " values (%s, %s, %s)",
1336
                         (nodeid, plc.nodetype, 1))
1337
            
Kirk Webb's avatar
Kirk Webb committed
1338
            DBQueryFatal("replace into node_status"
1339 1340
                         " (node_id, status, status_timestamp)"
                         " values (%s, %s, now())",
Kirk Webb's avatar
Kirk Webb committed
1341
                         (nodeid, 'down'))
1342