APT_Profile.pm.in 29.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2014 University of Utah and the Flux Group.
# 
# {{{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/>.
# 
# }}}
#
package APT_Profile;

26
27
28
29
30
31
32
33
34
35
36
#
# Note about permissions bits.
#
# listed - The profile will be listed on the home page for anyone to see/use.
# public - Anyone can instantiate the profile, regardless of its listed bit
#          Say, if you send a URL to someone. 
# shared - Shared with logged in users. If not listed, then the default is
#          that only project members can see/use the profile, unless the public
#          is set (but they need a url). Shared says any logged in user can
#          see and use the profile.  

37
38
39
40
41
42
43
44
45
46
use strict;
use Carp;
use Exporter;
use vars qw(@ISA @EXPORT $AUTOLOAD);

@ISA    = "Exporter";
@EXPORT = qw ( );

# Must come after package declaration!
use EmulabConstants;
47
use emutil;
48
use emdb;
49
use APT_Dataset;
50
use GeniXML;
51
use GeniHRN;
52
53
54
55
56
57
58
59
60
use libtestbed;
use English;
use Data::Dumper;
use overload ('""' => 'Stringify');

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

61
62
my $debug = 0;

63
64
65
66
67
68
69
70
# Concat id/vers.
sub versid($)
{
    my ($self) = @_;

    return $self->profileid() . ":" . $self->version();
}

71
72
73
74
75
76
77
78
79
80
sub BlessRow($$)
{
    my ($class, $row) = @_;
    
    my $self           = {};
    $self->{'DBROW'}   = $row;

    bless($self, $class);
    return $self;
}
81
82

#
83
# Lookup. 
84
#
85
sub Lookup($$;$$)
86
{
87
    my ($class, $arg1, $arg2, $arg3) = @_;
88
89

    #
90
91
    # A single arg is either an index or "pid,profile[:version]" or
    # "pid/profile[:version]" string.
92
93
94
    #
    if (!defined($arg2)) {
	if ($arg1 =~ /^(\d*)$/) {
95
	    my $result =
96
97
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    " from apt_profiles as i ".
98
99
100
101
102
103
104
105
			    "left join apt_profile_versions as v on ".
			    "     v.profileid=i.profileid and ".
			    "     v.version=i.version ".
			    "where i.profileid='$arg1'");
	    return undef
		if (! $result || !$result->numrows);

	    return BlessRow($class, $result->fetchrow_hashref());
106
107
	}
	elsif ($arg1 =~ /^([-\w]*),([-\w\.\+]*)$/ ||
108
109
		$arg1 =~ /^([-\w]*)\/([-\w\.\+]*)$/) {
	    my $result =
110
111
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    " from apt_profiles as i ".
112
113
114
115
116
117
118
119
120
121
122
123
			    "left join apt_profile_versions as v on ".
			    "     v.profileid=i.profileid and ".
			    "     v.version=i.version ".
			    "where i.pid='$1' and i.name='$2'");
	    return undef
		if (! $result || !$result->numrows);

	    return BlessRow($class, $result->fetchrow_hashref());
	}
	elsif ($arg1 =~ /^([-\w]*),([-\w\.\+]*):(\d*)$/ ||
		$arg1 =~ /^([-\w]*)\/([-\w\.\+]*):(\d*)$/) {
	    my $result =
124
125
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    " from apt_profiles as i ".
126
127
128
129
130
131
132
133
			    "left join apt_profile_versions as v on ".
			    "     v.profileid=i.profileid ".
			    "where i.pid='$1' and i.name='$2' and ".
			    "      v.version='$3' and v.deleted is null");
	    return undef
		if (!$result || !$result->numrows);

	    return BlessRow($class, $result->fetchrow_hashref())
134
135
	}
	elsif ($arg1 =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/) {
136
137
138
139
140
	    #
	    # First look to see if the uuid is for the profile itself,
	    # which means current version. Otherwise look for a
	    # version with the uuid.
	    #
141
	    my $result =
142
143
144
145
146
147
148
149
150
151
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    "  from apt_profiles as i ".
			    "left join apt_profile_versions as v on ".
			    "     v.profileid=i.profileid and ".
			    "     v.version=i.version ".
			    "where i.uuid='$arg1'");
	    return undef
		if (! $result);
	    return BlessRow($class, $result->fetchrow_hashref())
		if ($result->numrows);
152

153
154
155
156
157
158
159
	    $result =
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    "  from apt_profile_versions as v ".
			    "left join apt_profiles as i on ".
			    "     v.profileid=i.profileid ".
			    "where v.uuid='$arg1' and ".
			    "      v.deleted is null");
