blob: 4ea9c3f8f37d64383a3212fd4b090169ada5e08b [file] [log] [blame]
Mohamed Abukar34e43832019-11-13 17:57:15 +02001#!/usr/bin/perl -w
Abukar Mohamed8504f6a2019-04-03 11:07:48 +00002#
3# Copyright (c) 2019 AT&T Intellectual Property.
4# Copyright (c) 2019 Nokia.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19#############################
20# Simple cli for xapp manager
21#
Mohamed Abukar34e43832019-11-13 17:57:15 +020022# In addition to standard shell tools, requires basic Perl installation
23# (Ubuntu package "perl-base", installed by default), packages "curl" and
Abukar Mohamed8504f6a2019-04-03 11:07:48 +000024# "yajl-tools" (the second provides json_reformat on Ubuntu; on Red Hat-style
25# distributions install "yajl" instead).
26#
Mohamed Abukar34e43832019-11-13 17:57:15 +020027use strict;
28use Getopt::Long;
29use Fcntl;
Abukar Mohamed8504f6a2019-04-03 11:07:48 +000030
Mohamed Abukar34e43832019-11-13 17:57:15 +020031my $myname="appmgrcli";
32
33sub usage {
34 print <<"EOF1";
35usage: $myname [-h host] [-p port] [-v] [-c curlprog] command params...
36- command is deploy, undeploy, status, subscriptions, health, config, help
Abukar Mohamed8504f6a2019-04-03 11:07:48 +000037- (abbreviations dep, undep, stat, subs, heal allowed)
38- Parameters of the commands that may have parameters:
39-- deploy: name of the xapp to deploy
Mohamed Abukar34e43832019-11-13 17:57:15 +020040---- Deployment parameters come from the Help chart but the following can be
41---- overridden by option with the same name (for example --configName=cname):
42----- helmVersion ReleaseName namespace podHost (host of pods).
Abukar Mohamed8504f6a2019-04-03 11:07:48 +000043-- undeploy: name of the xapp to undeploy
44-- status:
45---- No parameters: Lists information about all deployed xapps
46---- xapp name as parameter: Prints information about the given xapp
47---- xapp name and instance: Lists information about the given instance only
48-- subscriptions is followed by sub-command list, add, delete, or modify
Mohamed Abukar34e43832019-11-13 17:57:15 +020049--- (abbreviations del and mod for delete and modify are allowed):
Abukar Mohamed8504f6a2019-04-03 11:07:48 +000050---- list without parameters lists all subscriptions
51---- list with subscription id prints that subscription
52---- add URL eventType maxRetry retryTimer
53------- URL is the URL to notify
54------- eventType one of created,deleted,all
55------- maxRetry and retryTimer are positive decimal numbers
56---- modify id URL eventType maxRetry retryTimer
57------- id is the subscription id (find out with the list command)
58--------the rest of the parameters are like in add
59---- delete id
60------- id is the subscription id to delete (find out with the list command)
Mohamed Abukar34e43832019-11-13 17:57:15 +020061-- config is followed by sub-command list, add, delete, or modify
62--- (abbreviations del and mod for delete and modify are allowed):
63---- list (no pars)
64------ lists the configuration of all xapps
65---- add jsonfile
66------ Creates xapp configuration. the jsonfile must contain all data (see API)
67---- add name configName namespace configSchemaFile configDataFile
68------ Creates xapp configuration, but unlike in the 1-parameter form,
69------ Xapp name, config map name and namespace are separate parameters.
70------ The other data come from JSON files.
71---- modify
72------ Modifies existing configuration. Same parameters (1 or 5) as in add.
73---- delete name configName namespace
74------ Deletes the configuration identified by the parameters.
75--------------------------------------------------------------
Abukar Mohamed8504f6a2019-04-03 11:07:48 +000076- Default values for host and port can be set in environment
77- variables APPMGR_HOST and APPMGR_PORT
78- Option -v sets verbose mode.
Mohamed Abukar34e43832019-11-13 17:57:15 +020079- Option -c overrides the used curl program name ("curl" by default).
80- Exit code is 0 for success, 1 for any kind of failure.
Abukar Mohamed8504f6a2019-04-03 11:07:48 +000081EOF1
Mohamed Abukar34e43832019-11-13 17:57:15 +020082 exit 0;
83}
84
85sub helphint {
86 print "run $myname help (or --help) for instructions\n";
Abukar Mohamed8504f6a2019-04-03 11:07:48 +000087}
88
89# Defaults
90
Mohamed Abukar34e43832019-11-13 17:57:15 +020091my $host="localhost";
92my $port=8080;
93my $verbose=0;
94my $showhelp = 0;
95
96# API URLs
97
98my $base="/ric/v1";
99my $base_xapps="$base/xapps";
100my $base_health="$base/health";
101my $base_subs="$base/subscriptions";
102my $base_config="$base/config";
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000103
104# Check for environment override
Mohamed Abukar34e43832019-11-13 17:57:15 +0200105if (exists $ENV{"APPMGR_HOST"}) {
106 $host=$ENV{"APPMGR_HOST"};
107}
108if (exists $ENV{"APPMGR_PORT"}) {
109 $port=$ENV{"APPMGR_PORT"};
110}
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000111
Mohamed Abukar34e43832019-11-13 17:57:15 +0200112# Overrides for some deploy parameters
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000113
Mohamed Abukar34e43832019-11-13 17:57:15 +0200114my $configName = "";
115my $namespace = "ricxapp";
rangajal2a409972020-11-03 12:03:40 +0300116# Check for environment override
117if (exists $ENV{"XAPP_NAMESPACE"}) {
118 $namespace=$ENV{"XAPP_NAMESPACE"};
119}
Mohamed Abukar34e43832019-11-13 17:57:15 +0200120my $releaseName = "";
121my $helmVersion = "0.0.1";
122my $overrideFile = "";
123my $podHost = "";
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000124
Mohamed Abukar34e43832019-11-13 17:57:15 +0200125# The curl command can be overridden for testing with a dummy.
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000126
Mohamed Abukar34e43832019-11-13 17:57:15 +0200127my $curl = "curl";
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000128
Mohamed Abukar34e43832019-11-13 17:57:15 +0200129Getopt::Long::Configure("no_auto_abbrev", "permute");
130if (! GetOptions("h=s" => \$host,
131 "p=i" => \$port,
132 "c=s" => \$curl,
133 "ConfigName=s" => \$configName,
134 "Namespace=s" => \$namespace,
135 "ReleaseName=s" => \$releaseName,
136 "HelmVersion=s" => \$helmVersion,
137 "OverrideFile=s" => \$overrideFile,
138 "podHost=s" => \$podHost,
139 "help" => \$showhelp,
140 "v" => \$verbose)) {
141 print "$myname: Error in options\n";
142 helphint();
143 exit 1;
144}
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000145
Mohamed Abukar34e43832019-11-13 17:57:15 +0200146if ($showhelp) {
147 usage();
148 exit 0;
149}
150
151if ($verbose) {
152 print "host = $host\n";
153 print "port = $port\n";
154 print "ConfigName = $configName\n";
155 print "Namespace = $namespace\n";
156 print "ReleaseName = $releaseName\n";
157 print "HelmVersion = $helmVersion\n";
158 print "OverrideFile = $overrideFile\n";
159 print "podHost = $podHost\n";
160 for (my $idx = 0; $idx <= $#ARGV; ++$idx) {
161 print "\$ARGV[$idx] = $ARGV[$idx]\n";
162 }
163}
164
165# Verify command and call handler function
166
167my %commands = (
168 "deploy" => \&do_deploy,
169 "dep" => \&do_deploy,
170 "undeploy" => \&do_undeploy,
171 "undep" => \&do_undeploy,
172 "status" => \&do_status,
173 "stat" => \&do_status,
174 "subscriptions" => \&do_subscriptions,
175 "subs" => \&do_subscriptions,
176 "health" => \&do_health,
177 "heal" => \&do_health,
178 "config" => \&do_config,
179 "help" => \&usage
180);
181
182if ($#ARGV < 0) {
183 print "$myname: Missing command\n";
184 helphint();
185 exit 1;
186}
187
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000188# Variable status used for the return value of the whole script.
Mohamed Abukar34e43832019-11-13 17:57:15 +0200189my $status = 0;
190
191my $command = $ARGV[0];
192shift;
193if (exists $commands{$command}) {
194 # Call the handler function with the rest of the command line
195 $commands{$command}(@ARGV);
196 exit $status; # Default exit. A handler can exit also if more convenient
197}
198print "$myname: Unrecognised command $command\n";
199helphint();
200exit 1;
201
202my $errfile;
203my $resultfile;
204
205
206sub make_temp_name($) {
207 my $tmpsuffix = "${$}${^T}";
208 return "$_[0].$tmpsuffix";
209}
210
211sub make_temps {
212 $errfile = make_temp_name("/tmp/appmgr_e");
213 $resultfile = make_temp_name("/tmp/appmgr_r");
214}
215
216sub remove_temps {
217 unlink ($errfile, $resultfile);
218}
219
220sub print_file($$) {
221 my $outputhandle = $_[0];
222 my $filename = $_[1];
223 my $buffer;
224 my $inhandle;
225 if (!open($inhandle, "<", $filename)) {
226 print $outputhandle "$myname print_file: cannot open $filename: $!\n";
227 return;
228 }
229 while (read($inhandle, $buffer, 4000) > 0) {
230 print $outputhandle $buffer;
231 }
232 close($inhandle);
233}
234
235# The HTTP protocol result code, filled in by rest().
236
237my $http_code = "";
238
239# Helper: Given a curl output file, extract the number from ##code line.
240# return ERROR if file cannot be opened, or "" if no code found.
241
242sub find_http_code($) {
243 my ($fh, $line, $code);
244 open($fh, "<", $_[0]) or return "ERROR";
245 while ($line = <$fh>) {
246 if ($line =~ /^##([0-9]+)/) {
247 return $1;
248 }
249 }
250 return "";
251}
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000252
253# Helper for command execution:
254# Do a rest call with "curl": $1 = method, $2 = path (without host and port
255# which come from variables), $3 data to POST if needed
Mohamed Abukar34e43832019-11-13 17:57:15 +0200256# returns true (1) if OK, and any returned data is in $resultfile
257# else 0, and error message from curl is in $errfile, which is printed
258# before returning the 0.
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000259#
260# On curl options: --silent --show-error disables progress bar, but allows
261# error messages. --connect-timeout 20 limits waiting for connection to
262# 20 seconds. In practice connection will succeed almost immediately,
263# or in the case of wrong address not at all.
Mohamed Abukar34e43832019-11-13 17:57:15 +0200264# To get the http code, using -w with format. The result comes at the end
265# of the output, so "decorating" it for easier filtering.
266# The code is put to global $http_code.
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000267#
Mohamed Abukar34e43832019-11-13 17:57:15 +0200268sub rest($$_) {
269 my $method = $_[0];
270 my $path = $_[1];
271 my $data = $_[2] || "";
272 my $retval = 1;
273 my $http_status_file = make_temp_name("/tmp/appmgr_h");
Mohamed Abukarb175b942019-05-09 16:30:58 +0300274
Mohamed Abukar34e43832019-11-13 17:57:15 +0200275 # This redirects stderr (fd 2) to $errfile, but saving normal stderr
276 # so that if can be restored.
277 open(OLDERR, ">&", \*STDERR) or die "Can't dup STDERR: $!";
278 open(ERRFILE, ">", $errfile) or die "open errorfile failed";
279 open(STDERR, ">&", \*ERRFILE) or die "Can't dup ERRFILE: $!";
280
281 # This redirects stdout (fd 1) to $http_status_file, but saving original
282 # so that if can be restored.
283 open(OLDSTDOUT, ">&", \*STDOUT) or die "Can't dup STDOUT: $!";
284 open(HTTP_STATUS_FILE, ">", $http_status_file) or die "open http status file failed";
285 open(STDOUT, ">&", \*HTTP_STATUS_FILE) or die "Can't dup HTTP_STATUS_FILE: $!";
286
287 my @args = ($curl, "--silent", "--show-error", "--connect-timeout", "20",
288 "--header", "Content-Type: application/json", "-X", $method,
289 "-o", $resultfile, "-w", '\n##%{http_code}\n',
290 "http://${host}:${port}${path}");
291 if ($data ne "") {
292 push(@args, "--data");
293 push(@args, $data);
294 }
295 if ($verbose) {
296 print OLDSTDOUT "Running: " . join(" ", @args) . "\n";
297 }
298 if (system(@args) == -1) {
299 print OLDSTDOUT "$myname: failed to execute @args\n";
300 $retval = 0;
301 }
302 elsif ($? & 127) {
303 printf OLDSTDOUT "$myname: child died with signal %d, %s coredump\n",
304 ($? & 127), ($? & 128) ? 'with' : 'without';
305 $retval = 0;
306 }
307 else {
308 my $curl_exit_code = $? >> 8;
309 if ($curl_exit_code == 0) {
310 seek HTTP_STATUS_FILE, 0, 0; # Ensures flushing
311 $http_code = find_http_code($http_status_file);
312 if ($http_code eq "ERROR") {
313 print OLDSTDOUT "$myname: failed to open temp file $http_status_file\n";
314 $retval = 0;
315 }
316 elsif ($http_code eq "") {
317 print OLDSTDOUT "$myname: curl failed to provide HTTP code\n";
318 $retval = 0;
319 }
320 else {
321 if ($verbose) {
322 print OLDSTDOUT "HTTP status code = $http_code\n";
323 }
324 $retval = 1; # Interaction OK from REST point of view
325 }
326 }
327 else {
328 print_file(\*OLDSTDOUT, $errfile);
329 $retval = 0;
330 }
331 }
332 open(STDOUT, ">&", \*OLDSTDOUT) or die "Can't dup OLDSTDOUT: $!";
333 open(STDERR, ">&", \*OLDERR) or die "Can't dup OLDERR: $!";
334 unlink($http_status_file);
335 return $retval;
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000336}
337
Mohamed Abukar34e43832019-11-13 17:57:15 +0200338# Pretty-print a JSON file to stdout.
339# (currently uses json_reformat command)
340# Skips the ##httpcode line we make "curl"
341# add in order to get access to the HTTP status.
342
343sub print_json($) {
344 my $filename = $_[0];
345 my ($line, $inhandle, $outhandle);
346 if (!open($inhandle, "<", $filename)) {
347 print "$myname print_json: cannot open $filename: $!\n";
348 return;
349 }
350 if (!open($outhandle, "|json_reformat")) {
351 print "$myname print_json: cannot pipe to json_reformat: $!\n";
352 return;
353 }
354 while ($line = <$inhandle>) {
355 if (! ($line =~ /^##[0-9]+/)) {
356 print $outhandle $line;
357 }
358 }
359 close($outhandle);
360 close($inhandle);
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000361}
362
Mohamed Abukar34e43832019-11-13 17:57:15 +0200363# Append an entry like ","name":"value" to the first parameter, if "name"
364# names a variable with non-empty value.
365# Else returns the unmodified first parameter.
366
367sub append_option($$) {
368 my $result = $_[0];
369 my $var = $_[1];
370 my $val = eval("\$$var");
371 if ($val ne "") {
372 $result = "$result,\"$var\":\"$val\"";
373 }
374 return $result;
375}
376
377# Command handlers
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000378# Assumes the API currently implemented.
Mohamed Abukar34e43832019-11-13 17:57:15 +0200379# Functions for each command below
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000380
Mohamed Abukar34e43832019-11-13 17:57:15 +0200381# Deploy:
382# The deploy command has one mandatory parameter "name" in the API,
383# and several optional ones. Used mainly internally for testing, because
384# they all override Helm chart values:
385# "helmVersion": Helm chart version to be used
386# "releaseName": The releas name of xApp visible in K8s
387# "namespace": Name of the namespace to which xApp is deployed.
388# "overrideFile": The file content used to override values.yaml file
389# this host from the host the xapp manager is running in, we use the term
390# and variable name "podHost" here.
391# The options come from options (see GetOptions() call).
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000392
Mohamed Abukar34e43832019-11-13 17:57:15 +0200393sub do_deploy(@) {
394 my $name = $_[0] || "";
395 if ($name ne "") {
396 my $data = "{\"XappName\":\"$name\"";
397 $data = append_option($data, "helmVersion");
398 $data = append_option($data, "releaseName");
399 $data = append_option($data, "namespace");
400 $data = append_option($data, "overrideFile");
401 $data = $data . "}";
402 make_temps();
403 if (rest("POST", $base_xapps, $data)) {
404 if ($http_code eq "201") {
405 print_json $resultfile;
406 $status = 0;
407 }
408 else {
409 my $error;
410 if ($http_code eq "400") {
411 $error = "INVALID PARAMETERS SUPPLIED";
412 }
413 elsif ($http_code eq "500") {
414 $error = "INTERNAL ERROR";
415 }
416 else {
417 $error = "UNKNOWN STATUS $http_code";
418 }
419 print "$error\n";
420 $status = 1;
421 }
422 }
423 else {
424 $status=1;
425 }
426 remove_temps();
427 }
428 else {
429 print "$myname: Error: expected the name of xapp to deploy\n";
430 $status = 1;
431 }
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000432}
433
Mohamed Abukar34e43832019-11-13 17:57:15 +0200434sub do_undeploy(@) {
435 my $name = $_[0] || "";
436 my $urlpath = $base_xapps;
437 if ($name ne "") {
438 make_temps();
439 $urlpath = "$urlpath/$name";
440 if (rest("DELETE", $urlpath)) {
441 if ($http_code eq "204") {
442 print "SUCCESSFUL DELETION\n";
443 $status = 0;
444 }
445 else {
446 my $error;
447 if ($http_code eq "400") {
448 $error = "INVALID XAPP NAME SUPPLIED";
449 }
450 elsif ($http_code eq "500") {
451 $error = "INTERNAL ERROR";
452 }
453 else {
454 $error = "UNKNOWN STATUS $http_code";
455 }
456 print "$error\n";
457 $status = 1;
458 }
459 }
460 else {
461 $status = 1;
462 }
463 remove_temps();
464 }
465 else {
466 print "$myname: Error: expected the name of xapp to undeploy\n";
467 $status = 1;
468 }
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000469}
470
Mohamed Abukar34e43832019-11-13 17:57:15 +0200471sub do_status(@) {
472 my $name = $_[0] || "";
473 my $instance = $_[1] || "";
474 my $urlpath = $base_xapps;
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000475
Mohamed Abukar34e43832019-11-13 17:57:15 +0200476 if ($name ne "") {
477 $urlpath = "$urlpath/$name";
478 }
479 if ($instance ne "") {
480 $urlpath = "$urlpath/instances/$instance"
481 }
482 make_temps();
483 if (rest("GET", $urlpath)) {
484 if ($http_code eq "200") {
485 print_json $resultfile;
486 $status = 0;
487 }
488 else {
489 my $error;
490 if ($http_code eq "400") {
491 $error = "INVALID XAPP NAME SUPPLIED";
492 }
493 if ($http_code eq "404") {
494 $error = "XAPP NOT FOUND";
495 }
496 elsif ($http_code eq "500") {
497 $error = "INTERNAL ERROR";
498 }
499 else {
500 $error = "UNKNOWN STATUS $http_code";
501 }
502 print "$error\n";
503 $status = 1;
504 }
505 }
506 else {
507 $status = 1;
508 }
509 remove_temps();
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000510}
511
Mohamed Abukar34e43832019-11-13 17:57:15 +0200512# Helpers for subscription:
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000513# Validate the subscription data that follows a subscription add or modify
514# subcommand. $1=URL, $2=eventType, $3=maxRetries, $4=retryTimer
515# URL must look like URL, event type must be one of created deleted all,
516# maxRetries and retryTimer must be non-negative numbers.
Mohamed Abukar34e43832019-11-13 17:57:15 +0200517# If errors, returns false (0) and prints errors, else returns 1.
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000518#
Mohamed Abukar34e43832019-11-13 17:57:15 +0200519sub validate_subscription(@) {
520 # Using the API parameter names
521 my $targetUrl = $_[0] || "";
522 my $eventType = $_[1] || "";
523 my $maxRetries = $_[2] || "";
524 my $retryTimer = $_[3] || "";
525 my $retval = 1;
526
527 if (! ($targetUrl =~ /^http:\/\/.*/ or $targetUrl =~ /^https:\/\/.*/)) {
528 print "$myname: bad URL $targetUrl\n";
529 $retval = 0;
530 }
531 if ($eventType ne "created" and $eventType ne "deleted" and
532 $eventType ne "all") {
533 print "$myname: unrecognized event $eventType\n";
534 $retval = 0;
535 }
536 if (! ($maxRetries =~ /^[0-9]+$/)) {
537 print "$myname: invalid maximum retries count $maxRetries\n";
538 $retval = 0;
539 }
540 if (! ($retryTimer =~ /^[0-9]+$/)) {
541 print "$myname: invalid retry time $retryTimer\n";
542 $retval = 0;
543 }
544 return $retval;
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000545}
546
Mohamed Abukar34e43832019-11-13 17:57:15 +0200547# Format a subscriptionRequest JSON object
548
549sub make_subscriptionRequest(@) {
550 my $targetUrl = $_[0];
551 my $eventType = $_[1];
552 my $maxRetries = $_[2];
553 my $retryTimer = $_[3];
554 return "{\"Data\": {\"TargetUrl\":\"$targetUrl\",\"EventType\":\"$eventType\",\"MaxRetries\":$maxRetries,\"RetryTimer\":$retryTimer}}";
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000555}
556
Mohamed Abukar34e43832019-11-13 17:57:15 +0200557# Subscriptions:
558# $1 is sub-command: list, add, delete, modify
559
560sub do_subscriptions(@) {
561 my $subcommand = $_[0] || "";
562 shift;
563
564 my %subcommands = (
565 "list" => \&do_subscription_list,
566 "add" => \&do_subscription_add,
567 "delete" => \&do_subscription_delete,
568 "del" => \&do_subscription_delete,
569 "modify" => \&do_subscription_modify,
570 "mod" => \&do_subscription_modify
571 );
572 if (exists $subcommands{$subcommand}) {
573 $subcommands{$subcommand}(@_);
574 }
575 else {
576 print "$myname: unrecognized subscriptions subcommand $subcommand\n";
577 helphint();
578 $status=1
579 }
Mohamed Abukarb175b942019-05-09 16:30:58 +0300580}
581
Mohamed Abukar34e43832019-11-13 17:57:15 +0200582# list: With empty parameter, list all, else the parameter is
583# a subscriptionId
Abukar Mohamed8504f6a2019-04-03 11:07:48 +0000584
Mohamed Abukar34e43832019-11-13 17:57:15 +0200585sub do_subscription_list(@) {
586 my $urlpath=$base_subs;
587 my $subscriptionId = $_[0] || "";
588 if ($subscriptionId ne "") {
589 $urlpath = "$urlpath/$subscriptionId";
590 }
591 make_temps();
592 if (rest("GET", $urlpath)) {
593 if ($http_code eq "200") {
594 print_json $resultfile;
595 $status = 0;
596 }
597 else {
598 my $error;
599 if ($http_code eq "400") {
600 $error = "INVALID SUBSCRIPTION ID $subscriptionId";
601 }
602 elsif ($http_code eq "404") {
603 $error = "SUBSCRIPTION $subscriptionId NOT FOUND";
604 }
605 elsif ($http_code eq "500") {
606 $error = "INTERNAL ERROR";
607 }
608 else {
609 $error = "UNKNOWN STATUS $http_code";
610 }
611 print "$error\n";
612 $status = 1;
613 }
614 }
615 else {
616 $status=1;
617 }
618 remove_temps();
619}
620
621sub do_subscription_add(@) {
622 my $urlpath=$base_subs;
623
624 if (validate_subscription(@_)) {
625 make_temps();
626 if (rest("POST", $urlpath, make_subscriptionRequest(@_))) {
627 if ($http_code eq "201") {
628 print_json $resultfile;
629 $status = 0;
630 }
631 else {
632 my $error;
633 if ($http_code eq "400") {
634 $error = "INVALID INPUT";
635 }
636 elsif ($http_code eq "500") {
637 $error = "INTERNAL ERROR";
638 }
639 else {
640 $error = "UNKNOWN STATUS $http_code";
641 }
642 print "$error\n";
643 $status = 1;
644 }
645 }
646 else {
647 $status=1;
648 }
649 remove_temps();
650 }
651 else {
652 $status = 1;
653 }
654}
655
656sub do_subscription_delete(@) {
657 my $urlpath=$base_subs;
658 my $subscriptionId = $_[0] || "";
659 if ($subscriptionId ne "") {
660 $urlpath = "$urlpath/$subscriptionId";
661 }
662 else {
663 print "$myname: delete: Subscription id required\n";
664 $status=1;
665 return;
666 }
667 make_temps();
668 if (rest("DELETE", $urlpath)) {
669 if ($http_code eq "204") {
670 print "SUBSCRIPTION $subscriptionId DELETED\n";
671 $status = 0;
672 }
673 else {
674 my $error;
675 if ($http_code eq "400") {
676 $error = "INVALID SUBSCRIPTION ID $subscriptionId";
677 }
678 elsif ($http_code eq "500") {
679 $error = "INTERNAL ERROR";
680 }
681 else {
682 $error = "UNKNOWN STATUS $http_code";
683 }
684 print "$error\n";
685 $status = 1;
686 }
687 }
688 else {
689 $status = 1;
690 }
691 remove_temps();
692}
693
694sub do_subscription_modify(@) {
695 my $urlpath=$base_subs;
696 if (defined $_[0]) {
697 $urlpath = "$urlpath/$_[0]";
698 }
699 else {
700 print "$myname: modify: Subscription id required\n";
701 $status=1;
702 return;
703 }
704 shift;
705 if (validate_subscription(@_)) {
706 make_temps();
707 if (rest("PUT", $urlpath, make_subscriptionRequest(@_))) {
708 if ($http_code eq "200") {
709 print_json $resultfile;
710 $status = 0;
711 }
712 else {
713 my $error;
714 if ($http_code eq "400") {
715 $error = "INVALID INPUT";
716 }
717 elsif ($http_code eq "500") {
718 $error = "INTERNAL ERROR";
719 }
720 else {
721 $error = "UNKNOWN STATUS $http_code";
722 }
723 print "$error\n";
724 $status = 1;
725 }
726 }
727 else {
728 $status=1;
729 }
730 remove_temps();
731 }
732 else {
733 $status = 1;
734 }
735}
736
737sub do_health(@) {
738 my $urlpath=$base_health;
739 my $check = $_[0] || "";
740 # API now defines two types of checks, either of
741 # which must be specified.
742 if ($check ne "alive" and $check ne "ready") {
743 print "$myname: health check type required (alive or ready)\n";
744 $status=1;
745 return;
746 }
747 $urlpath = "$urlpath/$check";
748 make_temps();
749 if (rest("GET", $urlpath)) {
750 my $res;
751 if ($check eq "alive") {
752 # If GET succeeds at all, the xapp manager is alive, no
753 # need to check the HTTP code.
754 $res = "ALIVE";
755 }
756 else {
757 if ($http_code eq "200") {
758 $res = "READY";
759 }
760 elsif ($http_code eq "503") {
761 $res = "NOT READY";
762 }
763 elsif ($http_code eq "500") {
764 $res = "INTERNAL ERROR";
765 }
766 else {
767 $res = "UNKNOWN STATUS $http_code";
768 }
769 }
770 print "$res\n";
771 }
772 else {
773 $status = 1;
774 print "$myname: health check failed to contact appmgr\n";
775 }
776 remove_temps();
777}
778
779sub do_config(@) {
780 my $subcommand = $_[0] || "";
781 shift;
782
783 my %subcommands = (
784 "list" => \&do_config_list,
785 "add" => \&do_config_add,
786 "delete" => \&do_config_delete,
787 "del" => \&do_config_delete,
788 "modify" => \&do_config_modify,
789 "mod" => \&do_config_modify
790 );
791 if (exists $subcommands{$subcommand}) {
792 $subcommands{$subcommand}(@_);
793 }
794 else {
795 print "$myname: unrecognized config subcommand $subcommand\n";
796 helphint();
797 $status=1
798 }
799}
800
801sub do_config_list(@) {
802 if (defined $_[0]) {
803 print "$myname: \"config list\" has no parameters\n";
804 $status = 1;
805 return;
806 }
807 make_temps();
808 if (rest("GET", $base_config)) {
809 if ($http_code eq "200") {
810 print_json $resultfile;
811 $status = 0;
812 }
813 else {
814 my $error;
815 if ($http_code eq "500") {
816 $error = "INTERNAL ERROR";
817 }
818 else {
819 $error = "UNKNOWN STATUS $http_code";
820 }
821 print "$error\n";
822 $status = 1;
823 }
824 }
825 else {
826 $status=1;
827 }
828 remove_temps();
829}
830
831# validate_config() checks configuration commmand line.
832# "config add" and "config modify" expect either single parameter which
833# must be a JSON file that contains the whole thing to send (see API),
834# or 5 parameters, where the first three are
835# $_[0] = name
836# $_[1] = configName (name of the configMap)
837# $_[2] = namespace
838# Followed by two file names:
839# $_[3] = file containing configSchema
840# $_[4] = file containing data for configMap
841# Giving the last two literally on the command line does not make much sense,
842# since they are arbitrary JSON data.
843# On success, returns parameter count (1 or 5), depending on which kind of
844# command line found.
845# 0 if errors.
846
847# Check only the 3 names at the beginning of config add/modify/delete
848sub validate_config_names(@) {
849 my $retval = 1;
850 # Names in the Kubernetes world consist of lowercase alphanumerics
851 # and - and . as specified in
852 # https://kubernetes.io/docs/concepts/overview/working-with-objects/name
853 for (my $idx = 0; $idx <= 2; ++$idx) {
854 if (! ($_[$idx] =~ /^[a-z][-a-z0-9.]*$/)) {
855 print "$myname: invalid characters in name $_[$idx]\n";
856 $retval = 0;
857 }
858 }
859 return $retval;
860}
861
862sub validate_config(@) {
863 my $retval = 1;
864 print "validate_config args @_\n";
865 if ($#_ == 0) {
866 if (! -r $_[0]) {
867 print "$myname: config file $_[0] cannot be read: $!\n";
868 $retval = 0;
869 }
870 }
871 elsif ($#_ == 4) {
872 $retval = 5;
873 if (! validate_config_names(@_)) {
874 $retval = 0;
875 }
876 for (my $idx = 3; $idx <= 4; ++$idx) {
877 if (! -r $_[$idx]) {
878 print "$myname: cannot read file $_[$idx]\n";
879 $retval = 0;
880 }
881 }
882 }
883 else {
884 print "$myname: config add: 1 or 5 parameter expected\n";
885 $retval = 0;
886 }
887 return $retval;
888}
889
890# Generate JSON for the xAppConfig element (see API).
891
892sub make_xAppConfigInfo($$$) {
893 return "{\"xAppName\":\"$_[0]\",\"configMapName\":\"$_[1]\",\"namespace\":\"$_[2]\"}";
894}
895
896sub make_xAppConfig(@) {
897 my $retval = "{\"xAppConfigInfo\":" . make_xAppConfigInfo($_[0],$_[1],$_[2]);
898 my $fh;
899 open($fh, "<", $_[3]) or die "failed to open $_[3]";
900 my @obj = <$fh>;
901 close($fh);
902 $retval = $retval . ",\"configSchema\":" . join("", @obj);
903 open($fh, "<", $_[4]) or die "failed to open $_[4]";
904 @obj = <$fh>;
905 close($fh);
906 $retval = $retval . ",\"configMap\":" . join("", @obj) . "}";
907}
908
909sub do_config_add(@) {
910 my $paramCount;
911
912 $paramCount = validate_config(@_);
913 if ($paramCount > 0) {
914 my $xAppConfig;
915 if ($paramCount == 1) {
916 $xAppConfig = "\@$_[0]";
917 }
918 else {
919 $xAppConfig = make_xAppConfig(@_);
920 }
921 make_temps();
922 if (rest("POST", $base_config, $xAppConfig)) {
923 if ($http_code eq "201") {
924 print_json $resultfile;
925 $status = 0;
926 }
927 elsif ($http_code eq "422") { # Validation failed, details in result
928 print_json $resultfile;
929 $status = 1;
930 }
931 else {
932 my $error;
933 if ($http_code eq "400") {
934 $error = "INVALID INPUT";
935 }
936 elsif ($http_code eq "500") {
937 $error = "INTERNAL ERROR";
938 }
939 else {
940 $error = "UNKNOWN STATUS $http_code";
941 }
942 print "$error\n";
943 $status = 1;
944 }
945 }
946 else {
947 $status=1;
948 }
949 remove_temps();
950 }
951 else {
952 $status = 1;
953 }
954}
955
956sub do_config_modify(@) {
957 my $paramCount;
958
959 $paramCount = validate_config(@_);
960 if ($paramCount > 0) {
961 my $xAppConfig;
962 if ($paramCount == 1) {
963 $xAppConfig = "\@$_[0]";
964 }
965 else {
966 $xAppConfig = make_xAppConfig(@_);
967 }
968 make_temps();
969 if (rest("PUT", $base_config, $xAppConfig)) {
970 if ($http_code eq "200") {
971 print_json $resultfile;
972 $status = 0;
973 }
974 elsif ($http_code eq "422") { # Validation failed, details in result
975 print_json $resultfile;
976 $status = 1;
977 }
978 else {
979 my $error;
980 if ($http_code eq "400") {
981 $error = "INVALID INPUT";
982 }
983 elsif ($http_code eq "500") {
984 $error = "INTERNAL ERROR";
985 }
986 else {
987 $error = "UNKNOWN STATUS $http_code";
988 }
989 print "$error\n";
990 $status = 1;
991 }
992 }
993 else {
994 $status=1;
995 }
996 remove_temps();
997 }
998 else {
999 $status = 1;
1000 }
1001}
1002
1003# In config delete, allow either 1 parameter naming a file that contains
1004# a JSON xAppConfigInfo object, or 3 parameters giving the
1005# components (xAppName, configMapName, namespace), same as
1006# in add and modify operations.
1007
1008sub do_config_delete(@) {
1009 my $xAppConfigInfo = "";
1010
1011 if ($#_ != 0 and $#_ != 2) {
1012 print "$myname: wrong number of parameters for config delete\n";
1013 $status = 1;
1014 }
1015 elsif ($#_ == 0) {
1016 if (-r $_[0]) {
1017 $xAppConfigInfo = "\@$_[0]";
1018 }
1019 else {
1020 print "$myname: config file $_[0] cannot be read: $!\n";
1021 $status = 1;
1022 }
1023 }
1024 elsif (($#_ == 2) && validate_config_names(@_)) {
1025 $xAppConfigInfo = make_xAppConfigInfo($_[0],$_[1],$_[2]);
1026 }
1027 else {
1028 print "$myname: bad parameters for config delete\n";
1029 $status = 1;
1030 }
1031 if ($xAppConfigInfo ne "") {
1032 make_temps();
1033 if (rest("DELETE", $base_config, $xAppConfigInfo)) {
1034 if ($http_code eq "204") {
1035 print "SUCCESFUL DELETION OF CONFIG\n";
1036 $status = 0;
1037 }
1038 else {
1039 my $error;
1040 if ($http_code eq "400") {
1041 $error = "INVALID PARAMETERS SUPPLIED";
1042 }
1043 elsif ($http_code eq "500") {
1044 $error = "INTERNAL ERROR";
1045 }
1046 else {
1047 $error = "UNKNOWN STATUS $http_code";
1048 }
1049 print "$error\n";
1050 $status = 1;
1051 }
1052 }
1053 else {
1054 $status=1;
1055 }
1056 remove_temps();
1057 }
1058}