-
Notifications
You must be signed in to change notification settings - Fork 2
/
lc
executable file
·2110 lines (1717 loc) · 67.1 KB
/
lc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python3
# SPDX-License-Identifier: MIT
# vim: set ts=4 sw=4 et :
#
# lc - 'lab control' - a tool for interacting with the LabControl server
# and for accessing lab information and controlling lab resources,
# for test programs
#
# Some material, and lots of style, copied from ftc
#
# Copyright 2020-2021 Sony Corporation
# Author: Tim Bird <tim.bird (at) sony.com>
#
# To Do:
# - add support for "live" run command
#
# Deferred:
#
# - add setenv, and use for board-based commands, if the board is missing
# - maybe can use 'lc config default_board bbb' to get same effect?
#
# see lc.legacy for some code that could be re-used for future features
#
import os
import sys
import re
import tempfile
import importlib
# avoid UnicodeEncodeError exceptions by switching my default encoding
if sys.version_info[0] < 3:
importlib.reload(sys)
sys.setdefaultencoding('utf8')
import requests
# python 2to3 added the following urllib imports
# It's not clear that urllib.request or urllib.error are needed
# They are not used anywhere. Comment them out for now.
import urllib.request, urllib.parse, urllib.error
try:
import simplejson as json
except ImportError:
import json
# handle version 2 or 3 of python
if sys.version_info[0] == 2:
get_input = raw_input
else:
get_input = input
try:
from subprocess import getstatusoutput
except:
from commands import getstatusoutput
using_commands_gso = True
# MAJOR, MINOR, REVISION, extra_revision
# extra_revision, if non-empty, should start with a dash
# (e.g. "-next", "-rc1")
VERSION = (0, 6, 6, "")
# this is from the REST API standard
RSLT_OK = "success"
# define these as globals
log = None
tail_fd = None
quiet = False
verbose = False
debug = False
# local config file is hidden (starts with a dot), and
# is in the user home directory
config_filename = ".lc.conf"
system_config_filepath = "/etc/lc.conf"
# command_help is a python map with:
# key=name, value=(summary, long description)
#
command_help = {
"config": ("Show Lab Control configuration setting.",
"""Usage: lc config <name>
Shows the value for the indicated configuration option
If no name is specified, shows all configuration items.
"""),
"login": ("Login to a LabControl server.",
"""Usage: lc login
Authenticate to a LabControl server, and get a token used for future
requests.
This command also allows you to set the initial configuration for the
server URL, if desired.
You will be prompted for the server, user name and password to use.
You can press <Enter> to use already-configured values.
"""),
"list": ("Show a list of objects registered with LabControl.",
"""Usage: lc list <obj-type> [-q]
Prints a list of objects with the indicated object type.
Options:
-q Use "quiet" mode. This prints only the board names, with no
additional information. This is suitable for piping to other
commands.
<obj-type> Specify the type of objects to list
(must be one of: boards, devices, or resources)
Example: lc list boards
"""),
"reserve": ("Reserve a board for use.",
"""Usage: lc {board} reserve [-f] [{duration}]
Reserve a board for my use. This assigns a board
for use with my user account, so that my account has
exclusive access for testing and manipulating the board.
If a duration is specified, try to make the reservation for
that amount of time. Time is specified using as a string with an 'h'
or 'm' suffix indicating the number of hours and minutes. For example:
'30m' = 30 minutes, '2h' = 2 hours, '1h15m' = 1 hour and 15 minutes.
If no duration is specified, the reservation may be infinite (lasting
until manually released) or have a set time, depending on server
policy.
If -f is specified, then the reservation is 'forced' and any
current reservation by another user is released, before the board
is reserved for this user.
A message and the exit code indicate whether the resource is
already reserved.
ex: lc beaglebone reserve 2h"""),
"allocate": ("Reserve a board for my use.",
"""Usage: lc {board} allocate [-f] [{duration}]
Reserve a board for my use.
This is the same as the 'reserve' command, and is included
for compatibility with 'ebf'.
"""),
"release": ("Release a board reservation.",
"""Usage: lc {board} release [force]
Release a board that was reserved for my use.
Options:
force Release a board that was reserved by another user.
By default, the 'release' command will only release
a board that the current user has reserved. This
option is used to force the release of a reservation
by another user.
"""),
"mydevices": ("Show boards that are assigned to me.",
"""Usage: lc mydevices
Show boards that are reserved my use. This shows a list of boards
that were previously allocated or reserved for me. These are boards
that are available for my use and testing.
"""),
"power": ("Perform a power operation on a board.",
"""Usage: lc {board} power {operation}
Perform a power operation on a board.
Operations:
status Show the power status of a board. The power status
will be one of "ON", "OFF", or "UNKNOWN", if the
configured power controller for the device is incapable
of reporting the power status.
on Turn power on to the board.
off Turn power off to the board.
reboot Reboot the board.
ex: lc myboard power reboot
The labcontrol server will reboot the board. If the board cannot be rebooted
a message will be provided.
"""),
"get-resource": ("Get a resource associated with a board.",
"""Usage: lc {board} get-resource {resource_type} [{feature-str}]
Get a resource associated with a board. The resource type can
be one of: "power-measurement","serial","camera", "audio".
The resource can then be to perform an action related to the board.
The output from 'lc' is the name of the requested resource, or if
no resource is assigned, a string starting with the word "Error:"
Also, on an error, the return code from 'lc' is non-zero.
ex: pm_resource=$(lc bbb get-resource power-measurement)
if [ "$?" == 0 ] ; then
echo "The power management resource for board bbb is '$pm_resource'"
else:
echo "$pm_resource"
"""),
"set-config": ("Set configuration for a resource",
"""Usage: lc <resource> set-config <resource_type> [<endpoint_id]
This command reads standard input to set the configuration of
a lab resource. The input data is in json format, and the allowed
attributes are specific to the resource type.
ex: echo "{ \\"baud_rate\\": \\"115200\\" }" | lc serial-u5 set-config serial
if [ "$?" == 0 ] ; then
echo "baud rate was set successfully"
fi
"""),
"power-measurement": ("Perform a power measurement operation using a resource.",
"""Usage: lc <resource> power-measurement {operation}
Perform a power measurement operation on a board.
Note that you can also use the alias 'pm' instead of 'power-measurement'
Operations:
start Start capturing power measurement data, using the
indicated resource. lc will output a string which
is a token that can be used to control this instance
of power-measurement (that is, to stop, get or delete
the power-measurement data).
stop <token> Stop capturing power measurement data.
get-data <token> Return the captured power measurement data.
delete <token> Delete the captured power measurement data, on the server.
ex: token=$(lc acme1 power-measurement start)
lc acme pm stop $token
lc acme pm get-data $token >power-log.txt
lc acme pm delete $token
"""),
"serial": ("Perform a serial operation using a resource.",
"""Usage: lc <resource> serial {operation}
Perform a serial operation on a board.
Operations:
start Start capturing serial data, using the
indicated resource. lc will output a string which
is a token that can be used to control this instance
of serial capture (that is, to stop, get or delete
the serial data).
stop <token> Stop capturing serial data.
get-data <token> Return the captured serial data.
delete <token> Delete the captured serial data, on the server.
put-data Put data to the serial resource. Data is read from
standard input.
ex: token=$(lc uart10 serial start)
lc uart10 serial stop $token
lc uart10 serial get-data $token >power-log.txt
lc uart10 serial delete $token
cat testfile | lc uart10 serial put-data
"""),
"camera": ("Perform a camera operation using a resource.",
"""Usage: lc {resource} camera {operation} [-o {filename}] [-v]
Perform a camera operation on a board.
Operations:
capture Capture a still image, using the indicated resource.
lc outputs the URL reference to the image file.
start [{secs}] Start capturing (recording) video, using the indicated
resource. lc will output a string which is a token
that can be used to control this instance of video
recording (that is, to stop, get or delete
the video recording).
stop <token> Stop the video recording.
get-ref <token> Return a URL reference to the video recording.
delete <token> Delete the video recording on the server.
Options:
-o {filename} Download the image or video, and save it to the
indicated file.
-v View the image or video in the default browser.
ex1: url=$(lc logitech camera capture)
lc logitech camera capture -o image.jpeg
lc logitech camera capture -v
ex2: token=$(lc logitech camera start 60)
url=$(lc logitech camera get-ref $token)
lc logitech camera get-ref $token -o movie.mp4
lc logitech camera delete $token
"""),
"audio": ("Perform a audio operation using a resource.",
"""Usage: lc <resource> audio {operation}
Perform a audio operation on a board.
Operations:
start Start capturing audio data, using the
indicated resource. lc will output a string which
is a token that can be used to control this instance
of audio capture (that is, to stop, get or delete
the audio data).
stop <token> Stop capturing audio data.
get-ref <token> [-o <outfile>] Return a web reference to the captured
audio data. The reference is put to stdout, unless
'-o <outfile>' is used, in which case the data is
downloaded and stored in the specified filename.
delete <token> Delete the captured audio data, on the server.
ex: token=$(lc sound-card1 audio start)
lc sound-card1 audio stop $token
lc sound-card1 audio get-ref $token -o sound-file.wav
lc sound-card1 audio delete $token
"""),
"run": ("Run a command on a board",
"""Usage: lc {board} run {command} {args}...
Run a command on a board. Output from the command is displayed.
The return code of the command is the exit code of lc.
"""),
"upload": ("Upload a file or directory to a board",
"""Usage: lc {board} upload {src_path} {dest_path} [{permissions}]
Upload a file or directory to the indicated file or directory on
the board. Permissions can be optionally specified (in UNIX
numeric octal format). If the permissions are not specified, then
the ones currently on the file are used.
The return code indicates success or failure of the upload.
"""),
"download": ("Download a file or directory from a board (unstable)",
"""Usage: lc {board} download {src_path} {dest_path}
Download a file or directory from the board to the indicated destination
path in the local filesystem.
The return code indicates success or failure of the upload.
This feature is not complete, and it is in "unstable" status.
"""),
"help": ("Show this online help.",
"""Usage: lc help [<command>]
If a command is specified, show the usage information for that command."""),
"version": ("Show version information and exit.", ""),
"status": ("Show status of a board.",
"""Usage: lc {board} status [{item}]
Show the status of a board, including the reservation for a board.
{item} can be one of: "power", "network", or "command", and shows that
individual status item for the board.
ex: lc bbb status network
shows whether the board networking is operational or not.
"""),
}
# here's a helper routine to print a variable with it's name and value
def dvar(name):
print("DEBUG: python var %s=%s" % (name, caller.f_locals[name]))
# define the debug/verbosity-level based output routines
# Note that not every print should use these.
# if a print statement is fundamentally part of the execution
# of a command (e.g. the actual data from a command), it should
# continue to use 'print()', rather than these level-based
# functions.
def dprint(msg):
global debug
if debug:
print("DEBUG: " + msg)
def vprint(msg):
global verbose
if verbose:
print(msg)
class config_class:
def __init__(self, config_filepath):
# read configuration data from a file
try:
data = open(config_filepath, "r").read()
except:
error_out("ERROR: could not read LabControl configuration data from %s" % config_filepath)
conf_map = self.parse_conf(data)
# set values from conf_map
self.host = conf_map.get("host", "localhost")
self.user = conf_map.get("user", "lc_user")
self.auth_token = conf_map.get("token", "abcd01234")
self.server = conf_map.get("server", "http://localhost:8000/lcserver.py")
self.default_board = conf_map.get("default_board", "")
self.API_URL_BASE = "%s/" % self.server
self.config_filepath = config_filepath
# fuego configuration file syntax:
# ------------------------
# empty lines and lines starting with # are ignored
# single-line attribute:
# name=value
# multi-line attribute:
# name="""value line 1
# line 2, etc."""
# returns a map with key/value pairs for each item.
def parse_conf(self, data):
attr_map = {}
lines = data.split('\n')
line_no = 0
in_block = 0
block = ""
for line in lines:
line_no += 1
if in_block:
# try to find end of block
if line.rstrip().endswith('"""'):
# remove quotes and end block
line = line.rstrip()
block += line[:-3]
attr_map[attr] = block
in_block = 0
continue
else:
block += line + '\n'
continue
# ignore comments
if line.startswith("#"):
continue
# ignore empty lines
line = line.strip()
if not line:
continue
# if we're outside a block, look for name=value lines
# line should have an equals in it
# (either single line name=value, or multi-line block start)
if line.find("=") == -1:
print("ERROR: Missing '=' at line %d in Fuego config file" % line_no)
continue
(attr, value) = line.split('=', 1)
attr = attr.strip()
value = value.strip()
if value.find('"""') == -1:
# if a single-line, just record the attribute
if value.startswith('"') and value.endswith('"'):
# remove single-quotes
# (if value needs quotes, enclose in triple-quotes)
value = value[1:-1]
attr_map[attr] = value
else:
# if the start of a multi-line block...
vstart = value.find('"""')
block = value[vstart+3:] + '\n'
in_block = 1
# sanity check for block terminator on same line
# if this line has triple-quotes, then the
# block begins and ends on the same line.
if block.endswith('"""\n'):
block = block[:block.index('"""')]
attr_map[attr] = block
in_block = 0
# NOTE: there's a weird corner case with a line like:
# 'my_attr=""" foo bar """ more stuff '
# this will not terminate the block
# check for dangling material
if in_block:
print('ERROR: Syntax error in configuration file.')
print('Missing """ at end of multiline value for item "%s", at end of file' % attr)
attr_map[attr] = block
return attr_map
def save(self):
# note: This overwrites any comments in the file
# It would be nicer to scan the file and replace lines as they appear
# and to save other config items found in config_class
# (excepting generated ones)
fd = open(self.config_filepath, "w+")
fd.write("server=%s\n" % self.server)
fd.write("host=%s\n" % self.host)
fd.write("user=%s\n" % self.user)
fd.write("token=%s\n" % self.auth_token)
if self.default_board:
fd.write("default_board=%s\n" % self.default_board)
fd.close()
def usage(rcode, options=[]):
command = ""
if len(options):
command = options[0]
# check if command is legal
if command and command not in command_help.keys():
print("Unknown command: %s" % command)
command = ""
# drop through to showing list of commands
if not command:
# show list of commands
print("""Usage: lc [global_options] command [options]
Here are the available global options:
-h, --help Show this usage help
-v Be verbose
-q Be quiet
-c <conf_file> Use specified configuration file
--debug Show debugging information
command is one of:
""")
command_list = list(command_help.keys())
command_list.sort()
for command in command_list:
print(" %13s %s" % (command, command_help[command][0]))
else:
# print help for individual command
print("lc %s: %s" % (command, command_help[command][0]))
if command_help[command][1]:
print()
print(command_help[command][1])
sys.exit(rcode)
def print_error(message):
sys.stderr.write("Error: "+message+"\n")
sys.stderr.flush()
def error_out(message, rcode=1):
print_error(message)
sys.exit(rcode)
def dequote(str):
if str.startswith('"') and str.endswith('"'):
return str[1:-1]
else:
return str
# shows a list title, if not quiet, and
# returns an indent to use for the list
def show_list_title(title):
global quiet
if not quiet:
print(title)
indent = " "
else:
# machine-readable (-q) output omits the title and indent
indent = ""
return indent
# returns a list of objects
# if an error occurs, then the routine errors out with a message
def get_objects_via_api(conf, obj_type, options):
url = conf.API_URL_BASE+"api/v0.2/" + obj_type
headers = { "Authorization": "token " + conf.auth_token }
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
error_out("Cannot read %s from server" % obj_type)
resp_data = resp.json()
try:
result = resp_data["result"]
except:
error_out("Malformed response from server. Missing 'result'. resp=" + resp_data)
if result != RSLT_OK:
# print error
try:
reason = resp_data["message"]
except:
reason = "for unknown reasons"
reason = "%s" % reason
error_out("Could not do operation '%s %s'. From server:\n %s" % (res_type, operation, reason))
list_data = resp_data["data"]
if type(list_data) != type([]):
# print error
try:
reason = resp_data["message"]
except:
reason = "for unknown reasons"
error_out("Could not get %s from server: %s" % (obj_type, reason))
return list_data
def list_objects_via_api(conf, obj_type, options):
global quiet
server_obj_type = obj_type
if obj_type == "boards":
server_obj_type = "devices"
obj_list = get_objects_via_api(conf, server_obj_type, options)
indent = show_list_title("%s on the LabControl server:" % obj_type.title())
if obj_list:
for obj in obj_list:
print(indent + obj)
else:
if options:
extra_msg = " that match the specified criteria"
else:
extra_msg = ""
if not quiet:
print("No %s found%s." % (obj_type, extra_msg))
sys.exit(0)
def do_config(conf, options):
"""
Show all config options, or a single config option
"""
try:
name = options[0]
except:
# list configuration options
conf_item_list = []
for item in list(conf.__dict__.keys()):
# filter out computed items
if item not in ["API_URL_BASE"]:
conf_item_list.append(item)
conf_item_list.sort()
for item in conf_item_list:
print("%s=%s" % (item, getattr(conf, item)))
sys.exit(1)
print(getattr(conf, name, ""))
def do_login(conf, options):
"""
Login to a server, which consists of providing a user and password, and
getting the authentication token (used for future requests)
"""
# for now, support only interactive operations
print("Currently configured for user '%s' and server '%s'" % (conf.user, conf.server))
new_server = get_input("Enter Server: [%s] " % conf.server)
if not new_server:
new_server = conf.server
new_user = get_input("Enter User Name: [%s] " % conf.user)
if not new_user:
new_user = conf.user
import getpass
password = getpass.getpass("Enter Password: ")
# get token from server
url = new_server+"/api/v0.2/token"
headers = { "Content-Type": "application/json" }
jdata = '{ "username": "%s", "password": "%s" }' % (new_user, password)
resp = requests.post(url, headers=headers, data=jdata)
if resp.status_code != 200:
error_out("Cannot login to server '%s'" % new_server )
resp_data = resp.json()
try:
result = resp_data["result"]
except:
error_out("Malformed response from server. Missing 'result'. resp=%s" % resp_data)
if result != RSLT_OK:
# print error
try:
reason = resp_data["message"]
except:
reason = "for unknown reason (response missing reason)"
error_out("Login failure: " + reason)
try:
token = resp_data["data"]["token"]
except:
error_out("Malformed response from server. Missing 'token'. resp=%s" % resp_data)
# save token to configuration file
conf.server = new_server
conf.user = new_user
conf.auth_token = token
conf.save()
print("Succesfully logged in as user '%s'" % conf.user)
sys.exit(0)
def do_list(conf, options):
"""
Show a list of objects on the server. object type is a required
first argument.
"""
try:
obj_type = options[0]
del options[0]
except:
error_out("No object type specified for list operation\n" + \
"Please specify either 'boards', 'devices', or 'resources'.")
if obj_type not in ["boards", "devices", "resources"]:
error_out(("Invalid object type '%s'\n" % obj_type) + \
"Please specify one of: 'boards', 'devices', or 'resources'.")
list_objects_via_api(conf, obj_type, options)
def do_list_mydevices(conf, options):
"""
Show devices (boards) assigned to me (that is, with a reservation by me
on the server).
"""
url = conf.API_URL_BASE+"api/v0.2/devices/mine"
headers = { "Authorization": "token " + conf.auth_token }
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
error_out("Cannot read %s from server" % obj_type)
resp_data = resp.json()
try:
result = resp_data["result"]
except:
error_out("Malformed response from server. Missing 'result'. resp=" + resp_data)
if result != RSLT_OK:
# print error
try:
reason = resp_data["message"]
except:
reason = "for unknown reasons"
reason = "%s" % reason
error_out("Could not do operation 'mydevices'. From server:\n %s" % (reason))
list_data = resp_data["data"]
if type(list_data) != type([]):
# print error
try:
reason = resp_data["message"]
except:
reason = "for unknown reasons"
error_out("Could not get %s from server: %s" % (obj_type, reason))
indent = show_list_title("Boards reserved for me on the LabControl server:")
if list_data:
for board in list_data:
print(indent + board)
else:
if not quiet:
print("No boards found assigned to me.")
sys.exit(0)
def do_power(conf, options):
# board is a required first argument
try:
board = options[0]
del options[0]
except:
error_out("No board specified for power operation\n" + \
"Please specify a board from the list available with 'lc list boards'.")
# figure out what power operation we're performing
# should be one of 'status', 'on', 'off', 'reboot'
try:
operation = options[0].lower()
del options[0]
except:
error_out("No power operation specified.\n" + \
"Please specify one of 'status', 'on', 'off', or 'reboot'.")
if operation not in ["status", "on", "off", "reboot"]:
error_out("Invalid power operation specified.\n" + \
"Please specify one of 'status', 'on', 'off', or 'reboot'.")
url = conf.API_URL_BASE+"api/v0.2/devices/%s/power/%s" % (board, operation)
headers = { "Authorization": "token " + conf.auth_token }
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
error_out("Cannot perform power %s operation on server" % operation )
resp_data = resp.json()
try:
result = resp_data["result"]
except:
error_out("Malformed response from server. Missing 'result'. resp=%s" % resp_data)
if result != RSLT_OK:
# print error
try:
reason = resp_data["message"]
except:
reason = "for unknown reasons"
error_out("Could not do operation 'power %s'. %s" % (operation, reason))
# operation was performed, result was "success"
# report status depending on operation
if operation in ["on", "off"]:
print("Device %s is powered %s" % (board, operation.upper()))
return
if operation == "reboot":
print("Device %s was rebooted." % board)
return
if operation == "status":
power_status = resp_data["data"]
print("Device %s is powered %s" % (board, power_status))
return
# this seems unlikely, given the checks above
# but be thorough in error handling
error_out("Invalid operation '%s' for power command" % operation)
def do_run(conf, options):
# board is a required first argument
try:
board = options[0]
del options[0]
except:
error_out("No board specified for run operation\n" + \
"Please specify a board from the list available with 'lc list boards'.")
if not options:
error_out("No command was specified to run.")
run_cmd = " ".join(options)
run_cmd_escaped = json.dumps(run_cmd)
url = conf.API_URL_BASE+"api/v0.2/devices/%s/run/" % board
jdata = '{ "command": %s, "device_ip": "*", "username":"*" }' % run_cmd_escaped
headers = { "Authorization": "token " + conf.auth_token,
"Content-type": "application/json"}
resp = requests.post(url, headers=headers, data=jdata)
if resp.status_code != 200:
error_out("Cannot perform 'run' operation on server, status=%d" % resp.status_code)
#print("resp.content='%s'" % resp.content)
resp_data = resp.json()
#print("resp_data='%s'" % resp_data)
try:
result = resp_data["result"]
except:
error_out("Malformed response from server. Missing 'result'. resp=%s" % resp_data)
if result != RSLT_OK:
# print error
try:
reason = resp_data["message"]
except:
reason = "for unknown reasons"
error_out("Could not do operation 'run %s'.\n%s" % (run_cmd, reason))
# command was performed, result was "success"
return_code = resp_data["data"]["return_code"]
output = resp_data["data"]["data"]
for line in output:
sys.stdout.write(line)
#sys.stdout.write(line + "\n")
sys.exit(return_code)
# this is experimental code to receive both stdout and stderr, and the
# correct return code, in real time, from the server, for a long-running
# operation.
# the data is passed as a single stream, with stdout and stderr interleaved
# by the server, with markers to indicate a shift from one to the other
# stream. Buffering may be an issue.
# the markers are: "&now^for#std%xxx"
# the marker for the return code is: &now^for#rcode%yyy
def do_run2(conf, options):
# board is a required first argument
try:
board = options[0]
del options[0]
except:
error_out("No board specified for run operation\n" + \
"Please specify a board from the list available with 'lc list boards'.")
if not options:
error_out("No command was specified to run.")
run_cmd = " ".join(options)
url = conf.API_URL_BASE+"api/v0.2/devices/%s/run2/" % board
#headers = { "Authorization": "token " + conf.auth_token,
# "Content-type": "application/json"}
data = urllib.parse.urlencode({'command': run_cmd })
print("data=%s" % data)
binary_data = data.encode("utf-8")
print("binary_data=%s" % binary_data)
resp = urllib.request.urlopen(url=url, data=binary_data)
marker_err = b"&now^for#std%err"
marker_out = b"&now^for#std%out"
marker_rcode = b"&now^for#rcode%"
read_size = 1024
# this code will fail to catch markers that cross the boundaries
# of the read_size
resp_data = resp.read(1024)
rcode = "255"
in_stdout = True
while resp_data:
#print("at start of loop, resp_data='%s'" % resp_data.decode("utf-8"))
if marker_rcode in resp_data:
pos = resp_data.find(marker_rcode)
temp_data = resp_data[:pos]
rcode = resp_data[pos+len(marker_rcode):pos+len(marker_rcode)+3]
rest = resp_data[pos+len(marker_rcode)+3:]
resp_data = temp_data + rest
# check for transitions
if in_stdout:
if marker_err in resp_data:
pos = resp_data.find(marker_err)
stdout_data = resp_data[:pos]
sys.stdout.write(stdout_data.decode("utf-8"))
resp_data = resp_data[pos+len(marker_err):]
in_stdout = False
if not resp_data:
resp_data = resp.read(1024)
continue
else:
sys.stdout.write(resp_data.decode("utf-8"))
resp_data = resp.read(1024)
else:
if marker_out in resp_data:
pos = resp_data.find(marker_out)
stderr_data = resp_data[:pos]
sys.stderr.write(stderr_data.decode("utf-8"))
resp_data = resp_data[pos+len(marker_out):]
in_stdout = True
if not resp_data:
resp_data = resp.read(1024)
continue
else:
sys.stderr.write(resp_data.decode("utf-8"))
resp_data = resp.read(1024)
sys.exit(int(rcode))
def do_upload(conf, options):
# board is a required first argument
try:
board = options[0]
del options[0]
except:
error_out("No board specified for upload operation\n" + \
"Please specify a board from the list available with 'lc list boards'.")
if not options:
error_out("No file was specified to upload.")
src_filename = options[0]
del options[0]
if not options:
error_out("Missing destination location on board for upload.")
dest_path = options[0]
del options[0]
permissions = None
if options:
permissions = options[0]
del options[0]
if options:
error_out("Too many arguments to upload command.")