160
161
	    return undef
		if (! $result || !$result->numrows);
162
	    return BlessRow($class, $result->fetchrow_hashref());
163
164
165
	}
	return undef;
    }
166
167
168
    elsif (!defined($arg3)) {
	if ($arg1 =~ /^\d+$/ && $arg2 =~ /^\d+$/) {
	    my $result =
169
170
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    "  from apt_profiles as i ".
171
172
173
174
175
176
			    "left join apt_profile_versions as v on ".
			    "     v.profileid=i.profileid ".
			    "where i.profileid='$arg1' and v.version='$arg2' ".
			    " and  v.deleted is null");
	    return undef
		if (! $result || !$result->numrows);
177

178
179
180
181
	    return BlessRow($class, $result->fetchrow_hashref());
	}
	elsif ($arg1 =~ /^[-\w]*$/ && $arg2 =~ /^([-\w\.\+]*):(\d+)$/) {
	    my $result =
182
183
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    " from apt_profiles as i ".
184
185
186
187
188
189
			    "left join apt_profile_versions as v on ".
			    "     v.profileid=i.profileid ".
			    "where i.pid='$arg1' and i.name='$1' and ".
			    "      v.version='$2'");
	    return undef
		if (! $result || !$result->numrows);
190

191
192
193
194
	    return BlessRow($class, $result->fetchrow_hashref());
	}
	elsif ($arg1 =~ /^[-\w]*$/ && $arg2 =~ /^[-\w\.\+]*$/) {
	    my $result =
195
196
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    "  from apt_profiles as i ".
197
198
199
200
201
202
			    "left join apt_profile_versions as v on ".
			    "     v.profileid=i.profileid and ".
			    "     v.version=i.version ".
			    "where i.pid='$arg1' and i.name='$arg2'");
	    return undef
		if (! $result || !$result->numrows);
203

204
205
206
	    return BlessRow($class, $result->fetchrow_hashref());
	}
	return undef;
207
    }
208
209
210
211
    else {
	if ($arg1 =~ /^[-\w]*$/ &&
	    $arg2 =~ /^[-\w\.\+]*$/ && $arg3 =~ /^\d+$/) {
	    my $result =
212
213
		DBQueryWarn("select i.*,v.*,i.uuid as profile_uuid ".
			    "  from apt_profiles as i ".
214
215
216
217
218
219
			    "left join apt_profile_versions as v on ".
			    "     v.profileid=i.profileid ".
			    "where i.pid='$arg1' and i.name='$arg2' and ".
			    "      v.version='$arg3' and v.deleted is null");
	    return undef
		if (!$result || !$result->numrows);
220

221
222
223
224
	    return BlessRow($class, $result->fetchrow_hashref());
	}
    }
    return undef;
225
226
227
228
229
230
231
232
233
}

AUTOLOAD {
    my $self  = $_[0];
    my $type  = ref($self) or croak "$self is not an object";
    my $name  = $AUTOLOAD;
    $name =~ s/.*://;   # strip fully-qualified portion

    # A DB row proxy method call.
234
235
    if (exists($self->{'DBROW'}->{$name})) {
	return $self->{'DBROW'}->{$name};
236
237
238
239
240
241
242
243
244
    }
    carp("No such slot '$name' field in class $type");
    return undef;
}

# Break circular reference someplace to avoid exit errors.
sub DESTROY {
    my $self = shift;

245
    $self->{'DBROW'} = undef;
246
247
248
249
250
251
252
253
254
255
256
257
}

#
# Refresh a class instance by reloading from the DB.
#
sub Refresh($)
{
    my ($self) = @_;

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

258
259
    my $profileid = $self->profileid();
    my $version   = $self->version();
260
261
    
    my $query_result =
262
263
	DBQueryWarn("select * from apt_profile_versions ".
		    "where profileid='$profileid' and version='$version'");
264
265
266
267

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

268
    $self->{'DBROW'} = $query_result->fetchrow_hashref();
269
270
271
272
273
274
275

    return 0;
}

