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

New cluster status page that provides a global view of all of the nodes

at all participating clusters. Not finished yet, but good enough to let
people at it.
parent 14f231c1
......@@ -32,12 +32,12 @@ SUBDIRS =
BIN_SCRIPTS = manage_profile manage_instance manage_dataset \
create_instance rungenilib
SBIN_SCRIPTS = apt_daemon aptevent_daemon
SBIN_SCRIPTS = apt_daemon aptevent_daemon portal_xmlrpc
LIB_SCRIPTS = APT_Profile.pm APT_Instance.pm APT_Dataset.pm APT_Geni.pm \
APT_Aggregate.pm APT_Utility.pm
WEB_BIN_SCRIPTS = webmanage_profile webmanage_instance webmanage_dataset \
webcreate_instance webrungenilib
WEB_SBIN_SCRIPTS=
webcreate_instance webrungenilib
WEB_SBIN_SCRIPTS= webportal_xmlrpc
LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
USERLIBEXEC = rungenilib.proxy genilib-jail genilib-iocage
......
#!/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.
#
# }}}
#
use strict;
use English;
use Getopt::Std;
use Data::Dumper;
use Time::HiRes qw( gettimeofday tv_interval );
use JSON;
sub usage()
{
print "Usage: portal_xmlrpc [-d] [-a urn] method\n";
exit(1);
}
my $optlist = "da:";
my $debug = 0;
my $aggurn;
# For development.
my $usemydevtree = 0;
#
# Configure variables
#
my $TB = "@prefix@";
my $EMCERT = "$TB/etc/emulab.pem";
my $EMKEY = "$TB/etc/emulab.key";
my $OURDOMAIN = "@OURDOMAIN@";
my $MYURN = "urn:publicid:IDN+${OURDOMAIN}+authority+cm";
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Protos
sub fatal($);
# We always use the CM db.
use vars qw($GENI_DBNAME);
$GENI_DBNAME = "geni-cm";
#
# Turn off line buffering on output
#
$| = 1;
# Now we can load the libraries after setting the proper DB.
use lib '@prefix@/lib';
use GeniHRN;
use Genixmlrpc;
use GeniAuthority;
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"a"})) {
$aggurn = $options{"a"};
}
else {
$aggurn = $MYURN;
}
usage()
if (@ARGV != 1);
my $method = shift(@ARGV);
my $context = Genixmlrpc->Context($EMCERT, $EMKEY);
if (!defined($context)) {
fatal("Could not create context to talk to image server");
}
Genixmlrpc->SetContext($context);
# Shorten default timeout.
Genixmlrpc->SetTimeout(15);
my $authority = GeniAuthority->Lookup($aggurn);
if (!defined($authority)) {
fatal("No such aggregate: $aggurn");
}
my $cmurl = $authority->url();
$cmurl =~ s/\/cm$/\/cluster/;
if ($usemydevtree) {
$cmurl =~ s/protogeni/protogeni\/stoller/;
}
my $starttime = [gettimeofday()];
my $response = Genixmlrpc::CallMethod($cmurl, undef, $method);
my $elapsed = tv_interval($starttime);
print encode_json($response->value());
sub fatal($)
{
my ($msg) = @_;
print STDERR "*** $0:\n".
" $msg\n";
# exit value important.
exit(-1);
}
<?php
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
chdir("..");
chdir("apt");
include_once("instance_defs.php");
#
# Server side of getting dashboard stats.
#
function Do_GetStatus()
{
global $ajax_args, $geni_response_codes, $urn_mapping, $TBBASE;
global $TBSUEXEC_PATH;
if (! (ISADMIN() || ISFOREIGN_ADMIN())) {
SPITAJAX_ERROR(1, "Not enough permission");
return;
}
if (Instance::ValidURN($ajax_args["cluster"])) {
$cluster = $ajax_args["cluster"];
}
else {
$cluster = $ajax_args["cluster"] . ".cm";
}
$inuse = array();
$fp = popen("$TBSUEXEC_PATH elabman tbadmin ".
"webportal_xmlrpc -a $cluster InUse", "r");
$string = "";
while (!feof($fp)) {
$string .= fgets($fp, 1024);
}
pclose($fp);
$inuse = json_decode($string);
#
# We want to convert slice_urns to local instances so that we
# convert the uid/pids to local names and provide links to the
# instances.
#
$instances = array();
foreach ($inuse as $details) {
if (property_exists($details, "slice_uuid") &&
$details->slice_uuid != "") {
if (array_key_exists($details->slice_uuid, $instances)) {
$instance = $instances[$details->slice_uuid];
}
else {
$instance = Instance::LookupBySlice($details->slice_uuid);
$instances[$details->slice_uuid] = $instance;
}
if ($instance) {
$details->pid = $instance->pid();
$details->instance_uuid = $instance->uuid();
$details->instance_name = $instance->name();
}
}
}
$dashboard["inuse"] = $inuse;
SPITAJAX_RESPONSE($dashboard);
}
# Local Variables:
# mode:php
# End:
?>
<?php
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
chdir("..");
include("defs.php3");
include_once("geni_defs.php");
chdir("apt");
include("quickvm_sup.php");
include_once("instance_defs.php");
$page_title = "Cluster Status";
#
# Get current user.
#
RedirectSecure();
$this_user = CheckLoginOrRedirect();
$isadmin = (ISADMIN() ? 1 : 0);
$isfadmin = (ISFOREIGN_ADMIN() ? 1 : 0);
if (! (ISADMIN() || ISFOREIGN_ADMIN())) {
SPITUSERERROR("You do not have permission to view the dashboard");
}
SPITHEADER(1);
#
# The apt_aggregates table should tell us what clusters, but for
# now it is always the local cluster
#
$aggregates =
array("Emulab" => "urn:publicid:IDN+emulab.net+authority+cm",
"APT" => "urn:publicid:IDN+apt.emulab.net+authority+cm",
"Wisconsin" => "urn:publicid:IDN+wisc.cloudlab.us+authority+cm",
"Clemson" => "urn:publicid:IDN+clemson.cloudlab.us+authority+cm",
"Utah" => "urn:publicid:IDN+utah.cloudlab.us+authority+cm");
echo "<link rel='stylesheet'
href='css/tablesorter.css'>\n";
# Place to hang the toplevel template.
echo "<div id='page-body'></div>\n";
echo "<script type='text/javascript'>\n";
echo " window.ISADMIN = $isadmin;\n";
echo " window.ISFADMIN = $isfadmin;\n";
echo "</script>\n";
echo "<script type='text/plain' id='agglist-json'>\n";
echo htmlentities(json_encode($aggregates)) . "\n";
echo "</script>\n";
echo "<script src='js/lib/jquery-2.0.3.min.js'></script>\n";
echo "<script src='js/lib/jquery.tablesorter.min.js'></script>\n";
echo "<script src='js/lib/jquery.tablesorter.widgets.min.js'></script>\n";
echo "<script src='js/lib/bootstrap.js'></script>\n";
echo "<script src='js/lib/require.js' data-main='js/cluster-status'></script>\n";
SPITFOOTER();
?>
require(window.APT_OPTIONS.configObject,
['underscore', 'js/quickvm_sup', 'moment',
'js/lib/text!template/cluster-status.html'],
function (_, sup, moment, mainString)
{
'use strict';
var isadmin = 0;
var mainTemplate = _.template(mainString);
var amlist = null;
function initialize()
{
window.APT_OPTIONS.initialize(sup);
isadmin = window.ISADMIN;
amlist = JSON.parse(_.unescape($('#agglist-json')[0].textContent));
var html = mainTemplate({
amlist: amlist,
});
$('#page-body').html(html);
LoadData();
}
function LoadData()
{
_.each(amlist, function(urn, name) {
var callback = function(json) {
console.log(json);
if (json.code) {
console.log("Could not get cluster data: " + json.value);
return;
}
var inuse = json.value.inuse;
var html = "";
inuse.forEach(function(value, index) {
var type = "";
if (_.has(value, "type")) {
type = value.type;
}
var expires = "";
if (_.has(value, "ttl")) {
var ttl = value.ttl;
if (ttl != "") {
expires = moment().add(ttl, 'seconds').fromNow();
}
}
var uid = "";
if (_.has(value, "uid")) {
uid = value.uid;
}
var eid = value.eid;
if (_.has(value, "instance_uuid")) {
var uuid = value.instance_uuid;
eid = "<a href='status.php?uuid=" + uuid +
"' target=_blank>" + value.instance_name + "</a>";
}
html = html + "<tr>" +
"<td>" + value.node_id + "</td>" +
"<td>" + type + "</td>" +
"<td>" + value.pid + "</td>" +
"<td>" + eid + "</td>" +
"<td>" + uid + "</td>" +
"<td>" + expires + "</td>" +
"<td>" + value.reserved_pid + "</td>" + "</tr>";
});
$('#' + name + '-tbody').html(html);
InitTable(name);
}
var xmlthing = sup.CallServerMethod(null, "cluster-status",
"GetStatus",
{"cluster" : urn});
xmlthing.done(callback);
});
}
function InitTable(name)
{
var tablename = "#inuse-table-" + name;
var searchname = "#inuse-search-" + name;
var countname = "#inuse-count-" + name;
var table = $(tablename)
.tablesorter({
theme : 'green',
widgets: ["filter", "resizable"],
widgetOptions: {
// include child row content while filtering, if true
filter_childRows : true,
// include all columns in the search.
filter_anyMatch : true,
// class name applied to filter row and each input
filter_cssFilter : 'form-control',
// search from beginning
filter_startsWith : false,
// Set this option to false for case sensitive search
filter_ignoreCase : true,
// Only one search box.
filter_columnFilters : false,
}
});
table.bind('filterEnd', function(e, filter) {
$(countname).text(filter.filteredRows);
});
// Target the $('.search') input using built in functioning
// this binds to the search using "search" and "keyup"
// Allows using filter_liveSearch or delayed search &
// pressing escape to cancel the search
$.tablesorter.filter.bindSearch(table, $(searchname));
$(tablename).removeClass("hidden");
}
$(document).ready(initialize);
});
......@@ -47,9 +47,14 @@ $routing = array("myprofiles" =>
"Do_VerifySpeaksfor")),
"dashboard" =>
array("file" => "dashboard.ajax",
"guest" => true,
"guest" => false,
"methods" => array("GetStats" =>
"Do_GetStats")),
"cluster-status" =>
array("file" => "cluster-status.ajax",
"guest" => false,
"methods" => array("GetStatus" =>
"Do_GetStatus")),
"sumstats" =>
array("file" => "sumstats.ajax",
"guest" => false,
......
<style>
table {
font-size: 14px;
}
.panel {
font-size: 80%;
}
.panel-body-dashboard {
padding: 2px;
}
.table-dashboard {
margin: 1px;
}
.popover{
max-width: 800px;
}
.table-condensed > thead > tr > th,
.table-condensed > tbody > tr > th,
.table-condensed > tfoot > tr > th,
.table-condensed > thead > tr > td,
.table-condensed > tbody > tr > td,
.table-condensed > tfoot > tr > td {
padding: 2px;
}
.panel-heading {
padding: 5px;
}
.inuse-panel {
height:300px;
overflow-y:scroll;
}
</style>
<% _.each(amlist, function(urn, name) { %>
<div class='row'>
<div class='col-sm-8 col-sm-offset-2'>
<div class='panel panel-default' id='inuse-panel'>
<div class="panel-heading">
<h5><center>InUse <%- name %></center></h5>
</div>
<div class='panel-body panel-body-dashboard'>
<div class="row">
<div class='col-sm-6'>
<input class='form-control search'
type='search' data-column='all'
id='inuse-search-<%- name %>' placeholder='Search'>
</div>
<div class='col-sm-5'>
<span id='inuse-count-<%- name %>'>0</span>
<span> matched rows</span>
</div>
</div>
<div class="inuse-panel">
<table class="tablesorter hidden"
id='inuse-table-<%- name %>'>
<thead>
<tr>
<th>Node</th>
<th>Type</th>
<th>Pid</th>
<th>Eid</th>
<th>User</th>
<th>Expires</th>
<th>PreRes</th>
</tr>
</thead>
<tbody id='<%- name %>-tbody'>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<% }); %>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment