libplab.py.in 109 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
Kirk Webb's avatar
   
Kirk Webb committed
22
import signal
Kirk Webb's avatar
   
Kirk Webb committed
23
import socket
Kirk Webb's avatar
Kirk Webb committed
24
25
26
import httplib
import xml.parsers.expat
import re
Kirk Webb's avatar
   
Kirk Webb committed
27
import calendar
Kirk Webb's avatar
   
Kirk Webb committed
28
import shlex
Kirk Webb's avatar
   
Kirk Webb committed
29

Kirk Webb's avatar
   
Kirk Webb committed
30
from popen2 import Popen4
Kirk Webb's avatar
   
Kirk Webb committed
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
Kirk Webb's avatar
   
Kirk Webb committed
44
from mod_PLCNM import mod_PLCNM
45
from mod_PLC4 import mod_PLC4
46
47

agents = {'PLC'    : mod_PLC,
Kirk Webb's avatar
   
Kirk Webb committed
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
Kirk Webb's avatar
   
Kirk Webb committed
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

Kirk Webb's avatar
   
Kirk Webb committed
76
77
USERNODE = "@USERNODE@"
TBOPS = "@TBOPSEMAIL_NOSLASH@"
78
MAILTAG = "@THISHOMEBASE@"
Kirk Webb's avatar
   
Kirk Webb committed
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

Kirk Webb's avatar
   
Kirk Webb committed
88
89
MAGIC_INET2_GATEWAYS = ("205.124.237.10",  "205.124.244.18",
                        "205.124.244.178", )
Kirk Webb's avatar
   
Kirk Webb committed
90
91
92
MAGIC_INET_GATEWAYS =  ("205.124.244.150", "205.124.239.185",
                        "205.124.244.154", "205.124.244.138",
                        "205.124.244.130", )
93
LOCAL_PLAB_DOMAIN = ".flux.utah.edu"
94
LOCAL_PLAB_LINKTYPE = "inet2"
Kirk Webb's avatar
   
Kirk Webb committed
95
96
97

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

99
100
101
102
# 'critical' node identifiers - those that are actually used to uniquely
# identify a planetlab node
ATTR_CRIT_KEYS = ('HNAME', 'IP', 'PLABID', 'MAC',)

103
104
105
106
# The amount by which latitude and longitude are allowed to differ before we
# classify them ask changed
LATLONG_DELTA = 0.001

Kirk Webb's avatar
   
Kirk Webb committed
107
PLABNODE = "@prefix@/sbin/plabnode"
108
SSH = "@prefix@/bin/sshtb"
109
NAMED_SETUP = "@prefix@/sbin/named_setup"
Kirk Webb's avatar
   
Kirk Webb committed
110
PELAB_PUSH  = "@prefix@/sbin/pelab_opspush"
111

Kirk Webb's avatar
Kirk Webb committed
112
113
114
ROOTBALL_URL = "http://localhost:1492/" # ensure this ends in a slash

DEF_SITE_XML = "/xml/sites.xml"
Kirk Webb's avatar
   
Kirk Webb committed
115
116
IGNORED_NODES_FILE = "@prefix@/etc/plab/IGNOREDNODES"
ALLOWED_NODES_FILE = "@prefix@/etc/plab/ALLOWEDNODES"
117

Kirk Webb's avatar
Kirk Webb committed
118
119
BADSITECHARS = re.compile(r"\W+")
PLABBASEPRIO = 20000
Kirk Webb's avatar
   
Kirk Webb committed
120
PLAB_SVC_SLICENAME = "utah_svc_slice"
121
122
123
124
125
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@."
Kirk Webb's avatar
   
Kirk Webb committed
126
127
PLABMON_PID = "emulab-ops"
PLABMON_EID = "plab-monitor"
128
129
DEF_SLICE_DESC = "Slice created by @THISHOMEBASE@"
DEF_EMULAB_URL = "http://@WWWHOST@"
Kirk Webb's avatar
   
Kirk Webb committed
130

Kirk Webb's avatar
   
Kirk Webb committed
131
132
PLABEXPIREWARN = 1*WEEK        # one week advance warning for slice expiration.
NODEEXPIREWARN = 2*WEEK+2*DAY  # about two weeks advance warning for slivers.
133

134
135
136
137
138
#
# var to track failed renewals
#
failedrenew = []

139
140
141
142
143
#
# Disable line buffering
#
sys.stdout = os.fdopen(sys.stdout.fileno(), sys.stdout.mode, 0)

Kirk Webb's avatar
   
Kirk Webb committed
144
145
146
147
148
#
# Ensure SIGPIPE doesn't bite us:
#
signal.signal(signal.SIGPIPE, signal.SIG_IGN)

149
150
151
#
# Plab abstraction
#
Kirk Webb's avatar
Kirk Webb committed
152

Kirk Webb's avatar
   
Kirk Webb committed
153
154
155
156
157
158
159
160
161
162
#
# 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
163
164
165
#
# Class responsible for parsing planetlab sites file
#
Kirk Webb's avatar
Kirk Webb committed
166
167
class siteParser:

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

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

257
    pass
Kirk Webb's avatar
   
Kirk Webb committed
258

259
260
261
262
263
264
265
266
267
268
269
270
271
272
#
# 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 = []

        which_agent = plc.getAttrVal("nmagent")
        if which_agent == None:
            which_agent = DEF_AGENT
273
            pass
274
275
276
277
278
279

        # grab a mod_PLC4 agent
        self.agent = agents[which_agent](plc)

        pass

280
    def getPlabNodeInfo(self,ignoreErrors=True):
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
        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:
            adi = { 'HNAME'     : n['hostname'],
                    'PLABID'    : n['node_id'],
                    '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'] }
            if n.has_key('boot_status'):
                adi['STATUS'] = n['boot_status']
                pass
            
            self.__hosts.append(adi)