#
# Create a profile
#
276
sub Create($$$$$$)
277
{
278
    my ($class, $parent, $project, $creator, $argref, $usrerr_ref) = @_;
279
280
281
282
283
284
285
286
287
288

    my $name    = DBQuoteSpecial($argref->{'name'});
    my $pid     = $project->pid();
    my $pid_idx = $project->pid_idx();
    my $uid     = $creator->uid();
    my $uid_idx = $creator->uid_idx();

    #
    # The pid/imageid has to be unique, so lock the table for the check/insert.
    #
289
290
    DBQueryWarn("lock tables apt_profiles write, apt_profile_versions write, ".
		"            emulab_indicies write")
291
292
293
294
295
296
297
298
299
300
301
302
	or return undef;

    my $query_result =
	DBQueryWarn("select name from apt_profiles ".
		    "where pid_idx='$pid_idx' and name=$name");

    if ($query_result->numrows) {
	DBQueryWarn("unlock tables");
	$$usrerr_ref = "Profile already exists in project!";
	return undef;
    }
    
303
    my $profileid = TBGetUniqueIndex("next_profile", undef, 1);
304
305
    my $puuid     = NewUUID();
    my $vuuid     = NewUUID();
306
307
308
    my $rspec     = DBQuoteSpecial($argref->{'rspec'});
    my $cquery    = "";
    my $vquery    = "";
309

310
311
312
313
314
315
316
317
    #
    # This part is common between the two tables.
    #
    $cquery .= "name=$name,profileid='$profileid'";
    $cquery .= ",pid='$pid',pid_idx='$pid_idx'";

    # And the versions table.
    $vquery  = $cquery;
318
    $vquery .= ",uuid='$vuuid',created=now()";
319
320
321
322
323
324
325
326
    $vquery .= ",creator='$uid',creator_idx='$uid_idx'";
    $vquery .= ",rspec=$rspec";

    # Set derived from pointer.
    if (defined($parent)) {
	$vquery .= ",parent_profileid=" . $parent->profileid();
	$vquery .= ",parent_version=" . $parent->version();
    }
327
328
    if (exists($argref->{'script'}) && $argref->{'script'} ne "") {
	$vquery .= ",script=" . DBQuoteSpecial($argref->{'script'});
329
330
331
	if (exists($argref->{'paramdefs'}) && $argref->{'paramdefs'} ne "") {
	    $vquery .= ",paramdefs=" . DBQuoteSpecial($argref->{'paramdefs'});
	}
332
    }
333

334
    # Back to the main table.
335
    $cquery .= ",uuid='$puuid'";
336
    $cquery .= ",public=1"
337
	if (exists($argref->{'public'}) && $argref->{'public'});
338
    $cquery .= ",listed=1"
339
	if (exists($argref->{'listed'}) && $argref->{'listed'});
340
    $cquery .= ",shared=1"
341
	if (exists($argref->{'shared'}) && $argref->{'shared'});
342
343
    $cquery .= ",topdog=1"
	if (exists($argref->{'topdog'}) && $argref->{'topdog'});
344

345
346
347
348
349
350
351
352
353
    # Create the main entry:
    if (! DBQueryWarn("insert into apt_profiles set $cquery")) {
	DBQueryWarn("unlock tables");
	tberror("Error inserting new apt_profiles record!");
	return undef;
    }
    # And the versions entry.
    if (! DBQueryWarn("insert into apt_profile_versions set $vquery")) {
	DBQueryWarn("delete from apt_profiles where profileid='$profileid'");
354
	DBQueryWarn("unlock tables");
355
	tberror("Error inserting new apt_profile_versions record!");
356
357
358
359
360
361
	return undef;
    }
    DBQueryWarn("unlock tables");
    return Lookup($class, $pid, $argref->{'name'});
}

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
#
# Create a new version of a profile.
#
sub NewVersion($$)
{
    my ($self, $creator) = @_;
    my $profileid   = $self->profileid();
    my $version     = $self->version();
    my $uid         = $creator->uid();
    my $uid_idx     = $creator->uid_idx();

    DBQueryWarn("lock tables apt_profiles write, ".
		"            apt_profile_versions write, ".
		"            apt_profile_versions as v write")
	or return undef;

    #
    # This might not be the head version, so have to find the
    # current max.
    #
    my $query_result =
	DBQueryWarn("select max(version) from apt_profile_versions ".
		    "where profileid='$profileid'");
    goto bad
	if (!$query_result || !$query_result->numrows);

    my ($newvers) = $query_result->fetchrow_array() + 1;

    #
    # Insert new version. The "current" version becomes this one.
    #
    goto bad
	if (! DBQueryWarn("insert into apt_profile_versions ".
			  "  (name,profileid,version,pid,pid_idx, ".
			  "   creator,creator_idx,created,uuid, ".
397
398
			  "   parent_profileid,parent_version,rspec, ".
			  "   script,paramdefs) ".
399
400
			  "select name,profileid,'$newvers',pid,pid_idx, ".
			  "  '$uid','$uid_idx',now(),uuid(),parent_profileid, ".
401
			  "  '$version',rspec,script,paramdefs ".
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
			  "from apt_profile_versions as v ".
			  "where v.profileid='$profileid' and ".
			  "      v.version='$version'"));
    if (! DBQueryWarn("update apt_profiles set version=$newvers ".
		      "where profileid='$profileid'")) {
	DBQueryWarn("delete from apt_profile_versions ".
		    "where profileid='$profileid' and version='$version'");
	goto bad;
    }
    DBQueryWarn("unlock tables");
    return APT_Profile->Lookup($profileid, $newvers);
  bad:
    DBQueryWarn("unlock tables");
    return undef;
}

