APT_Aggregate.pm.in 8.35 KB
Newer Older
1 2
#!/usr/bin/perl -wT
#
3
# Copyright (c) 2007-2019 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 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
# 
# {{{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_Aggregate;

use strict;
use English;
use Data::Dumper;
use Carp;
use Exporter;
use vars qw(@ISA @EXPORT $AUTOLOAD);

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

# Must come after package declaration!
use emdb;
38
use emutil;
39
use EmulabFeatures;
40 41 42 43 44 45 46
use GeniHRN;
use overload ('""' => 'Stringify');

# Configure variables
my $TB		  = "@prefix@";
my $TBOPS         = "@TBOPSEMAIL@";
my $OURDOMAIN     = "@OURDOMAIN@";
47
my $MYURN	  = "urn:publicid:IDN+${OURDOMAIN}+authority+cm";
48

49 50 51
# Protos
sub STATUS($$;$);

52 53 54 55 56 57
#
# Lookup by uuid.
#
sub Lookup($$)
{
    my ($class, $token) = @_;
58
    my $safe_urn = DBQuoteSpecial($token);
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
    my $query_result;

    if (GeniHRN::IsValid($token)) {
	$query_result =
	    DBQueryWarn("select * from apt_aggregates where urn=$safe_urn");
    }
    else {
	return undef;
    }
    return undef
	if (!$query_result || !$query_result->numrows);

    my $self             = {};
    $self->{'AGGREGATE'} = $query_result->fetchrow_hashref();

74 75 76 77 78 79 80 81 82 83
    #
    # Look to see if there is a status row. Create it if it does not exist.
    #
    $query_result = 
	DBQueryWarn("select * from apt_aggregate_status where urn=$safe_urn");
    return undef
	if (!$query_result);

    if (!$query_result->numrows) {
	DBQueryWarn("replace into apt_aggregate_status set ".
84
		    " urn=$safe_urn, status='up'");
85 86 87 88 89 90 91 92
	$query_result = 
	    DBQueryWarn("select * from apt_aggregate_status ".
			"where urn=$safe_urn");
	return undef
	    if (!$query_result);
    }
    $self->{'STATUS'} = $query_result->fetchrow_hashref();

93 94 95 96 97 98 99 100 101 102 103 104 105 106
    bless($self, $class);
    return $self;
}

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.
    if (exists($self->{'AGGREGATE'}->{$name})) {
	return $self->{'AGGREGATE'}->{$name};
    }
107 108 109 110 111 112 113 114 115 116 117
    elsif (exists($self->{'STATUS'}->{$name})) {
	#
	# We always want to go to the DB for this.
	#
	if (scalar(@_) == 2) {
	    return STATUS($self, $name, $_[1]);
	}
	else {
	    return STATUS($self, $name);
	}
    }
118 119 120 121 122 123 124 125 126 127 128
    carp("No such slot '$name' field in class $type");
    return undef;
}

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

    $self->{'AGGREGATE'} = undef;
}

129 130 131 132 133 134 135
sub IsLocalCluster($)
{
    my ($self) = @_;

    return ($self->urn() eq $MYURN ? 1 : 0);
}

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
#
# Refresh a class instance by reloading from the DB.
#
sub Refresh($)
{
    my ($self) = @_;

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

    my $safe_urn = DBQuoteSpecial($self->urn());
    
    my $query_result =
	DBQueryWarn("select * from apt_aggregates where urn=$safe_urn");

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

    $self->{'AGGREGATE'} = $query_result->fetchrow_hashref();

156 157 158 159 160 161 162 163 164 165 166
    #
    # Grab new status row.
    #
    $query_result = 
	DBQueryWarn("select * from apt_aggregate_status where urn=$safe_urn");
    return -1
	if (!$query_result);

    if ($query_result->numrows) {
	$self->{'STATUS'} = $query_result->fetchrow_hashref();
    }
167 168 169 170 171 172 173 174 175 176 177 178 179 180
    return 0;
}

#
# Stringify for output.
#
sub Stringify($)
{
    my ($self) = @_;
    my $urn    = $self->urn();

    return "[APT_Aggregate: $urn]";
}

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
#
# We always want to go to the DB when updating the status table.
#
sub STATUS($$;$)
{
    my ($self, $name, $newval) = @_;
    my $urn = $self->urn();

    if (!defined($newval)) {
	return $self->{'STATUS'}->{$name};
    }
    my $set = "";

    #
    # Convenience.
    #
    if (($name eq "last_success" || $name eq "last_attempt") &&
	$newval =~ /^\d+$/) {
	$newval = TBDateStringLocal($newval);
    }
    $set = "${name}=" . DBQuoteSpecial($newval);

    DBQueryWarn("update apt_aggregate_status set $set ".
		"where urn='$urn'")
	or return undef;

    $self->{'STATUS'}->{$name} = $newval;
    return $self->{'STATUS'}->{$name};
}