348
            pass
349
350
351
352
353
354
355
356

        return self.__hosts
    
    pass


class Plab:
    def __init__(self):
357
        pass
358

359
    def getPLCs(self):
360
        """
361
        Returns a list of PLC (name,idx) tuples currently in the Emulab db.
362
        """
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
        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
            if existing.has_key(plcidx):
                slicename = existing[plcidx]
                pass
            else:
                res = DBQueryFatal("select idx from experiments"
                                   " where pid=%s and eid=%s",
                                   (pid,eid))
                if not len(res):
                    raise RuntimeError, \
                          "Didn't get any results while looking up info on " \
                          "experiment %s/%s" % (self.pid, self.eid)
                (exptidx,) = res[0]
                slicename = "%s_elab_%d" % (prefix,exptidx)
                pass
            
            try:
                plc = PLC(plcidx,slicename)
            except:
                raise
            
            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:
            plc = PLC(plcidx,slicename)
        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

482
483
        return slice

484
485
    def createSliceDirect(self,plcidx,slicename,description,sliceurl=None,
                          userlist=[],nodelist=[],instmethod=None):
David Johnson's avatar
David Johnson committed
486
487
488
        """
        Slice factory function that doesn't use the Emulab db.
        """
489
490
491
492
493
494
        plc = None
        try:
            plc = PLC(plcidx,slicename)
        except:
            raise
        slice = Slice(plc,slicename,slicedescr=description,sliceurl=sliceurl,
495
496
                      userlist=userlist,nodelist=nodelist,
                      instmethod=instmethod)
David Johnson's avatar
David Johnson committed
497
498
499
        slice._create()
        return slice

500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
    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:
            plc = PLC(plcidx,slicename)
        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:
                plc = PLC(plcidx,slicename)
            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):
590
        """
591
592
        Slice factory function that loads all slices necessary for an Emulab
        experiment.
593
        """
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
        slice = None
        plc = None
        try:
            plc = PLC(plcidx,slicename)
        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
        
611
612
        return slice

613
614
    def loadSliceDirect(self,plcidx,slicename,slicedescr=None,sliceurl=None,
                        userlist=[],nodelist=[],instmethod=None):
David Johnson's avatar
David Johnson committed
615
616
617
        """
        Slice factory function that doesn't use the Emulab db.
        """
618
619
620
621
622
623
        plc = None
        try:
            plc = PLC(plcidx,slicename)
        except:
            raise
        slice = Slice(plc,slicename,slicedescr=slicedescr,sliceurl=sliceurl,
624
625
                      userlist=userlist,nodelist=nodelist,
                      instmethod=instmethod)
626
        slice.load()
David Johnson's avatar
David Johnson committed
627
628
        return slice

629
    def updateNodeEntries(self, plcid, ignorenew = False):