Leigh B Stoller's avatar
Leigh B Stoller committed
418
419
420
421
422
423
424
425
426
#
# Stringify for output.
#
sub Stringify($)
{
    my ($self) = @_;
    
    my $pid       = $self->pid();
    my $name      = $self->name();
427
    my $version   = $self->version();
Leigh B Stoller's avatar
Leigh B Stoller committed
428

429
    return "[Profile: $pid,$name:$version]";
Leigh B Stoller's avatar
Leigh B Stoller committed
430
431
}

432
433
434
#
# Perform some updates ...
#
435
sub UpdateVersion($$)
436
437
438
439
440
441
442
{
    my ($self, $argref) = @_;

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

443
444
    my $profileid = $self->profileid();
    my $version   = $self->version();
445

446
    my $query = "update apt_profile_versions set ".
447
448
	join(",", map("$_=" . DBQuoteSpecial($argref->{$_}), keys(%{$argref})));

449
    $query .= " where profileid='$profileid' and version='$version'";
450
451
452
453
454
455
456

    return -1
	if (! DBQueryWarn($query));

    return Refresh($self);
}

457
458
459
460
#
# Perform some updates ...
#
sub UpdateMetaData($$)
461
{
462
    my ($self, $argref) = @_;
463
464
465
466
467

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

468
    my $profileid = $self->profileid();
469

470
471
472
473
    #
    # This is the only metadata we can update.
    #
    my %mods = ();
474
    foreach my $key ("listed", "shared", "public", "topdog") {
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
	if (exists($argref->{$key})) {
	    $mods{$key} = $argref->{$key};
	}
    }
    my $query = "update apt_profiles set ".
	join(",", map("$_=" . DBQuoteSpecial($mods{$_}), keys(%mods)));

    $query .= " where profileid='$profileid'";

    return -1
	if (! DBQueryWarn($query));

    return Refresh($self);
}

sub Delete($$)
{
    my ($self, $purge) = @_;
    my $profileid = $self->profileid();
494

495
496
497
498
499
500
501
502
503
504
505
    $purge = 0
	if (!defined($purge));
    
    DBQueryWarn("lock tables apt_profiles write, apt_profile_versions write")
	or return -1;

    DBQueryWarn("delete from apt_profiles where profileid='$profileid'")
	or goto bad;
    
    if ($purge) {
	goto bad
506
507
	    if (! DBQueryWarn("delete from apt_profile_versions ".
			      "where profileid='$profileid'"));
508
509
510
511
512
513
514
515
516
    }
    else {
	# Set deleted on all of the versions.
	DBQueryWarn("update apt_profile_versions set ".
		    "    deleted=now(),locked=null,locker_pid=0 ".
		    "where profileid='$profileid'")
	    or goto bad;
    }
    DBQueryWarn("unlock tables");
517
    return 0;
518
519
520
521

  bad:
    DBQueryWarn("unlock tables");
    return -1;
522
523
}

524
#
525
526
# Delete a profile version, only allow it if it is the highest
# numbered version.
527
#
528
sub DeleteVersion($)
529
530
531
{
    my ($self) = @_;

532
533
    DBQueryWarn("lock tables apt_profile_versions write, apt_profiles write")
	or return -1;
534

535
536
    my $profileid = $self->profileid();
    my $version   = $self->version();
537

538
539
540
541
542
543
544
545
    #
    # Only the "head" version can be deleted
    #
    my $query_result =
	DBQueryWarn("select max(version) from apt_profile_versions ".
		    "where profileid='$profileid'");
    goto bad
	if (!$query_result || !$query_result->numrows);
546

547
548
549
550
551
552
553
554
555
556
557
558
559
560
    my ($head) = $query_result->fetchrow_array();
    if ($head != $version) {
	print STDERR "Profile::DeleteVersion: not the head version of $self\n";
	goto bad;
    }
    goto bad
	if (!DBQueryWarn("delete from apt_profile_versions ".
			 "where profileid='$profileid' and ".
			 "      version='$version'"));
    goto bad
	if (!DBQueryWarn("update apt_profiles set version=version-1 ".
			 "where profileid='$profileid' and ".
			 "      version='$version'"));
    DBQueryWarn("unlock tables");
561
    return 0;
562
563
564
  bad:
    DBQueryWarn("unlock tables");
    return -1;
565
566
}

