#!/usr/bin/perl -wT
#
# Copyright (c) 2005-2013 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 .
#
# }}}
#
package Node;
use strict;
use Exporter;
use SelfLoader ();
use vars qw(@ISA @EXPORT $AUTOLOAD @EXPORT_OK);
@ISA = qw(Exporter SelfLoader);
@EXPORT = qw();
# Configure variables
use vars qw($TB $BOSSNODE $WOL $OSSELECT $ISUTAH $CONTROL_NETMASK
$TBOPS $JAILIPMASK);
$TB = "@prefix@";
$BOSSNODE = "@BOSSNODE@";
$TBOPS = "@TBOPSEMAIL@";
$WOL = "$TB/sbin/whol";
$OSSELECT = "$TB/bin/os_select";
# XXX stinky hack detection
$ISUTAH = @TBMAINSITE@;
# Need this for jail ip assignment.
$CONTROL_NETMASK = "@CONTROL_NETMASK@";
$JAILIPMASK = "@JAILIPMASK@";
use libdb;
use libtestbed;
use emutil;
use event;
use English;
use Socket;
use Data::Dumper;
use overload ('""' => 'Stringify');
use vars qw($NODEROLE_TESTNODE $MFS_INITIAL $STATE_INITIAL
%nodes @cleantables %carped);
# Exported defs
$NODEROLE_TESTNODE = 'testnode';
# Why, why, why?
@EXPORT_OK = qw($NODEROLE_TESTNODE);
# Cache of instances to avoid regenerating them.
%nodes = ();
# Sorry, one carp per customer...
%carped = ();
#
# XXX debugging
# Find out where nulls at end of strings are coming from
#
sub checknodeid($$)
{
my ($nid,$msg) = @_;
if ($nid =~ /^(.*)\x0/) {
my $onid = $nid;
$nid = $1;
if (!exists($carped{$nid}) && open(MAIL, "| /usr/sbin/sendmail -t")) {
$carped{$nid} = 1;
require Carp;
print MAIL "To: $TBOPS\n";
print MAIL "Subject: NUL in node_id '$nid' ('$onid')\n";
print MAIL "$msg\n";
print MAIL "\n";
print MAIL Carp::longmess();
print MAIL "\n";
close(MAIL);
}
}
return $nid;
}
# Little helper and debug function.
sub mysystem($)
{
my ($command) = @_;
print STDERR "Running '$command'\n"
if (0);
return system($command);
}
# To avoid writing out all the methods.
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");
#
# Lookup a (physical) node and create a class instance to return.
#
sub Lookup($$)
{
my ($class, $token) = @_;
my $nodeid;
if ($token =~ /^\w{8}\-\w{4}\-\w{4}\-\w{4}\-\w{12}$/) {
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;
}
# XXX debug
$nodeid = checknodeid($nodeid, "Argument");
# Look in cache first
if (exists($nodes{$nodeid})) {
# XXX debug
my $row = $nodes{$nodeid}->{"DBROW"};
$row->{'node_id'} = checknodeid($row->{'node_id'}, "Cache");
return $nodes{$nodeid};
}
my $query_result =
DBQueryWarn("select * from nodes as n ".
"where n.node_id='$nodeid'");
return undef
if (!$query_result || !$query_result->numrows);
#
# Make a copy of the array. Still fighting memory corruption error.
#
my $hash_orig = $query_result->fetchrow_hashref();
my $hash_copy = {};
foreach my $key (keys(%{ $hash_orig })) {
my $val = $hash_orig->{$key};
$hash_copy->{$key} = $val;
}
return LookupRow($class, $hash_copy);
}
#
# Lookup a (physical) node based on an existing row from the database.
# Useful for bulk lookups.
#
sub LookupRow($$)
{
my ($class, $hash_copy) = @_;
my $self = {};
$self->{"DBROW"} = $hash_copy;
$self->{"RSRV"} = undef;
$self->{"TYPEINFO"} = undef;
$self->{"ATTRS"} = undef;
$self->{"IFACES"} = undef;
$self->{"WAROW"} = undef;
$self->{"HASH"} = {};
bless($self, $class);
# XXX debug
my $row = $self->{"DBROW"};
my $nodeid = checknodeid($row->{'node_id'}, "From DB");
# Add to cache.
$nodes{$nodeid} = $self;
return $self;
}
# 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;
$self->{"IFACES"} = undef;
$self->{"HASH"} = undef;
$self->{"WAROW"} = undef;
}
sub node_id($)
{
my $self = shift;
return checknodeid($self->{"DBROW"}->{'node_id'}, "node_id method");
}
sub phys_nodeid($)
{
my $self = shift;
return $self->{"DBROW"}->{'phys_nodeid'};
}
#
# 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;
@SELFLOADER_DATA@
#
# 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;
$self->{"IFACES"} = undef;
$self->{"WAROW"} = undef;
$self->{"HASH"} = {};
bless($self, $class);
# Add to cache.
$nodes{$nodeid} = $self;
return $self;
}
#
# 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()) {
my $nodeid = checknodeid($row->{'node_id'}, "BulkLookup method I");
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;
$node->{"IFACES"} = undef;
$node->{"WAROW"} = undef;
$node->{"HASH"} = {};
$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()) {
my $nodeid = checknodeid($row->{'node_id'}, "BulkLookup method II");
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()) {
my $nodeid = checknodeid($row->{'node_id'}, "BulkLookup method III");
my $key = $row->{'attrkey'};
my $node = $nodelist{$nodeid};
return -1
if (!defined($node));
$node->{"ATTRS"}->{$key} = $row;
}
@$pref = values(%nodelist);
return 0;
}
sub Create($$$$)
{
my ($class, $node_id, $experiment, $argref) = @_;
my ($control_iface,$virtnode_capacity,$adminmfs,$adminmfs_osid);
my ($priority, $osid, $opmode, $state);
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();
my $type = $argref->{'type'};
my $role = $argref->{'role'};
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);
my $typeinfo = NodeType->Lookup($type);
return undef
if (!defined($typeinfo));
my $isremote = $typeinfo->isremotenode();
if ($role eq "testnode") {
if ($typeinfo->virtnode_capacity(\$virtnode_capacity)) {
print STDERR "*** No virtnode_capacity for $type! Using zero.\n";
$virtnode_capacity = 0;
}
if ($typeinfo->isswitch()) {
$osid = "NULL";
$opmode = "ALWAYSUP";
}
elsif ($isremote || $typeinfo->isfakenode()) {
$osid = "NULL";
$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;
}
$osid = $osinfo->osid();
$opmode = $osinfo->op_mode();
}
}
else {
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;
}
$osid = $adminmfs->osid();
$opmode = $adminmfs->op_mode();
}
}
else {
$osid = "NULL";
$opmode = "";
}
if (exists($argref->{'initial_eventstate'})) {
$state = $argref->{'initial_eventstate'};
}
else {
$state = $STATE_INITIAL;
}
#
# Lock the tables to prevent concurrent creation
#
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;
#
# Make up a priority (just used for sorting)
#
if ($node_id =~ /^(.*\D)(\d+)$/) {
$priority = $2;
}
else {
$priority = 1;
}
#
# 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);
}
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', " .
" def_boot_osid=$osid, " .
" 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'};
# 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 ".
" (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;
}
}
if ($role eq "testnode") {
DBQueryWarn("insert into node_hostkeys (node_id) ".
"values ('$node_id')")
or goto bad;
DBQueryWarn("insert into node_status ".
"(node_id, status, status_timestamp) ".
"values ('$node_id', 'down', now()) ")
or goto bad;
DBQueryWarn("insert into node_activity ".
"(node_id) values ('$node_id')")
or goto bad;
DBQueryWarn("insert into node_utilization ".
"(node_id) values ('$node_id')")
or goto bad;
}
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) {
my $vtype;
if (exists($argref->{'vtype'})) {
$vtype = $argref->{'vtype'};
}
else {
$vtype = $type;
if (!($vtype =~ s/pc/pcvm/)) {
$vtype = "$vtype-vm";
}
}
DBQueryWarn("insert into node_auxtypes set node_id='$node_id', " .
"type='$vtype', count=$virtnode_capacity")
or goto bad;
}
DBQueryWarn("unlock tables");
return Node->Lookup($node_id);
bad:
foreach my $table (@cleantables) {
DBQueryWarn("delete from $table where node_id='$node_id'");
}
DBQueryWarn("unlock tables");
return undef;
}
#
# 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;
}
#
# Refresh a class instance by reloading from the DB.
#
sub Refresh($)
{
my ($self) = @_;
return -1
if (! ref($self));
my $nodeid = $self->node_id();
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();
# XXX debug
my $row = $nodes{$nodeid}->{"DBROW"};
$row->{'node_id'} = checknodeid($row->{'node_id'}, "Refresh");
# Force reload
$self->{"RSRV"} = undef;
$self->{"TYPEINFO"} = undef;
$self->{"ATTRS"} = undef;
$self->{"IFACES"} = undef;
$self->{"WAROW"} = undef;
return 0;
}
#
# Flush from our little cache, as for the expire daemon.
#
sub Flush($)
{
my ($self) = @_;
delete($nodes{$self->node_id()});
}
sub FlushAll($)
{
my ($class) = @_;
%nodes = ();
}
#
# 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};
}
#
# 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.
return 0
if (! ref($self));
if ($access_type < TB_NODEACCESS_MIN ||
$access_type > TB_NODEACCESS_MAX) {
print STDERR "*** Invalid access type: $access_type!\n";
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));
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);
}
#
# Lazily load the reservation info.
#
sub IsReserved($)
{
my ($self) = @_;
return 0
if (! ref($self));
if (! defined($self->{"RSRV"})) {
my $nodeid = $self->node_id();
my $query_result =
DBQueryWarn("select * from reserved " .
"where node_id='$nodeid'");
return 0
if (!$query_result);
return 0
if (!$query_result->numrows);
$self->{"RSRV"} = $query_result->fetchrow_hashref();
return 1;
}
return 1;
}
#
# 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;
}
}
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;
}
#
# Flush the reserve info so it reloads.
#
sub FlushReserved($)
{
my ($self) = @_;
$self->{"RSRV"} = undef;
return 0;
}
#
# Is node up.
#
sub IsUp($)
{
my ($self) = @_;
return 0
if (! ref($self));
return $self->eventstate() eq TBDB_NODESTATE_ISUP;
}
#
# 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.
# 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.
#
my $query_result =
DBQueryFatal("select distinct p.type, p.pid_idx from nodes as n ".
"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;
}
my ($ptype,$pid_idx) = $query_result->fetchrow_array();
# No rows, or a pid match.
if (!defined($ptype) || $pid_idx eq $pid->pid_idx()) {
return 1;
}
# Okay, must be rows in the permissions table. Check each pid for a match.
while (my ($ptype,$pid_idx) = $query_result->fetchrow_array()) {
if ($pid_idx eq $pid->pid_idx()) {
return 1;
}
}
return 0;
}
# Naming confusion.
sub AllocCheck($$)
{
my ($self, $pid) = @_;
return $self->NodeAllocCheck($pid);
}
#
# 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();
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;
}
#
# We do this cause we always want to go to the DB.
#
sub GetEventState($$)
{
my ($self, $pstate) = @_;
my $node_id = $self->node_id();
my $query_result =
DBQueryWarn("select eventstate from nodes where node_id='$node_id'");
return -1
if (!$query_result || !$query_result->numrows);
my ($state) = $query_result->fetchrow_array();
$state = TBDB_NODESTATE_UNKNOWN
if (!defined($state));
$self->{'DBROW'}->{'eventstate'} = $state
if (defined($self->{'DBROW'}));
$$pstate = $state;
return 0;
}
#
# 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();
}
#
# Get the experiment this node is reserved too, or null.
#
sub Reservation($)
{
my ($self) = @_;
return undef
if (! ref($self));
return undef
if (! $self->IsReserved());
return LocalExpLookup($self->{"RSRV"}->{'exptidx'});
}
#
# Return just the ID of the reservation experiment. Avoids locking problems
# within nalloc and nfree.
#
sub ReservationID($)
{
my ($self) = @_;
return undef
if (! ref($self));
return undef
if (! $self->IsReserved());
return $self->{"RSRV"}->{'exptidx'};
}
#
# Get the NEXT experiment this node is reserved too, or null.
#
sub NextReservation($)
{
my ($self) = @_;
return undef
if (! ref($self));
my $node_id = $self->node_id();
my $query_result =
DBQueryFatal("select pid,eid from next_reserve ".
"where node_id='$node_id'");
return undef
if (!$query_result || !$query_result->numrows);
my ($pid,$eid) = $query_result->fetchrow_array();
return LocalExpLookup($pid, $eid);
}
#
# Move a node from its experiment to another. Must treat oldreserved special.
#
sub MoveReservation($$)
{
my ($self, $newexperiment) = @_;
return -1
if (! (ref($self) && ref($newexperiment)));
my $node_id = $self->node_id();
my $newpid = $newexperiment->pid();
my $neweid = $newexperiment->eid();
my $newidx = $newexperiment->idx();
my $oldpid = "";
my $oldeid = "";
my $oldidx = 0;
# Must remember old reservation when moving to new oldreserved.
if ($newpid eq OLDRESERVED_PID() && $neweid eq OLDRESERVED_EID()) {
#
# Cannot do an experiment Lookup cause reserved table may be locked.
# IsReserved() will load the reserved table entry only.
#
return -1
if (!$self->IsReserved());
$oldpid = $self->{"RSRV"}->{'pid'};
$oldeid = $self->{"RSRV"}->{'eid'};
$oldidx = $self->{"RSRV"}->{'exptidx'};
}
my $sets = "rsrv_time=now(), ".
" vname='$node_id', ".
" exptidx=$newidx, ".
" pid='$newpid', ".
" eid='$neweid', ".
" old_exptidx=$oldidx, ".
" old_pid='$oldpid', ".
" old_eid='$oldeid' ";
if ($self->IsReserved()) {
DBQueryWarn("update reserved set $sets where node_id='$node_id'")
or return -1;
}
else {
DBQueryWarn("insert into reserved set $sets, node_id='$node_id'")
or return -1;
}
# Force this to reload.
$self->{"RSRV"} = undef;
return 0;
}
#
# Change reservation table for a node.
#
sub ModifyReservation($$)
{
my ($self, $argref) = @_;
return -1
if (! (ref($self) && ref($argref)));
return -1
if (! $self->IsReserved());
my $node_id = $self->node_id();
my @sets = ();
foreach my $key (keys(%{$argref})) {
my $val = $argref->{$key};
push(@sets, "$key=" . ("$val" eq "NULL" ? "NULL" : "'$val'"));
}
my $query = "update reserved set ". join(",", @sets);
$query .= " where node_id='$node_id'";
return -1
if (! DBQueryWarn($query));
foreach my $key (keys(%{$argref})) {
my $val = $argref->{$key};
$self->{'DBROW'}->{$key} = $val;
}
return 0;
}
#
# Map nodeid to its pid/eid in the oldreserved holding reservation
#
sub OldReservation($)
{
my ($self) = @_;
return undef
if (! ref($self));
my $nodeid = $self->node_id();
my $oldreserved_pid = OLDRESERVED_PID;
my $oldreserved_eid = OLDRESERVED_EID;
my $query_result =
DBQueryWarn("select old_pid,old_eid from reserved ".
"where node_id='$nodeid' and pid='$oldreserved_pid' ".
"and eid='$oldreserved_eid'");
return undef
if (! $query_result || !$query_result->num_rows);
my ($pid,$eid) = $query_result->fetchrow_array();
return LocalExpLookup($pid, $eid);
}
sub OldReservationID($)
{
my ($self) = @_;
return undef
if (! ref($self));
my $nodeid = $self->node_id();
my $oldreserved_pid = OLDRESERVED_PID;
my $oldreserved_eid = OLDRESERVED_EID;
my $query_result =
DBQueryWarn("select old_exptidx from reserved ".
"where node_id='$nodeid' and pid='$oldreserved_pid' ".
"and eid='$oldreserved_eid'");
return undef
if (! $query_result || !$query_result->num_rows);
my ($idx) = $query_result->fetchrow_array();
return $idx;
}
#
# Return the tip server (and tipname) for a node.
#
sub TipServer($$;$$$)
{
my ($self, $pserver, $ptipname, $pportnum, $pkeydata) = @_;
return -1
if (! ref($self));
$$pserver = undef;
$$ptipname = undef
if (defined($ptipname));
my $nodeid = $self->node_id();
my $query_result =
DBQueryWarn("select server,tipname,portnum,keydata from tiplines " .
"where node_id='$nodeid'");
return -1
if (!$query_result);
return 0
if (!$query_result->numrows);
my ($server,$tipname,$portnum,$keydata) = $query_result->fetchrow_array();
$$pserver = $server;
$$ptipname = $tipname
if (defined($ptipname));
$$pkeydata = $keydata
if (defined($pkeydata));
$$pportnum = $portnum
if (defined($pportnum));
return 0;
}
#
# Get the raw reserved table info and return it, or null if no reservation
#
sub ReservedTableEntry($)
{
my ($self) = @_;
return undef
if (! ref($self));
return undef
if (! $self->IsReserved());
return $self->{"RSRV"};
}
#
# Return a list of virtual nodes on the given physical node.
#
sub VirtualNodes($$)
{
my ($self, $plist) = @_;
return -1
if (! ref($self));
@$plist = ();
my $reservation = $self->Reservation();
return 0
if (!defined($reservation));
my $node_id = $self->node_id();
my $exptidx = $reservation->idx();
my @result = ();
my $query_result =
DBQueryWarn("select r.node_id from reserved as r ".
"left join nodes as n ".
"on r.node_id=n.node_id ".
"where n.phys_nodeid='$node_id' and ".
" n.node_id!=n.phys_nodeid and exptidx='$exptidx'");
return -1
if (!$query_result);
return 0
if (!$query_result->numrows);
while (my ($node_id) = $query_result->fetchrow_array()) {
my $node = Node->Lookup($node_id);
if (!defined($node)) {
print STDERR "*** VirtualNodes: no such virtual node $node_id!\n";
return -1;
}
push(@result, $node);
}
@$plist = @result;
return 0;
}
#
# Does a node have any virtual nodes on it. Table might be locked.
#
sub HasVirtualNodes($)
{
my ($self) = @_;
return -1
if (! ref($self));
my $node_id = $self->node_id();
my $query_result =
DBQueryWarn("select nodes.node_id from nodes ".
"left join reserved on reserved.node_id=nodes.node_id ".
"where nodes.phys_nodeid='$node_id' and ".
" nodes.node_id!=nodes.phys_nodeid and ".
" reserved.node_id is not null");
return -1
if (!$query_result);
return $query_result->numrows;
}
#
# Access methods for the reservation.
#
sub L__reservation($$)
{
my ($self, $slotname) = @_;
return undef
if (! ref($self));
return undef
if (! $self->IsReserved());
return undef
if (! exists($self->{"RSRV"}->{$slotname}));
return $self->{"RSRV"}->{$slotname};
}
sub vname($) { return L__reservation($_[0], 'vname'); }
sub sharing_mode($) { return L__reservation($_[0], 'sharing_mode'); }
sub erole($) { return L__reservation($_[0], 'erole'); }
sub eid($) { return L__reservation($_[0], 'eid'); }
sub pid($) { return L__reservation($_[0], 'pid'); }
sub external_resource_index($) {
return L__reservation($_[0], 'external_resource_index'); }
sub external_resource_id($) {
return L__reservation($_[0], 'external_resource_id'); }
sub external_resource_key($) {
return L__reservation($_[0], 'external_resource_key'); }
sub inner_elab_role($) {
return L__reservation($_[0], 'inner_elab_role'); }
#
# Load all attributes from the node_attributes table,
#
sub LoadNodeAttributes($)
{
my ($self) = @_;
return -1
if (!ref($self));
my $node_id = $self->node_id();
if (!defined($self->{"ATTRS"})) {
my $query_result =
DBQueryWarn("select * from node_attributes ".
"where node_id='$node_id'");
return -1
if (!defined($query_result));
$self->{"ATTRS"} = {};
while (my $row = $query_result->fetchrow_hashref()) {
my $key = $row->{'attrkey'};
$self->{"ATTRS"}->{$key} = $row;
}
}
return 0;
}
# Iterate through rows adding node attributes. Each row is a hashref.
sub PreloadNodeAttributes($$)
{
my ($self, $rows) = @_;
$self->{"ATTRS"} = {};
foreach my $row (@{ $rows }) {
my $key = $row->{'attrkey'};
$self->{"ATTRS"}->{$key} = $row;
}
}
#
# Lookup a specific attribute in the node_attributes table,
#
sub NodeAttribute($$$;$)
{
my ($self, $attrkey, $pattrvalue, $pattrtype) = @_;
return -1
if (!ref($self));
my $node_id = $self->node_id();
if (!defined($self->{"ATTRS"})) {
if ($self->LoadNodeAttributes()) {
return -1;
}
}
if (!exists($self->{"ATTRS"}->{$attrkey})) {
$$pattrvalue = undef;
return 0;
}
my $ref = $self->{"ATTRS"}->{$attrkey};
$$pattrvalue = $ref->{'attrvalue'};
$$pattrtype = $ref->{'attrtype'}
if (defined($pattrtype));
return 0;
}
#
# Return a hash of the node attributes for this node.
#
sub GetNodeAttributes($)
{
my ($self) = @_;
return undef
if (!ref($self));
my $node_id = $self->node_id();
if (!defined($self->{"ATTRS"})) {
if ($self->LoadNodeAttributes()) {
return undef;
}
}
return $self->{"ATTRS"};
}
#
# Return type info. We cache this in the instance since node_type stuff
# does not change much.
#
sub NodeTypeInfo($)
{
my ($self) = @_;
require NodeType;
return undef
if (! ref($self));
return $self->{"TYPEINFO"}
if (defined($self->{"TYPEINFO"}));
my $type = $self->type();
my $nodetype = NodeType->Lookup($type);
$self->{"TYPEINFO"} = $nodetype
if (defined($nodetype));
return $nodetype;
}
sub SetNodeTypeInfo($$)
{
my ($self, $nodetype) = @_;
if ($self->type() eq $nodetype->type()) {
$self->{"TYPEINFO"} = $nodetype;
}
}
#
# Lookup a specific attribute in the nodetype info.
#
sub NodeTypeAttribute($$$;$)
{
my ($self, $attrkey, $pattrvalue, $pattrtype) = @_;
return -1
if (!ref($self));
my $typeinfo = $self->NodeTypeInfo();
return -1
if (!defined($typeinfo));
return $typeinfo->GetAttribute($attrkey, $pattrvalue, $pattrtype);
}
#
# Returns a hash of node type attributes in the nodetype info.
#
sub GetNodeTypeAttributes($)
{
my ($self) = @_;
return undef
if (!ref($self));
my $typeinfo = $self->NodeTypeInfo();
return undef
if (!defined($typeinfo));
return $typeinfo->GetAttributes();
}
#
# Shortcuts to "common" type information.
# Later these might be overriden by node attributes.
#
sub class($) { return NodeTypeInfo($_[0])->class(); }
sub isvirtnode($) { return NodeTypeInfo($_[0])->isvirtnode(); }
sub isjailed($) { return NodeTypeInfo($_[0])->isjailed(); }
sub isdynamic($) { return NodeTypeInfo($_[0])->isdynamic(); }
sub isremotenode($) { return NodeTypeInfo($_[0])->isremotenode(); }
sub issubnode($) { return NodeTypeInfo($_[0])->issubnode(); }
sub isplabdslice($) { return NodeTypeInfo($_[0])->isplabdslice(); }
sub isplabphysnode($) { return NodeTypeInfo($_[0])->isplabphysnode(); }
sub issimnode($) { return NodeTypeInfo($_[0])->issimnode(); }
sub isgeninode($) { return NodeTypeInfo($_[0])->isgeninode(); }
sub isfednode($) { return NodeTypeInfo($_[0])->isfednode(); }
sub isdedicatedremote($) { return NodeTypeInfo($_[0])->isdedicatedremote(); }
sub isswitch($) { return NodeTypeInfo($_[0])->isswitch(); }
#
# Later has arrived...
# Look for node_attributes settings first and if none, fall back on
# node_type_attributes info.
#
sub default_osid($;$) {
my ($self,$stuff) = @_;
my $val = undef;
if (NodeAttribute($self, "default_osid", \$val) == 0 && defined($val)) {
return $val;
}
return NodeTypeInfo($self)->default_osid($stuff);
}
sub default_imageid($;$) {
my ($self,$stuff) = @_;
my $val = undef;
if (NodeAttribute($self, "default_imageid", \$val) == 0 && defined($val)) {
return $val;
}
return NodeTypeInfo($self)->default_imageid($stuff);
}
sub default_pxeboot($) {
my ($self) = @_;
my $val = undef;
if (NodeAttribute($self, "pxe_boot_path", \$val) == 0 && defined($val)) {
return $val;
}
return NodeTypeAttribute($self, "pxe_boot_path", undef);
}
#
# And these are the less common attributes, but still common enough to
# warrant shortcuts.
#
sub delay_osid($;$) {
return NodeTypeInfo($_[0])->delay_osid($_[1]);
}
sub jail_osid($;$) {
return NodeTypeInfo($_[0])->jail_osid($_[1]);
}
sub imageable($;$) {
return NodeTypeInfo($_[0])->imageable($_[1]);
}
sub disksize($;$) {
return NodeTypeInfo($_[0])->disksize($_[1]);
}
sub disktype($;$) {
return NodeTypeInfo($_[0])->disktype($_[1]);
}
sub bootdisk_unit($;$) {
return NodeTypeInfo($_[0])->bootdisk_unit($_[1]);
}
sub control_iface($;$) {
return NodeTypeInfo($_[0])->control_iface($_[1]);
}
sub rebootable($;$) {
return NodeTypeInfo($_[0])->rebootable($_[1]);
}
sub bios_waittime($;$) {
return NodeTypeInfo($_[0])->bios_waittime($_[1]);
}
#
# Perform some updates ...
#
sub Update($$)
{
my ($self, $argref) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $nodeid = $self->node_id();
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 nodes set " . join(",", @sets) .
" where node_id='$nodeid'";
return -1
if (! DBQueryWarn($query));
return Refresh($self);
}
#
# Insert a Log entry for a node.
#
sub InsertNodeLogEntry($$$$)
{
my ($self, $user, $type, $message) = @_;
# Must be a real reference.
return -1
if (! ref($self));
return -1
if (! grep {$_ eq $type} TB_NODELOGTYPES());
# XXX Eventually this should change, but it uses non-existent uids!
my $dbid = (defined($user) ? $user->uid_idx() : 0);
my $dbuid = (defined($user) ? $user->uid() : "root");
my $node_id = $self->node_id();
$message = DBQuoteSpecial($message);
return -1
if (! DBQueryWarn("insert into nodelog values ".
"('$node_id', NULL, '$type', '$dbuid', '$dbid', ".
" $message, now())"));
return 0;
}
#
# Clear a bunch of stuff from the nodes tale entry so boot is clean.
#
sub ClearBootAttributes($)
{
my ($self) = @_;
my $node_id = (ref($self) ? $self->node_id() : $self);
my $allocFreeState = TBDB_ALLOCSTATE_FREE_DIRTY();
DBQueryWarn("update nodes set startupcmd='',rpms='',deltas='', ".
"tarballs='',failureaction='fatal', routertype='none', ".
"def_boot_cmd_line='',next_boot_cmd_line='', ".
"temp_boot_osid=NULL,next_boot_osid=NULL, ".
"update_accounts=0,ipport_next=ipport_low,rtabid=0, ".
"sfshostid=NULL,allocstate='$allocFreeState',boot_errno=0, ".
"destination_x=NULL,destination_y=NULL, ".
"destination_orientation=NULL ".
"where node_id='$node_id'")
or return -1;
return 0;
}
#
# Clear the experimental interfaces for a node.
#
sub ClearInterfaces($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
DBQueryWarn("update interfaces set IP='',IPaliases=NULL,mask=NULL,".
" rtabid='0',vnode_id=NULL,current_speed='0',trunk='0' ".
"where node_id='$node_id' and ".
" role='" . TBDB_IFACEROLE_EXPERIMENT() . "'")
or return -1;
DBQueryWarn("update interface_state,interfaces set ".
" remaining_bandwidth=0 ".
"where interface_state.node_id=interfaces.node_id and ".
" interface_state.iface=interfaces.iface and ".
" interfaces.node_id='$node_id' and ".
" role='" . TBDB_IFACEROLE_EXPERIMENT() . "'")
or return -1;
return 0;
}
#
# Clear the shared bandwidth being used by a (virtual) node.
#
sub ReleaseSharedBandwidth($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
DBQueryWarn("lock tables vinterfaces write, interface_state write")
or return -1;
#
# A negative BW is not reserved yet.
#
my $query_result =
DBQueryWarn("select iface,unit from vinterfaces ".
"where vnode_id='$node_id' and ".
" iface is not null and ".
" bandwidth>0");
if (!$query_result) {
DBQueryWarn("unlock tables");
return -1;
}
goto done
if (!$query_result->numrows);
#
# For each vinterface indicating reserved bandwidth, atomically
# update interface_state adding back the bandwidth, and decrementing
# vinterface to indicate the bandwidth has been released.
#
while (my ($iface,$unit) = $query_result->fetchrow_array()) {
if (!DBQueryWarn("update interface_state,vinterfaces set ".
" remaining_bandwidth=remaining_bandwidth+bandwidth, ".
" bandwidth=0-bandwidth ".
"where interface_state.node_id=vinterfaces.node_id and ".
" interface_state.iface=vinterfaces.iface and ".
" vinterfaces.vnode_id='$node_id' and ".
" vinterfaces.iface='$iface' and ".
" vinterfaces.unit='$unit'")) {
DBQueryWarn("unlock tables");
return -1;
}
}
done:
DBQueryWarn("unlock tables");
return 0;
}
#
# Relase the reserved blockstore, which requires updating the
# remaining_capacity on the underyling store. At the present
# time, one blockstore is mapped to one pcvm.
#
sub ReleaseBlockStore($)
{
my ($self) = @_;
require Blockstore;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
my $blockstore = Blockstore::Reservation->LookupByNodeid($node_id);
return 0
if (!defined($blockstore));
return -1
if (!ref($blockstore));
return $blockstore->Release();
}
#
# Look up all interfaces for a node, return list of objects.
#
sub AllInterfaces($$)
{
my ($self, $pref) = @_;
require Interface;
# Must be a real reference.
return -1
if (! (ref($self) && ref($pref)));
return Interface->LookupAll($self->node_id(), $pref);
}
#
# Load the interfaces for a node, which we then access by name.
#
sub LoadInterfaces($)
{
my ($self) = @_;
require Interface;
# Must be a real reference.
return -1
if (! ref($self));
if (!defined($self->{'IFACES'})) {
my @interfaces;
return -1
if (Interface->LookupAll($self->node_id(), \@interfaces) != 0);
$self->{'IFACES'} = {};
foreach my $interface (@interfaces) {
$self->{'IFACES'}->{$interface->iface()} = $interface;
}
}
return 0;
}
sub GetInterface($$$)
{
my ($self, $iface, $pref) = @_;
my $interface;
# Must be a real reference.
return -1
if (! (ref($self) && ref($pref)));
if (exists($self->{'IFACES'}->{$iface})) {
# Might be undef if we already tried.
$interface = $self->{'IFACES'}->{$iface};
}
else {
$interface = Interface->LookupByIface($self, $iface);
$self->{'IFACES'}->{$iface} = $interface;
}
$$pref = $interface;
return -1
if (!defined($interface));
return 0;
}
#
# Mark a node for an update.
#
sub MarkForUpdate($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
return -1
if (! DBQueryWarn("update nodes set ".
"update_accounts=GREATEST(update_accounts,1) ".
"where node_id='$node_id'"));
return Refresh($self);
}
# Class method!
sub CheckUpdateStatus($$$@)
{
my ($class, $pdone, $pnotdone, @nodelist) = @_;
my @done = ();
my @notdone = ();
my $where = join(" or ",
map("node_id='" . $_->node_id() . "'", @nodelist));
my $query_result =
DBQueryWarn("select node_id,update_accounts from nodes ".
"where ($where)");
return -1
if (! $query_result);
while (my ($node_id,$update_accounts) = $query_result->fetchrow_array) {
my $node = Node->Lookup($node_id);
if (! $update_accounts) {
Refresh($node);
push(@done, $node);
}
else {
push(@notdone, $node);
}
}
@$pdone = @done;
@$pnotdone = @notdone;
return 0;
}
#
# Clear the bootlog.
#
sub ClearBootLog($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
return -1
if (! DBQueryWarn("delete from node_bootlogs ".
"where node_id='$node_id'"));
return 0;
}
#
# Get the bootlog.
#
sub GetBootLog($$)
{
my ($self, $pref) = @_;
# Must be a real reference.
return -1
if (! ref($self));
$$pref = undef;
my $node_id = $self->node_id();
my $query_result =
DBQueryWarn("select bootlog from node_bootlogs ".
"where node_id='$node_id'");
return -1
if (! $query_result);
if ($query_result->numrows) {
my ($bootlog) = $query_result->fetchrow_array();
$$pref = $bootlog;
} else {
$$pref = "";
}
return 0;
}
#
# Set event state for a node.
#
sub SetEventState($$)
{
my ($self, $state) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
#
# If using the event system, we send out an event for the state daemon to
# pick up. Otherwise, we just set the state in the database ourselves
#
return EventSendFatal(objtype => TBDB_TBEVENT_NODESTATE,
objname => $node_id,
eventtype => $state,
host => $BOSSNODE);
return $self->Update({"eventstate" => $state,
"state_timestamp" => time()});
}
#
# Create new vnodes. The argument is a reference; to a a hash of options to
# be used when creating the new node(s). A list of the node names is
# returned.
#
sub CreateVnodes($$$)
{
my ($class, $rptr, $options) = @_;
my @created = ();
my @ifaceargs = ();
my @interfaces= ();
my @tocreate = ();
my @vlist = ();
require Interface;
require NodeType;
if (!defined($options->{'pid'})) {
print STDERR "*** CreateVnodes: Must supply a pid!\n";
return -1;
}
if (!defined($options->{'eid'})) {
print STDERR "*** CreateVnodes: Must supply a eid!\n";
return -1;
}
if (!defined($options->{'count'})) {
print STDERR "*** CreateVnodes: Must supply a count!\n";
return -1;
}
if (!defined($options->{'vtype'})) {
print STDERR "*** CreateVnodes: Must supply a vtype!\n";
return -1;
}
if (!defined($options->{'nodeid'})) {
print STDERR "*** CreateVnodes: Must supply a pnode!\n";
return -1;
}
my $debug = defined($options->{'debug'}) && $options->{'debug'};
my $impotent= defined($options->{'impotent'}) && $options->{'impotent'};
my $verbose = defined($options->{'verbose'}) && $options->{'verbose'};
my $regression = defined($options->{'regression'}) &&
$options->{'regression'};
my $sharedokay = defined($options->{'sharedokay'}) &&
$options->{'sharedokay'};
my $user = $options->{'user'} if (exists($options->{'user'}));
my $pid = $options->{'pid'};
my $eid = $options->{'eid'};
my $count = $options->{'count'};
my $vtype = $options->{'vtype'};
my $pnode = $options->{'nodeid'};
# Caller can specify uuids, otherwise we make them up.
my @uuids = @$rptr;
my $node = Node->Lookup($pnode);
if (!defined($node)) {
print STDERR "*** CreateVnodes: No such node $pnode!\n";
return -1;
}
my $experiment = LocalExpLookup($pid, $eid);
if (!defined($experiment)) {
print STDERR "*** CreateVnodes: No such experiment $pid/$eid!\n";
return -1;
}
my $exptidx = $experiment->idx();
#
# Need the vtype node_type info.
#
my $nodetype = NodeType->Lookup($vtype);
if (! defined($nodetype)) {
print STDERR "*** CreateVnodes: No such node type '$vtype'\n";
return -1;
}
if (!$nodetype->isdynamic()) {
print STDERR "*** CreateVnodes: Not a dynamic node type: '$vtype'\n";
return -1;
}
my $isremote = $nodetype->isremotenode();
my $isjailed = $nodetype->isjailed();
my $isfednode = $nodetype->isfednode();
#
# Make up a priority (just used for sorting). We need the name prefix
# as well for consing up the node name.
#
my $nodeprefix;
my $nodenum;
my $ipbase;
if ($isfednode) {
$nodeprefix = $nodetype->type();
$nodenum = "";
$ipbase = 0;
}
elsif ($pnode =~ /^(.*\D)(\d+)$/) {
$nodeprefix = $1;
$nodenum = $2;
$ipbase = $nodenum;
}
else {
$nodeprefix = $pnode;
$nodenum = "";
$ipbase = 0;
if ($isjailed) {
#
# Determine ipbase from the control IP (jailed nodes).
#
my $interface = Interface->LookupControl($node);
if (!defined($interface)) {
print STDERR
"*** CreateVnodes: No control interface for $node\n";
return -1;
}
my $ctrlip = $interface->IP();
if (!defined($ctrlip) || $ctrlip eq "") {
print STDERR
"*** CreateVnodes: No control IP for $interface\n";
return -1;
}
my $tmp = ~inet_aton($CONTROL_NETMASK) & inet_aton($ctrlip);
$ipbase = unpack("N", $tmp);
if ($ipbase == 0 || $ipbase < 0 || $ipbase > 0x3fff) {
print STDERR
"*** CreateVnodes: Bad ipbase '$ipbase' for $interface\n";
return -1;
}
}
}
#
# Need the opmode, which comes from the OSID, which is in the node_types
# table.
#
my $osid = $nodetype->default_osid();
my $query_result =
DBQueryWarn("select op_mode from os_info where osid='$osid'");
return -1
if (! $query_result);
if (! $query_result->numrows) {
print STDERR "*** CreateVnodes: No such OSID '$osid'\n";
return -1;
}
my ($opmode) = $query_result->fetchrow_array();
#
# Need IP for jailed nodes.
#
my $IPBASE = TBDB_JAILIPBASE();
my $IPBASE1;
my $IPBASE2;
if ($IPBASE =~ /^(\d+).(\d+).(\d+).(\d+)/) {
$IPBASE1 = $1;
$IPBASE2 = $2;
}
else {
print STDERR "*** CreateVnodes: Bad IPBASE '$IPBASE'\n";
return -1;
}
#
# Assign however many we are told to (typically by assign). Locally
# this is not a problem since nodes are not shared; assign always
# does the right thing and we do what it says. In the remote case,
# nodes are shared and so it is harder to make sure that nodes are not
# over committed. I am not going to worry about this right now though
# cause it would be too hard. For RON nodes this is fine; we just
# tell people to log in and use them. For plab nodes, this is more
# of a problem, but I am going to ignore that for now too since we do
# not ever allocate enough to worry; must revisit this at some point.
#
# Look to see what nodes are already allocated on the node, and skip
# those. Must do this with tables locked, of course.
#
DBQueryFatal("lock tables nodes write, reserved write, ".
"node_status write, node_hostkeys write, node_activity write,".
"virt_node_public_addr write, virt_node_attributes read");
#
# Reload the reservation status now that tables are locked and confirm
# that the shared node is still reserved. See nfree, which checks shared
# nodes after locking reserved table.
#
$node->FlushReserved();
my $sharing_mode = $node->sharing_mode();
if (! ($impotent || $isfednode)) {
if (!$node->IsReserved()) {
print STDERR "*** CreateVnodes: no reservation for $node!\n";
DBQueryFatal("unlock tables");
return -1;
}
#
# The only time that the reservation can be different then the
# the experiment we are creating for, is if the sharedokay flag
# is on and the pnode is in sharedmode. Locking in nfree and in
# the pool daemon prevents the race.
#
# Cause of locking above, we need to make the comparison directly
# using the slot data in the node.
#
if (! ($experiment->pid() eq $node->pid() &&
$experiment->eid() eq $node->eid())) {
if (! ($sharedokay && $sharing_mode)) {
print STDERR "*** CreateVnodes: $node is not shared!\n";
DBQueryFatal("unlock tables");
return -1;
}
}
}
if (0 && !$isremote) {
for (my $i = 1; $i <= $count; $i++) {
push(@tocreate, $i);
}
}
else {
my $n = 1;
my $i = 0;
while ($i < $count) {
my $vnodeid = $nodeprefix . "vm" . $nodenum . "-" . $n;
$query_result =
DBQueryWarn("select node_id from nodes ".
"where node_id='$vnodeid'");
goto bad
if (!$query_result);
if (!$query_result->numrows) {
push(@tocreate, [$n, $options->{'vlist'}->[$i]]);
$i++;
}
$n++;
}
}
# See below.
my $eventstate = TBDB_NODESTATE_SHUTDOWN();
$eventstate = TBDB_NODESTATE_ISUP()
if ($opmode eq "ALWAYSUP");
my $allocstate = TBDB_ALLOCSTATE_FREE_CLEAN();
#
# Create a bunch.
#
foreach my $ref (@tocreate) {
my ($n, $vname) = @{ $ref };
my $vpriority = 10000000 + ($ipbase * 1000) + $n;
my $vnodeid = $nodeprefix . "vm" . $nodenum . "-" . $n;
my ( $jailip, $jailmask );
if ($isjailed) {
$query_result =
DBQueryWarn( "SELECT attrvalue FROM virt_node_attributes " .
"WHERE pid='$pid' AND eid='$eid' AND " .
"vname='$vname' AND " .
"attrkey='routable_control_ip'" );
if( $query_result && $query_result->numrows &&
( $query_result->fetchrow_array() )[ 0 ] eq "true" ) {
#
# Grab a public IP address from the free pool, if there
# is one.
#
$query_result =
DBQueryWarn( "SELECT IP, mask FROM virt_node_public_addr ".
"WHERE node_id IS NULL" );
if (!$query_result || !$query_result->numrows ) {
print STDERR "*** CreateVnodes: no free public address\n";
goto bad;
}
( $jailip, $jailmask ) = $query_result->fetchrow_array();
DBQueryFatal( "UPDATE virt_node_public_addr SET " .
"node_id='$vnodeid', card='0', port='1' " .
"WHERE IP='$jailip'" )
if (!$impotent);
} else {
#
# Construct a vnode private IP. The general form is:
# ...
# but if is greater than 254 we have to increment
# IPBASE2.
#
# XXX at Utah our second big cluster of nodes starts at
# nodenum=201 and I would like our vnode IPs to align
# at that boundary, so 254 becomes 200.
#
my $nodenumlimit = $ISUTAH ? 200 : 254;
my $pnet = $IPBASE2;
my $pnode2 = int($ipbase);
while ($pnode2 > $nodenumlimit) {
$pnet++;
$pnode2 -= $nodenumlimit;
}
$jailip = "${IPBASE1}.${pnet}.${pnode2}.${n}";
$jailmask = $JAILIPMASK;
}
}
# Need to keep the UUIDs consistent across regression mode.
my $uuid;
if ($regression) {
$uuid = "0000${n}-1111-2222-3333-44444444";
}
else {
$uuid = (@uuids ? shift(@uuids) : NewUUID());
}
if (!defined($uuid)) {
print STDERR "Could not generate a UUID!\n";
goto bad;
}
if ($verbose) {
print STDERR "Jail IP for $vnodeid is $jailip\n"
if ($jailip);
if ($impotent) {
print STDERR
"Would allocate $vnodeid on $pnode ($vtype, $osid)\n";
}
else {
print STDERR
"Allocating $vnodeid on $pnode ($vtype, $osid)\n";
}
}
my %nodesets = ("node_id" => $vnodeid,
"uuid" => $uuid,
"type" => $vtype,
"phys_nodeid" => $pnode,
"role" => "virtnode",
"priority" => $vpriority,
"op_mode" => $opmode,
"eventstate" => $eventstate,
"allocstate" => $allocstate,
"def_boot_osid" => $osid,
"update_accounts" => 1,
"jailflag" => $isjailed);
# This is deprecated and will be removed. We now create real
# interfaces, see below.
if (0 && $isjailed && !$isremote) {
$nodesets{"jailip"} = $jailip;
$nodesets{"jailipmask"} = $JAILIPMASK;
}
my $statement = "insert into nodes set ".
join(",", map("$_='" . $nodesets{$_} . "'", keys(%nodesets)));
print STDERR "$statement\n"
if ($debug);
if (!$impotent && !DBQueryWarn($statement)) {
print STDERR "*** CreateVnodes: Could not create nodes entry\n";
goto bad;
}
#
# Also reserve the node.
#
my %rsrvsets = ("node_id" => $vnodeid,
"exptidx" => $exptidx,
"pid" => $pid,
"eid" => $eid,
"vname" => $vnodeid,
"old_pid" => "",
"old_eid" => "");
# This is temporary for prototyping the shared local node support.
# Not sure how this will shake out yet.
$rsrvsets{"sharing_mode"} = "using_shared_local"
if (defined($sharing_mode));
$statement = "insert into reserved set ".
join(",", map("$_='" . $rsrvsets{$_} . "'", keys(%rsrvsets)));
print STDERR "$statement\n"
if ($debug);
if (!$impotent && !DBQueryWarn($statement)) {
print STDERR "*** CreateVnodes: Could not create reserved entry\n";
goto bad;
}
$statement =
"insert into node_status set ".
" node_id='$vnodeid', " .
" status='up', ".
" status_timestamp=now()";
print STDERR "$statement\n"
if ($debug);
if (!$impotent && !DBQueryWarn($statement)) {
print STDERR "*** CreateVnodes: Could not create status entry\n";
goto bad;
}
$statement =
"insert into node_hostkeys set ".
" node_id='$vnodeid'";
print STDERR "$statement\n"
if ($debug);
if (!$impotent && !DBQueryWarn($statement)) {
print STDERR "*** CreateVnodes: Could not create hostkeys entry\n";
goto bad;
}
$statement =
"insert into node_activity set ".
" node_id='$vnodeid'";
print STDERR "$statement\n"
if ($debug);
if (!$impotent && !DBQueryWarn($statement)) {
print STDERR "*** CreateVnodes: Could not create activity entry\n";
goto bad;
}
Node->MakeFake($vnodeid, \%nodesets, \%rsrvsets)
if ($impotent);
push(@created, $vnodeid);
#
# Save up interfaces we need to create after table unlock.
#
if ($isjailed && !$isremote) {
my $ifaceargs = {
"node_id" => $vnodeid,
"card" => 0,
"port" => 1,
"iface" => "eth0",
"role" => TBDB_IFACEROLE_CONTROL(),
"MAC" => "genfake",
"IP" => $jailip,
"mask" => $jailmask,
"type" => "generic",
"logical" => 1,
};
push(@ifaceargs, $ifaceargs);
}
}
DBQueryFatal("unlock tables");
#
# Now create a control network interface for local jailed nodes.
# Now that tables are unlocked.
#
foreach my $ifaceargs (@ifaceargs) {
my $vnodeid = $ifaceargs->{'node_id'};
my $node = Node->Lookup($vnodeid);
if (!defined($node)) {
print STDERR
"*** CreateVnodes: Could not lookup node object for $vnodeid\n";
goto bad;
}
my $interface = ($impotent ?
Interface->MakeFake($node, $ifaceargs) :
Interface->Create($node, $ifaceargs));
if (!defined($interface)) {
print STDERR
"*** CreateVnodes: Could not create interface for $vnodeid\n";
goto bad;
}
print STDERR Dumper($interface)
if ($debug);
push(@interfaces, $interface);
}
#
# Finally, add the history records.
#
if (!$impotent) {
foreach my $vnodeid (@created) {
my $node = Node->Lookup($vnodeid);
if (!defined($node)) {
print STDERR
"*** CreateVnodes: Could not lookup $vnodeid\n";
next;
}
$node->SetNodeHistory(TB_NODEHISTORY_OP_CREATE(),
$user, $experiment);
}
}
@$rptr = @created;
return 0;
bad:
if (!$impotent) {
foreach my $interface (@interfaces) {
$interface->Delete();
}
foreach my $vnodeid (@created) {
DBQueryWarn("delete from reserved where node_id='$vnodeid'");
DBQueryWarn("delete from nodes where node_id='$vnodeid'");
DBQueryWarn("delete from node_hostkeys where node_id='$vnodeid'");
DBQueryWarn("delete from node_status where node_id='$vnodeid'");
DBQueryWarn("delete from node_activity where node_id='$vnodeid'");
DBQueryWarn("delete from node_idlestats where node_id='$vnodeid'");
DBQueryWarn("delete from iface_counters where node_id='$vnodeid'");
}
}
DBQueryFatal("unlock tables");
return -1;
}
#
# Delete vnodes created in above step.
#
sub DeleteVnodes(@)
{
my (@vnodes) = @_;
DBQueryWarn("lock tables nodes write, reserved write");
foreach my $vnodeid (@vnodes) {
DBQueryWarn("delete from reserved where node_id='$vnodeid'");
DBQueryWarn("delete from nodes where node_id='$vnodeid'");
}
DBQueryFatal("unlock tables");
foreach my $vnodeid (@vnodes) {
my $interface = Interface->LookupControl($vnodeid);
if( defined( $interface ) ) {
my $addr = $interface->IP();
DBQueryWarn( "UPDATE virt_node_public_addr SET node_id=NULL, " .
"card='0', port='0' WHERE IP='$addr'" );
$interface->Delete();
}
DBQueryWarn("delete from node_bootlogs where node_id='$vnodeid'");
DBQueryWarn("delete from node_hostkeys where node_id='$vnodeid'");
DBQueryWarn("delete from node_status where node_id='$vnodeid'");
DBQueryWarn("delete from node_rusage where node_id='$vnodeid'");
# Need to clean out some additional tables since some vnodes can be
# reimaged now!
DBQueryWarn("delete from current_reloads where node_id='$vnodeid'");
DBQueryWarn("delete from partitions where node_id='$vnodeid'");
# Slothd updates/creates these records.
DBQueryWarn("delete from node_activity where node_id='$vnodeid'");
DBQueryWarn("delete from node_idlestats where node_id='$vnodeid'");
DBQueryWarn("delete from iface_counters where node_id='$vnodeid'"); }
return 0;
}
sub SetNodeHistory($$$$)
{
my ($self, $op, $user, $experiment) = @_;
my $exptidx = $experiment->idx();
my $nodeid = $self->node_id();
my $cnet_ip;
my $cnet_mac;
my $phys_nodeid;
my $now = time();
my $uid;
my $uid_idx;
if (!defined($user)) {
# Should this be elabman instead?
$uid = "root";
$uid_idx = 0;
}
else {
$uid = $user->uid();
$uid_idx = $user->uid_idx();
}
if ($op eq TB_NODEHISTORY_OP_MOVE() || $op eq TB_NODEHISTORY_OP_FREE()) {
# Summary info. We need the last entry made.
my $query_result =
DBQueryWarn("select exptidx,stamp from node_history ".
"where node_id='$nodeid' ".
"order by stamp desc limit 1");
if ($query_result && $query_result->numrows) {
my ($lastexptidx,$stamp) = $query_result->fetchrow_array();
my $checkexp;
if ($op eq TB_NODEHISTORY_OP_FREE()) {
$checkexp = $experiment;
}
else {
$checkexp = LocalExpLookup($lastexptidx);
}
if (defined($checkexp)) {
if ($checkexp->pid() eq NODEDEAD_PID() &&
$checkexp->eid() eq NODEDEAD_EID()) {
my $diff = $now - $stamp;
DBQueryWarn("update node_utilization set ".
" down=down+$diff ".
"where node_id='$nodeid'")
}
else {
my $diff = $now - $stamp;
DBQueryWarn("update node_utilization set ".
" allocated=allocated+$diff ".
"where node_id='$nodeid'");
}
}
}
}
elsif ($op eq TB_NODEHISTORY_OP_CREATE()) {
#
# No need to waste space on cnet_IP for phys nodes, or on
# the destroy op. Ditto the phys_nodeid.
#
require Interface;
# Lets make sure no one calls this for a real node.
if (! $self->isvirtnode()) {
print STDERR
"*** SetNodeHistory: '$op' issued for phys node $self\n";
return -1;
}
my $interface = Interface->LookupControl($self);
if (!defined($interface)) {
print STDERR
"*** SetNodeHistory: No control interface for $self\n";
}
else {
$cnet_ip = $interface->IP();
$cnet_mac = $interface->mac()
if (defined($interface->mac()));
if (!defined($cnet_ip) || $cnet_ip eq "") {
print STDERR
"*** SetNodeHistory: No control IP for $interface\n";
$cnet_ip = undef;
}
}
$phys_nodeid = $self->phys_nodeid();
}
elsif ($op eq TB_NODEHISTORY_OP_DESTROY()) {
# Lets make sure no one calls this for a real node.
if (! $self->isvirtnode()) {
print STDERR
"*** SetNodeHistory: '$op' issued for phys node $self\n";
return -1;
}
}
return DBQueryWarn("insert into node_history set ".
" history_id=0, node_id='$nodeid', op='$op', ".
" uid='$uid', uid_idx='$uid_idx', ".
(defined($cnet_ip) ? "cnet_IP='$cnet_ip'," : "").
(defined($cnet_mac) ? "cnet_mac='$cnet_mac'," : "").
(defined($phys_nodeid) ? "phys_nodeid='$phys_nodeid'," : "").
" stamp=$now, exptidx=$exptidx");
}
#
# Set the scheduled_reloads for a node. Type is optional and defaults to
# testbed default load type. See above.
#
sub SetSchedReload($$;$)
{
my ($self, $imageid, $type) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
$type = TB_DEFAULT_RELOADTYPE
if (!defined($type));
return -1
if (! DBQueryWarn("replace into scheduled_reloads ".
"(node_id, image_id, reload_type) values ".
"('$node_id', '$imageid', '$type')"));
return 0;
}
sub GetSchedReload($)
{
my ($self) = @_;
my $node_id = $self->node_id();
my $query_result =
DBQueryWarn("select image_id,reload_type from scheduled_reloads " .
"where node_id='$node_id'");
return ()
if (! (defined($query_result) && $query_result->numrows));
return $query_result->fetchrow_array();
}
sub ClearSchedReload($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
DBQueryWarn("delete from scheduled_reloads where node_id='$node_id'");
return 0;
}
sub ClearCurrentReload($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
DBQueryWarn("delete from current_reloads where node_id='$node_id'");
return 0;
}
sub ClearPartitions($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
DBQueryWarn("delete from partitions where node_id='$node_id'");
return 0;
}
sub ClearReservation($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
if (DBQueryWarn("delete from reserved where node_id='$node_id'")) {
$self->FlushReserved();
}
return 0;
}
#
# Mark a node as down. We schedule a next reservation for it so that it
# remains in the users experiment through the termination so that there
# are no permission errors (say, from snmpit).
#
sub MarkAsDown($)
{
my ($self) = @_;
my $nodeid = (ref($self) ? $self->node_id() : $self);
if (ClearBootAttributes($nodeid)) {
print STDERR "*** WARNING: Could not clear boot attributes: $self!\n";
}
my $pid = NODEDEAD_PID();
my $eid = NODEDEAD_EID();
my $experiment = LocalExpLookup($pid, $eid);
if (!defined($experiment)) {
print STDERR "*** WARNING: No such experiment $pid/$eid!\n";
return -1;
}
my $exptidx = $experiment->idx();
my $query_result =
DBQueryWarn("replace into next_reserve " .
"(node_id, exptidx, pid, eid) " .
"values ('$nodeid', '$exptidx', '$pid', '$eid')");
if (!$query_result || !$query_result->num_rows) {
print STDERR "*** WARNING: Could not mark $self as down\n";
return -1;
}
return 0;
}
sub MarkAsIll($)
{
my ($self) = @_;
my $nodeid = (ref($self) ? $self->node_id() : $self);
if (ClearBootAttributes($nodeid)) {
print STDERR "*** WARNING: Could not clear boot attributes: $self!\n";
}
my $pid = NODEILL_PID();
my $eid = NODEILL_EID();
my $experiment = LocalExpLookup($pid, $eid);
if (!defined($experiment)) {
print STDERR "*** WARNING: No such experiment $pid/$eid!\n";
return -1;
}
my $exptidx = $experiment->idx();
my $query_result =
DBQueryWarn("replace into next_reserve " .
"(node_id, exptidx, pid, eid) " .
"values ('$nodeid', '$exptidx', '$pid', '$eid')");
if (!$query_result || !$query_result->num_rows) {
print STDERR "*** WARNING: Could not mark $self as ill\n";
return -1;
}
return 0;
}
#
# Set the boot status for a node. We also update the fail stamp/count
# as appropriate.
#
sub SetBootStatus($$)
{
my ($self, $bstat) = @_;
my $nodeid = (ref($self) ? $self->node_id() : $self);
return -1
if (!DBQueryWarn("update nodes set bootstatus='$bstat' ".
"where node_id='$nodeid'"));
return 0;
}
#
# Do a normal wakeonlan after power cycle. This is for laptops that like
# to go to sleep (especially while in PXEWAIT).
#
sub SimpleWOL($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
# XXX Must know the outgoing interface. Using the whol flag. Ick.
my $query_result =
DBQueryFatal("select iface from interfaces ".
"where node_id='boss' and whol=1");
if ($query_result->numrows != 1) {
warn "SimpleWOL: Could not get outgoing interface for boss node.\n";
return -1;
}
my ($iface) = $query_result->fetchrow_array();
#
# Grab the control interface MAC for the node.
#
$query_result =
DBQueryFatal("select mac from interfaces ".
"where node_id='$node_id' and ".
" role='" . TBDB_IFACEROLE_CONTROL() . "'");
if ($query_result->numrows != 1) {
warn "SimpleWOL: Could not get control interface MAC for $node_id.\n";
return -1;
}
my ($mac) = $query_result->fetchrow_array();
print "Doing a plain WOL to $node_id ($mac) via interface $iface\n";
#
# Do this a few times since the packet could get lost and
# it seems to take a couple of packets to kick it.
#
for (my $i = 0; $i < 5; $i++) {
system("$WOL $iface $mac");
select(undef, undef, undef, 0.1);
}
select(undef, undef, undef, 5.0);
return 0;
}
sub NewRootPasswd($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
my $hash = TBGenSecretKey();
# But only part of it.
$hash = substr($hash, 0, 10);
DBQueryWarn("replace into node_attributes set ".
" node_id='$node_id',".
" attrkey='root_password',attrvalue='$hash'")
or return -1;
return 0;
}
#
# Invoke OS selection. Currently we use this to reset to default boot,
# but might change later to take an argument.
#
sub SelectOS($;$)
{
my ($self, $osid) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
my $args = (defined($osid) ? "$osid" : "-b");
system("$OSSELECT $args $node_id");
return -1
if ($?);
return 0;
}
#
# Set geni sliver idx,tmcd for the node. Called out of the geni libraries
# when the sliver that corresponds to the node has been created.
#
sub SetGeniSliverInfo($$;$)
{
my ($self, $idx, $tmcd_redirect) = @_;
return -1
if (! (ref($self)));
my $args = {"genisliver_idx" => $idx};
if (defined($tmcd_redirect)) {
$args->{'tmcd_redirect'} =
($tmcd_redirect eq "" ? "NULL" : $tmcd_redirect);
}
return $self->ModifyReservation($args);
}
#
# Get the geni info for a node.
#
sub GetGeniSliverInfo($$;$)
{
my ($self, $idx, $tmcd_redirect) = @_;
return -1
if (! (ref($self)));
my $reservation = $self->ReservedTableEntry();
return -1
if (!defined($reservation));
$$idx = $reservation->{'genisliver_idx'};
$$tmcd_redirect = $reservation->{'tmcd_redirect'}
if defined($tmcd_redirect);
return 0;
}
#
# Set the status slot for a node.
#
sub SetStatus($$)
{
my ($self, $status) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $node_id = $self->node_id();
return -1
if (! DBQueryWarn("update node_status set status='$status' ".
"where node_id='$node_id'"));
$self->{"DBROW"}->{'node_status'} = $status;
return 0;
}
#
#
#
sub OSSelect($$$$)
{
my ($self, $osinfo, $field, $debug) = @_;
my $nodeid = $self->node_id();
my $curmode = $self->op_mode();
require OSinfo;
# Why? When will this happen?
return 0
if (!$curmode);
# Special token, sorry.
if (defined($osinfo) && "$osinfo" eq "") {
$osinfo = OSinfo->Lookup($self->default_osid());
if (!defined($osinfo)) {
print STDERR "Could not map default_osid for $nodeid\n";
return -1;
}
my $nextosinfo = $osinfo->ResolveNextOSID();
if (!defined($nextosinfo)) {
print STDERR "Could not resolve nextosid for $osinfo\n";
return -1;
}
$osinfo = $nextosinfo;
}
if (defined($osinfo) && !defined($field)) {
print STDERR "No field specified for OSSelect on $nodeid\n";
return -1;
}
if ($debug) {
print STDERR "Current opmode for $nodeid is $curmode.\n";
if (defined($osinfo)) {
print STDERR "Setting $field for $nodeid to $osinfo.\n";
} elsif (defined($field)) {
print STDERR "Clearing $field for $nodeid.\n";
} else {
print STDERR "Clearing all boot_osids for $nodeid.\n";
}
}
if (!defined($field)) {
# Clear all osids.
DBQueryWarn("update nodes set ".
"def_boot_osid=NULL,next_boot_osid=NULL,temp_boot_osid=NULL ".
"where node_id='$nodeid'")
or return -1;
} else {
# Set/Clear the osid.
DBQueryWarn("update nodes set ${field}=".
(defined($osinfo) ? "'" . $osinfo->osid() . "' " : "NULL ") .
"where node_id='$nodeid'")
or return -1;
return -1
if ($self->ResetNextOpMode($debug) < 0);
}
return Refresh($self);
}
#
#
#
sub PXESelect($$$$$)
{
my ($self, $path, $field, $debug, $changedp) = @_;
my $nodeid = $self->node_id();
my $didit = 0;
print STDERR "Setting $field for $nodeid to '$path'.\n"
if ($debug && $path);
my $cur = ($field eq "pxe_boot_path") ?
$self->pxe_boot_path() : $self->next_pxe_boot_path();
$cur = "" if (!$cur);
if ($cur ne $path) {
DBQueryWarn("update nodes set ${field}=".
($path ? "'$path'" : "NULL") .
" where node_id='$nodeid'")
or return -1;
$didit = 1;
}
$$changedp = $didit
if (defined($changedp));
return Refresh($self);
}
sub ResetNextOpMode($$)
{
my ($self,$debug) = @_;
my $nodeid = $self->node_id();
my $curmode = $self->op_mode();
# Why? When will this happen?
return 0
if (!$curmode);
#
# Determine what osid the node will now boot. We need to know this so we
# can set the next opmode. This call has to return *something* or we are
# screwed since we will not be able to figure out the opmode.
#
my ($bootosid, $bootopmode) = TBBootWhat($nodeid, $debug);
if (!defined($bootosid)) {
print STDERR "Bootwhat query failed for $nodeid!\n";
return -1;
}
#
# If it returned 0 the node is in PXEWAIT.
# For the node to do anything useful going forward, someone will
# have to first set one of the osids with os_select.
#
if ($bootosid == 0) {
return 0;
}
print STDERR "Bootwhat says: $nodeid => $bootosid,$bootopmode\n"
if ($debug);
#
# If its different then what the node is currently booting, then
# set up a transition in stated. If no change, be sure to clear
# is since stated does not like a transition to be specified when
# none is actually going to be made.
#
if ($curmode eq $bootopmode) {
$bootopmode = "";
}
DBQueryWarn("update nodes set next_op_mode='$bootopmode' ".
"where node_id='$nodeid'")
or return -1;
return 0;
}
#
# Get the next rtabid for a shared node. Need locking in this case
# since multiple mappers can be running at once.
#
# We only need to do this on the physical node. On the virtual node, use
# the same slot to store what rtabid was assigned to the vnode.
#
sub Nextrtabid($)
{
my ($self) = @_;
my $rtabid = undef;
my $node_id = $self->node_id();
DBQueryWarn("lock tables nodes write")
or return undef;
DBQueryWarn("update nodes set rtabid=rtabid+1 ".
"where node_id='$node_id'")
or goto bad;
my $query_result =
DBQueryWarn("select rtabid from nodes ".
"where node_id='$node_id'");
goto bad
if (!$query_result || !$query_result->numrows);
($rtabid) = $query_result->fetchrow_array();
bad:
DBQueryWarn("unlock tables");
return $rtabid;
}
#
# Set rtabid for a node.
#
sub Setrtabid($$)
{
my ($self, $rtabid) = @_;
return -1
if (! (ref($self)));
my $node_id = $self->node_id();
DBQueryWarn("update nodes set rtabid='$rtabid' where node_id='$node_id'")
or return -1;
return Refresh($self);
}
#
# Get the max share count for a node. This is actually the pcvm count
# from the aux table, but might change someday, I hope.
#
sub MaxShareCount($)
{
my ($self) = @_;
return -1
if (! (ref($self)));
my $node_id = $self->node_id();
my $query_result =
DBQueryWarn("select count from node_auxtypes ".
"where node_id='$node_id' and type='pcvm'");
return -1
if (!$query_result);
return 10
if (!$query_result->numrows);
my ($count) = $query_result->fetchrow_array();
return $count;
}
sub IsVirtHost($)
{
my $self = shift;
return 1
if ($self->{"DBROW"}{"role"} eq 'virthost');
return 0;
}
sub GetPhysHost($) {
my $self = shift;
my $phys_nodeid = $self->{"DBROW"}{"phys_nodeid"};
if (defined($phys_nodeid) && $phys_nodeid ne '') {
return Node->Lookup($phys_nodeid);
}
return undef;
}
sub GetOsids($) {
my $self = shift;
return ($self->{"DBROW"}{"def_boot_osid"},
$self->{"DBROW"}{"temp_boot_osid"},
$self->{"DBROW"}{"next_boot_osid"});
}
sub ClearOsids($) {
my $self = shift;
my $node_id = $self->node_id();
DBQueryWarn("update nodes set ".
" def_boot_osid=NULL,".
" next_boot_osid=NULL,".
" temp_boot_osid=NULL ".
"where node_id='$node_id'")
or return -1;
$self->{"DBROW"}{"def_boot_osid"} = undef;
$self->{"DBROW"}{"temp_boot_osid"} = undef;
$self->{"DBROW"}{"next_boot_osid"} = undef;
return 0;
}
#
# Look for a widearea node by its external node id.
#
sub LookupWideArea($$)
{
my ($class, $external_node_id) = @_;
my $safe_id = DBQuoteSpecial($external_node_id);
my $query_result =
DBQueryWarn("select node_id from widearea_nodeinfo ".
"where external_node_id=$safe_id");
return undef
if (!defined($query_result) || !$query_result->numrows);
my ($node_id) = $query_result->fetchrow_array();
return Node->Lookup($node_id);
}
#
# Return the partition that an OSID is loaded on.
#
sub IsOSLoaded($$)
{
my ($self, $osinfo) = @_;
require OSinfo;
if (!ref($osinfo)) {
my $tmp = OSinfo->Lookup($osinfo);
if (!defined($tmp)) {
print STDERR "Cannot lookup osinfo for $osinfo\n";
return -1;
}
$osinfo = $tmp;
}
my $osid = $osinfo->osid();
my $nodeid = $self->node_id();
my $query_result =
DBQueryWarn("select osid from partitions as p ".
"where p.node_id='$nodeid' and p.osid='$osid'");
return -1
if (!$query_result);
return $query_result->numrows;
}
#
# Determine an IP address for a jail node. Lifted from CreateVnodes() above.
#
sub GetJailIP($;$)
{
my ($self, $num) = @_;
my $ipbase;
$num = 2
if (!defined($num));
#
# Need IP for jailed nodes.
#
my $IPBASE = TBDB_JAILIPBASE();
my $IPBASE1;
my $IPBASE2;
if ($IPBASE =~ /^(\d+).(\d+).(\d+).(\d+)/) {
$IPBASE1 = $1;
$IPBASE2 = $2;
}
else {
print STDERR "*** GetJailIP: Bad IPBASE '$IPBASE'\n";
return undef;
}
#
# Determine ipbase from the control IP (jailed nodes).
#
my $interface = Interface->LookupControl($self);
if (!defined($interface)) {
print STDERR "*** GetJailIP: No control interface for $self\n";
return undef;
}
my $ctrlip = $interface->IP();
if (!defined($ctrlip) || $ctrlip eq "") {
print STDERR "*** GetJailIP: No control IP for $interface\n";
return undef;
}
my $tmp = ~inet_aton($CONTROL_NETMASK) & inet_aton($ctrlip);
$ipbase = unpack("N", $tmp);
if ($ipbase == 0 || $ipbase < 0 || $ipbase > 0x3fff) {
print STDERR "*** GetJailIP: Bad ipbase '$ipbase' for $interface\n";
return undef;
}
my $node_id = $self->node_id();
my $query_result =
DBQueryWarn( "SELECT IP, mask FROM virt_node_public_addr WHERE " .
"node_id='$node_id' AND card='0' AND port='1'" );
if( $query_result && $query_result->numrows ) {
# we've assigned a public address to this one
return $query_result->fetchrow_array();
} else {
my $nodenumlimit = $ISUTAH ? 200 : 254;
my $pnet = $IPBASE2;
my $pnode2 = int($ipbase);
while ($pnode2 > $nodenumlimit) {
$pnet++;
$pnode2 -= $nodenumlimit;
}
return ("${IPBASE1}.${pnet}.${pnode2}.${num}", $JAILIPMASK);
}
}
#
# Another variant of above, this one looks in the virt_node_attributes
# table of the vnode assigned to the pnode.
#
sub SetJailIPFromVnode($$$)
{
my ($self, $experiment, $vnode_id) = @_;
my ($jailip, $jailipmask);
return -1
if ($experiment->GetVirtNodeAttribute($vnode_id,
"jailip", \$jailip) < 0 ||
$experiment->GetVirtNodeAttribute($vnode_id,
"jailipmask", \$jailipmask) < 0);
if (defined($jailip)) {
if (!defined($jailipmask)) {
print STDERR
"*** $vnode_id has a jailip attribute but no jailipmask.\n";
return -1;
}
#
# We now create interface entries for local jailed nodes, so have
# to update the entry. We still use jailip under some circumstances.
#
if (!$self->isremotenode() && !defined($self->jailip())) {
my $interface = Interface->LookupControl($self);
if (!defined($interface)) {
print STDERR "*** $vnode_id does not have a control interface\n";
return -1;
}
$interface->Update({"IP" => $jailip,
"mask" => $jailipmask})
== 0 or return -1;
}
else {
return $self->Update({"jailip" => $jailip,
"jailipmask" => $jailipmask});
}
}
return 0;
}
#
# Check for, and update a node pre reservation.
#
sub CheckPreReserve($$)
{
my ($self, $quiet) = @_;
my $result = undef;
#
# Look for a pre-reserve request.
#
my $type = $self->type();
my $node_id = $self->node_id();
DBQueryWarn("lock tables node_reservations write, nodes write")
or return undef;
#
# Need to check for existing reserved_pid, but have to go to the DB,
# not look in the object ($self) since it might be stale.
#
my $query_result =
DBQueryWarn("select reserved_pid,count,active from nodes ".
"left join node_reservations on ".
" node_reservations.pid=nodes.reserved_pid ".
"where nodes.node_id='$node_id'");
goto done
if (!defined($query_result) || !$query_result->numrows);
#
# If there is a reserved pid already set for this node, check to see
# if the reervation request is still active. If not, we can clear it,
# which will allow it to be set again below, if needed.
#
my ($pid,$count,$active) = $query_result->fetchrow_array();
if (defined($pid)) {
goto done
if (defined($count) && $active);
DBQueryWarn("update nodes set reserved_pid=null ".
"where node_id='$node_id'");
if (!$quiet) {
print "Clearing pre reserve for $node_id\n";
}
}
# Find only active unfilled reservations.
$query_result =
DBQueryWarn("select pid,count from node_reservations ".
"where active=1 and count>0 and ".
" (types is null or ".
" FIND_IN_SET('$type', types)) ".
"order by priority desc, created asc ".
"limit 1");
goto done
if (!$query_result);
if ($query_result->numrows) {
my ($pid,$count) = $query_result->fetchrow_array();
if (DBQueryWarn("update nodes set reserved_pid='$pid' ".
"where node_id='$node_id'")) {
DBQueryWarn("update node_reservations set count=count-1 ".
"where pid='$pid'");
$result = $pid;
if ($count == 1) {
SENDMAIL($TBOPS, "Pre Reservation for $pid has completed",
"The pre reservation request for project $pid, ".
"has been fullfilled\n", $TBOPS);
}
}
}
done:
DBQueryWarn("unlock tables");
if (defined($result) && !$quiet) {
print "Setting pre reserve for $node_id to $result\n";
}
return $result;
}
#
# Add an outlet entry. Optional authorization info.
#
sub AddOutlet($$$$)
{
my ($self, $powerid, $outlet, $authinfo) = @_;
my $safe_powerid = DBQuoteSpecial($powerid);
my $safe_outlet = DBQuoteSpecial($outlet);
my $node_id = $self->node_id();
DBQueryWarn("replace into outlets set ".
" node_id='$node_id', power_id=$safe_powerid, ".
" outlet=$safe_outlet")
or return -1;
if (defined($authinfo)) {
my $key_type = DBQuoteSpecial($authinfo->{"key_type"});
my $key_role = DBQuoteSpecial($authinfo->{"key_role"});
my $key_uid = DBQuoteSpecial($authinfo->{"key_uid"});
my $key = DBQuoteSpecial($authinfo->{"key"});
DBQueryWarn("replace into outlets_remoteauth set ".
" node_id='$node_id', key_type=$key_type, ".
" key_role=$key_role, key_uid=$key_uid, mykey=$key")
or return -1;
}
return 0;
}
sub DeleteOutlet($)
{
my ($self) = @_;
my $node_id = $self->node_id();
DBQueryWarn("delete from outlets_remoteauth where node_id='$node_id'")
or return -1;
DBQueryWarn("delete from outlets where node_id='$node_id'")
or return -1;
return 0;
}
sub GetOutletAuthInfo($$)
{
my ($self, $keytype) = @_;
my $node_id = $self->node_id();
my $query_result =
DBQueryWarn("select key_uid,mykey from outlets_remoteauth ".
"where node_id='$node_id' and key_role='$keytype'");
return undef
if (!defined($query_result) || !$query_result->numrows);
my ($login,$auxinfo) = $query_result->fetchrow_array();
return ($login, $auxinfo);
}
#
# Get the currently running os/image for a node.
#
sub RunningOsImage($)
{
require OSinfo;
my ($self) = @_;
my $nodeid = $self->node_id();
my $osid = $self->def_boot_osid();
my $imageid;
my $osinfo = OSinfo->Lookup($osid);
return ()
if (!defined($osinfo));
#
# No partition entries for virtnodes, they are plain EZ images.
#
if ($self->isvirtnode()) {
$imageid = $osid;
}
else {
my $query_result =
DBQueryWarn("select imageid from partitions as p ".
"where p.node_id='$nodeid' and p.osid='$osid'");
return ()
if (!$query_result || !$query_result->numrows);
($imageid) = $query_result->fetchrow_array();
}
# This might not exist for a virtnode; caller has to deal with it.
my $image = Image->Lookup($imageid);
return ($osinfo, $image);
}
# _Always_ make sure that this 1 is at the end of the file...
1;