630
        """
Kirk Webb's avatar
Kirk Webb committed
631
        Finds out which Plab nodes are available, and
632
633
634
635
636
        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
637
        discovered.
638
639
640
641
642
643

        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
644
645
646
        
        print "Getting available Plab nodes ..."

647
648
649
650
651
652
653
654
        # grab our plc:
        plc = PLC(plcid)

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

655
        avail = []
656
        try:
657
658
659
660
661
662
663
664
            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
665
            avail = parser.getPlabNodeInfo()
666
                
Kirk Webb's avatar
Kirk Webb committed
667
            pass
668
        # XXX: rewrite to use more elegant exception info gathering.
669
670
        except:
            extype, exval, extrace = sys.exc_info()
671
            print "Error talking to agent: %s: %s" % (extype, exval)
Kirk Webb's avatar
Kirk Webb committed
672
            if debug:
673
674
675
676
                #print extrace
                traceback.print_exc()
                pass
            
677
678
            print "Going back to sleep until next scheduled poll"
            return
Kirk Webb's avatar
   
Kirk Webb committed
679

Kirk Webb's avatar
Kirk Webb committed
680
        if debug:
681
682
            print "Got advertisement list:"
            print avail
Kirk Webb's avatar
Kirk Webb committed
683
            pass
Kirk Webb's avatar
   
Kirk Webb committed
684

685
686
687
688
689
690
        # 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))
Kirk Webb's avatar
   
Kirk Webb committed
691

Kirk Webb's avatar
   
Kirk Webb committed
692
693
694
695
696
697
        # 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
Kirk Webb's avatar
   
Kirk Webb committed
698
        if len(allowed_nodes) or len(ignored_nodes):
Kirk Webb's avatar
   
Kirk Webb committed
699
            allowed = []
Kirk Webb's avatar
Kirk Webb committed
700
            for nodeent in avail:
Kirk Webb's avatar
   
Kirk Webb committed
701
                if nodeent['PLABID'] in ignored_nodes:
Kirk Webb's avatar
   
Kirk Webb committed
702
                    continue
Kirk Webb's avatar
   
Kirk Webb committed
703
704
                elif len(allowed_nodes):
                    if nodeent['IP'] in allowed_nodes:
Kirk Webb's avatar
   
Kirk Webb committed
705
706
707
708
709
                        allowed.append(nodeent)
                        pass
                    pass
                else:
                    allowed.append(nodeent)
Kirk Webb's avatar
Kirk Webb committed
710
711
                    pass
                pass
Kirk Webb's avatar
   
Kirk Webb committed
712
713
714
715
            if verbose:
                print "Advertisements in allowed nodes list:\n%s" % allowed
                pass
            avail = allowed
Kirk Webb's avatar
Kirk Webb committed
716
            pass
717

Kirk Webb's avatar
   
Kirk Webb committed
718
719
720
        # Check for duplicate node attributes (sanity check)
        availdups = self.__findDuplicateAttrs(avail)
        if len(availdups):
721
            SENDMAIL(TBOPS, "Duplicates in %s advertised node list" % plc.name,
Kirk Webb's avatar
   
Kirk Webb committed
722
723
                     "Duplicate attributes:\n"
                     "%s\n\n"
724
                     "Let plab support know!" % availdups,
Kirk Webb's avatar
   
Kirk Webb committed
725
726
727
728
                     TBOPS)
            raise RuntimeError, \
                  "Duplicate attributes in plab node listing:\n%s" % availdups

729
        # Get node info we already have.
730
        known = self.__getKnownPnodes(plc)
Kirk Webb's avatar
Kirk Webb committed
731
        if debug:
732
733
            print "Got known pnodes:"
            print known
Kirk Webb's avatar
Kirk Webb committed
734
            pass
735

Kirk Webb's avatar
Kirk Webb committed
736
        # Create list of nodes to add or update
Kirk Webb's avatar
   
Kirk Webb committed
737
738
        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
739
        for nodeent in avail:
Kirk Webb's avatar
Kirk Webb committed
740
741
            # Replace sequences of bad chars in the site entity with
            # a single "-".
Kirk Webb's avatar
Kirk Webb committed
742
            nodeent['SITE'] = BADSITECHARS.sub("-", nodeent['SITE'])
Kirk Webb's avatar
   
Kirk Webb committed
743
744
745
            # Determine if we already know about this node.
            matchres = self.__matchPlabNode(nodeent, known)
            if not matchres:
Kirk Webb's avatar
   
Kirk Webb committed
746
                toadd.append(nodeent)
Kirk Webb's avatar
Kirk Webb committed
747
                pass
Kirk Webb's avatar
   
Kirk Webb committed
748
749
            elif len(matchres[1]):
                toupdate.append((nodeent,matchres))
Kirk Webb's avatar
Kirk Webb committed
750
                pass
Kirk Webb's avatar
   
Kirk Webb committed
751
            pass
Kirk Webb's avatar
Kirk Webb committed
752

Kirk Webb's avatar
   
Kirk Webb committed
753
754
        # Process the list of nodes to add
        addstr = ""
755
        if len(toadd):
Kirk Webb's avatar
Kirk Webb committed
756
            # Are we ignoring new entries?
757
            if ignorenew:
Kirk Webb's avatar
Kirk Webb committed
758
                if verbose:
759
                    print "%d new Plab nodes, but ignored for now" % len(toadd)
Kirk Webb's avatar
Kirk Webb committed
760
761
                    pass
                pass
Kirk Webb's avatar
Kirk Webb committed
762
            # If not ignoring, do the addition/update.
763
            else:
Kirk Webb's avatar
   
Kirk Webb committed
764
765
                print "There are %d new Plab nodes." % len(toadd)
                for nodeent in toadd:
Kirk Webb's avatar
Kirk Webb committed
766
                    # Get the linktype here so we can report it in email.
Kirk Webb's avatar
Kirk Webb committed
767
                    self.__findLinkType(nodeent)
Kirk Webb's avatar
Kirk Webb committed
768
                    if debug:
Kirk Webb's avatar
Kirk Webb committed
769
770
                        print "Found linktype %s for node %s" % \
                              (nodeent['LINKTYPE'], nodeent['IP'])
Kirk Webb's avatar
Kirk Webb committed
771
                        pass
Kirk Webb's avatar
   
Kirk Webb committed
772
                    # Add the node.
773
                    self.__addNode(plc,nodeent)
Kirk Webb's avatar
   
Kirk Webb committed
774
                    # Add a line for the add/update message.
Kirk Webb's avatar
Kirk Webb committed
775
                    nodestr = "%s\t\t%s\t\t%s\t\t%s\t\t%s\n" % \
Kirk Webb's avatar
   
Kirk Webb committed
776
                              (nodeent['PLABID'],
Kirk Webb's avatar
Kirk Webb committed
777
778
779
780
                               nodeent['IP'],
                               nodeent['HNAME'],
                               nodeent['SITE'],
                               nodeent['LINKTYPE'])
Kirk Webb's avatar
   
Kirk Webb committed
781
                    addstr += nodestr
Kirk Webb's avatar
Kirk Webb committed
782
                    pass
Kirk Webb's avatar
   
Kirk Webb committed
783
784
                pass
            pass
785

Kirk Webb's avatar
   
Kirk Webb committed
786
        # Process node updates.
Kirk Webb's avatar
   
Kirk Webb committed
787
        updstr = ""
Kirk Webb's avatar
   
Kirk Webb committed
788
        chgerrstr = ""
Kirk Webb's avatar
   
Kirk Webb committed
789
790
        if len(toupdate):
            print "There are %d plab node updates." % len(toupdate)
Kirk Webb's avatar
   
Kirk Webb committed
791
            for (nodeent,(nodeid,diffattrs)) in toupdate:
Kirk Webb's avatar
   
Kirk Webb committed
792
                try:
Kirk Webb's avatar
   
Kirk Webb committed
793
                    self.__updateNodeMapping(nodeid, diffattrs)
Kirk Webb's avatar
   
Kirk Webb committed
794
795
796
797
798
                    pass
                except MultiChangeError, e:
                    print "%s not updated: Too many attribute changes." % \
                          e.nodeid
                    chgerrstr += "%s:\n" % e.nodeid
Kirk Webb's avatar
   
Kirk Webb committed
799
800
                    for (attr,(old,new)) in e.chattrs.items():
                        chgerrstr += "\t%s:\t%s => %s\n" % (attr,old,new)
Kirk Webb's avatar
   
Kirk Webb committed
801
802
803
                        pass
                    chgerrstr += "\n"
                    continue
804
                self.__updateNode(plc, nodeid, nodeent)
Kirk Webb's avatar
   
Kirk Webb committed
805
                # Add a line for the add/update message.
Kirk Webb's avatar
   
Kirk Webb committed
806
807
808
                nodestr = nodeid + "\n"
                for (attr,(old,new)) in diffattrs.items():
                    nodestr += "\t%s:\t%s => %s\n" % (attr,old,new)
Kirk Webb's avatar
   
Kirk Webb committed
809
810
                    pass
                updstr += nodestr + "\n"
Kirk Webb's avatar
Kirk Webb committed
811
812
                pass
            pass
Kirk Webb's avatar
   
Kirk Webb committed
813

814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
        # 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
        
Kirk Webb's avatar
   
Kirk Webb committed
834
835
836
837
838
839
840
841
        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

Kirk Webb's avatar
   
Kirk Webb committed
842
843
844
845
        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)
Kirk Webb's avatar
   
Kirk Webb committed
846
847
            print "Pushing out site_mapping ..."
            os.spawnl(os.P_WAIT, PELAB_PUSH, PELAB_PUSH)
Kirk Webb's avatar
   
Kirk Webb committed
848
849
850
851
852
853
854
855
856
857
858
859
            # 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
860
        return
861

Kirk Webb's avatar
   
Kirk Webb committed
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
    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
880
881
882
                elif (attr == "LATITUDE") or (attr == "LONGITUDE"):
                    # Special rules for latitude and longitude to avoid
                    # FP errors
883
884
885
886
887
888
889
890
891
                    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) \
892
893
894
895
896
                           and (ent[attr] != "" and plabent[attr] != "") \
                           and ((float(ent[attr]) > \
                                 (float(plabent[attr]) + LATLONG_DELTA)) \
                                or (float(ent[attr]) < \
                                    (float(plabent[attr]) - LATLONG_DELTA))):
897
898
899
900
                        diff[attr] = (ent[attr], plabent[attr])
                    else:
                        same[attr] = ent[attr]
                        pass
Kirk Webb's avatar
   
Kirk Webb committed
901
902
903
904
905
906
907
                elif ent[attr] == plabent[attr]:
                    same[attr] = ent[attr]
                    pass
                else:
                    diff[attr] = (ent[attr], plabent[attr])
                    pass
                pass
908
909
            # Only consider these to be the same if at least one 'critical'
            # attr is the same
Kirk Webb's avatar
   
Kirk Webb committed
910
            if len(same):
911
912
913
                for attr in same:
                    if attr in ATTR_CRIT_KEYS:
                        return (nid, diff)
Kirk Webb's avatar
   
Kirk Webb committed
914
915
916
            pass
        return ()

917
    def __getKnownPnodes(self,plc):
918
919
920
921
        """
        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