567
568
569
570
#
# Condomize a profile rspec by inserting the necessary firewall section
# to each of the nodes.
#
571
sub CheckFirewall($$)
572
{
573
    my ($self, $condomize) = @_;
574
575
576
577
578
579
580
581
582
583
584

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

    my $rspec = GeniXML::Parse($self->rspec());
    if (! defined($rspec)) {
	print STDERR "Could not parse rspec\n";
	return undef;
    }
    foreach my $ref (GeniXML::FindNodes("n:node", $rspec)->get_nodelist()) {
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
	my @routable_control_ip =
	    GeniXML::FindNodesNS("n:routable_control_ip",
				 $ref,
				 $GeniXML::EMULAB_NS)->get_nodelist();
	my $virtualization_type = GeniXML::GetVirtualizationSubtype($ref);
	#
	# If a XEN container but not a routable IP, then use the basic
	# rules instead of closed, so that ssh is allowed in on the
	# alternate port. That is the only real difference between basic
	# and closed.
	#
	my $style = "closed";
	if (defined($virtualization_type) &&
	    $virtualization_type eq "emulab-xen" && !@routable_control_ip) {
	    $style = "basic";
	}
	
602
603
604
605
606
607
608
	if ($condomize) {
	    #
	    # No settings is easy; wrap it tight.
	    #
	    if (!GeniXML::HasFirewallSettings($ref)) {
		my $firewall = GeniXML::AddElement("firewall", $ref,
						   $GeniXML::EMULAB_NS);
609
		GeniXML::SetText("style", $firewall, $style);
610
611
612
613
614
615
616
617
618
		next;
	    }
	    #
	    # Make sure the existing section has a reasonable setting.
	    #
	    my $settings = GeniXML::FindNodesNS("n:firewall", $ref,
						$GeniXML::EMULAB_NS)->pop();
	    my $style = GeniXML::GetText("style", $settings);
	    if (!defined($style) || $style ne "basic" || $style ne "closed") {
619
		GeniXML::SetText("style", $settings, $style);
620
	    }
621
622
	}
	#
623
624
	# Quick pass over the exceptions to see if we need to substitute
	# the callers IP address.
625
	#
626
627
628
629
630
631
	foreach my $exception (GeniXML::FindNodesNS("n:firewall/n:exception",
				$ref, $GeniXML::EMULAB_NS)->get_nodelist()) {
	    my $ip = GeniXML::GetText("ip", $exception);
	    if (defined($ip) && $ip eq "myip" && exists($ENV{'REMOTE_ADDR'})) {
		GeniXML::SetText("ip", $exception, $ENV{'REMOTE_ADDR'});
	    }
632
633
634
635
	}
    }
    return GeniXML::Serialize($rspec);
}
Leigh B Stoller's avatar
Leigh B Stoller committed
636
637
638
639
640
641
642

#
# Lock and Unlock
#
sub Lock($)
{
    my ($self) = @_;
643
    my $profileid = $self->profileid();
Leigh B Stoller's avatar
Leigh B Stoller committed
644
645
646
647
648

    return -1
	if (!DBQueryWarn("lock tables apt_profiles write"));

    my $query_result =
649
650
	DBQueryWarn("update apt_profiles set locked=now(),locker_pid='$PID' " .
		    "where profileid='$profileid' and locked is null");
Leigh B Stoller's avatar
Leigh B Stoller committed
651
652
653
654
655
656
657

    if (! $query_result ||
	$query_result->numrows == 0) {
	DBQueryWarn("unlock tables");
	return -1;
    }
    DBQueryWarn("unlock tables");
658
    $self->{'DBROW'}->{'locked'} = time();
Leigh B Stoller's avatar
Leigh B Stoller committed
659
660
661
662
663
664
    return 0;
}

