snmpit.proxynew.in 23.3 KB
Newer Older
1
2
3
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2009 University of Utah and the Flux Group.
5
6
# All rights reserved.
#
7
use strict;
8
9
use English;
use Getopt::Std;
10
11
use RPC::XML::Parser;
use Data::Dumper;
12
13
14
15
16
17

#
# Snmpit proxy for ElabInElab.
#
sub usage()
{
18
    print STDOUT "Usage: snmpit.proxy [-d] -p <pid> -e <eid> <xmldoc>\n";
19
20
21
    exit(-1);
}
my $optlist  = "dnp:e:";
22
my $debug    = 1;
23
my $impotent = 0;
24
25
my $exitval  = 0;
my $output;
26
27
my $pid;
my $eid;
28
29
30
31
my @inner_ids   = ();
my @outer_ids   = ();
my %outer_vlans = ();
my %mapping     = ();
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";

# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# Turn off line buffering on output
#
$| = 1; 

# Load the Testbed support stuff.
use lib "@prefix@/lib";
use libdb;
51
use libaudit;
52
use libtestbed;
53
use User;
54
use Experiment;
55
use Lan;
56
57
use Interface;
use Node;
58

59
60
61
# Locals
my $exptidx;

62
# Protos
63
64
65
66
67
68
69
70
sub SetupVlans($);
sub MakeVlan($);
sub DestroyVlans($);
sub PortControl($);
sub Trunk($);
sub List($);
sub MapVlans(@);
sub fatal($);
71
72
73
74
75

#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
76
my %options = ();
77
78
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
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"d"})) {
    $debug = 1;
}
if (defined($options{"n"})) {
    $impotent = 1;
}
if (defined($options{"p"})) {
    $pid = $options{"p"};
    
    #
    # Untaint the arguments.
    #
    if ($pid =~ /^([-\w\.]+)$/) {
	$pid = $1;
    }
    else {
	die("Tainted argument $pid!\n");
    }
}
if (defined($options{"e"})) {
    $eid = $options{"e"};

    if ($eid =~ /^([-\w\.]+)$/) {
	$eid = $1;
    }
    else {
	die("Tainted argument $eid!\n");
    }
}
usage()
110
    if (@ARGV != 1 || !defined($pid) || !defined($eid));
111

112
113
114
115
116
117
118
119
120
121
122
123
124
125
#
# Log audit since it is hard to debug this one.
#
LogStart(0, undef, LIBAUDIT_LOGTBOPS()|LIBAUDIT_NODELETE);

my $xmldoc = $ARGV[0];
# Note different taint check (allow /).
if ($xmldoc =~ /^([-\w\.\/]+)$/) {
    $xmldoc = $1;
}
else {
    fatal("Tainted argument $xmldoc");
}
# Cause we are invoked from the xmlrpc server with this name format.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
126
if (! ($xmldoc =~ /^\/var\/tmp\/php[-\w]+/)) {
127
128
129
130
131
132
133
134
135
    fatal("$xmldoc does not resolve to an appropriate directory!");
}
if (! -e $xmldoc) {
    fatal("$xmldoc does not exist!");
}
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    fatal("You ($UID) do not exist!");
}
136
137
138
139
140

#
# Sanity check. Must be an ElabInElab experiment and user must have
# permission (be the creator).
#
141
142
my $experiment = Experiment->Lookup($pid, $eid);
if (!defined($experiment)) {
143
    fatal("Experiment $pid/$eid is not active!");
144
}
145
$exptidx = $experiment->idx();
146
147
if (!$experiment->AccessCheck($this_user, TB_EXPT_MODIFY)) {
    fatal("You do not have permission to swap or modify this experiment!");
148
149
}

150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#
# Open the file and pass the stream to the parser. 
#
open(XMLDOC, $xmldoc) or
    fatal("$xmldoc could not be opened for reading!");