922
923
924
925
926
        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"
927
928
                           "    pm.node_id = wni.node_id"
                           " where pm.plc_idx=%s",(plc.idx,))
Kirk Webb's avatar
   
Kirk Webb committed
929
        
930
        ret = {}
Kirk Webb's avatar
   
Kirk Webb committed
931
932
        for (nodeid, plabid, hostname, ip, mac, site,
             latitude, longitude, bwlimit) in res:
933
934
935
936
937
938
            ret[nodeid] = {'PLABID'    : plabid,
                           'HNAME'     : hostname,
                           'IP'        : ip,
                           'MAC'       : mac,
                           'SITE'      : site,
                           'LATITUDE'  : latitude,
Kirk Webb's avatar
   
Kirk Webb committed
939
940
                           'LONGITUDE' : longitude,
                           'BWLIMIT'   : bwlimit}
Kirk Webb's avatar
Kirk Webb committed
941
            pass
Kirk Webb's avatar
   
Kirk Webb committed
942
943
944
        # Check for duplicate node attributes: report any that are found.
        dups = self.__findDuplicateAttrs(ret.values())
        if len(dups):
945
            SENDMAIL(TBOPS, "Duplicate %s node attributes in the DB!",
Kirk Webb's avatar
   
Kirk Webb committed
946
947
                     "Duplicate node attrs:\n"
                     "%s\n\n"
948
                     "Fix up please!" % (plc.name,dups),
Kirk Webb's avatar
   
Kirk Webb committed
949
950
951
                     TBOPS)
            raise RuntimeError, \
                  "Duplicate node attributes in DB:\n%s" % dups            
