Commit 8b66840c authored by Gary Wong's avatar Gary Wong

Merge branch 'lbs-greatness'

parents 88dd5a80 33129222
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2017 University of Utah and the Flux Group.
# Copyright (c) 2007-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -270,5 +270,34 @@ sub LookupByNickname($$)
return Lookup($class, $aggregate_urn);
}
#
# Check status of aggregate.
#
sub CheckStatus($$;$)
{
my ($self, $perrmsg, $portalrpc) = @_;
require APT_Geni;
if (0 || $self->disabled()) {
$$perrmsg = "The " . $self->name() . " cluster ".
"is currently offline, please try again later.";
return 1;
}
# Ping test. If we cannot get to the aggregate right now, bail.
my $retval = APT_Geni::PingAggregate($self, $perrmsg, $portalrpc);
if ($retval) {
if ($retval < 0) {
$$perrmsg = "Internal error contacting the ".
$self->name() . " cluster: " . $perrmsg;
}
else {
$$perrmsg = "The " . $self->name() . " cluster ".
"is currently unreachable, please try again later.";
}
return 1;
}
return 0;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2017 University of Utah and the Flux Group.
# Copyright (c) 2007-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -574,6 +574,18 @@ sub GetCertificate($)
return $cert;
}
# Helper functions for below.
sub ContextError()
{
return GeniResponse->Create(GENIRESPONSE_ERROR(), undef,
"Could not generate context for RPC");
}
sub CredentialError()
{
return GeniResponse->Create(GENIRESPONSE_ERROR(), undef,
"Could not generate credentials for RPC");
}
#
# Create a dataset on the remote aggregate.
#
......@@ -584,13 +596,14 @@ sub CreateDataset($)
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
return ContextError()
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential, $speaksfor_credential) =
APT_Geni::GenCredentials($cert, $geniuser, ["blockstores"]);
return undef
return CredentialError
if (! (defined($speaksfor_credential) &&
defined($credential)));
......@@ -624,13 +637,13 @@ sub DeleteDataset($)
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
return ContextError()
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential, $speaksfor_credential) =
APT_Geni::GenCredentials($cert, $geniuser, ["blockstores"], 1);
return undef
return CredentialError()
if (!defined($credential));
my $credentials = [$credential->asString()];
......@@ -657,13 +670,13 @@ sub ModifyDataset($)
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
return ContextError()
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential, $speaksfor_credential) =
APT_Geni::GenCredentials($cert, $geniuser, ["blockstores"], 1);
return undef
return CredentialError()
if (!defined($credential));
my $credentials = [$credential->asString()];
......@@ -692,13 +705,13 @@ sub ExtendDataset($)
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
return ContextError()
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential, $speaksfor_credential) =
APT_Geni::GenCredentials($cert, $geniuser, ["blockstores"], 1);
return undef
return CredentialError()
if (!defined($credential));
my $credentials = [$credential->asString()];
......@@ -726,13 +739,13 @@ sub DescribeDataset($)
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
return ContextError()
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential, $speaksfor_credential) =
APT_Geni::GenCredentials($cert, $geniuser, ["blockstores"], 1);
return undef
return CredentialError()
if (!defined($credential));
my $credentials = [$credential->asString()];
......@@ -759,13 +772,13 @@ sub GetCredential($)
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
return ContextError()
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential) =
APT_Geni::GenAuthCredential($cert, ["blockstores"]);
return undef
return CredentialError()
if (!defined($credential));
my $args = {
......@@ -789,13 +802,13 @@ sub ApproveDataset($)
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
return ContextError()
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential) =
APT_Geni::GenAuthCredential($cert, ["admin"]);
return undef
return CredentialError()
if (!defined($credential));
my $args = {
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2017 University of Utah and the Flux Group.
# Copyright (c) 2007-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -523,26 +523,62 @@ sub PortalRPC($$$@)
if ($usemydevtree) {
$cmurl =~ s/protogeni/protogeni\/stoller/;
}
#
# We use the root context to talk to the Cluster RPC server
#
if (!defined($context)) {
$context = RootContext();
if (!defined($context)) {
return GeniResponse->Create(GENIRESPONSE_RPCERROR(), undef,
return GeniResponse->Create(GENIRESPONSE_ERROR(), undef,
"Could not get root context for RPC");
}
}
my $response = Genixmlrpc::CallMethod($cmurl, $context, $method, @args);
if ($response->code() != GENIRESPONSE_SUCCESS()) {
if (!defined($response->output())) {
$response->output("Operation failed, returned " .
$response->code());
$response->output(GENIRESPONSE_STRING($response->code()));
}
}
return $response;
}
#
# Ping an aggregate to see if its alive, using getversion.
#
sub PingAggregate($$;$)
{
my ($aggregate, $perrmsg, $portalrpc) = @_;
my $authority = GetAuthority($aggregate->urn());
my $context = RootContext();
if (!defined($authority)) {
$$perrmsg = "Could not lookup authority for $aggregate!";
return -1;
}
if (!defined($context)) {
$$perrmsg = "Could generate root context!";
return -1;
}
my $cmurl = $authority->url();
if (defined($portalrpc)) {
$cmurl =~ s/\/cm$/\/cluster/;
}
if ($usemydevtree) {
$cmurl =~ s/protogeni/protogeni\/stoller/;
}
my $oldto = Genixmlrpc->SetTimeout(10);
my $response = Genixmlrpc::CallMethod($cmurl, $context, "GetVersion");
Genixmlrpc->SetTimeout($oldto);
# Success is good!
return 0
if ($response->code() == GENIRESPONSE_SUCCESS());
#print STDERR Dumper($response);
$$perrmsg = $response->error();
return $response->code();
}
# _Always_ make sure that this 1 is at the end of the file...
1;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -81,12 +81,14 @@ use GeniXML;
use GeniUser;
use APT_Geni;
use APT_Profile;
use APT_Aggregate;
# Protos
sub fatal($);
sub UserError($);
sub DoListImages();
sub DoDeleteImage();
sub ExitWithError($);
#
# Parse command arguments. Once we return from getopts, all that should be
......@@ -162,9 +164,17 @@ sub DoListImages()
# Shorten default timeout.
Genixmlrpc->SetTimeout(90);
# Lets do a cluster check to make sure its reachable.
my $aggregate = APT_Aggregate->Lookup($aggregate_urn);
if (!defined($aggregate)) {
fatal("No such aggregate");
}
if ($aggregate->CheckStatus(\$errmsg)) {
UserError($errmsg);
}
my $authority = GeniAuthority->Lookup($aggregate_urn);
if (!defined($authority)) {
fatal("No such aggregate");
fatal("No authority for aggregate");
}
my $cmurl = $authority->url();
if ($usemydevtree) {
......@@ -184,23 +194,8 @@ sub DoListImages()
my $response = Genixmlrpc::CallMethod($cmurl, undef, "ListImages", $args);
if ($response->code() != GENIRESPONSE_SUCCESS) {
if ($response->output()) {
print STDERR $response->output() . "\n";
if (defined($webtask)) {
$webtask->output($response->output());
}
}
else {
print STDERR "Operation failed, returned " .
$response->code() . "\n";
if (defined($webtask)) {
$webtask->output("Operation failed");
}
}
if (defined($webtask)) {
$webtask->Exited($response->code());
}
exit($response->code());
print STDERR $response->error() . "\n";
ExitWithError($response);
}
#
# We get back a flat list, which can include mulitple versions of
......@@ -526,9 +521,17 @@ sub DoDeleteImage()
# Shorten default timeout.
Genixmlrpc->SetTimeout(90);
# Lets do a cluster check to make sure its reachable.
my $aggregate = APT_Aggregate->Lookup($aggregate_urn);
if (!defined($aggregate)) {
fatal("No such aggregate");
}
if ($aggregate->CheckStatus(\$errmsg)) {
UserError($errmsg);
}
my $authority = GeniAuthority->Lookup($aggregate_urn);
if (!defined($authority)) {
fatal("No such aggregate");
fatal("No authority for aggregate");
}
my $cmurl = $authority->url();
if ($usemydevtree) {
......@@ -610,3 +613,33 @@ sub escapeshellarg($)
return $str;
}
#
# These are errors which the user might need to see. Some errors are
# exceptions though, and those we want to treat as internal errors.
#
sub ExitWithError($)
{
my ($response) = @_;
my $mesg = $response->error();
my $code = $response->code();
#
# In general, these errors are to be expected by the caller.
#
if ($code == GENIRESPONSE_REFUSED ||
$code == GENIRESPONSE_SEARCHFAILED ||
$code == GENIRESPONSE_SERVER_UNAVAILABLE ||
$code == GENIRESPONSE_NETWORK_ERROR ||
$code == GENIRESPONSE_BUSY) {
if (defined($webtask)) {
$webtask->output($mesg);
$webtask->Exited($code);
}
print STDERR "*** $0:\n".
" $mesg\n";
exit(1);
}
fatal($mesg);
}
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2016 University of Utah and the Flux Group.
# Copyright (c) 2008-2016, 2018 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -41,11 +41,9 @@ sub usage()
}
my $optlist = "da:";
my $debug = 0;
my $errmsg;
my $aggurn;
# For development.
my $usemydevtree = 0;
#
# Configure variables
#
......@@ -77,6 +75,8 @@ use GeniHRN;
use Genixmlrpc;
use GeniResponse;
use GeniAuthority;
use APT_Aggregate;
use APT_Geni;
my %options = ();
if (! getopts($optlist, \%options)) {
......@@ -103,15 +103,20 @@ Genixmlrpc->SetContext($context);
# Shorten default timeout.
Genixmlrpc->SetTimeout(15);
my $aggregate = APT_Aggregate->Lookup($aggurn);
if (!defined($aggregate)) {
fatal("No such aggregate: $aggurn");
}
my $authority = GeniAuthority->Lookup($aggurn);
if (!defined($authority)) {
fatal("No such aggregate: $aggurn");
fatal("No such aggregate authority: $aggurn");
}
my $cmurl = $authority->url();
$cmurl =~ s/\/cm$/\/cluster/;
if ($usemydevtree) {
$cmurl =~ s/protogeni/protogeni\/stoller/;
# Check online and reachable. Additional arg says to test Portal RPC.
if ($aggregate->CheckStatus(\$errmsg, 1)) {
print STDERR $errmsg . "\n";
exit(1);
}
my @args = ();
if (@ARGV) {
my $args = {};
......@@ -128,16 +133,11 @@ if (@ARGV) {
@args = ($args);
}
my $starttime = [gettimeofday()];
my $response = Genixmlrpc::CallMethod($cmurl, undef, $method, @args);
my $response = APT_Geni::PortalRPC($authority, undef, $method, @args);
my $elapsed = tv_interval($starttime);
if ($response->code() != GENIRESPONSE_SUCCESS) {
if ($response->output()) {
print STDERR $response->output() . "\n";
}
else {
print STDERR "Operation failed, returned " . $response->code() . "\n";
}
print STDERR $response->error() . "\n";
exit($response->code());
}
print encode_json($response->value());
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2013-2017 University of Utah and the Flux Group.
# Copyright (c) 2013-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -71,7 +71,7 @@ sub Lookup($$)
$self->{'DATA'} =
eval { decode_json($self->{'DBROW'}->{'task_data'}); };
if ($@) {
print STDERR "Could not json decode task data\n";
print STDERR "Could not json decode task data: $@\n";
return undef
}
}
......@@ -236,7 +236,7 @@ sub Store($)
my $id = $self->{'DBROW'}->{'task_id'};
my $data = eval { encode_json($self->{'DATA'}); };
if ($@) {
print STDERR "Could not json encode task data\n";
print STDERR "Could not json encode task data: $@\n";
return -1;
}
$data = emdb::DBQuoteSpecial($data);
......@@ -274,7 +274,7 @@ sub Refresh($)
$self->{'DATA'} =
eval { decode_json($self->{'DBROW'}->{'task_data'}); };
if ($@) {
print STDERR "Could not json decode task data\n";
print STDERR "Could not json decode task data: $@\n";
return -1;
}
}
......
This diff is collapsed.
#!/usr/bin/perl -wT
#
# Copyright (c) 2008-2016 University of Utah and the Flux Group.
# Copyright (c) 2008-2016, 2018 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -361,7 +361,7 @@ sub CreateFromRegistry($$$)
# Check for expiration.
#
if (!$authority->urn() || $authority->IsExpired()) {
print STDERR "Aged out stale or expired $authority ...\n";
#print STDERR "Aged out stale or expired $authority ...\n";
$authority->Delete();
$authority = undef;
}
......
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/perl -wT
#
# Copyright (c) 2008-2016 University of Utah and the Flux Group.
# Copyright (c) 2008-2018 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -55,6 +55,7 @@ my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBAPPROVAL = "@TBAPPROVALEMAIL@";
my $TBAUDIT = "@TBAUDITEMAIL@";
my $TBBASE = "@TBBASE@";
my $BOSSNODE = "@BOSSNODE@";
my $CONTROL = "@USERNODE@";
my $OURDOMAIN = "@OURDOMAIN@";
......@@ -328,6 +329,9 @@ sub speaksfor_uuid($) { return field($_[0], "speaksfor_uuid"); }
sub expiration_max($) { return field($_[0], "expiration_max"); }
sub renew_limit($) { return field($_[0], "renew_limit"); }
sub description($) { return field($_[0], "description"); }
sub async_mode($) { return field($_[0], "async_mode"); }
sub async_code($) { return field($_[0], "async_code"); }
sub async_output($) { return field($_[0], "async_output"); }
sub cert($) { return $_[0]->{'CERT'}->cert(); }
sub GetCertificate($) { return $_[0]->{'CERT'}; }
sub LOCKED($) { return $_[0]->{'LOCKED'}; }
......@@ -1007,6 +1011,45 @@ sub IsExpired($)
return (time() >= $slice_expires);
}
#
# Set async mode
#
sub SetAsyncMode($$)
{
my ($self, $onoff) = @_;
my $uuid = $self->uuid();
$onoff = ($onoff ? 1 : 0);
DBQueryWarn("update geni_slices set async_mode='$onoff' " .
"where uuid='$uuid'")
or return -1;
$self->{'SLICE'}->{'async_mode'} = $onoff;
return 0;
}
sub SetAsyncError($$)
{
my ($self, $response) = @_;
my $uuid = $self->uuid();
# Not a blessed reference.
my $code = $response->{'code'};
my $output = $response->{'output'};
if (defined($output) && $output ne "") {
$output = DBQuoteSpecial($output);
}
else {
$output = "''";
}
DBQueryWarn("update geni_slices set async_code='$code', ".
" async_output=$output ".
"where uuid='$uuid'")
or return -1;
$self->{'SLICE'}->{'async_code'} = $code;
return 0;
}
#
# Set the speaksfor stuff.
#
......
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2017 University of Utah and the Flux Group.
# Copyright (c) 2008-2018 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -251,7 +251,8 @@ sub Start($$)
# Check for NoLogins; return XMLRPC
#
if (NoLogins()) {
return XMLError(503, "CM temporarily offline; please try again later");
return XMLError(HTTP_SERVICE_UNAVAILABLE(),
"CM temporarily offline; please try again later");
}
if (0) {
# For timing.
......@@ -367,10 +368,10 @@ sub Start($$)
# Check Emulab users table.
#
my $user = User->Lookup($hrn->id());
return XMLError(XMLRPC_APPLICATION_ERROR(),
return XMLError(GENIRESPONSE_REFUSED(),
"Not a valid local user. Who are you really?")
if (!defined($user));
return XMLError(XMLRPC_APPLICATION_ERROR(),
return XMLError(GENIRESPONSE_FORBIDDEN(),
"Your account is no longer active!")
if ($user->status() ne USERSTATUS_ACTIVE());
}
......@@ -379,7 +380,7 @@ sub Start($$)
# So we know who/what we are acting as, in case we have to make a
# callout RPC. The "context" is a global variable.
#
return XMLError(XMLRPC_SERVER_ERROR(),
return XMLError(XMLRPC_SYSTEM_ERROR(),
"There is no certificate for this server")
if (!exists($MODULEDEFS->{"CERTIFICATE"}));
......@@ -421,7 +422,7 @@ sub Start($$)
return XMLError(XMLRPC_PARSE_ERROR(), "error decoding RPC:\n" . "$@");
}
if ($call->{'type'} ne 'call') {
return XMLError(XMLRPC_APPLICATION_ERROR(),
return XMLError(XMLRPC_PARSE_ERROR(),
"expected RPC methodCall, got $call->{'type'}");
}
my $method = $call->{'method_name'};
......@@ -548,7 +549,7 @@ sub Start($$)
#
$rpcerror = $iserror = 1;
print STDERR "Error executing RPC method $method:\n" . $@ . "\n\n";
$response = $decoder->encode_fault(XMLRPC_SERVER_ERROR(),
$response = $decoder->encode_fault(XMLRPC_SYSTEM_ERROR(),
"Internal Error executing $method");
print STDERR "-------------- Request -----------------\n";
......@@ -716,6 +717,11 @@ sub CreateLogFile($)
{
my ($fname) = @_;
# So tbops people can read the files ...
if (!chmod(0664, $fname)) {
print STDERR "Could not chmod $fname\n";
return undef;
}
my $group = Group->Lookup($GENIGROUP, $GENIGROUP);
if (!defined($group)) {
print STDERR "Could not lookup group $GENIGROUP";
......
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2017 University of Utah and the Flux Group.
# Copyright (c) 2008-2018 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -53,7 +53,15 @@ use vars qw(@ISA @EXPORT);
GENIRESPONSE_NOSPACE
XMLRPC_PARSE_ERROR XMLRPC_SERVER_ERROR XMLRPC_APPLICATION_ERROR
XMLRPC_NO_SUCH_METHOD
XMLRPC_SYSTEM_ERROR XMLRPC_TRANSPORT_ERROR);
XMLRPC_SYSTEM_ERROR XMLRPC_TRANSPORT_ERROR
HTTP_INTERNAL_SERVER_ERROR HTTP_SERVICE_UNAVAILABLE
GENIRESPONSE_NETWORK_ERROR GENIRESPONSE_NETWORK_ERROR_TIMEDOUT
GENIRESPONSE_NETWORK_ERROR_NOCONNECT
GENIRESPONSE_SETUPFAILURE GENIRESPONSE_SETUPFAILURE_OSSETUP
GENIRESPONSE_SETUPFAILURE_NETWORK
GENIRESPONSE_SETUPFAILURE_BOOTFAILED
GENIRESPONSE_SETUPFAILURE_EVENTSYS
GENIRESPONSE_SETUPFAILURE_MAXERROR);
use overload ('""' => 'Stringify');
my $current_response = undef;
......@@ -85,11 +93,23 @@ sub GENIRESPONSE_INSUFFICIENT_BANDWIDTH() {25; }
sub GENIRESPONSE_INSUFFICIENT_NODES() {26; }
sub GENIRESPONSE_INSUFFICIENT_MEMORY() {27; }
sub GENIRESPONSE_NO_MAPPING() {28; }
sub GENIRESPONSE_NETWORK_ERROR() {35; }
sub GENIRESPONSE_NETWORK_ERROR_TIMEDOUT() {1;}
sub GENIRESPONSE_NETWORK_ERROR_NOCONNECT() {2;}
sub GENIRESPONSE_NOT_IMPLEMENTED() {100; }
# These are boot failure indicators.
sub GENIRESPONSE_SETUPFAILURE() {150; }
sub GENIRESPONSE_SETUPFAILURE_BOOTFAILED() {151; }
sub GENIRESPONSE_SETUPFAILURE_OSSETUP() {152; }
sub GENIRESPONSE_SETUPFAILURE_NETWORK() {153; }
sub GENIRESPONSE_SETUPFAILURE_EVENTSYS() {154; }
sub GENIRESPONSE_SETUPFAILURE_MAXERROR() {170; }
# Yes, an odd place for this but I need it defined someplace.
sub GENIRESPONSE_STITCHER_ERROR() {101; }
# This is HTTP_SERVICE_UNAVAILABLE
sub GENIRESPONSE_SERVER_UNAVAILABLE() {503;}
sub HTTP_INTERNAL_SERVER_ERROR() {500; }
sub HTTP_SERVICE_UNAVAILABLE() {503; }
sub GENIRESPONSE_SERVER_UNAVAILABLE() {HTTP_SERVICE_UNAVAILABLE();}
sub GENIRESPONSE() { return $current_response; }
my @GENIRESPONSE_STRINGS =
......@@ -102,7 +122,7 @@ my @GENIRESPONSE_STRINGS =
"Server Error",
"Too Big",
"Operation Refused",
"Operation Times Out",
"Operation Timed Out",
"Database Error",
"RPC Error",
"Unavailable",
......@@ -123,6 +143,13 @@ my @GENIRESPONSE_STRINGS =
"Insufficient Nodes",
"Insufficient Memory",
"No Mapping Possible",
"Error 29",
"Error 30",
"Error 31",
"Error 32",
"Error 33",
"Error 34",
"Server timed out or could not be reached",
);
$GENIRESPONSE_STRINGS[GENIRESPONSE_NOT_IMPLEMENTED] = "Not Implemented";
sub GENIRESPONSE_STRING($)
......@@ -140,8 +167,8 @@ sub GENIRESPONSE_STRING($)
#
sub XMLRPC_PARSE_ERROR() { -32700; }
sub XMLRPC_SERVER_ERROR() { -32600; }
sub XMLRPC_NO_SUCH_METHOD() { -32601; }
sub XMLRPC_APPLICATION_ERROR() { -32500; }
sub XMLRPC_NO_SUCH_METHOD() { XMLRPC_APPLICATION_ERROR() + 3; }
sub XMLRPC_SYSTEM_ERROR() { -32400; }
sub XMLRPC_TRANSPORT_ERROR() { -32300; }
......@@ -204,20 +231,60 @@ sub Create($$;$$)