Node.pm.in 97.1 KB
Newer Older
1
#!/usr/bin/perl -wT
2
#
Leigh B Stoller's avatar
Leigh B Stoller committed
3
# Copyright (c) 2005-2016 University of Utah and the Flux Group.
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
23
24
#
package Node;
25
26
use strict;
use Exporter;
27
28
29
use SelfLoader ();
use vars qw(@ISA @EXPORT $AUTOLOAD @EXPORT_OK);
@ISA = qw(Exporter SelfLoader);
30
@EXPORT = qw();
31

Leigh B. Stoller's avatar
Leigh B. Stoller committed
32
# Configure variables
33
use vars qw($TB $BOSSNODE $WOL $OSSELECT $IPOD $ISUTAH $CONTROL_NETMASK
34
            $TBOPS $JAILIPMASK $TBBASE);
35
36
$TB	     = "@prefix@";
$BOSSNODE    = "@BOSSNODE@";
37
$TBOPS       = "@TBOPSEMAIL@";
38
39
$WOL         = "$TB/sbin/whol";
$OSSELECT    = "$TB/bin/os_select";
40
$IPOD	     = "$TB/sbin/ipod";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
41
# XXX stinky hack detection
42
$ISUTAH	     = @TBMAINSITE@;
43
# Need this for jail ip assignment.
44
$CONTROL_NETMASK = "@CONTROL_NETMASK@";
45
$JAILIPMASK  = "@JAILIPMASK@";
46
$TBBASE      = "@TBBASE@";
47

48
use libdb;
49
use libtestbed;
50
use emutil;
51
use English;
52
use Socket;
53
54
use Data::Dumper;
use overload ('""' => 'Stringify');
55
56

use vars qw($NODEROLE_TESTNODE $MFS_INITIAL $STATE_INITIAL
57
	    %nodes @cleantables);
58

59
60
61
62
63
64
# Exported defs
$NODEROLE_TESTNODE	= 'testnode';

# Why, why, why?
@EXPORT_OK = qw($NODEROLE_TESTNODE);

65
# Cache of instances to avoid regenerating them.
66
%nodes = ();
67
68
69
70
71
72
73

# Little helper and debug function.
sub mysystem($)
{
    my ($command) = @_;

    print STDERR "Running '$command'\n"
74
	if (0);
75
76
    return system($command);
}
77

78
# To avoid writing out all the methods.
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
AUTOLOAD {
#    print STDERR "$AUTOLOAD $_[0]\n";

    if (!ref($_[0])) {
	$SelfLoader::AUTOLOAD = $AUTOLOAD;
	return SelfLoader::AUTOLOAD(@_);
    }
    my $self  = $_[0];
    my $name  = $AUTOLOAD;
    $name =~ s/.*://;   # strip fully-qualified portion

    # A DB row proxy method call.
    if (exists($self->{'DBROW'}->{$name})) {
	return $self->{'DBROW'}->{$name};
    }

    # The method is possibly for a SelfLoader method after __DATA__
    # Or it is for a local storage slot.
    if ($name =~ /^_.*$/) {
	if (scalar(@_) == 2) {
	    return $self->{'HASH'}->{$name} = $_[1];
	}
	elsif (exists($self->{'HASH'}->{$name})) {
	    return $self->{'HASH'}->{$name};
	}
    }
    $SelfLoader::AUTOLOAD = $AUTOLOAD;
    my $ref = \&SelfLoader::AUTOLOAD;
    goto &$ref;
}

#
# The list of table we have to clear if anything goes wrong when
# creating a new node.
# 
@cleantables = ("nodes", "node_hostkeys", "node_status",
		"node_activity", "node_utilization",
		"node_auxtypes", "reserved", "widearea_nodeinfo");