952
        return ret
Kirk Webb's avatar
   
Kirk Webb committed
953
954
955
956
957
958
959
960
961

    def __findDuplicateAttrs(self, nodelist):
        """
        Find duplicate node attributes in the node list passed in.
        """
        attrs = {}
        dups = {}
        
        for ent in nodelist:
Kirk Webb's avatar
   
Kirk Webb committed
962
            for attr in ATTR_CRIT_KEYS:
Kirk Webb's avatar
   
Kirk Webb committed
963
964
965
966
967
968
969
970
971
972
973
974
975
976
                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
Kirk Webb's avatar
   
Kirk Webb committed
977
        
Kirk Webb's avatar
Kirk Webb committed
978
    def __findLinkType(self, nodeent):
979
980
981
982
983
984
985
        """
        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
986
        This can't detect DSL links..
987
        """
988
        # Is host international (or flux/emulab local)?
989
        from socket import gethostbyaddr, getfqdn, herror
Kirk Webb's avatar
Kirk Webb committed
990
991
992
993
994
995
996
997
        
        if not nodeent.has_key('HNAME'):
            try:
                (hname, ) = gethostbyaddr(ip)
                nodeent['HNAME'] = getfqdn(hname)
                pass
            except herror:
                nodeent['HNAME'] = nodeent['IP']