sub Unlock($)
{
    my ($self) = @_;
665
    my $profileid = $self->profileid();
Leigh B Stoller's avatar
Leigh B Stoller committed
666
667

    return -1
668
669
670
671
672
673
674
675
676
677
678
679
680
	if (! DBQueryWarn("update apt_profiles set ".
			  "   locked=null,locker_pid=0 ".
			  "where profileid='$profileid'"));
    
    $self->{'DBROW'}->{'locked'} = 0;
    return 0;
}

#
# Update the disk image inside a single node profile. 
#
sub UpdateDiskImage($$)
{
681
    my ($self, $image_url) = @_;
682
683
684
685
686
687
    my $rspec = GeniXML::Parse($self->rspec());
    if (! defined($rspec)) {
	print STDERR "UpdateDiskImage: Could not parse rspec\n";
	return -1;
    }
    my ($node) = GeniXML::FindNodes("n:node", $rspec)->get_nodelist();
688
    GeniXML::SetDiskImage($node, $image_url);
689
690
691
692
693
694
695
    if ($self->UpdateVersion({"rspec" => GeniXML::Serialize($rspec)})) {
	print STDERR "UpdateDiskImage: Could not update rspec\n";
	return -1;
    }
    return 0;
}

696
697
698
699
700
701
702
#
# We need to convert from using URNs to using URLs for disk images,
# since we want to support choosing the backend. This list is the
# list as of the conversion, at the APT. Before we instantiate, look
# at the rspec and update the URNs to URLs based on this list.
#
my %APTImages = (
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
728
729
730
731
732
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:demo-profile' => 'https://www.apt.emulab.net/image_metadata.php?uuid=39383c39-7b2f-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:docker' => 'https://www.apt.emulab.net/image_metadata.php?uuid=5ae53ff8-7b2f-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:docker-running-env' => 'https://www.apt.emulab.net/image_metadata.php?uuid=a1317423-7b2f-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:docker-running-env-02' => 'https://www.apt.emulab.net/image_metadata.php?uuid=f30e8657-7b2f-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:Docker1' => 'https://www.apt.emulab.net/image_metadata.php?uuid=31d9f5c1-7b30-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:ESA' => 'https://www.apt.emulab.net/image_metadata.php?uuid=72d94622-7b35-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:firstclone' => 'https://www.apt.emulab.net/image_metadata.php?uuid=d49e30a8-7b31-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:FPTaylorVM' => 'https://www.apt.emulab.net/image_metadata.php?uuid=f2e99dbe-7b31-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:FrequentDirection' => 'https://www.apt.emulab.net/image_metadata.php?uuid=1c6b4e98-7b32-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:geni-lib' => 'https://www.apt.emulab.net/image_metadata.php?uuid=441fc279-7b32-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:gimiovsomf6' => 'https://www.apt.emulab.net/image_metadata.php?uuid=626184be-7b32-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:hadoop' => 'https://www.apt.emulab.net/image_metadata.php?uuid=ffa8f859-3524-11e4-8944-81966d62745f',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:hg' => 'https://www.apt.emulab.net/image_metadata.php?uuid=91dcd7d5-7b32-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:imageusage' => 'https://www.apt.emulab.net/image_metadata.php?uuid=adc61bad-7b32-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:Iperf' => 'https://www.apt.emulab.net/image_metadata.php?uuid=d0c8aba4-7b32-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:labwikiXORP' => 'https://www.apt.emulab.net/image_metadata.php?uuid=eee5f68d-7b32-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:myapt' => 'https://www.apt.emulab.net/image_metadata.php?uuid=13baa069-7b33-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:myclone' => 'https://www.apt.emulab.net/image_metadata.php?uuid=301d4978-7b33-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:myimage' => 'https://www.apt.emulab.net/image_metadata.php?uuid=03a342be-7b2c-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:myvm' => 'https://www.apt.emulab.net/image_metadata.php?uuid=6334cf1c-7a59-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:myvm2' => 'https://www.apt.emulab.net/image_metadata.php?uuid=aa8b3638-7a79-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:ricci-cav-2014' => 'https://www.apt.emulab.net/image_metadata.php?uuid=b3d6f6f7-7b35-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:rob-profile' => 'https://www.apt.emulab.net/image_metadata.php?uuid=31278fc7-7b34-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:robtestimage' => 'https://www.apt.emulab.net/image_metadata.php?uuid=4d43d6bb-7b34-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:singlenodelime' => 'https://www.apt.emulab.net/image_metadata.php?uuid=60905cf9-7b34-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:singlenodelime-2' => 'https://www.apt.emulab.net/image_metadata.php?uuid=8db24268-7b34-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:singlevmlime' => 'https://www.apt.emulab.net/image_metadata.php?uuid=b192a572-7b34-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:smack-cav2014-test' => 'https://www.apt.emulab.net/image_metadata.php?uuid=a4594b69-7b36-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:ubuntu1204maas' => 'https://www.apt.emulab.net/image_metadata.php?uuid=e532f26d-7b36-11e4-9439-db9edc46fe2c',
    'urn:publicid:IDN+utahddc.geniracks.net+image+emulab-net:Ubuntu1404' => 'https://www.apt.emulab.net/image_metadata.php?uuid=0838c611-7b37-11e4-9439-db9edc46fe2c'
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
);

