Commit bc8afd40 authored by Leigh B Stoller's avatar Leigh B Stoller
Browse files

Anytime the state for a slice or sliver changes, inject a geni style event

into the local event stream. These events are different then normal emulab
events in that the SITE is set to the URN of the aggregate, and there is
json representation of the slice/sliver status. There are other fields as
well that are not in normal emulab events. These events can mix okay with
emulab events on the local boss, nothing will care about them. But they
will get forwarded to pubsubd at the portal if CLUSTER_PORTAL is set in the
defs file.
parent 6e391619
#
# Copyright (c) 2008-2015 University of Utah and the Flux Group.
# Copyright (c) 2008-2016 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -43,7 +43,7 @@ LIB_SCRIPTS = GeniDB.pm GeniUser.pm \
GeniSES.pm GeniResource.pm GeniXML.pm GeniAM.pm \
GeniEmulab.pm GeniFoam.pm GeniStitch.pm GeniIMS.pm \
GeniStd.pm GeniMA.pm GeniStdSA.pm GeniSR.pm GeniPortal.pm \
GeniImage.pm
GeniImage.pm GeniEvent.pm
SBIN_SCRIPTS = plabnodewrapper plabslicewrapper
SCRIPTS = genischemacheck.pl
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2008-2015 University of Utah and the Flux Group.
# Copyright (c) 2008-2016 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -50,6 +50,7 @@ use GeniUtil;
use GeniUser;
use GeniComponent;
use GeniHRN;
use GeniEvent;
use GeniXML;
use emutil;
use EmulabConstants;
......@@ -591,26 +592,46 @@ sub IsMember($$)
}
#
# Set the status for the aggregate
# Set the state and status for the aggregate. The goal here is to send
# just a single event when both state/status are being updated.
#
sub SetStatus($$)
sub SetStateStatus($$$)
{
my ($self, $status) = @_;
my ($self, $state, $status) = @_;
return undef
if (! ref($self));
my $idx = $self->idx();
my $query_result =
DBQueryWarn("update geni_aggregates set ".
" status='$status',state='$state' ".
"where idx='$idx' and ".
" (status!='$status' or state!='$state')");
return -1
if (!DBQueryWarn("update geni_aggregates set ".
" status='$status' ".
"where idx='$idx'"));
if (!defined($query_result));
$self->{'AGGREGATE'}->{'state'} = $state;
$self->{'AGGREGATE'}->{'status'} = $status;
# Any change, send the event.
$self->SendStatusEvent()
if ($self->type() eq "Aggregate" && $query_result->affectedrows);
return 0;
}
#
# Set the status for the aggregate.
#
sub SetStatus($$)
{
my ($self, $status) = @_;
return $self->SetStateStatus($self->state(), $status);
}
#
# Set the state for the aggregate
#
......@@ -618,17 +639,26 @@ sub SetState($$)
{
my ($self, $state) = @_;
return undef
if (! ref($self));
return $self->SetStateStatus($state, $self->status());
}
my $idx = $self->idx();
sub SendStatusEvent($)
{
my ($self) = @_;
my $slice = $self->GetSlice();
return -1
if (!DBQueryWarn("update geni_aggregates set ".
" state='$state' ".
"where idx='$idx'"));
if (!defined($slice));
$self->{'AGGREGATE'}->{'state'} = $state;
my $blob = {
"state" => $self->state(),
"status" => $self->status(),
"utc" => time(),
};
GeniEvent->SendEvent({"type" => "SLICESTATUS",
"slice" => $slice->urn(),
"urn" => $slice->urn(),
"details" => $blob});
return 0;
}
......@@ -2366,33 +2396,36 @@ sub ComputeState($)
}
}
my ($newstate,$newstatus);
if ($stopped == $count) {
$self->SetState("stopped");
$newstate = "stopped";
}
elsif ($started == $count) {
$self->SetState("started");
$newstate = "started";
}
else {
$self->SetState("mixed");
$newstate = "mixed";
}
if ($ready == $count) {
$self->SetStatus("ready");
$newstatus = "ready";
}
elsif ($notready == $count) {
$self->SetStatus("notready");
$newstatus = "notready";
}
elsif ($changing == $count) {
$self->SetStatus("changing");
$newstatus = "changing";
}
elsif ($failed) {
$self->SetStatus("failed");
$newstatus = "failed";
}
else {
$self->SetStatus("mixed");
$newstatus = "mixed";
}
if ($boot_failure == 1) {
$self->SetStatus("failed");
$newstatus = "failed";
}
$self->SetStateStatus($newstate, $newstatus);
return 0;
}
......
......@@ -1226,6 +1226,7 @@ sub SliverStatus($)
my $blob = {
"state" => $aggregate->state(),
"status" => $aggregate->status(),
"utc" => time(),
"details" => {},
};
$blob->{'public_url'} =
......@@ -1242,54 +1243,15 @@ sub SliverStatus($)
elsif ($sliver->resource_type() ne "Node") {
next;
}
my $sliver_urn = $sliver->sliver_urn();
my $resource_id = $sliver->resource_id();
my $state = $sliver->state();
my $status = $sliver->status();
my $nickname = $sliver->nickname() || "";
my $error = "";
# New is the same as stopped. Separate state is handy.
$state = "stopped"
if ($state eq "new");
if ($status eq "failed") {
$error = $sliver->ErrorLog();
}
$blob->{'details'}->{$sliver_urn} = {
"component_urn" => $resource_id,
"client_id" => $nickname,
"state" => $state,
"status" => $status,
"error" => $error,
};
if (ref($sliver) eq "GeniSliver::Node") {
my $details = $blob->{'details'}->{$sliver_urn};
if (defined($sliver->rawstate())) {
$details->{'rawstate'} = $sliver->rawstate();
}
my $node = Node->Lookup($sliver->resource_id());
if (defined($node) &&
defined($node->startupcmd()) && $node->startupcmd() ne "") {
if (defined($node->startstatus()) &&
$node->startstatus() ne "none") {
$details->{'execute_state'} = "exited";
$details->{'execute_status'} = $node->startstatus();
}
else {
if ($node->eventstate() eq "ISUP") {
$details->{'execute_state'} = "running";
$details->{'execute_status'} = "unknown";
}
else {
$details->{'execute_state'} = "n/a";
$details->{'execute_status'} = "n/a";
}
}
}
my $sliver_urn = $sliver->sliver_urn();
my $details = $sliver->GenerateStatusBlob();
next
if (!defined($details));
if ($details->{'status'} eq "failed") {
$details->{'error'} = $sliver->ErrorLog();
}
$blob->{'details'}->{$sliver_urn} = $details;
}
$slice->UnLock();
return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob);
......@@ -3016,6 +2978,7 @@ sub CreateImage($)
require EmulabConstants;
require Image;
require WebTask;
require GeniEvent;
if (! (defined($credentials) && defined($imagename) &&
defined($slice_urn) && defined($sliver_urn))) {
......@@ -3240,7 +3203,7 @@ dataset:
return GeniResponse->Create(GENIRESPONSE_SUCCESS, \@blob);
}
$slice->UnLock();
#
# Do the snapshot.
#
......@@ -3252,7 +3215,47 @@ dataset:
# After clone_image, we can determine the new image name. Versioning
# was handled in clone_image.
$imagename = $image->imagename() . ":" . $image->version();
#
# Okay we are going to fork again and watch the webtask for
# size changes (and exit of course) and generate Geni style
# events.
#
$mypid = main::WrapperFork();
if ($mypid) {
my $lastsize = 0;
my $laststatus = "";
do {
sleep(5);
# Probably deleted.
$webtask->Refresh() == 0
or last;
# Check the child, if it is gone we want to stop too.
if (kill(0, $mypid) == 0) {
last
if ($!{ESRCH});
}
if ($webtask->imagesize() != $lastsize ||
$webtask->status() ne $laststatus) {
GeniEvent->SendEvent({"type" => "IMAGESTATUS",
"slice" => $slice->urn(),
"urn" => $image_urn,
"details" =>
{ "utc" => time(),
"size" => $webtask->imagesize(),
"status" => $webtask->status()}});
$lastsize = $webtask->imagesize();
$laststatus = $webtask->status();
}
} while (!$webtask->HasExited());
# Sleep a little while to give the caller time to pick up status
# since it is polling. Then delete the webtask.
sleep(30);
$webtask->Delete();
waitpid($mypid, &WNOHANG);
return 0;
}
$output =
GeniUtil::ExecQuiet("$CREATEIMAGE -F -e -f $opt ".
" -p $pid $imagename $node_id");
......@@ -3465,7 +3468,7 @@ sub ImageInfo($)
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"Not enough permission to access image");
}
my $blob = { "size" => 0, "status" => "ready" };
my $blob = { "size" => 0, "status" => "ready", "utc" => time() };
#
# Is there an active webtask, then we are taking a snapshot, so
......@@ -3475,15 +3478,6 @@ sub ImageInfo($)
if (defined($webtask)) {
$blob->{'size'} = $webtask->imagesize() . "KB";
$blob->{'status'} = $webtask->status();
#
# We have reported status, kill it. So if two parties are
# trying to determine the status, one gets nothing. Maybe
# we need a notion of staleness.
#
if ($webtask->HasExited()) {
$webtask->Delete();
}
}
else {
my $path = ($image->HaveDeltaImage() ?
......@@ -4912,7 +4906,7 @@ sub RunLinktest($)
# This is a pain; if linktest bailed early, the log file will
# be empty, and the user will not see any output cause we have
# forked and so there is no way to return the output. So stuff it
# into the logfile.
# into the logfile for it to be spewed to the user.
#
if ($exitval && -z $fname) {
if (open(LOG, ">" . $logfile->filename())) {
......
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2016 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
# GENI Public License
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and/or hardware specification (the "Work") to
# deal in the Work without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Work, and to permit persons to whom the Work
# is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Work.
#
# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
# IN THE WORK.
#
# }}}
#
package GeniEvent;
#
# Event hooks for Geni. The point of this to inject Geni style events
# to the event stream. They look different from standard emulab events,
# so they can mix okay. The igevent_daemon will handle getting them to
# the portal.
#
use strict;
use Exporter;
use vars qw(@ISA @EXPORT @EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT = qw();
# Must come after package declaration!
use event;
use GeniDB;
use GeniSlice;
use GeniHRN;
use emutil;
use Node;
use libtestbed;
use Data::Dumper;
use English;
use JSON;
# Configure variables
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $BOSSNODE = "@BOSSNODE@";
my $OURDOMAIN = "@OURDOMAIN@";
my $MAINSITE = @TBMAINSITE@;
my $MYURN = "urn:publicid:IDN+${OURDOMAIN}+authority+cm";
my $GeniEventHande;
#
# Create a connection to the local clusterd, which is going to forward
# events to the mothership.
#
sub Create($;$$)
{
my ($class, $server, $port) = @_;
$server = "localhost"
if (!defined($server));
$port = (defined($port) ? ":$port" : "");
my $url = my $URL = "elvin://${server}${port}";
my $handle = event_register($URL, 0);
return undef
if (!$handle);
my $self = {};
$self->{'HANDLE'} = $handle;
bless($self, $class);
return $self;
}
sub DESTROY
{
my $self = shift;
if (defined($self->{'HANDLE'})) {
event_unregister($self->{'HANDLE'});
$self->{'HANDLE'} = undef;
}
}
#
# Send an event to the local pubsubd.
#
sub SendEvent($$)
{
my ($self, $hash) = @_;
#print STDERR Dumper($hash);
if (!ref($self)) {
if (!defined($GeniEventHande)) {
$GeniEventHande = GeniEvent->Create();
if (!defined($GeniEventHande)) {
print STDERR "Could not register with event server\n";
return -1;
}
}
}
my $handle = (ref($self) ? $self->{'HANDLE'} : $GeniEventHande->{'HANDLE'});
my $tuple = address_tuple_alloc();
if (!defined($tuple)) {
print STDERR "Could not allocate tuple\n";
return -1;
}
my $notification = event_notification_alloc($handle, $tuple);
if (!$notification) {
print STDERR "Could not allocate notification\n";
address_tuple_free($tuple);
return -1;
}
address_tuple_free($tuple);
#
# We fill in the tuple stuff ourself instead.
#
foreach my $key (keys(%{ $hash })) {
my $val = $hash->{$key};
if (ref($val)) {
$val = eval { encode_json($val); };
if ($@) {
print STDERR "Could not json encode event data:\n";
print STDERR Dumper($hash->{$key});
next;
}
}
event_notification_put_string($handle, $notification, $key, $val);
}
event_notification_remove($handle, $notification, "SITE");
event_notification_put_string($handle, $notification, "SITE", $MYURN);
if (!event_notify($handle, $notification)) {
event_notification_free($handle, $notification);
print STDERR "Could not send event\n";
return -1;
}
event_notification_free($handle, $notification);
return 0;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
#!/usr/bin/perl -wT
#
# Copyright (c) 2008-2015 University of Utah and the Flux Group.
# Copyright (c) 2008-2016 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -46,6 +46,7 @@ use GeniAggregate;
use GeniUsage;
use GeniHRN;
use GeniXML;
use GeniEvent;
use emutil;
use Node;
use English;
......@@ -141,7 +142,6 @@ sub Lookup($$)
$self->{'AGGREGATE'} = undef; # server
$self->{'RSPEC'} = undef; # server
$self->{'CERTIFICATE'} = undef;
$self->{'RAWSTATE'} = undef;
my $rspec_string = $self->{'SLIVER'}->{'rspec_string'};
if (defined($rspec_string) && $rspec_string ne "") {
......@@ -193,6 +193,16 @@ sub Stringify($)
return "[GeniSliver: $uuid, IDX: $idx]";
}
#
# Flush from our little cache, as for the expire daemon.
#
sub Flush($)
{
my ($self) = @_;
delete($slivers{$self->idx()});
}
#
# Create a sliver record in the DB. On the client side we save the credential
# that allows control of it, for later operations.
......@@ -301,7 +311,6 @@ sub aggregate_uuid($) { return field($_[0], "aggregate_uuid"); }
sub rspec_string($) { return field($_[0], "rspec_string"); }
sub status($) { return field($_[0], "status"); }
sub state($) { return field($_[0], "state"); }
sub rawstate($) { return $_[0]->{'RAWSTATE'}; }
sub ErrorLog($) { return field($_[0], "errorlog"); }
sub cert($) { return GetCertificate($_[0])->cert(); }
sub rspec($) { return $_[0]->{'RSPEC'}; }
......@@ -532,6 +541,14 @@ sub SetState($$)
return 0;
}
#
# Generate the blob for status. Redefined below.
#
sub GenerateStatusBlob($)
{
return undef;
}
#
# And the ErrorLog. These are intended to be short ...
#
......@@ -687,6 +704,7 @@ use GeniCertificate;
use GeniUtil;
use emutil;
use XML::Simple;
use Data::Dumper;
use libdb qw(TBDB_ALLOCSTATE_RES_INIT_DIRTY TBDB_NODESTATE_SHUTDOWN
TBResolveNextOSID TBDB_NODESTATE_ISUP TBDB_NODESTATE_TBFAILED
TBDB_NODESTATE_RELOADFAILED TBDB_NODESTATE_PXEWAIT);
......@@ -1305,7 +1323,7 @@ sub Stop($$)
return 0;
}
sub ComputeStatus($$)
sub ComputeStatus($;$)
{
my ($self, $pref) = @_;
my $status = undef;
......@@ -1318,7 +1336,8 @@ sub ComputeStatus($$)
# but if the node actually came up okay later, we set it back to okay.
#
if ($self->status() eq "broken" || $self->status() eq "canceled") {
$$pref = "failed";
$$pref = "failed"
if ($pref);
return 0;
}
......@@ -1335,8 +1354,6 @@ sub ComputeStatus($$)
print STDERR "$node was already released from $self\n";
return -1;
}
# Stash this away.
$self->{'RAWSTATE'} = $node->eventstate();
#
# Special state for updating user accounts. We never go into the
......@@ -1353,7 +1370,7 @@ sub ComputeStatus($$)
$self->SetState("started");
}
}
elsif ($self->state() eq "imaging") {
if ($self->state() eq "imaging") {
$status = "changing";
goto done;
}
......@@ -1400,10 +1417,144 @@ sub ComputeStatus($$)
}
done:
$self->SetStatus($status);
$$pref = $status;
$$pref = $status
if (defined($pref));
return 0;
}
#
# Set the status for the sliver.
#
sub SetStatus($$)
{
my ($self, $status) = @_;
my $idx = $self->idx();
#
# We want to know if the DB actually changed, and if so, we send
# out an event indicating a change.
#
my $query_result =
DBQueryWarn("update geni_slivers set status='$status' ".