-
Notifications
You must be signed in to change notification settings - Fork 2
/
manage.py
executable file
·274 lines (231 loc) · 10.5 KB
/
manage.py
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
#!/usr/bin/env python3
"""
Management script for the VLAB. Used to rebuild images, start containers, manage keypairs, etc.
"""
import argparse
import datetime
import glob
import json
import os
import re
import subprocess
import sys
containers = ['vlabcommon', 'boardserver', 'relay', 'web']
def build_arg_parsers():
main_parser = argparse.ArgumentParser(description='VLAB management')
subparsers = main_parser.add_subparsers(help='Operation', dest='mode')
build_parser = subparsers.add_parser('build', help='Rebuild the VLAB containers')
build_parser.add_argument('images', nargs='*', help='List the images to build, or none to build all images.')
subparsers.add_parser('list', help='If the relay is running, list the currently available boards.')
subparsers.add_parser('status', help='Displays current status of the VLAB and available boards.')
subparsers.add_parser('stats', help='If the relay is running, parse the access log and display usage stats.')
start_parser = subparsers.add_parser('start', help='Restart the VLAB relay')
start_parser.add_argument('-p', '--port', nargs=1, default=["2222"],
help='The SSH port to bind the relay container to')
regenkeys_parser = subparsers.add_parser('generatekeys', help='Regenerate keypairs')
regenkeys_parser.add_argument('-i', '--internal', action="store_true", default=False,
help='Regenerate the internal keypair')
regenkeys_parser.add_argument('-u', '--user', nargs=1,
help='Regenerate the keypair for a given user')
regenkeys_parser.add_argument('-a', '--allnew', action="store_true", default=False,
help='Generate keys for all users in vlab.conf which do not already have a keypair')
return main_parser
def build_docker_image(image_name):
def find_hardware_server_archive():
"""
We need to extract the name of the HW server installer which varies because of the version number
Returns the base name of the archive, without path or file extensions
Errors if there is not exactly one suitable archive.
"""
res = glob.glob('./boardserver/Xilinx_HW_Server_Lin*')
if len(res) < 1:
err("Download the Xilinx Hardware Server tar.gz and place it in the ./boardserver folder before building "
"this image.")
if len(res) > 1:
err("There are multiple versions of the Xilinx_HW_Server_Lin in the ./boardserver folder. Select only one.")
name = os.path.basename(res[0])
if not name.endswith(".tar.gz"):
err("Ensure that you downloaded the .tar.gz version of the Xilinx hardware server.")
basename = name[:-len(".tar.gz")]
return basename
if image_name == "boardserver":
os.system(
'docker build --build-arg HWFILE="{}" -t vlab/{} {}/'.format(find_hardware_server_archive(), image_name,
image_name))
else:
os.system("docker build -t vlab/{} {}/".format(image_name, image_name))
def main():
main_parser = build_arg_parsers()
args = main_parser.parse_args()
# Grab the config file
conf_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "vlab.conf")
if not os.path.exists(conf_file):
err("vlab.conf should be located in the directory {}".format(os.path.dirname(os.path.realpath(__file__))))
config = load_vlab_conf(conf_file)
if args.mode == "build":
print("Rebuilding VLAB docker containers...")
if args.images:
for im in args.images:
if im in containers:
build_docker_image(im)
else:
print("Unknown image specified: {}".format(im))
print("Known images: {}".format(containers))
else:
for im in containers:
build_docker_image(im)
elif args.mode == "generatekeys":
if args.internal:
print("Generating new internal key pair...")
remove_if_exists("keys/id_rsa")
remove_if_exists("keys/id_rsa.pub")
os.system("ssh-keygen -q -N '' -f keys/id_rsa")
os.system("cp keys/id_rsa.pub boardserver/authorized_keys")
print("Generating new SSH host keys for relay server...")
remove_if_exists("keys/ssh_host_rsa_key")
remove_if_exists("keys/ssh_host_rsa_key.pub")
remove_if_exists("keys/ssh_host_ed25519_key")
remove_if_exists("keys/ssh_host_ed25519_key.pub")
remove_if_exists("keys/ssh_host_ecdsa_key")
remove_if_exists("keys/ssh_host_ecdsa_key.pub")
os.system("ssh-keygen -q -t rsa -b 4096 -N '' -f keys/ssh_host_rsa_key")
os.system("ssh-keygen -q -t ed25519 -N '' -f keys/ssh_host_ed25519_key")
os.system("ssh-keygen -q -t ecdsa -b 521 -N '' -f keys/ssh_host_ecdsa_key")
print("Creating 'VLAB keys owner' user and setting key permissions (requires root)...")
os.system("sudo useradd -M -d /nonexistent -s /usr/sbin/nologin -u 50000 vlab_keys_owner")
os.system("sudo chown 50000:50000 keys/id_rsa keys/id_rsa.pub keys/ssh_host_*_key keys/ssh_host_*_key.pub")
os.system("sudo chmod 444 keys/id_rsa keys/id_rsa.pub keys/ssh_host_*_key keys/ssh_host_*_key.pub")
print("Keys generated. Now run: {} build".format(sys.argv[0]))
elif args.allnew:
for user, _ in config['users'].items():
if not os.path.isfile("keys/{}".format(user)) or not os.path.isfile("keys/{}.pub".format(user)):
print("Generating keypair for user {}".format(user))
generate_key(user)
else:
if args.user is None:
err("Specify either --internal, --allnew, or --user")
else:
if not args.user[0] in config['users']:
print("Warning: User {} is not currently in vlab.conf.".format(args.user[0]))
print("Generating key pair for user {}...".format(args.user[0]))
generate_key(args.user[0])
elif args.mode == "start":
print("Restarting the VLAB relay and web...")
os.system("docker-compose up --force-recreate")
elif args.mode == "list":
print("Currently available boards:")
os.system("docker exec vlab_relay_1 python3 /vlab/checkboards.py -v")
elif args.mode == "status":
print("**********************************************")
print("Current VLAB Status at", current_time())
print("**********************************************")
# First pass to ping SSH on each board server from the relay
subprocess.run(['docker', 'exec', 'vlab_relay_1', 'python3', '/vlab/checkboards.py', '-vs'],
stdout=subprocess.PIPE)
# Second pass to get board lock details from relay
result = subprocess.run(['docker', 'exec', 'vlab_relay_1', 'python3', '/vlab/checkboards.py', '-v'],
stdout=subprocess.PIPE)
result_text = result.stdout.decode('utf-8')
output = parse_checkboards_output(result_text)
print(output)
elif args.mode == "stats":
os.system("docker exec vlab_relay_1 python3 /vlab/logparse.py")
else:
main_parser.print_usage()
def parse_timestamp(timestamp):
time = datetime.datetime.fromtimestamp(int(timestamp)).astimezone()
return time.strftime('%Y-%m-%d %H:%M:%S %Z')
def current_time():
time = datetime.datetime.now().astimezone()
return time.strftime('%Y-%m-%d %H:%M:%S %Z')
def parse_checkboards_output(text):
boards_list = {}
result = ""
pattern = re.compile(
"Boardclass: (.*?)$\\n(.*?)$(?=\\n\\d\\d\\d\\d-\\d\\d-\\d\\d-\\d\\d:\\d\\d:\\d\\d "
"checkboards\\.py: Boardclass|\\n*\\Z)", re.DOTALL | re.MULTILINE)
boardclasses = pattern.findall(text)
for boardclass in boardclasses:
pattern = re.compile("Board: (.*?)\\n.*?Server: (.*?)\\n.*?checkboards\\.py: \\t*(.*?)$", re.MULTILINE)
boards = pattern.findall(boardclass[1])
boards_list[boardclass[0]] = sorted(boards, key=lambda tup: tup[1])
for boardclass in sorted(boards_list.keys()):
boards = boards_list[boardclass]
result += "\n{} - {} boards\n\n".format(boardclass, len(boards))
for board in boards:
serial = board[0]
server = board[1]
status = board[2]
pattern1 = re.compile("Available since (\\d*)")
status1 = pattern1.findall(status)
pattern2 = re.compile("Locked by (.*) at (\\d*) until (\\d*)\\.")
status2 = pattern2.findall(status)
pattern3 = re.compile("In use by (.*) since (\\d*) \\(last ping at (\\d*)\\)\\.")
status3 = pattern3.findall(status)
pattern4 = re.compile("In use, but unlocked since (\\d*)\\.")
status4 = pattern4.findall(status)
pattern5 = re.compile("Board .* marked as locked but has no lock info\\. Setting as unlocked\\.")
status5 = pattern5.findall(status)
pattern6 = re.compile("Board .* marked as in-use but has no session info\\. Setting as available\\.")
status6 = pattern6.findall(status)
if len(status1) > 0:
unlocked_time = status1[0]
if unlocked_time == "0":
since = "first connected"
else:
since = parse_timestamp(unlocked_time)
formatted_status = "Available since {}".format(since)
elif len(status2) > 0:
user = status2[0][0]
lock_time = parse_timestamp(status2[0][1])
expiry_time = parse_timestamp(status2[0][2])
formatted_status = "Locked by {} ({} to {})".format(user, lock_time, expiry_time)
elif len(status3) > 0:
user = status3[0][0]
start_time = parse_timestamp(status3[0][1])
ping_time = parse_timestamp(status3[0][2])
formatted_status = "In use by {} since {} (last ping at {})".format(user, start_time, ping_time)
elif len(status4) > 0:
lock_expiry_time = parse_timestamp(status4[0][0])
formatted_status = "In use, but unlocked since {}".format(lock_expiry_time)
elif len(status5) > 0:
formatted_status = "Locked but no lock info - now set as unlocked"
elif len(status6) > 0:
formatted_status = "In use but no session info - now set as available"
else:
formatted_status = status
result += "{}\t{}\t{}\n".format(server, serial, formatted_status)
result += "\n**********************************************\n"
return result
def remove_if_exists(filename):
if os.path.isfile(filename):
os.remove(filename)
def err(s):
print(s)
sys.exit(1)
def load_vlab_conf(filename):
with open(filename) as f:
f_no_comments = ""
for line in f:
ls = line.strip()
if len(ls) > 0 and ls[0] != '#':
f_no_comments = f_no_comments + ls + "\n"
try:
config = json.loads(f_no_comments)
except ValueError as e:
print("Error in {}".format(filename))
num = 1
for line in f_no_comments.split("\n"):
print("{}: {}".format(num, line))
num = num + 1
print("\nLine numbers refer to the file as printed above.\nERROR: {}".format(e))
sys.exit(1)
return config
def generate_key(user):
remove_if_exists("keys/{}".format(user))
remove_if_exists("keys/{}.pub".format(user))
os.system('ssh-keygen -q -N "" -f keys/{}'.format(user))
os.system('sudo chown 50000:50000 keys/{0} keys/{0}.pub'.format(user))
os.system('sudo chmod 444 keys/{0} keys/{0}.pub'.format(user))
if __name__ == '__main__':
main()