998
                print "Warning: Failed to get hostname for %s" % nodeent['IP']
Kirk Webb's avatar
Kirk Webb committed
999
1000
1001
1002
1003
                pass
            pass
        
        tld = nodeent['HNAME'].split(".")[-1].lower()
        if not tld in ("edu", "org", "net", "com", "gov", "us", "ca"):
1004
            nodeent['LINKTYPE'] = "intl"
Kirk Webb's avatar
Kirk Webb committed
1005
1006
1007
1008
1009
1010
1011
            return
        
        # Is it us?
        if nodeent['HNAME'].endswith(LOCAL_PLAB_DOMAIN):
            nodeent['LINKTYPE'] = LOCAL_PLAB_LINKTYPE
            return
        
1012
        # Is host on I2?
Kirk Webb's avatar
Kirk Webb committed
1013
        traceroute = os.popen("traceroute -nm 10 -q 1 %s" % nodeent['IP'])
1014
1015
1016
1017
1018
        trace = traceroute.read()
        traceroute.close()

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

1022
1023
        for gw in MAGIC_INET_GATEWAYS:
            if trace.find(gw) != -1:
1024
                nodeent['LINKTYPE'] = "inet"
Kirk Webb's avatar
   
Kirk Webb committed
1025
                return
1026
        else:
1027
            print "Warning: Unknown gateway for host %s" % nodeent['IP']
1028

Kirk Webb's avatar
   
Kirk Webb committed
1029
1030
        # We don't know - must manually classify.
        nodeent['LINKTYPE'] = "*Unknown*"
Kirk Webb's avatar
Kirk Webb committed
1031
        return
1032

1033
    def __addNode(self, plc, nodeent):
1034
        """
Kirk Webb's avatar
   
Kirk Webb committed
1035
1036
1037
        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.
1038
        """
Kirk Webb's avatar
   
Kirk Webb committed
1039
        # Generate/grab variables to be used when creating the node.
1040
        defosid, controliface = self.__getNodetypeInfo(plc)
Kirk Webb's avatar
Kirk Webb committed
1041
        hostonly = nodeent['HNAME'].replace(".", "-")
1042
1043
1044
        nidnum, priority = self.__nextFreeNodeid(plc)
        nodeid = "%s%d" % (plc.nodename_prefix, nidnum)
        vnodeprefix = "%svm%d" % (plc.nodename_prefix, nidnum)
Kirk Webb's avatar
   
Kirk Webb committed
1045
1046
1047
1048
1049
1050
        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.
1051
        self.__updateNode(plc, nodeid, nodeent)
Kirk Webb's avatar
   
Kirk Webb committed
1052
1053

        # Now perform stuff specific to node addition
Kirk Webb's avatar
   
Kirk Webb committed
1054
        try:
1055
1056
1057
            res_exptidx = TBExptIDX(RESERVED_PID, RESERVED_EID)
            mon_exptidx = TBExptIDX(MONITOR_PID, MONITOR_EID)
            