my $parser   = RPC::XML::Parser->new();
my $goo      = $parser->parse(*XMLDOC);
if (! ref($goo)) {
    fatal("$xmldoc could not be parsed!");
}
my $opargs  = $goo->value();
if (! (exists($opargs->{'op'}) && exists($opargs->{'args'}))) {
    fatal("missing arguments in xmlgoo");
}
my $op    = $opargs->{'op'};
my $args  = $opargs->{'args'};

# Add stuff for log message if sent.
AddAuditInfo("message", $op . "\n\n" . Dumper($args));

171
172
173
174
175
176
#
# I'm going to serialize this for now. When the script exits, the lock
# will be released.
#
DBQueryFatal("select get_lock('snmpit.proxy', 999999)");

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
SWITCH: for ($op) {
    /setup/ && do {
	($exitval, $output) = SetupVlans($args);
	last;
    };
    /makevlan/ && do {
	($exitval, $output) = MakeVlan($args);
	last;
    };
    /destroy/ && do {
	($exitval, $output) = DestroyVlans($args);
	last;
    };
    /trunk/ && do {
	($exitval, $output) = Trunk($args);
	last;
    };
    /list/ && do {
	($exitval, $output) = List($args);
	last;
    };
    /portcontrol/ && do {
	($exitval, $output) = PortControl($args);
	last;
    };
    # default case
    fatal("Unknown operation $op");
204
}
205
206
207
208
209
210
211
212
213
214

# Update with output for log message if sent.
AddAuditInfo("message", "$op\n\n" . Dumper($args) . "\n\n$output\n");

#
# Terminate the log capture so that we can print the response to STDOUT
# for the RPC server. 
#
if ($exitval) {
    LogEnd($exitval);
215
}
216
217
else {
    LogAbort();
218
}
219
220
221
print $output
    if (defined($output));
exit($exitval);
222
223