211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
#
# Insert a status (change) event.
#
sub StatusEvent($$)
{
    my ($self, $event) = @_;
    my $urn    = $self->urn();

    DBQueryWarn("insert into apt_aggregate_events set ".
		"  urn='$urn', event='$event', stamp=now()")
	or return -1;

    return 0;
}

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
#
# Lookup all aggregates for a portal.
#
sub LookupForPortal($$)
{
    my ($class, $portal) = @_;
    my @result = ();

    if ($portal !~ /^[-\w]+$/) {
	return undef;
    }
    my $query_result =
	DBQueryWarn("select urn from apt_aggregates ".
		    "where disabled=0 and reservations=1 and ".
		    "      FIND_IN_SET('$portal', portals)");
    return ()
	if (!$query_result);
    return ()
	if (!$query_result->numrows);

    while (my ($aggregate_urn) = $query_result->fetchrow_array()) {
	my $aggregate = Lookup($class, $aggregate_urn);
	next
	    if (!defined($aggregate));
	push(@result, $aggregate);
    }
    return @result;
}

255 256 257
#
# Lookup using the short auth name (emulab.net).
#
258
sub LookupByDomain($$)
259
{
260
    my ($class, $domain) = @_;
261 262
    my @result = ();

263
    if ($domain !~ /^[-\w\.]+$/) {
264 265 266 267
	return undef;
    }
    my $query_result =
	DBQueryWarn("select urn from apt_aggregates ".
268
		    "where urn like 'urn:publicid:IDN+${domain}+%'");
269 270 271 272 273 274 275 276 277
    return undef
	if (!$query_result);
    return undef
	if (!$query_result->numrows);

    my ($aggregate_urn) = $query_result->fetchrow_array();
    return Lookup($class, $aggregate_urn);
}

Leigh Stoller's avatar
Leigh Stoller committed
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
sub LookupByNickname($$)
{
    my ($class, $nickname) = @_;

    if ($nickname !~ /^[-\w]+$/) {
	return undef;
    }
    my $query_result =
	DBQueryWarn("select urn from apt_aggregates ".
		    "where nickname='$nickname'");
    return undef
	if (!$query_result);
    return undef
	if (!$query_result->numrows);

    my ($aggregate_urn) = $query_result->fetchrow_array();
    return Lookup($class, $aggregate_urn);
}

297 298 299 300 301 302 303 304
#
# Check status of aggregate.
#
sub CheckStatus($$;$)
{
    my ($self, $perrmsg, $portalrpc) = @_;
    require APT_Geni;

Leigh Stoller's avatar
Leigh Stoller committed
305
    if ($self->disabled()) {
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
	$$perrmsg = "The " . $self->name() . " cluster ".
	    "is currently offline, please try again later.";
	return 1;
    }
    # Ping test. If we cannot get to the aggregate right now, bail.
    my $retval = APT_Geni::PingAggregate($self, $perrmsg, $portalrpc);
    if ($retval) {
	if ($retval < 0) {
	    $$perrmsg = "Internal error contacting the ".
		$self->name() . " cluster: " . $perrmsg;
	}
	else {
	    $$perrmsg = "The " . $self->name() . " cluster ".
		"is currently unreachable, please try again later.";
	}
	return 1;
    }
    return 0;
}

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
#
# Perform some updates ...
#
sub Update($$)
{
    my ($self, $argref) = @_;

    my $urn  = $self->urn();
    my @sets = ();

    foreach my $key (keys(%{$argref})) {
	my $val = $argref->{$key};

	# Treat NULL special.
	push (@sets, "${key}=" . ($val eq "NULL" ?
				  "NULL" : DBQuoteSpecial($val)));
    }

    my $query = "update apt_aggregates set " . join(",", @sets) .
	" where urn='$urn'";

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

    return Refresh($self);
}

353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
#
# Permission check to see if user/project is allowed to use the aggregate.
#
sub CanInstantiate($$$$)
{
    my ($self, $portal, $user, $project) = @_;

    # Admins always allowed to use.
    return 1
	if ($user->IsAdmin());

    # Admin only cluster
    return 0
	if ($self->adminonly() && !($user->IsAdmin() || $user->stud()));

    if (defined($self->canuse_feature())) {
	my $feature = $portal . "-" . $self->canuse_feature();

	return 1
	    if (EmulabFeatures->FeatureEnabled($feature, $user));

	my $membership = $project->LookupUser($user);
	return 0
	    if (! (defined($membership) && $membership->IsApproved()));

	return 1
	    if ($project->approved() && !$project->disabled() &&
		EmulabFeatures->FeatureEnabled($feature, undef, $project));
Leigh Stoller's avatar
Leigh Stoller committed
381 382

	return 0;
383
    }
Leigh Stoller's avatar
Leigh Stoller committed
384
    return 1;
385 386
}

387 388
# _Always_ make sure that this 1 is at the end of the file...
1;