sub ConvertDiskImages($)
{
    my ($self) = @_;
    my $modified = 0;

    my $rspec = GeniXML::Parse($self->rspec());
    if (! defined($rspec)) {
	print STDERR "Could not parse rspec\n";
	return -1;
    }
    foreach my $ref (GeniXML::FindNodes("n:node", $rspec)->get_nodelist()) {
	my $diskref = GeniXML::GetDiskImage($ref);
	next
	    if (!defined($diskref));
	my $imageurn = GeniXML::GetText("name", $diskref);
	next
	    if (!defined($imageurn));
	if (exists($APTImages{$imageurn})) {
	    GeniXML::SetDiskImage($ref, $APTImages{$imageurn});
	    $modified = 1;
	}
    }
    if ($modified) {
	$self->{'DBROW'}->{'rspec'} = GeniXML::Serialize($rspec);

	my $profileid  = $self->profileid();
	my $version    = $self->version();
	my $safe_rspec = DBQuoteSpecial($self->rspec());
	DBQueryWarn("update apt_profile_versions set rspec=$safe_rspec ".
		    "where profileid='$profileid' and version='$version'")
	    if (1);
    }
    return 0;
}

# Total nonsense, to be thrown away.
771
sub CheckNodeConstraints($$$)
772
773
774
775
776
777
778
779
780
781
782
783
{
    my ($self, $iscloudlab, $pmsg) = @_;
    my $cloudwww = "www.utah.cloudlab.us";
    require URI;

    my $rspec = GeniXML::Parse($self->rspec());
    if (! defined($rspec)) {
	print STDERR "Could not parse rspec\n";
	return -1;
    }
    foreach my $ref (GeniXML::FindNodes("n:node", $rspec)->get_nodelist()) {
	my $client_id = GetVirtualId($ref);
784
785
786
787
788
789
790
791
	my $virtualization_type = GeniXML::GetVirtualizationSubtype($ref);

	if (defined($virtualization_type) && $iscloudlab &&
	    $virtualization_type eq "emulab-xen") {
	    $$pmsg = "Node '$client_id' is a XEN VM, which is ".
		"not supported on the Cloudlab cluster";
	    return -1;
	}
792
793
794
795
	my $diskref   = GeniXML::GetDiskImage($ref);
	next
	    if (!defined($diskref));
	my $image_url = GeniXML::GetText("url", $diskref);
796
	my $image_urn = GeniXML::GetText("name", $diskref);
797
	next
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
	    if (!(defined($image_url) || defined($image_urn)));

	if (defined($image_urn)) {
	    if ($iscloudlab && $image_urn !~ /ARM/) {
		$$pmsg = "Node '$client_id' disk_image will not run ".
		    "on the Cloudlab cluster";
		return -1;
	    }
	    elsif (!$iscloudlab && $image_urn =~ /ARM/) {
		$$pmsg = "Node '$client_id' disk_image will only run ".
		    "on the Cloudlab cluster";
		return -1;
	    }
	}
	next if
	    (!defined($image_url));
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840

	# Get the hostname for the image URL.
	my $uri = URI->new($image_url);
	if (!defined($uri)) {
	    print STDERR "Could not parse $image_url\n";
	    return -1;
	}
	my $image_host = $uri->host();

	if ($iscloudlab) {
	    if ($image_host ne $cloudwww) {
		$$pmsg = "The disk image specified for node '$client_id' ".
		    "will not run on the Cloudlab cluster";
		return -1;
	    }
	}
	else {
	    if ($image_host eq $cloudwww) {
		$$pmsg = "The disk image specified for node '$client_id' ".
		    "will not run on cluster you selected";
		return -1;
	    }
	}
    }
    return 0;
}