#
224
225
226
227
228
229
230
# Setup vlans for an inner emulab. For each vlan id, gather up all if
# its member ports (node:iface), along with the speed and duplex for
# each port, which goes into the interfaces table so that the real
# snmpit can find the right values.
#
# Note that this function assumes the experiment stack. See MakeVlan()
# below for the more basic function to create a single vlan on a stack.
231
# 
232
sub SetupVlans($)
233
{
234
235
    my ($argtable) = @_;
    
236
    my $vlantable = {};
237
    my %vmaptable = ();
238
    my %vtagtable = ();
239
    my %stacktable= ();
240
241
242
    my %nodes     = ();
    my @outer_ids = ();
    my $errors    = 0;
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

    foreach my $id (keys(%{ $argtable })) {
	my $vtag    = $argtable->{$id}->{'virtual'};
	my $stack   = $argtable->{$id}->{'stack'};
	my $members = $argtable->{$id}->{'members'};

	if (! ($id =~ /^[\d]+$/)) {
	    fatal("SetupVlans: Illegal id '$id'");
	}
	if (! ($vtag =~ /^[\-\w]+$/)) {
	    fatal("SetupVlans: Illegal vname '$vtag'");
	}
	if (!defined($stack)) {
	    $stack = "Experimental";
	}
	elsif ($stack ne "Control" && $stack ne "Experimental") {
	    fatal("SetupVlans: Illegal stack '$stack'");
	}

	foreach my $port (keys(%{ $members })) {
	    my $speed  = $members->{$port}->{'speed'};
	    my $duplex = $members->{$port}->{'duplex'};
265
266
267
	    my $node;
	    my $iface;

268
269
270
271
272
273
274
275
276
277
	    if (! ($port =~ /^[\-\w]+\:[\-\w]+$/)) {
		fatal("SetupVlans: Illegal port '$port'");
	    }
	    if (! ($speed =~ /^[\d]+$/)) {
		fatal("SetupVlans: Illegal speed '$speed'");
	    }
	    if (! ($duplex eq "full" || $duplex eq "half")) {
		fatal("SetupVlans: Illegal duplex '$duplex'");
	    }

278
279
280
	    if (! exists($vlantable->{$id})) {
		$vlantable->{$id} = {};
		$vtagtable{$id}   = $vtag;
281
		$stacktable{$id}  = $stack;
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
	    }
	    $vlantable->{$id}->{$port} = [$speed, $duplex];

	    # For doing access and sanity checks below.
	    if ($port =~ /^(.+):(.+)$/) {
		($node,$iface) = ($1, $2);
	    }
	    $nodes{$node} = []
		if (!exists($nodes{$node}));
	    push(@{ $nodes{$node} }, $iface);
	}
    }

    #
    # First check permission on the nodes. snmpit is going to repeat this
    # operation, but we have to do it here cause we first mess with the
    # speed and duplex values in the interfaces table for each node, cause
    # snmpit uses those values when setting up the vlan.
    #
    if (!TBNodeAccessCheck($UID, TB_NODEACCESS_MODIFYVLANS, keys(%nodes))) {
	die("*** $0:\n",
	    "    You do not have permission to modify some of the nodes\n" .
	    "    that will be affected by the operation you requested\n");
    }

307
    # Stoller wrote:
308
309
310
    # Sanity check the inner id numbers. If they already exist in the
    # mapping table, then bail now. We could probably support this, but
    # I do not see a reason to yet.
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331

    # Sklower explains:
    # for the federation boss, in order to calculate which
    # vlans go on which inter site trunks it's convenient to
    # push the vlan handling for master site's parent into
    # a pseudo-switch-module, but that will cause the parent
    # to be asked to add groups of interfaces in chunks.

#    my $query_result =
#	DBQueryFatal("select * from elabinelab_vlans ".
#		     "where pid='$pid' and eid='$eid' and ".
#		     # Join "id='foo'" with ORs
#		     "(" .
#		       join(' OR ', map("inner_id='$_'", keys(%$vlantable))) .
#		     ")");
#    if ($query_result->numrows) {
#	my @ids = keys(%$vlantable);
#	
#	die("*** $0:\n",
#	    "    One of the inner vlan ids (@ids) already exists!\n");
#    }
332
333
334
335
336
337
338
339

    #
    # Okay, sanity check the interfaces for each node. They have to be real
    # interfaces, marked as TBDB_IFACEROLE_EXPERIMENT(), nothing else. Also,
    # the IP must not be set; if the IP is set, its being used for the inner
    # control network, and we do not let those interfaces change.
    #
    foreach my $node (keys(%nodes)) {
340

341
342
	my @ifaces = @{ $nodes{$node} };

343
344
345
346
347
348
349
350
351
	# Sklower chimes in again
	# In Mike's canonical experiment about running two interfaces in
	# multiplexed mode the query that was here returned more than
	# one result, because the same interface was presented twice.
        
	# this check is going to become a lot more elaborate if we allow
	# an elabinelab to have firewalls within it ... let's *table* that
	# motion for now ...

352
	my $query_result =
353
354
	    DBQueryFatal("select iface,role,IP from interfaces ".
			 "where node_id='$node' and ".
355
356
357
			 # Join "iface='foo'" with ORs
			 "(" . join(' OR ', map("iface='$_'", @ifaces)) . ")");

358
359
360
361
362
363
364
	if ($query_result) {
	    while (my ($iface, $role, $IP) = $query_result->fetchrow()) {
		if (($role ne "expt") || ($IP ne "")) {
		    die("*** $0:\n",
			"Iface $iface for $node cannot be changed\n");
		}
	    }
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
	}
    }

    #
    # Okay, set the speed and duplex for all the interfaces. 
    #
    foreach my $id (keys(%$vlantable)) {
	foreach my $port (keys(%{ $vlantable->{$id} })) {
	    my ($speed, $duplex) = @{ $vlantable->{$id}->{$port} };
	    my ($node,$iface)    = ($port =~ /^(.+):(.+)$/);

	    if ($debug) {
		print STDERR "$id $node:$iface $speed $duplex\n";
	    }
	    if (! $impotent) {
		DBQueryFatal("update interfaces set ".
			     "   current_speed=$speed,duplex='$duplex'".
			     "where node_id='$node' and iface='$iface'");
	    }
	}
    }

    #
    # Okay, create an actual members list to insert into the DB vlans table.
    # We need to remember the association between the inner id and the outer
    # id, so after we insert the vlans entry, get the ID that was assigned and
    # remember it in the elabinelab_vlans table for later when the inner elab
    # requests deletion (see DestroyVlans() below).
    #
    # Note that on failure we keep going, which mirrors how snmpit operates.
    # Not sure if this is the right approach though.
    # 
    foreach my $id (keys(%$vlantable)) {
	my $vtag    = $vtagtable{$id};
399
	my $stack   = $stacktable{$id};
400
401
	my @members = keys(%{ $vlantable->{$id} });
	my $mstring = "@members";
402
	my $outer_id;
403
	my $vlan;
404

405
406
407
	# Not doing stacks yet.
	$stack = "Experimental";

408
409
410
411
412
413
	if ($debug) {
	    print STDERR "$pid $eid $id $mstring\n";
	}
	next
	    if ($impotent);

414
	# Insert (or modify) outer vlans entry.
415
	my $query_result =
416
417
418
419
	    DBQueryFatal("select outer_id from elabinelab_vlans ".
			 "where pid='$pid' and eid='$eid' and inner_id='$id'");
	if ($query_result->numrows == 1) {
	    ($outer_id) = $query_result->fetchrow();
420
	    $vlan = VLan->Lookup($outer_id);
421
	    if (!defined($vlan)) {
422
		print STDERR "*** $0:\n".
423
			     "    Could not lookup vlan for $outer_id\n";
424
425
426
		$errors++;
		next;
	    }
427
428
	}
	else {
429
430
	    $vlan = VLan->Create($experiment, $vtag);
	    if (!defined($vlan)) {
431
432
433
434
435
		print STDERR "*** $0:\n".
			     "    Could not insert vlan table entry for $id\n";
		$errors++;
		next;
	    }
436
	    $vlan->SetStack($stack);
437
	    $outer_id = $vlan->lanid();
438

439
	    # Insert mapping between inner and outer vlan entries.
440
441
442
443
444
	    $query_result =
		DBQueryWarn("insert into elabinelab_vlans ".
			    "      (exptidx,pid,eid,inner_id,outer_id,stack) ".
			    "values ($exptidx, '$pid', '$eid', '$id', ".
			    "        '$outer_id', '$stack')");
445
446
447
448
449
450
451
452
	    if (!$query_result || !$query_result->numrows) {
		#
		# Failed, must remove vlans entry too. We keep going though
		# 
		print STDERR "*** $0:\n".
			     "    Could not insert elabinelab_vlans table entry ".
			     "for $id/$outer_id\n";

453
		$vlan->Destroy();
454
455
456
		$errors++;
		next;
	    }
457
	}
458
459
460
461
	if ($debug) {
	    print STDERR "Mapping inner id $id to outer id $outer_id\n";
	    print STDERR "  $mstring\n";
	}
462
463
464
465
466
467
468
469
470
	foreach my $port (@members) {
	    my ($nodeid, $iface) = split(":", $port);
	    if (!$vlan->IsMember($nodeid, $iface) &&
		!$vlan->AddMember($nodeid, $iface)) {
		print STDERR "*** $0:\n".
		    "    Could not $port to $vlan\n";
		$errors++;
	    }
	}
471
	$vmaptable{$id} = $outer_id;
472
473
	# Okay, save outer_id up for passing to snmpit below.
	push(@outer_ids, $outer_id);
474
	# And save vlan object for getting the tag.
475
	$outer_vlans{$outer_id} = $vlan;
476
    }
477
478
    # Not doing stacks yet.
    my $stackopt = "";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
479
    my $debugopt = ($debug ? "-v" : "");
480
    
481
482
    # Now call snmpit to create the actual vlans.
    if ($debug) {
483
	print STDERR "Running 'snmpit $stackopt -t $pid $eid @outer_ids'\n";
484
485
486
487
    }
    return $errors
	if ($impotent);
    
Leigh B. Stoller's avatar
Leigh B. Stoller committed
488
    system("$TB/bin/snmpit $debugopt $stackopt -t $pid $eid @outer_ids");
489
490
491
492
493
494
495
496
497
498
499
500
    if ($?) {
	#
	# Yuck failed. We leave things as is, and wait for experiment
	# teardown to destroy any vlans that managed to get set up.
	# Obviously, we must leave the vlans in the DB or else we will not
	# be able to clean up later. 
	# This mirrors what happens when snmpit fails during a normal setup.
	#
	print STDERR "*** $0:\n".
	             "    snmpit -t failed!\n";
	$errors = $? >> 8;
    }
501
    my @results = ();
502
    foreach my $id (keys %vmaptable) {
503
504
505
506
507
508
	my $outer_vlan = $outer_vlans{$vmaptable{$id}};

	if ($outer_vlan->Refresh() != 0) {
	    print STDERR "*** Could not refresh $outer_vlan\n";
	    $errors++;
	    next;
509
	}
510
511
	my $tagnum = $outer_vlan->GetTag();
	if ($tagnum <= 0) {
512
513
514
515
516
	    print STDERR "*** Could not get vlan tag for $outer_vlan\n";
	    $errors++;
	    next;
	}
	push (@results, "$id#$tagnum");
517
    }
518
519
    return $errors, join(",", @results);

520
}
521