119
#
120
# Lookup a (physical) node and create a class instance to return.
121
#
122
123
sub Lookup($$)
{
124
125
126
    my ($class, $token) = @_;
    my $nodeid;

127
    if ($token =~ /^\w{8}\-\w{4}\-\w{4}\-\w{4}\-\w{12}$/) {
128
129
130
131
132
133
134
135
136
137
138
139
140
141
	my $query_result =
	    DBQueryWarn("select node_id from nodes ".
			"where uuid='$token'");
	    return undef
		if (! $query_result || !$query_result->numrows);

	    ($nodeid) = $query_result->fetchrow_array();
    }
    elsif ($token =~ /^[-\w]+$/) {
	$nodeid = $token;
    }
    else {
	return undef;
    }
142

143
    # Look in cache first
144
145
    return $nodes{$nodeid}
        if (exists($nodes{$nodeid}));
146
147

    my $query_result =
148
149
	DBQueryWarn("select * from nodes as n ".
		    "where n.node_id='$nodeid'");
150

151
152
153
    return undef
	if (!$query_result || !$query_result->numrows);

154
    return LookupRow($class, $query_result->fetchrow_hashref());
155
156
157
158
159
160
161
162
}

#
# Lookup a (physical) node based on an existing row from the database.
# Useful for bulk lookups.
#
sub LookupRow($$)
{
163
    my ($class, $row) = @_;
164

165
    my $self            = {};
166
    $self->{"DBROW"}    = $row;
167
168
    $self->{"RSRV"}     = undef;
    $self->{"TYPEINFO"} = undef;
169
    $self->{"ATTRS"}    = undef;
170
    $self->{"FEATURES"} = undef;
171
    $self->{"IFACES"}   = undef;
172
    $self->{"WAROW"}    = undef;
173
174
175
    $self->{"HASH"}     = {};
    bless($self, $class);

176
    $nodes{$row->{'node_id'}} = $self;
177
178
179
    return $self;
}

180
181
182
183
184
185
186
187
188
189
190
191
192
#
# Force a reload of the data.
#
sub LookupSync($$)
{
    my ($class, $nodeid) = @_;

    # delete from cache
    delete($nodes{$nodeid});

    return Lookup($class, $nodeid);
}

193
194
195
196
197
198
199
200
# Break circular reference someplace to avoid exit errors.
sub DESTROY {
    my $self = shift;

    $self->{"DBROW"}    = undef;
    $self->{"RSRV"}     = undef;
    $self->{"TYPEINFO"} = undef;
    $self->{"ATTRS"}    = undef;
201
    $self->{"FEATURES"} = undef;
202
203
    $self->{"IFACES"}   = undef;
    $self->{"HASH"}     = undef;
204
    $self->{"WAROW"}    = undef;
205
206
}

207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#
# Stringify for output.
#
sub Stringify($)
{
    my ($self) = @_;
    
    my $nodeid = $self->node_id();

    return "[Node: $nodeid]";
}

# Local lookup for an Experiment, to avoid dragging in the module.
sub LocalExpLookup(@)
{
    require Experiment;

    return Experiment->Lookup(@_);
}

1;
228
@SELFLOADER_DATA@
229

230
231
232
233
234
235
236
237
238
239
240
241
#
# Create a fake object, as for the mapper (assign_wrapper) during debugging.
#
sub MakeFake($$$$)
{
    my ($class, $nodeid, $dbrow, $rsrvrow) = @_;

    my $self            = {};
    $self->{"DBROW"}    = $dbrow;
    $self->{"RSRV"}     = $rsrvrow;
    $self->{"TYPEINFO"} = undef;
    $self->{"ATTRS"}    = undef;
242
    $self->{"FEATURES"} = undef;
243
    $self->{"IFACES"}   = undef;
244
    $self->{"WAROW"}    = undef;
245
    $self->{"HASH"}     = {};
246
247
248
    bless($self, $class);

    # Add to cache.
249
250
251
    $nodes{$nodeid} = $self;
    return $self;
}
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