841
842
843
844
845
#
# Check blockstores. 
#
sub CheckDatasets($$$)
{
846
847
848
    my ($xml, $ppid, $pmsg) = @_;
    my $pid = $ppid;
    
849
850
851
852
853
854
855
856
857
858
    my $rspec = GeniXML::Parse($xml);
    if (! defined($rspec)) {
	print STDERR "CheckDatasets: Could not parse rspec\n";
	return -1;
    }
    foreach my $ref (GeniXML::FindNodes("n:node", $rspec)->get_nodelist()) {
	foreach my $blockref (GeniXML::FindNodesNS("n:blockstore",
						   $ref,
				   $GeniXML::EMULAB_NS)->get_nodelist()) {
	    my $leaseurn = GeniXML::GetText("persistent", $blockref);
859
860
861
862
863
864
865
866
867

	    #
	    # We only care about datasets here, we let the backend
	    # do the error checking on ephemeral blockstores.
	    #
	    next
		if (!defined($leaseurn));
	    
	    if (!GeniHRN::IsValid($leaseurn)) {
868
869
870
		$$pmsg = "Persistent dataset name is not a valid URN";
		return 1;
	    }
871
872
873
874
875
876
877
878
879
880
	    my ($authority, $type, $id) = GeniHRN::Parse($leaseurn);
	    #
	    # Separate project from name; this is how the rspec specifies
	    # the dataset they want, since it might be in another project
	    #
	    if ($id =~ /^([-\w]+)\/\/(.+)$/) {
		$pid = $1;
		$id  = $2;
	    }
	    
881
882
883
884
885
886
887
888
889
	    #
	    # Not all backends have blockstore support.
	    #
	    if (!APT_Dataset::ValidBlockstoreBackend($authority)) {
		$$pmsg = "Persistent dataset is not on a valid aggregate";
		return 1;
	    }
	    my $dataset = APT_Dataset->Lookup("$pid/$id");
	    if (!defined($dataset)) {
890
891
892
893
894
		$dataset = APT_Dataset->LookupByRemoteURN($leaseurn);
		if (!defined($dataset)) {
		    $$pmsg = "Persistent dataset '$pid/$id' does not exist";
		    return 1;
		}
895
	    }
896
897
898
899
900
901
902
903

	    #
	    # Dataset must already exists on the aggregate. But we have to
	    # make these checks again at instantiation, since the dataset
	    # might be gone, or it might have different permissions
	    # settings.
	    #
	    my ($d_authority) = GeniHRN::Parse($dataset->aggregate_urn());
904
905
	    my ($domain,$subauth) = split(":", $authority);
	    if ($domain ne $d_authority) {
906
907
908
		$$pmsg = "Persistent dataset '$pid/$id' in not on $authority";
		return 1;
	    }
909
910

	    #
911
	    # XXX Need basic frontend permission checks?
912
	    #
913
914
915
916
917
	}
    }
    return 0;
}

918
919
920
sub IsHead($)
{
    my ($self) = @_;
Leigh B Stoller's avatar
Leigh B Stoller committed
921

922
    my $profileid = $self->profileid();
Leigh B Stoller's avatar
Leigh B Stoller committed
923

924
925
926
    my $query_result =
	DBQueryWarn("select max(version) from apt_profile_versions ".
		    "where profileid='$profileid'");
Leigh B Stoller's avatar
Leigh B Stoller committed
927
    return -1
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
	if (!$query_result || !$query_result->numrows);

    my ($head) = $query_result->fetchrow_array();
    return ($head == $self->version() ? 1 : 0);
}

#
# Publish a profile. Not sure what this really means yet.
#
sub Publish($)
{
    my ($self) = @_;
    my $profileid = $self->profileid();
    my $version   = $self->version();

    return -1
	if (! DBQueryWarn("update apt_profile_versions set published=now() ".
			  "where profileid='$profileid' and ".
			  "      version='$version'"));
Leigh B Stoller's avatar
Leigh B Stoller committed
947
    
948
    $self->{'DBROW'}->{'published'} = time();
Leigh B Stoller's avatar
Leigh B Stoller committed
949
    return 0;
950

951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
}

#
# Manage URL
#
sub AdminURL($)
{
    my ($self) = @_;
    my $uuid   = $self->uuid();
    
    require Project;
    
    my $project = Project->Lookup($self->pid_idx());
    return undef
	if (!defined($project));
    
    my $wwwbase = $project->wwwBase();
    $wwwbase .= "/apt"
	if ($project->Brand()->isEmulab());

    return $wwwbase . "/manage_profile.php?uuid=$uuid";
}
Leigh B Stoller's avatar
Leigh B Stoller committed
973

974
975
# _Always_ make sure that this 1 is at the end of the file...
1;