522
#
523
# This is common to list and destroy and trunk.
524
#
525
sub MapVlans(@)
526
{
527
528
    my @vlanids = @_;
    
529
530
531
532
533
534
535
536
    my $query_result =
	DBQueryFatal("select inner_id,outer_id from elabinelab_vlans ".
		     "where pid='$pid' and eid='$eid'");

    while (my ($inner_id,$outer_id) = $query_result->fetchrow()) {
	$mapping{$inner_id} = $outer_id;
    }
    #
537
538
539
540
    # Sanity check; make sure the set of vlans we got (from the inner
    # elab via the RPC server) are really vlans we have already setup.
    # Anything that does not match, skip with a warning, but go ahead
    # and tear down ones that match.
541
    #
542
543
    while (@vlanids) {
	my $id = shift(@vlanids);
544

545
	if ($id eq "") { next ; }
546
547
548
549
550
551
552
553
554
555
556
	if (! ($id =~ /^\d+$/)) {
	    print STDERR "*** $0:\n".
		         "    Illegal characters in id: $id\n";
	    next;
	}

	if (!exists($mapping{$id})) {
	    print STDERR "*** $0:\n".
		         "    No such elabinelab_vlans table entry: $id\n";
	    next;
	}
557
558
559
560
561
562
563

	my $vlan = VLan->Lookup($mapping{$id});
	if (!defined($vlan)) {
	    print STDERR "*** $0:\n".
		         "    Cannot find vlan object for vlan id: $id\n";
	    next;
	}
564
565
	push(@inner_ids, $id);
	push(@outer_ids, $mapping{$id});
566
	$outer_vlans{$mapping{$id}} = $vlan;
567
    }
568
569
570
    return 0;
}