#
# Bulk lookup of nodes reserved to an experiment. More efficient.
#
sub BulkLookup($$$)
{
    my ($class, $experiment, $pref) = @_;
    my %nodelist = ();
    my $exptidx  = $experiment->idx();

    my $query_result =
	DBQueryWarn("select n.* from reserved as r ".
		    "left join nodes as n on n.node_id=r.node_id ".
		    "where r.exptidx=$exptidx");

    return -1
	if (!defined($query_result));

    while (my $row = $query_result->fetchrow_hashref()) {
271
	my $nodeid = $row->{'node_id'};
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
	my $node;

	if (exists($nodes{$nodeid})) {
	    $node = $nodes{$nodeid};
	    $node->{"DBROW"} = $row;
	}
	else {
	    $node               = {};
	    $node->{"DBROW"}    = $row;
	    bless($node, $class);

	    # Add to cache.
	    $nodes{$nodeid} = $node;
	}
	$node->{"RSRV"}     = undef;
	$node->{"TYPEINFO"} = undef;
	$node->{"ATTRS"}    = undef;
Leigh B Stoller's avatar
Leigh B Stoller committed
289
	$node->{"FEATURES"} = undef;
290
	$node->{"IFACES"}   = undef;
Mike Hibler's avatar
Mike Hibler committed
291
	$node->{"WAROW"}    = undef;
292
	$node->{"HASH"}     = {};
293
294
295
296
297
298
299
300
301
302
303
	
	$nodelist{$nodeid} = $node;
    }

    $query_result =
	DBQueryWarn("select r.* from reserved as r ".
		    "where r.exptidx=$exptidx");
    return -1
	if (!defined($query_result));
    
    while (my $row = $query_result->fetchrow_hashref()) {
304
	my $nodeid = $row->{'node_id'};
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
	my $node   = $nodelist{$nodeid};

	return -1
	    if (!defined($node));

	$node->{"RSRV"} = $row;
    }

    $query_result =
	DBQueryWarn("select a.* from reserved as r ".
		    "left join node_attributes as a on a.node_id=r.node_id ".
		    "where r.exptidx=$exptidx and a.node_id is not null");
    return -1
	if (!defined($query_result));
    
    while (my $row = $query_result->fetchrow_hashref()) {
321
	my $nodeid = $row->{'node_id'};
322
323
324
325
326
327
328
329
330
331
332
333
	my $key    = $row->{'attrkey'};
	my $node   = $nodelist{$nodeid};

	return -1
	    if (!defined($node));

	$node->{"ATTRS"}->{$key} = $row;
    }
	
    @$pref = values(%nodelist);
    return 0;
}
334

Leigh B Stoller's avatar
Leigh B Stoller committed
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
#
# Lookup all nodes of a type
#
sub LookupByType($$)
{
    my ($class, $type) = @_;
    my @result = ();

    my $query_result =
	DBQueryWarn("select node_id from nodes as n where n.type='$type'");
    return ()
	if (!$query_result);

    while (my ($node_id) = $query_result->fetchrow_array()) {
	my $node = Node->Lookup($node_id);
	if (!defined($node)) {
	    print STDERR "No such node $node_id\n";
	    next;
	}
	push(@result, $node);
    }
    return @result;
}

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
#
# Do we have regular nodes? Class method.
#
sub HaveExperimentNodes()
{
    my $query_result =
	DBQueryWarn("select count(node_id) from nodes as n ".
		    "left join node_types as t on t.type=n.type ".
		    "where t.class='pc'");
    return 0
	if (!$query_result);
    
    my ($count) = $query_result->fetchrow_array();
    return $count;
}