Kirk Webb's avatar
Kirk Webb committed
1058
            DBQueryFatal("replace into nodes"
1059
                         " (node_id, type, phys_nodeid, role, priority,"
Kirk Webb's avatar
   
Kirk Webb committed
1060
                         "  op_mode, def_boot_osid,"
1061
                         "  allocstate, allocstate_timestamp,"
1062
                         "  eventstate, state_timestamp, inception)"
1063
                         " values (%s, %s, %s, %s, %s,"
1064
                         "  %s, %s, %s, now(), %s, now(), now())",
1065
                         (nodeid, "%s%s" % (plc.nodetype,'phys'), nodeid,
Kirk Webb's avatar
   
Kirk Webb committed
1066
1067
                          'testnode', priority*100,
                          'ALWAYSUP', defosid,
1068
                          'FREE_CLEAN',
Kirk Webb's avatar
   
Kirk Webb committed
1069
                          'ISUP'))
1070

1071
1072
1073
            DBQueryFatal("replace into node_hostkeys"
                         " (node_id)"
                         " values (%s)",
Kirk Webb's avatar
Kirk Webb committed
1074
                         (nodeid))
1075

1076
1077
1078
1079
1080
            DBQueryFatal("replace into node_utilization"
                         " (node_id)"
                         " values (%s)",
                         (nodeid))

Kirk Webb's avatar
Kirk Webb committed
1081
            DBQueryFatal("replace into reserved"
1082
                         " (node_id, exptidx, pid, eid, rsrv_time, vname)"
1083
                         " values (%s, %s, %s, %s, now(), %s)",
1084
1085
                         (nodeid, res_exptidx,
                          RESERVED_PID, RESERVED_EID, hostonly))
Kirk Webb's avatar
   
Kirk Webb committed
1086

Kirk Webb's avatar
   
Kirk Webb committed
1087
1088
            # XXX: This should probably be checked and updated if necessary
            #      when updating.
Kirk Webb's avatar
Kirk Webb committed
1089
            DBQueryFatal("replace into node_auxtypes"
Kirk Webb's avatar
   
Kirk Webb committed
1090
1091
                         " (node_id, type, count)"
                         " values (%s, %s, %s)",
1092
1093
                         (nodeid,"%s%s" % (plc.nodetype,nodeent['LINKTYPE']),
                          1))
Kirk Webb's avatar
   
Kirk Webb committed
1094
            
Kirk Webb's avatar
Kirk Webb committed
1095
            DBQueryFatal("replace into node_auxtypes"
1096
1097
                         " (node_id, type, count)"
                         " values (%s, %s, %s)",
1098
                         (nodeid, plc.nodetype, 1))
1099
            
Kirk Webb's avatar
Kirk Webb committed
1100
            DBQueryFatal("replace into node_status"
1101
1102
                         " (node_id, status, status_timestamp)"
                         " values (%s, %s, now())",
Kirk Webb's avatar
Kirk Webb committed
1103
                         (nodeid, 'down'))
Kirk Webb's avatar
   
Kirk Webb committed
1104

Kirk Webb's avatar
   
Kirk Webb committed
1105
            DBQueryFatal("insert into plab_mapping"
1106
1107
1108
                         " (node_id, plab_id, hostname, IP, mac, create_time,"
                         "  plc_idx)"
                         " values (%s, %s, %s, %s, %s, now(), %s)",
Kirk Webb's avatar
   
Kirk Webb committed
1109
                         (nodeid, nodeent['PLABID'], nodeent['HNAME'],
1110
                          nodeent['IP'], nodeent['MAC'], plc.idx))
1111

Kirk Webb's avatar
   
Kirk Webb committed
1112
1113
1114
1115
1116
1117
1118
            #
            # NowAdd the site_mapping entry for this node.
            #
            
            # See if we know about the associated site - grab idx if so
            siteidx = 0
            nodeidx = 1
1119
1120
1121
1122
            siteres = DBQueryFatal("select site_idx, node_idx"
                                   " from plab_site_mapping"
                                   " where site_name=%s and plc_idx=%s",
                                   (nodeent['SITE'],plc.idx));
Kirk Webb's avatar
   
Kirk Webb committed
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
            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:
1139
1140
1141
1142
1143
                    if not maxres[0][0]:
                        siteidx = 1
                    else:
                        siteidx = int(maxres[0][0]) + 1
                        pass