571
572
573
574
575
576
577
578
579
#
# Add ports to a vlan, creating it if needed. This is mostly to support
# inner firewalls.
# 
sub MakeVlan($)
{
    fatal("Unsupported for ElabinElab");
}

580
581
582
583
584
#
# Destroy a set of vlans. We get a list of inner vlan ID numbers for the
# inner elab vlans table. We have to map those to outer vlan table id
# numbers, and then remove those from the DB and from the switches.
# 
585
sub DestroyVlans($)
586
{
587
    my ($args) = @_;
588

589
590
591
592
593
594
595
596
597
    if (! exists($args->{'vlans'})) {
	fatal("DestroyVlans: Missing arguments");
    }
    my @vlanids  = @{ $args->{'vlans'} };
    my @done     = ();
    my $errors   = 0;
    my $stack;
    
    MapVlans(@vlanids);
598
599
600
601
602
603
604
605
606
    # If no vlans, do nothing! snmpit will end up removing all the vlans!
    return 0
	if (! @outer_ids);
    return 0
	if ($impotent);

    #
    # Okay, ask snmpit to tear down these vlans. 
    #
607
608
609
610
611
612
    foreach my $vlanid (@vlanids) {
	my $outer_id = $mapping{$vlanid};
	my $vlan     = $outer_vlans{$outer_id};
	my $stack    = $vlan->GetStack();
	# Not doing stacks yet;
	my $stackopt = "";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
613
	my $debugopt = ($debug ? "-v" : "");
614
615
616
617

	if ($debug) {
	    print STDERR "Running 'snmpit $stackopt -r $pid $eid $outer_id'\n";
	}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
618
	system("$TB/bin/snmpit $debugopt $stackopt -r $pid $eid $outer_id");
619
620
621
622
623
624
625
626
627
628
629
630
631
632
	if ($?) {
	    #
	    # Yuck failed. We leave things as is, and wait for the
	    # inner elab to request experiment teardown again. This
	    # mirrors what happens on a normal swapout; snmpit -r can
	    # be retried until all of the vlans are finally gone; At
	    # that point the DB state can be removed.
	    #
	    print STDERR "*** $0:\n".
		         "    snmpit $stackopt -r $outer_id failed!\n";
	    $errors = $? >> 8;
	    goto bad;
	}

633
	#
634
	# Remove the outer vlan table entries first. 
635
	#
636
637
638
	if ($debug) {
	    print STDERR "Removing $outer_id from vlans table\n";
	}
639
640
641
	if ($vlan->Destroy() != 0) {
	    print STDERR "*** $0:\n".
		         "    Could not Destroy() $vlan\n";
642
643
644
	    $errors++;
	    goto bad;
	    
645
	}
646
647
648
649
650
651
652
653
654
655
656
657
658
	#
	# Since the above worked, we can remove the mappings too.
	# 
	if ($debug) {
	    print STDERR "Removing $outer_id from elabinelab_vlans table\n";
	}
	if (!DBQueryWarn("delete from elabinelab_vlans ".
			 "where pid='$pid' and eid='$eid' and ".
			 "      inner_id='$vlanid'")) {
	    $errors++;
	    goto bad;
	}
	push(@done, $vlanid);
659
    }
660
661
  bad:
    return $errors, join(",", @done);
662
}
663
664
665
666
667
#
# List a set of vlans. We get a list of inner vlan ID numbers for the
# inner elab vlans table. We have to map those to outer vlan table id
# numbers, and then list.
# 
668
sub List($)
669
{
670
671
672
673
674
675
    my ($args)  = @_;

    if (! exists($args->{'vlans'})) {
	fatal("List: Missing arguments");
    }
    my @vlanids = @{ $args->{'vlans'} };
676
677
    my @pairs;

678
    MapVlans(@vlanids);
679
680
681
682
683
684
685
686
    if (!@inner_ids) { @inner_ids = keys %mapping; }
    if (!@inner_ids) { return 0; }

    foreach my $in (@inner_ids) {
	my $out = $mapping{$in};
	push @pairs, "$out#$in";
    }
    my $command = "$TB/bin/snmpit -L " . join(",", @pairs);
687
    my $output = `$command`;
688
689
690
691
692
    if ($?) {
	# Yuck failed.
	print STDERR "*** $0:\n".  "    snmpit -L failed!\n";
	return $? >> 8;
    }
693
    return 0, $output;
694
695
696
697
698
699
}