375
376
377
378
sub Create($$$$)
{
    my ($class, $node_id, $experiment, $argref) = @_;
    my ($control_iface,$virtnode_capacity,$adminmfs,$adminmfs_osid);
379
    my ($priority, $osid, $osid_vers, $opmode, $state);
380
381
382
383
384
385
386
387
388
    require OSinfo;
    require NodeType;

    # Defaults. Leave these here to avoid startup costs of libdb.
    #
    # MFS to boot the nodes into initially
    my $MFS_INITIAL   = TB_OSID_FREEBSD_MFS();
    # Initial event system state to put the nodes into
    my $STATE_INITIAL = TBDB_NODESTATE_SHUTDOWN();
389
390
391

    my $type = $argref->{'type'};
    my $role = $argref->{'role'};
392
393
394
395
396
397
398
399
400
401
402
403
404
    my $uuid;

    if (exists($argref->{'uuid'})) {
	$uuid = $argref->{'uuid'};
    }
    else {
	$uuid = NewUUID();
	if (!defined($uuid)) {
	    print STDERR "Could not generate a UUID!\n";
	    return undef;
	}
    }
    $uuid = DBQuoteSpecial($uuid);
405
406
407
408
409
410

    my $typeinfo = NodeType->Lookup($type);
    return undef
	if (!defined($typeinfo));

    my $isremote = $typeinfo->isremotenode();
411
    $osid_vers   = 0;
412

413
414
415
416
417
    if ($role eq "testnode") {
	if ($typeinfo->virtnode_capacity(\$virtnode_capacity)) {
	    print STDERR "*** No virtnode_capacity for $type! Using zero.\n";
	    $virtnode_capacity = 0;
	}
418
419
420
421
	if ($typeinfo->isswitch()) {
	    $osid   = "NULL";
	    $opmode = "ALWAYSUP";
	}
422
	elsif ($isremote || $typeinfo->isfakenode()) {
423
	    $osid   = "NULL";
424
425
426
427
428
429
430
431
432
433
434
	    $opmode = "";

	    if (defined($typeinfo->default_osid())) {
		$osid = $typeinfo->default_osid();
		
		my $osinfo = OSinfo->Lookup($osid);
		if (!defined($osinfo)) {
		    print STDERR
			"*** Could not find OSinfo object for $osid!\n";
		    return undef;
		}
435
		$osid      = $osinfo->osid();
Leigh B Stoller's avatar
Leigh B Stoller committed
436
		$osid_vers = $osinfo->vers();
437
		$opmode    = $osinfo->op_mode();
438
	    }
439
440
	}
	else {
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
	    if ($typeinfo->adminmfs_osid(\$adminmfs_osid)) {
		print STDERR "*** No adminmfs osid for $type!\n";
		return undef;
	    }
	    # Find object for the adminfs.
	    if (defined($adminmfs_osid)) {
		$adminmfs = OSinfo->Lookup($adminmfs_osid);
	    }
	    else {
		$adminmfs = OSinfo->Lookup(TBOPSPID(), $MFS_INITIAL);
	    }
	    if (!defined($adminmfs)) {
		print STDERR
		    "*** Could not find OSinfo object for adminmfs!\n";
		return undef;
	    }
457
458
459
	    $osid      = $adminmfs->osid();
	    $osid_vers = $adminmfs->vers();
	    $opmode    = $adminmfs->op_mode();
460
	}
461
462
    }
    else {
463
	$osid   = "NULL";
464
	$opmode = "";
465
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
466
467
    if (exists($argref->{'initial_eventstate'})) {
	$state = $argref->{'initial_eventstate'};
468
469
470
471
    }
    else {
	$state  = $STATE_INITIAL;
    }
472
473
474
475

    #
    # Lock the tables to prevent concurrent creation
    #
476
477
478
479
480
    DBQueryWarn("lock tables nodes write, widearea_nodeinfo write, ".
		"node_hostkeys write, node_status write, ".
		"node_utilization write, ".
		"node_activity write, reserved write, node_auxtypes write")
	or return undef;
481

482
483
484
485
486
487
488
489
490
    #
    # Make up a priority (just used for sorting)
    #
    if ($node_id =~ /^(.*\D)(\d+)$/) {
	$priority = $2;
    }
    else {
	$priority = 1;
    }
491

492
493
494
495
496
497
498
499
500
501
502
    #
    # See if we have a record; if we do, we can stop now and get the
    # existing record.
    #
    my $query_result =
	DBQueryWarn("select node_id from nodes where node_id='$node_id'");
    if ($query_result->numrows) {
	DBQueryWarn("unlock tables");
	return Node->Lookup($node_id);
    }

503
504
505
506
507
    if (!DBQueryWarn("insert into nodes set ".
		     "  node_id='$node_id', type='$type', " .
		     "  phys_nodeid='$node_id', role='$role', ".
		     "  priority=$priority, " .
		     "  eventstate='$state', op_mode='$opmode', " .
Leigh B Stoller's avatar
Leigh B Stoller committed
508
		     "  def_boot_osid=$osid, def_boot_osid_vers='$osid_vers',".
509
510
511
512
513
514
515
516
517
518
		     "  inception=now(), uuid=$uuid, ".
		     "  state_timestamp=unix_timestamp(NOW()), " .
		     "  op_mode_timestamp=unix_timestamp(NOW())")) {
	DBQueryWarn("unlock tables");
	return undef;
    }
    if ($isremote) {
	my $hostname = $argref->{'hostname'};
	my $external = $argref->{'external_node_id'};
	my $IP       = $argref->{'IP'};
519
520
521
522
523
524
525
526
527
528

	# Hmm, wanodecreate already does this.
	my $wa_result =
	    DBQueryWarn("select node_id from widearea_nodeinfo ".
			"where node_id='$node_id'");
	goto bad
	    if (!$wa_result);
	
	if ($wa_result->numrows == 0 &&
	    !DBQueryWarn("replace into widearea_nodeinfo ".
529
530
531
532
533
534
535
536
537
			 " (node_id, contact_uid, contact_idx, hostname," .
			 "  external_node_id, IP) ".
			 " values ('$node_id', 'nobody', '0', ".
			 "         '$hostname', '$external', '$IP')")) {
	    DBQueryWarn("delete from nodes where node_id='$node_id'");
	    DBQueryWarn("unlock tables");
	    return undef;
	}
    }
538

539
540
541
542
    if ($role eq "testnode") {
	DBQueryWarn("insert into node_hostkeys (node_id) ".
		    "values ('$node_id')")
	    or goto bad;
543
	
544
545
546
547
	DBQueryWarn("insert into node_status ".
		    "(node_id, status, status_timestamp) ".
		    "values ('$node_id', 'down', now()) ")
	    or goto bad;
548
    
549
550
551
	DBQueryWarn("insert into node_activity ".
		    "(node_id) values ('$node_id')")
	    or goto bad;
552
	
553
554
555
556
	DBQueryWarn("insert into node_utilization ".
		    "(node_id) values ('$node_id')")
	    or goto bad;
    }
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577

    if (defined($experiment)) {
	my $exptidx = $experiment->idx();
	my $pid     = $experiment->pid();
	my $eid     = $experiment->eid();

	# Reserve node to hold it from being messed with.
	print STDERR
	    "*** Reserving new node $node_id to $pid/$eid\n";

	DBQueryWarn("insert into reserved ".
		    "(node_id, exptidx, pid, eid, rsrv_time, vname) ".
		    "values ('$node_id', $exptidx, ".
		    "        '$pid', '$eid', now(), '$node_id')")
	    or goto bad;
    }

    #
    # Add vnode counts.
    #
    if ($role eq $Node::NODEROLE_TESTNODE && $virtnode_capacity) {
578
579
580
581
	my $vtype;
	
	if (exists($argref->{'vtype'})) {
	    $vtype = $argref->{'vtype'};
582
	}
583
584
585
586
587
588
589
	else  {
	    $vtype = $type;
	    if (!($vtype =~ s/pc/pcvm/)) {
		$vtype = "$vtype-vm";
	    }
	}
	
590
591
592
593
	DBQueryWarn("insert into node_auxtypes set node_id='$node_id', " .
		    "type='$vtype', count=$virtnode_capacity")
	    or goto bad;
    }
594
    DBQueryWarn("unlock tables");
595
596
597
598
599
600
    return Node->Lookup($node_id);
    
  bad:
    foreach my $table (@cleantables) {
	DBQueryWarn("delete from $table where node_id='$node_id'");
    }
601
    DBQueryWarn("unlock tables");
602
603
604
    return undef;
}

605
606
607
608
609
610
611
612
613
614
615
616
617
618
#
# Only use this for Create() errors.
#
sub Delete($)
{
    my ($self)  = @_;
    my $node_id = $self->node_id();

    foreach my $table (@cleantables) {
	DBQueryWarn("delete from $table where node_id='$node_id'");
    }
    return 0;
}

619
#
620
# Refresh a class instance by reloading from the DB.
621
#
622
sub Refresh($)
623
{
624
625
626
627
    my ($self) = @_;

    return -1
	if (! ref($self));
628

629
    my $nodeid = $self->node_id();
630

631
632
633
634
635
636
637
638
639
640
641
    my $query_result =
	DBQueryWarn("select * from nodes as n ".
		    "where n.node_id='$nodeid'");

    return -1
	if (!$query_result || !$query_result->numrows);

    $self->{"DBROW"}  = $query_result->fetchrow_hashref();
    # Force reload
    $self->{"RSRV"}     = undef;
    $self->{"TYPEINFO"} = undef;
642
    $self->{"ATTRS"}    = undef;
643
    $self->{"FEATURES"} = undef;
644
    $self->{"IFACES"}   = undef;
645
    $self->{"WAROW"}    = undef;
646
    return 0;
647
648
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
#
# Flush from our little cache, as for the expire daemon.
#
sub Flush($)
{
    my ($self) = @_;

    delete($nodes{$self->node_id()});
}
sub FlushAll($)
{
    my ($class) = @_;
    
    %nodes = ();
}

665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
#
# Convenience access method for widearea info
#
sub WideAreaInfo($$)
{
    my ($self, $slot) = @_;
    my $node_id = $self->node_id();

    if (!defined($self->{'WAROW'})) {
	my $query_result =
	    DBQueryWarn("select * from widearea_nodeinfo ".
			"where node_id='$node_id'");

	if (!$query_result || !$query_result->numrows) {
	    print STDERR "*** $node_id is not a widearea node\n";
	    return undef;
	}
	$self->{'WAROW'} = $query_result->fetchrow_hashref();
    }
    if (!exists($self->{'WAROW'}->{$slot})) {
	print STDERR
	    "*** Nonexistent slot '$slot' request for widearea node $node_id\n";
	return undef;
    }
    return $self->{'WAROW'}->{$slot};
}

692
693
694
695
696
697
698
699
700
#
# Check permissions. Allow for either uid or a user ref until all code
# updated.
#
sub AccessCheck($$$)
{
    my ($self, $user, $access_type) = @_;

    # Must be a real reference. 
701
    return 0
702
703
	if (! ref($self));

704
705
    if ($access_type < TB_NODEACCESS_MIN ||
	$access_type > TB_NODEACCESS_MAX) {
706
	print STDERR "*** Invalid access type: $access_type!\n";
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
	return 0;
    }
    # Admins do whatever they want.
    return 1
	if ($user->IsAdmin());

    my $mintrust;

    if ($access_type == TB_NODEACCESS_READINFO) {
	$mintrust = PROJMEMBERTRUST_USER;
    }
    else {
	$mintrust = PROJMEMBERTRUST_LOCALROOT;
    }

    # Get the reservation for this node. Only admins can mess with free nodes.
    my $experiment = $self->Reservation();
    return 0
	if (!defined($experiment));
726

727
728
729
730
731
732
733
734
735
736
737
738
739
740
    my $group = $experiment->GetGroup();
    return 0
	if (!defined($group));
    my $project = $experiment->GetProject();
    return 0
	if (!defined($project));

    #
    # Either proper permission in the group, or group_root in the
    # project. This lets group_roots muck with other people's
    # nodes, including those in groups they do not belong to.
    #
    return TBMinTrust($group->Trust($user), $mintrust) ||
	TBMinTrust($project->Trust($user), PROJMEMBERTRUST_GROUPROOT);
741
742
}

743
#
744
745
746
# Lazily load the reservation info.
#
sub IsReserved($)
747
{
748
    my ($self) = @_;
749

Leigh B. Stoller's avatar
Leigh B. Stoller committed
750
    return 0
751
	if (! ref($self));
752

753
754
755
756
757
758
    if (! defined($self->{"RSRV"})) {
	my $nodeid = $self->node_id();
	
	my $query_result =
	    DBQueryWarn("select * from reserved " .
			"where node_id='$nodeid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
759
	return 0
760
761
762
763
764
765
766
767
	    if (!$query_result);
	return 0
	    if (!$query_result->numrows);

	$self->{"RSRV"} = $query_result->fetchrow_hashref();
	return 1;
    }
    return 1;
768
769
}

770
771
772
773
774
775
776
777
778
779
#
# Set reserved member based on a database row. Useful for bulk lookups.
#
sub SetReservedRow($$)
{
    my ($self, $reserved) = @_;
    if ($reserved->{"node_id"} eq $self->node_id()) {
	$self->{"RSRV"} = $reserved;
    }
}
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

sub GetSubboss($$)
{
    my ($self, $service, $subboss_id) = @_;

    return 0
	if (! ref($self));

    my $ref;

    if (defined $self->{"SUBBOSSES"}) {
	my $ref = $self->{"SUBBOSSES"}->{$service};
    }

    if (!defined $ref) {
	my $nodeid = $self->node_id();

	my $query_result =
	    DBQueryWarn("select * from subbosses " .
			"where node_id='$nodeid' and " .
			"service = '$service'");

	return 0
	    if (!$query_result);
	return 0
	    if (!$query_result->numrows);

	if (!defined($self->{"SUBBOSSES"})) {
	    $self->{"SUBBOSSES"} = {};
	}

	$ref = $self->{"SUBBOSSES"}->{$service} =
	    $query_result->fetchrow_hashref();
    }

    $$subboss_id = $ref->{'subboss_id'};

    return 0;
}

820
821
822
823
824
825
826
827
828
829
830
#
# Flush the reserve info so it reloads.
#
sub FlushReserved($)
{
    my ($self) = @_;

    $self->{"RSRV"} = undef;
    return 0;
}

831
832
833
834
835
836
837
838
839
840
841
842
843
#
# Is node up.
#
sub IsUp($)
{
    my ($self) = @_;

    return 0
	if (! ref($self));

    return $self->eventstate() eq TBDB_NODESTATE_ISUP;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
#
# Determine if a node can be allocated to a project.
#
sub NodeAllocCheck($$)
{
    my ($self, $pid) = @_;

    # Must be a real reference. 
    return 0
	if (! ref($self));

    my $node_id = $self->node_id();

    #
    # Hmm. The point of this join is to find rows in the permissions table
    # with the corresponding type of the node. If no rows come back, its
    # a non-existent node! If the values are NULL, then there are no rows
    # with that type/class, and thus the type/class is free to be allocated
    # by anyone. Otherwise we get the list of projects that are allowed,
    # and so we have to look at those.
864
865
866
    # Note: nodetypeXpid_permissions has the pid_idx in addition to the pid -
    # presumably, the Right Thing would be to use that, but this function
    # is only passed the pid right now.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
867
868
    #
    my $query_result =
869
	DBQueryFatal("select distinct p.type, p.pid_idx from nodes as n ".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
870
871
872
873
874
875
876
877
878
		     "left join node_types as nt on n.type=nt.type ".
		     "left join nodetypeXpid_permissions as p on ".
		     "     (p.type=nt.type or p.type=nt.class) ".
		     "where node_id='$node_id'");

    if (!$query_result->numrows) {
	print STDERR "NodeAllocCheck: No such node $node_id!\n";
	return 0;
    }
879
    my ($ptype,$pid_idx) = $query_result->fetchrow_array();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
880
881

    # No rows, or a pid match.
882
    if (!defined($ptype) || $pid_idx eq $pid->pid_idx()) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
883
884
885
886
	return 1;
    }

    # Okay, must be rows in the permissions table. Check each pid for a match.
887
888
    while (my ($ptype,$pid_idx) = $query_result->fetchrow_array()) {
	if ($pid_idx eq $pid->pid_idx()) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
889
890
891
892
893
894
	    return 1;
	}
    }
    return 0;
}

895
896
897
898
899
900
901
902
# Naming confusion.
sub AllocCheck($$)
{
    my ($self, $pid) = @_;

    return $self->NodeAllocCheck($pid);
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
903
904
905
906
907
908
909
910
911
912
913
914
#
# Set alloc state for a node.
#
sub SetAllocState($$)
{
    my ($self, $state) = @_;

    return -1
	if (! (ref($self)));
    
    my $now = time();
    my $node_id = $self->node_id();
915

Leigh B. Stoller's avatar
Leigh B. Stoller committed
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
    DBQueryWarn("update nodes set allocstate='$state', " .
		"    allocstate_timestamp=$now where node_id='$node_id'")
	or return -1;

    return Refresh($self);
}

#
# Get alloc state for a node.
#
sub GetAllocState($$)
{
    my ($self, $pref) = @_;

    return -1
	if (! (ref($self) && ref($pref)));
    
    my $allocstate = $self->allocstate();
    
    if (defined($allocstate)) {
	$$pref = $allocstate;
    }
    else {
	$$pref = TBDB_ALLOCSTATE_UNKNOWN;
    }
    return 0;
}

944
945
946
#
# We do this cause we always want to go to the DB.
#
947
sub GetEventState($$;$)
948
{
949
    my ($self, $pstate, $popmode) = @_;
950
951
952
    my $node_id = $self->node_id();

    my $query_result =
953
954
	DBQueryWarn("select eventstate,op_mode from nodes ".
		    "where node_id='$node_id'");
955
956
957
    return -1
	if (!$query_result || !$query_result->numrows);

958
    my ($state,$op_mode) = $query_result->fetchrow_array();
959
960
    $state = TBDB_NODESTATE_UNKNOWN
	if (!defined($state));
961
962
    $op_mode = TBDB_NODEOPMODE_UNKNOWN
	if (!defined($op_mode));
963

964
    $self->{'DBROW'}->{'eventstate'} = $state
965
966
	if (defined($self->{'DBROW'}));
    $$pstate = $state;
967
968
    $$popmode= $op_mode
 	if (defined($popmode));
969
970
971
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
#
# Equality test for two experiments.
# Not strictly necessary in perl, but good form.
#
sub SameExperiment($$)
{
    my ($self, $other) = @_;

    # Must be a real reference. 
    return -1
	if (! (ref($self) && ref($other)));

    return $self->idx() == $other->idx();
}

987
988
989
990
991
992
993
#
# Get the experiment this node is reserved too, or null.
#
sub Reservation($)
{
    my ($self) = @_;

994
    return undef
995
996
	if (! ref($self));

997
    return undef
998
999
	if (! $self->IsReserved());

1000
    return LocalExpLookup($self->{"RSRV"}->{'exptidx'});
For faster browsing, not all history is shown. View entire blame