Kirk Webb's avatar
   
Kirk Webb committed
1144
1145
1146
1147
1148
1149
1150
1151
                    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 "
1152
1153
                         " values (%s, %s, %s, %s, %s)",
                         (nodeent['SITE'], siteidx, nodeid, nodeidx, plc.idx))
Kirk Webb's avatar
   
Kirk Webb committed
1154

1155
            # Create a single reserved plab vnode for the managment sliver.
Kirk Webb's avatar
   
Kirk Webb committed
1156
1157
1158
1159
            n = 1
            vprio = (priority * 100) + n
            sshdport = 38000 + n
            vnodeid = "%s-%d" % (vnodeprefix, n)
1160
            vnodetype = plc.nodetype
1161
1162
            if verbose:
                print "Creating vnode %s, priority %d" % (vnodeid, vprio)
Kirk Webb's avatar
Kirk Webb committed
1163
                pass
1164
1165
1166
1167
1168
                    
            DBQueryFatal("insert into nodes"
                         " (node_id, type, phys_nodeid, role, priority,"
                         "  op_mode, def_boot_osid, update_accounts,"
                         "  allocstate, allocstate_timestamp,"
Robert Ricci's avatar
Robert Ricci committed
1169
                         "  eventstate, state_timestamp, sshdport)"
1170
1171
1172
1173
1174
                         " values (%s, %s, %s, %s, %s,"
                         "  %s, %s, %s, %s, now(), %s, now(), %s)",
                         (vnodeid, vnodetype, nodeid, 'virtnode', vprio,
                          'PCVM', defosid, 1,
                          'FREE_CLEAN',
Robert Ricci's avatar
Robert Ricci committed
1175
                          'SHUTDOWN', sshdport))
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186

            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
1187
            # Put the last vnode created into the special monitoring expt.
Kirk Webb's avatar
   
Kirk Webb committed
1188
            DBQueryFatal("insert into reserved"
1189
                         " (node_id, exptidx, pid, eid, rsrv_time, vname)"
1190
                         " values (%s, %s, %s, %s, now(), %s)",
1191
1192
                         (vnodeid, mon_exptidx,
                          MONITOR_PID, MONITOR_EID, vnodeid))
Kirk Webb's avatar
Kirk Webb committed
1193
1194
            pass
        
Kirk Webb's avatar
   
Kirk Webb committed
1195
        except:
1196
1197
            print "Error adding %s (%s) to DB: someone needs to clean up!" % \
                  (nodeid,plc.name)
Kirk Webb's avatar
   
Kirk Webb committed
1198
            tbmsg = "".join(traceback.format_exception(*sys.exc_info()))
1199
1200
1201
            SENDMAIL(TBOPS, "Error adding new %s node to DB: %s\n" % \
                     (plc.name,nodeid),
                     "Some operation failed while trying to add a"
Kirk Webb's avatar
   
Kirk Webb committed
1202
                     " newly discovered plab node to the DB:\n %s"
1203
                     "\n Please clean up!\n" % tbmsg, TBOPS)
Kirk Webb's avatar
   
Kirk Webb committed
1204
            raise
Kirk Webb's avatar
   
Kirk Webb committed
1205
1206
        return

1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
    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
    
1245
    def __updateNode(self, plc, nodeid, nodeent):
Kirk Webb's avatar
   
Kirk Webb committed
1246
1247
1248
1249
1250
        """
        updateNodeEntries() helper function.  Updates attributes for plab
        nodes passed in via the nodeent argument.
        """
        # Get the name of the control interface for plab nodes.
1251
        defosid, controliface = self.__getNodetypeInfo(plc)
Kirk Webb's avatar
   
Kirk Webb committed
1252
1253
1254
1255
1256

        haslatlong = (('LATITUDE' in nodeent and 'LONGITUDE' in nodeent) and
            (nodeent['LATITUDE'] != 0 or nodeent['LONGITUDE'] != 0))
        try:
            DBQueryFatal("replace into widearea_nodeinfo"
1257
1258
1259
1260
1261
                         " (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'],
Kirk Webb's avatar
   
Kirk Webb committed
1262
1263
1264
1265
                          # Poor man's ternary operator
                          haslatlong and nodeent['LATITUDE'] or "NULL",
                          haslatlong and nodeent['LONGITUDE'] or "NULL",
                          nodeent['BWLIMIT']))