#
# Either put a(n experimental) port into standard, or dual-mode trunking
# or reset to its normal state (snmpit -E , -T or - U).
#
700
sub Trunk($)
701
{
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
    my ($args)  = @_;

    if (! (exists($args->{'mode'}) && exists($args->{'vlans'}) &&
	   exists($args->{'port'}))) {
	fatal("Trunk: Missing arguments");
    }
    my $mode    = $args->{'mode'};
    my $port    = $args->{'port'};
    my @vlanids = @{ $args->{'vlans'} };

    # Taint check these args.
    if (! ($mode eq "-T" || $mode eq "-E" || $mode eq "-U")) {
	fatal("Trunk: Improper mode '$mode'");
    }
    if (! ($port =~ /^[\-\w]+\:[\-\w]+$/)) {
	fatal("Trunk: Illegal port '$port'");
    }
    # Not doing stacks yet.
    my $stack    = "Experimental";
    my $stackopt = "";
    if (0 && exists($args->{'stack'})) {
	$stack = $args->{'stack'};
	if ($stack ne "Control" && $stack ne "Experimental") {
	    fatal("Trunk: Improper stack argument: $stack");
	}
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
728
    my $debugopt = ($debug ? "-v" : "");
729
730
731

    $port =~ /^(.+):(.+)/;
    my ($node, $card) = ($1,$2);
732
733
734
735

    my $query_result =
	DBQueryFatal("select iface from interfaces ".
		     "where node_id='$node' and card='$card'");
736
    if ($query_result->numrows != 1) {
737
	fatal("Cannot determine iface from $port in $pid/$eid");
738
739
740
741
742
743
744
    }
    my ($iface) = ($query_result->fetchrow())[0];

    $query_result =
	DBQueryFatal("select node_id from reserved where node_id='$node' ".
		     "and pid='$pid' and eid='$eid'");
    if ($query_result->numrows != 1) {
745
	fatal("Trunk: $node not allocated to $pid/$eid");
746
    }
747
    MapVlans(@vlanids);
748
    if ($mode eq "-U") {
749
750
751
752
753
754
755
756
	if ($stack eq "Experimental") {
	    $query_result =
		DBQueryFatal("select iface from vinterfaces " .
			     "where node_id='$node' and type='vlan' and ".
			     "      iface='$iface'");
	    if ($query_result->numrows == 0) {
		fatal("Trunk: $port not trunked in database");
	    }
757
758
759
760
761
	}
	#
	# Okay, ask snmpit to Untrunk
	#
	if ($debug) {
762
	    print STDERR "Running 'snmpit $stackopt -U $port'\n";
763
	}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
764
	system("$TB/bin/snmpit $debugopt $stackopt -U $port");
765
766
	if ($?) {
	    # Yuck failed.
767
	    fatal("snmpit $stackopt -U failed!");
768
769
770
771
	}
	#
	# and clean up the database
	#
772
773
774
775
776
777
	if ($stack eq "Experimental") {
	    $query_result =
		DBQueryFatal("delete from vinterfaces " .
			     "where node_id='$node' and type='vlan' and ".
			     "      iface='$iface'");
	}
778
779
780
781
782
783
784
785
786
787
788
789
790
	return 0;
    }
    if (($mode eq "-E") || ($mode eq "-T")) {
	# First, update the status of the port in the database
	# The code here is wrong and needs futher work.
	# apparently there is a vinterfaces entry for each vlan
	# however in order to figure out which one you have to
	# do a join through both the vlans and virt_lans tables.
	# There's no convenient way, given the management
	# interface, to come up with a unique IP address that's
	# appropriate to stuff.  It seems likely that the structure
	# of the database will be revised, in this area, but
	# for now, we'll just play Joe Isuzu.
791
792
793
794
795
796
797
798
799
800
801
802
	if ($stack eq "Experimental") {
	    $query_result =
		DBQueryFatal("select iface from vinterfaces where " .
			     "node_id='$node' and iface='$iface'");
	    if ($query_result->numrows != 0) {
		DBQueryFatal("update vinterfaces set type='vlan' where " .
			     "node_id='$node' and iface='$iface'");
	    }
	    else {
		DBQueryFatal("replace into vinterfaces (node_id,iface,type) " .
			     "values ('$node','$iface','vlan')");
	    }
803
804
805
806
	}
	#
	# Okay, ask snmpit to trunk these vlans. 
	#
807
	my $command =
Leigh B. Stoller's avatar
Leigh B. Stoller committed
808
	    "$TB/bin/snmpit $debugopt $stackopt $mode $port @outer_ids";
809
810
811
812
	if ($debug) { print STDERR "Running $command\n"; }
	system($command);
	if ($?) {
	    # Yuck failed.
813
	    fatal("Trunk: snmpit $stackopt $mode $mode @outer_ids failed!");
814
815
816
817
818
819
	}
	return 0;
    }
    print STDERR "*** unknown mode for trunk request: $mode \n";
    return 1;
}
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834

#
# Port control function.
#
sub PortControl($)
{
    my ($args)  = @_;

    if (! (exists($args->{'command'}) && exists($args->{'ports'}))) {
	fatal("PortControl: Missing arguments");
    }
    my $command = $args->{'command'};
    my @ports   = @{ $args->{'ports'} };

    # Not doing stacks yet;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
835
836
    my $stack    = "Experimental";
    my $debugopt = ($debug ? "-v" : "");
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905

    #
    # Check the ports. Must be valid format and belong to node reserved
    # to the elabinelab.
    #
    foreach my $port (@ports) {
	if (! ($port =~ /^[\-\w]+\:[\-\w]+$/)) {
	    fatal("PortControl: Illegal port '$port'");
	}

	$port =~ /^(.+):(.+)/;
	my ($nodeid, $card) = ($1,$2);

	my $node = Node->Lookup($nodeid);
	if (!defined($node)) {
	    fatal("PortControl: No such node $nodeid");
	}
	my $interface = Interface->Lookup($nodeid, $card, 1);
	if (!defined($interface)) {
	    fatal("PortControl: No such interface $card on $nodeid");
	}
	my $reservation = $node->Reservation();
	if (!defined($reservation) ||
	    !$experiment->SameExperiment($reservation)) {
	    fatal("PortControl: $nodeid is not reserved to $experiment");
	}
    }
    SWITCH: for ($command) {
	/enable/ && do {
	    $command = "-e";
	    last;
	};
	/disable/ && do {
	    $command = "-d";
	    last;
	};
	/auto/ && do {
	    $command = "-a";
	    last;
	};
	/10mbit/ && do {
	    $command = "-p 10";
	    last;
	};
	/100mbit/ && do {
	    $command = "-p 100";
	    last;
	};
	/1000mbit/ && do {
	    $command = "-p 1000";
	    last;
	};
	/full/ && do {
	    $command = "-u full";
	    last;
	};
	/half/ && do {
	    $command = "-u half";
	    last;
	};
	# 'default' case
	Fatal("PortControl: Unknown command $command");
    }
    #
    # Okay, ask snmpit to do the deed.
    #
    if ($debug) {
	print STDERR "Running 'snmpit $command @ports\n";
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
906
    system("$TB/bin/snmpit $debugopt $command @ports");
907
908
909
910
911
912
913
914
915
916
917
918
919
920
    if ($?) {
	# Yuck failed.
	fatal("snmpit $command @ports");
    }
    return 0;
}

sub fatal($)
{
    my ($msg) = @_;

    die("*** $0:\n",
	"    $msg\n");
}