-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcloudify
executable file
·786 lines (699 loc) · 26.2 KB
/
cloudify
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
#!/bin/bash
#Final state machine like
state=0
#Installation path
folder="/opt/Cloudify"
#Quality level for the connection
quality=5
#Compression level
compression=2
#Protocol to be used (default VNC)
protocol="vnc"
#Timeout to wait for the pod creation (expressed in seconds)
timeout=60
#Seconds for the server response to consider a good connection
connection_answer_time=2
#The Persistent Volume Claim file
pvcDeploy="$folder/kubernetes/volumes.yaml"
#The target deployment file
targetDeploy="$folder/kubernetes/deployment.yaml"
#Variable to select connection type, 0=clear, 1=encrypted
enc=0
#Pulseaudio port
pulsePort=34567
#List of supported apps
supported_apps=("firefox" "libreoffice" "blender" "cuda-blender")
#Creating cuda-blender alias
alias cuda-blender=blender
#List of supported protocols
supported_protocols=("vnc" "novnc")
#Variable containing the k8s namespace to be used
k8s_namespace="kod"
#Variable containing the ssh port
ssh_port=22
#Variables to select whether to use pvc or not
default_use_pvc="disabled"
if [[ $default_use_pvc == "disabled" ]]; then
use_pvc=0
else
use_pvc=1
fi
#The vncviewer pod file
tigervncPodFile="$folder/kubernetes/vncviewer.yaml"
#vncviewer run mode (0-> native app, 1-> docker container, 2-> k8s pod)
supported_run_modes=("native" "docker" "pod")
default_run_mode=2
run_mode=$default_run_mode
#TigerVNC viewer docker's name and version
tigervnc_docker="liqo/vncviewer:latest"
#TigerVNC Dockerfile path
tigervnc_dockerfile_path="$folder/docker/vncviewer"
#TigerVNC container name (used to tag the container)
tigervnc_container_name="vncviewer"
#Set SECURE_CONNECTION to 1/0 in deployment depending on encryption
function adjust_encription {
line=`grep -n 'SECURE_CONNECTION' ${targetDeploy} | cut -d : -f -1`
((line=line+1))
sed -i "${line}s/[0-1]/${enc}/" ${targetDeploy}
}
#Set application name, pid, secret (containing the ssh public key) and namespace in deployment
function adjust_appname_pid_secret_label {
sed -i "s/RAR_001_APP_NAME_PID/${vnc_srv_app_pid}/" ${targetDeploy}
sed -i "s/RAR_002_APP_NAME/${application_name}/" ${targetDeploy}
sed -i "s/RAR_003_SSH_SECRET/${ssh_secret_name}/" ${targetDeploy}
sed -i "s#RAR_004_NS_LABEL#${viewer_ns_label}#" ${targetDeploy}
}
#Function that replaces the ClusterIP type service with a NodePort one inside
#the deployment.yaml file. This is required if run_mode=[0|1].
function enable_nodeport {
sed -i "s/ClusterIP/NodePort/" ${targetDeploy}
}
#Set the new generated token in deployment
function adjust_token {
line=`grep -n 'VNC_PASSWORD' ${targetDeploy} | cut -d : -f -1`
((line=line+1))
sed -i "${line}s/\".*\"/\"$token\"/" ${targetDeploy}
}
#Uncomment the pvc volumes and volumeMounts in deployment depending on the application name
function adjust_volumes {
#Uncomment volumes
from=`grep -n "RAR_002_APP_NAME-persistent-storage" ${targetDeploy} | cut -d : -f -1`
((to=from+2))
sed -i "${from},${to} {s/^#//g}" ${targetDeploy}
#Uncomment volumeMounts
from=`grep -n "${application_name}-persistent-storage" ${targetDeploy} | cut -d : -f -1`
((to=from+1))
sed -i "${from},${to} {s/^#//g}" ${targetDeploy}
}
#Remove services and ports in deployment depending on protocol used
function adjust_protocol {
from_novnc_service=`grep -w -n 'novnc-svc-port' ${targetDeploy} | cut -d : -f -1`
((till_novnc_service=from_novnc_service+3))
from_vnc_service=`grep -w -n 'vnc-svc-port' ${targetDeploy} | cut -d : -f -1`
((till_vnc_service=from_vnc_service+3))
from_novnc_container=`grep -w -n 'novnc-cont-port' ${targetDeploy} | cut -d : -f -1`
((till_novnc_container=from_novnc_container+1))
from_vnc_container=`grep -w -n 'vnc-cont-port' ${targetDeploy} | cut -d : -f -1`
((till_vnc_container=from_vnc_container+1))
if [[ $enc -eq 1 ]]; then
sed -i "${from_novnc_container},${till_novnc_container}d;${from_novnc_service},${till_novnc_service}d;${from_vnc_container},${till_vnc_container}d;${from_vnc_service},${till_vnc_service}d" ${targetDeploy}
elif [ "$protocol" = "vnc" ]; then
sed -i "${from_novnc_container},${till_novnc_container}d;${from_novnc_service},${till_novnc_service}d" ${targetDeploy}
else
sed -i "${from_vnc_container},${till_vnc_container}d;${from_vnc_service},${till_vnc_service}d" ${targetDeploy}
fi
}
#Function to wait for the Nodeport to be opened
function scan_node_port {
for i in $(seq $timeout); do
nc -z $targetNodeIp $targetNodePortSsh
if [ $? -eq 0 ]; then
return 0
fi
done
return 1
}
#Retrieving the ip, ports, pod name and namespace
function retrieve_pod_info {
read -r targetNodeIp targetPodName targetPodNamespace <<<$(kubectl get pod -l app=$vnc_srv_app_pid -n $k8s_namespace -o "jsonpath={..status.hostIP} {.items..metadata.name} {.items..metadata.namespace}")
if [[ $enc -eq 1 ]]; then
read -r targetNodePortSsh <<<$(kubectl get svc -l app=$vnc_srv_app_pid -n $k8s_namespace -o 'jsonpath={..spec.ports[?(@.name=="ssh-svc-port")].nodePort}')
else
read -r targetNodePortProtocol targetNodePortSsh <<<$(kubectl get svc -l app=$vnc_srv_app_pid -n $k8s_namespace -o 'jsonpath={..spec.ports[?(@.name=="'${protocol}'-svc-port")].nodePort} {..spec.ports[?(@.name=="ssh-svc-port")].nodePort}')
fi
}
#Function to get the current k8s node info (Hostname and InternalIP)
function retrieve_curr_k8s_node_info {
#Retrieveing the node_name and node_address
old_IFS=$IFS
IFS='#' read -r -a current_node_info <<< $(kubectl get nodes -l kubernetes.io/hostname=`cat /etc/hostname` -o 'jsonpath={range .items[0].status.addresses[*]}{.type}={.address}#{end}')
for ((i=0; i<${#current_node_info[@]}; i++)); do
if [[ $(echo ${current_node_info[$i]} | cut -d '=' -f 1) == "Hostname" ]];then
current_node_name=$(echo ${current_node_info[$i]} | cut -d '=' -f 2)
fi
if [[ $(echo ${current_node_info[$i]} | cut -d '=' -f 1) == "InternalIP" ]];then
current_node_address=$(echo ${current_node_info[$i]} | cut -d '=' -f 2)
fi
done
IFS=$old_IFS
}
#Function to test namespace existence (returns 0 if it exists, 1 otherwise)
function test_ns_existance {
for ns_token_to_test in $(kubectl get ns | grep $k8s_namespace); do
if [[ $ns_token_to_test == $k8s_namespace ]]; then
return 0
fi
done
return 1
}
#Function to prepare the cluster for the cloudify execution
function prepare_cluster {
echo -n "Check $k8s_namespace namespace existence..."
#Check namespace existence
test_ns_existance
if [[ $? -eq 1 ]]; then
echo -n "It doesn't exist! => Creating namespace..."
#Create namespace
kubectl create ns $k8s_namespace &>/dev/null
#Check namespace creation
if [ $? -ne 0 ]; then
echo "Error: Unable to create the namespace. Running ${application_name} locally"
clear_and_exit 7
fi
echo "OK"
else
echo "It exists"
fi
#Add label to namespace useful to apply resources on liqo.io
echo -n "Apply liqo.io/enabled=true label to $k8s_namespace namespace..."
kubectl label ns $k8s_namespace "liqo.io/enabled=true" --overwrite &>/dev/null
#Check label return conde
if [ $? -ne 0 ]; then
echo "Error: Unable to apply label the namespace. Running ${application_name} locally"
clear_and_exit 8
fi
echo "OK"
#Add label to the current node to avoid vncserver pod scheduling on it
if [[ $run_mode -eq 2 ]]; then
retrieve_curr_k8s_node_info
echo -n "Apply $viewer_ns_label=true label to the current node ($current_node_name)..."
kubectl label no $current_node_name "$viewer_ns_label=true" --overwrite &>/dev/null
#Check label return conde
if [ $? -ne 0 ]; then
echo "Error: Unable to apply label the current node. Running ${application_name} locally"
clear_and_exit 9
fi
echo "OK"
fi
}
#Function to remove the labels created in prepare_cluster
function clean_cluster {
if [[ $run_mode -eq 2 ]]; then
#Remove node label if exists
echo -n "Removing $viewer_ns_label label from current node ($current_node_name)..."
kubectl label no $current_node_name "$viewer_ns_label-" &>/dev/null
echo "OK"
fi
}
#Function that runs the native vnc application
function run_native_vncviewver_novnc_application {
# Forwarding sound
echo -n "Loading PulseAudio tcp module..."
pactl load-module module-native-protocol-tcp port=${pulsePort} auth-ip-acl=127.0.0.1 &>/dev/null
echo "OK"
((state=state+1))
#state=3 ; enc=0,1 ; run_mode=0
echo -n "Creating PulseAudio ssh tunnel..."
ssh -o UserKnownHostsFile=/dev/null \
-i "${working_dir}/id_rsa" \
-oStrictHostKeyChecking=no -f -N \
-M -S "${working_dir}/ssh_socket:${targetNodePortSsh}" \
-R ${pulsePort}:localhost:${pulsePort} \
vncuser@${targetNodeIp} \
-p ${targetNodePortSsh} &>/dev/null
echo "OK"
((state=state+1))
#state=4 ; enc=0,1 ; run_mode=0
if [ $enc -eq 1 ]; then
#Creating ssh tunnel for the protocol
echo -n "Creating VNC ssh tunnel..."
ssh -o UserKnownHostsFile=/dev/null \
-i "${working_dir}/id_rsa" \
-oStrictHostKeyChecking=no -f -N \
-S "${working_dir}/ssh_socket:${targetNodePortSsh}" \
-L ${port}:localhost:${port} \
vncuser@${targetNodeIp} \
-p ${targetNodePortSsh} &>/dev/null
echo "OK"
((state=state+1))
#state=5 ; enc=1 ; run_mode=0
fi
#Check the protocol and run the corresponding native app
if [ "$protocol" = "vnc" ]; then
vncviewer -CompressLevel $compression -QualityLevel $quality $target -passwd <(echo ${token} | vncpasswd -f) 2>/dev/null
else
if [ $enc -eq 1 ]; then
echo -n "Starting encrypted NOVnc connection..."
url="http://localhost:$port"
else
echo -n "Starting clear NOVnc connection..."
url="http://$targetNodeIp:$targetNodePortProtocol"
fi
echo "OK"
echo ""
echo "Your token is ${token}, insert it into your browser"
notify-send -t 10000 -a 'Kubernetes on Desktop' "One time Token" "$token"
firefox $url &>/dev/null
pid=`pgrep firefox`
((timeout=timeout*timeout))
timeout $timeout tail --pid=$pid -f /dev/null
fi
}
#Function that creates the vncviewer container and launches it
function create_container_and_launch_vncviewer {
#Create the container and run vncviewer
echo "Running vncviewer docker..."
docker run -d --name $vnc_cli_app_pid \
-v ${working_dir}:/home/vnc/ssh_id_rsa \
-v /tmp/.X11-unix/:/tmp/.X11-unix/ \
--device /dev/snd:/dev/snd \
-v /dev/shm:/dev/shm \
-v /var/run/dbus:/var/run/dbus \
-e DISPLAY \
$tigervnc_docker \
enc=$1 `#enc` \
pod=0 `#pod` \
compression=$2 `#compression` \
quality=$3 `#quality` \
target=$4 `#target` \
token=$5 `#token` \
target_node_ip=$6 `#targetNodeIp` \
target_node_port_ssh=$7 `#targetNodePortSsh` \
enc_port=$8 `#port` \
&>/dev/null
#Check docker run exit code to see if a container error occurred
if [[ $? -ne 0 ]]; then
echo "Error: can't run the docker container"
clear_and_exit 3
fi
((state=state+1))
#state=3 ; enc=0,1 ; run_mode=1
#Wait for the docker to complete its execution
echo "Wait for the docker to complete its execution..."
docker wait $vnc_cli_app_pid &>/dev/null
echo "Done."
}
#Function to adjust the vncviewer.yaml file and apply the pod to k8s
function adjust_and_apply_pod {
#For safety let's copy the pod file and modify the temporary one
cp ${tigervncPodFile} "${working_dir}/vncviewer.yaml"
tigervncPodFile="${working_dir}/vncviewer.yaml"
#Get a free tcp port we can listen to
while true; do
#Generate random port number
pod_wait_port=$(shuf -i 1025-65535 -n 1)
read -r pod_port_to_check_local_ip <<< $(nc -zv localhost $pod_wait_port 2>&1)
read -r pod_port_to_check_localhost <<< $(nc -zv $current_node_address $pod_wait_port 2>&1)
#Check generated port
if [[ $( echo $pod_port_to_check_local_ip | grep succeeded) == "" ]] && \
[[ $( echo $pod_port_to_check_localhost | grep succeeded) == "" ]]; then
#It's a free port. OK
break;
fi
done
#Replace the pod parameters with their values in vncviewer.yaml
sed -i "s#RAR_000_APP_NAME_PID#$vnc_cli_app_pid#g" $tigervncPodFile
sed -i "s#RAR_001_K8S_NODE_NAME#$current_node_name#g" $tigervncPodFile
sed -i "s#RAR_002_TIGERVNC_CONTAINER_NAME#$vnc_cli_app_pid#g" $tigervncPodFile
sed -i "s#RAR_003_TIGERVNC_DOCKER#$tigervnc_docker#g" $tigervncPodFile
sed -i "s#RAR_004_ENC#enc=$enc#g" $tigervncPodFile
sed -i "s#RAR_005_POD#pod=1#g" $tigervncPodFile
sed -i "s#RAR_006_COMPRESSION#compression=$compression#g" $tigervncPodFile
sed -i "s#RAR_007_QUALITY#quality=$quality#g" $tigervncPodFile
sed -i "s#RAR_008_TARGET#target=$target#g" $tigervncPodFile
sed -i "s#RAR_009_TOKEN#token=$token#g" $tigervncPodFile
sed -i "s#RAR_010_TARGET_NODE_IP#target_node_ip=$targetNodeIp#g" $tigervncPodFile
sed -i "s#RAR_011_TARGET_NODE_PORT_SSH#target_node_port_ssh=$targetNodePortSsh#g" $tigervncPodFile
sed -i "s#RAR_012_ENC_PORT#enc_port=$port#g" $tigervncPodFile
sed -i "s#RAR_013_CLIENT_HOST_IP#client_host_ip=$current_node_address#g" $tigervncPodFile
sed -i "s#RAR_014_CLIENT_HOST_PORT#client_host_port=$pod_wait_port#g" $tigervncPodFile
sed -i "s#RAR_015_WORKING_DIR#$working_dir#g" $tigervncPodFile
#Applying the pod to the current node and wait for the process completion
kubectl apply -f $tigervncPodFile -n $k8s_namespace &>/dev/null && ((state++))
#state=3 ; enc=0,1 ; run_mode=2
#Waiting for the pod to start
echo -n "Waiting for vncviewer pod to start, max ${timeout} seconds..."
kubectl wait --for=condition=Ready pod -l "app=${vnc_cli_app_pid}" --timeout=${timeout}s -n $k8s_namespace &>/dev/null
#Check that vncviewer pod is running, otherwise exit
if [ $? -ne 0 ]; then
echo "Error: cannot create vncviewer pod."
clear_and_exit 6
else
echo "OK pod running"
fi
echo "Waiting for the pod to be completed..."
pod_finished_can_exit=0
while [ $pod_finished_can_exit -eq 0 ]; do
while read line; do
if [ "$line" == "Close_${token}" ]; then
pod_finished_can_exit=1
break
fi
done < <(nc -q -1 -l $pod_wait_port)
done
}
#Function to prepare the environment for run_mode 0-1
function prepare_env_runmode_0_1 {
echo -n "Retrieving node IP and PORT..."
retrieve_pod_info
echo "OK ip -> ${targetNodeIp}, $protocol -> ${targetNodePortProtocol}, ssh -> ${targetNodePortSsh}, podName -> ${targetPodName}, podNs -> ${targetPodNamespace}"
echo -n "Waiting for the NodePort to be opened..."
scan_node_port
if [ $? -ne 0 ]; then
echo "ERROR Nodeport took too much to be opened, running ${application_name} locally"
clear_and_exit 4
fi
echo "OK port opened"
#Set port depending on choosed protocol
if [ $enc -eq 1 ]; then
if [ "$protocol" = "vnc" ]; then
port=5900;
else
port=5800;
fi
fi
if [ "$protocol" = "vnc" ]; then
#Check encryption, if yes -> set variables to start vnc encrypted connection
#otherwise -> set variables to start normal vnc connection
if [ $enc -eq 1 ]; then
echo "Starting encrypted VNC connection..."
target="localhost::${port}"
else
echo "Starting clear VNC connection..."
target="$targetNodeIp::$targetNodePortProtocol"
fi
fi
}
#Function to prepare the environment for run_mode 0-1
function prepare_env_runmode_2 {
#Vnc port
port=5900
#Variable containing the vncserver service URL
svc_URL=$vnc_srv_app_pid"-service."$k8s_namespace".svc"
#Building 'target'
if [ $enc -eq 1 ]; then
echo "Starting encrypted VNC connection..."
target="localhost::$port"
else
echo "Starting clear VNC connection..."
target="$svc_URL::$port"
fi
#Use the svc URL instead of pod node IP
targetNodeIp=$svc_URL
#Use deployment ssh port instad of the nodeport port
targetNodePortSsh=$ssh_port
}
#Function to run vncviewer and connect it to vncserver pod
function connect {
case $run_mode in
0) #Native app (N.B. the protocol could be either vnc or novnc)
prepare_env_runmode_0_1
run_native_vncviewver_novnc_application
;;
1) #Docker container (N.B. the protocol MUST be vnc)
prepare_env_runmode_0_1
create_container_and_launch_vncviewer ${enc} ${compression} ${quality} ${target} ${token} ${targetNodeIp} ${targetNodePortSsh} ${port}
;;
2) #K8s pod (N.B. the protocol MUST be vnc)
prepare_env_runmode_2
adjust_and_apply_pod ${enc} ${compression} ${quality} ${target} ${token} ${port} ${targetNodeIp} ${targetNodePortSsh}
echo "Completed."
;;
\?) #Unsupported run_mode
echo "ERROR: Unsupported run_mode $run_mode"
clear_and_exit 2
;;
esac
echo "VNC execution completed."
}
#Main function to start the deployment of the desired application
function start_deploy {
#If run mode is docker => checking whether the docker image already exists or not and in the latter case creating it
if [ $run_mode -eq 1 ]; then
docker inspect $tigervnc_docker &>/dev/null
if [[ $? == 1 ]]; then
echo -n "The vncviewer image doesn't exist and will be created..."
tmp_prev_dir=$(pwd)
cd $tigervnc_dockerfile_path
docker build -t $tigervnc_docker . &>/dev/null
if [[ $? != 0 ]]; then
echo ""
echo "Error during vncviewer image build, running ${application_name} locally."
${application_name} &>/dev/null &
clear_and_exit 1
fi
cd $tmp_prev_dir
echo "Done."
else
echo "The vncviewer image already exists"
fi
fi
#For safety let's copy the deployment and modify the temporary one
working_dir="/tmp/Cloudify/$(date +%F_%H-%M-%S)"
mkdir -p $working_dir &>/dev/null
cp ${targetDeploy} "${working_dir}/deployment.yaml"
targetDeploy="${working_dir}/deployment.yaml"
#Check if pvc is enabled and adjust the volumes in the deployment.yaml file
if [ $use_pvc -eq 1 ]; then
adjust_volumes
fi
#Enable NodePort if run_mode=[0|1]
if [[ "$run_mode" =~ ^[0-1]$ ]]; then
enable_nodeport
fi
#Adjust deployment.yaml file
adjust_encription
adjust_appname_pid_secret_label
adjust_protocol
#Checking connectivity
#To perform that task, the command `kubectl describe pods` is used (we could use version, but too fast since tight)
echo -n "Checking connectivity..."
nettime=`(/usr/bin/time -f "Time:%e" kubectl describe pods) 2>&1 | tail -1 | grep "Time:" | sed "s/^.*://"`
if [ $? -eq 0 ]; then
#Checking network speed availability
if [[ $(echo "${nettime}<${connection_answer_time}" | bc) -eq 1 ]]; then
echo "OK cluster answered in ${nettime} seconds"
#Generating and adjusting vnc token
echo -n "Generating token..."
token=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
echo "OK"
adjust_token
#Generating ssh key pair
echo -n "Generating ssh key pair..."
ssh-keygen -t rsa -b 4096 -C "${USER}@kubernetes.io" -f "${working_dir}/id_rsa" -N "" &>/dev/null
echo "OK"
#Register Ctr+C interrupt
trap 'clear_and_exit -1' INT TERM KILL EXIT QUIT HUP
#Preparing the cluster by creating the kod namespace and
#appling the label kod/viewer-<pid>=true to the current node
#to avoid the vncserver pod to be scheduled on current node
prepare_cluster
#Creating the secret containing the ssh public key
echo -n "Creating ssh secret..."
kubectl create secret generic $ssh_secret_name --from-file=authorized_keys="${working_dir}/id_rsa.pub" -n $k8s_namespace &>/dev/null && ((state++))
#state=1 ; enc=0,1 ; run_mode=0,1,2
if [ $state -gt 0 ]; then
echo "OK"
echo -n "Applying deploy..."
#Apply both the pvc (if use_pvc=1) and the deploy incrementing the state variable at the end. This way:
# 1- If there's a 'Ctrl+C' between the two apply we don't run the risk of don't clear the resources already allocated due to the fact that
# the state was not incremented yet (if state++ would be done afterwards);
# 2- We don't run the risk of a wrong exit status check afterwards due to the fact that if the state is incremented after the apply
# (even if it fails) the $? variable is 0 because ((state++)) doesn't fail;
# 3- We don't run the risk of clear resources non allocated yet by increment the state before the apply.
# N.B.: It works because:
# a- if the second apply fails the state increment will not be done and the resources will not be allocated
# b- we don't care about the first apply because the pvc will never be deleted
if [ $use_pvc -eq 1 ]; then
kubectl apply -f $pvcDeploy -n $k8s_namespace &>/dev/null && kubectl apply -f $targetDeploy -n $k8s_namespace &>/dev/null && ((state++))
else
kubectl apply -f $targetDeploy -n $k8s_namespace &>/dev/null && ((state++))
fi
#state=2 ; enc=0,1 ; run_mode=0,1,2
if [ $? -ge 0 ]; then
echo "OK"
#Waiting for the pod to start
echo -n "Waiting for pod to start, max ${timeout}seconds..."
kubectl wait --for=condition=Ready pod -l "app=${vnc_srv_app_pid}" --timeout=${timeout}s -n $k8s_namespace &>/dev/null
if [ $? -eq 0 ]; then
echo "OK pod running"
connect
return
else
echo "ERROR cannot create pod, running ${application_name} locally"
fi
else
echo "ERROR cannot apply deployment, running ${application_name} locally"
fi
else
echo "ERROR cannot create secret, running ${application_name} locally"
fi
else
echo "ERROR cluster answered in ${nettime} seconds (too slow), running ${application_name} locally"
fi
else
echo "ERROR no Internet, running ${application_name} locally"
fi
${application_name} &>/dev/null &
}
#Delete only the deployment NOT the volume and clear all the allocated resources depending on the run_mode
function clear_and_exit {
#Check if an error occurred and run application locally. If $1 is 0 => no errors occurred
if [ $1 -ne 0 ]; then
if [ $1 -lt 0 ]; then
#If $1 is less than 0 it means that a signal has been caught
echo "Process interrupted with signal. Cleaning up and exiting..."
else
#If $1 is more than 0 it means that an error occured
echo "Running ${application_name} locally"
${application_name} &>/dev/null &
fi
fi
if [ $state -ge 4 ]; then
#Stop ssh VNC and/or PulseAudio tunnel
echo -n "Stopping SSH VNC and/or PulseAudio tunnel..."
ssh -S "${working_dir}/ssh_socket:${targetNodePortSsh}" -O "exit" vncuser@${targetNodeIp} &>/dev/null
echo "OK"
fi
if [ $state -ge 3 ] && [ $run_mode -eq 2 ]; then
#Deleteing the pod that was executing vncviewer
echo -n "Deleting the vncviewer pod..."
kubectl delete -f $tigervncPodFile -n $k8s_namespace &>/dev/null
echo "OK"
fi
if [ $state -ge 3 ] && [ $run_mode -eq 1 ]; then
#Deleting the container that was executing vncviewer
echo -n "Deleting the vncviewer container..."
docker container rm -f $vnc_cli_app_pid &>/dev/null
echo "OK"
fi
if [ $state -ge 3 ] && [ $run_mode -eq 0 ]; then
#Unloading PulseAudio TCP module
echo -n "Unloading PulseAudio TCP module..."
pactl unload-module module-native-protocol-tcp &>/dev/null
echo "OK"
fi
if [ $state -ge 2 ]; then
#Deleting deploy on cluster
echo -n "Deleting deploy on cluster..."
kubectl delete -f $targetDeploy -n $k8s_namespace &>/dev/null
echo "OK"
fi
if [ $state -ge 1 ]; then
#Deleting ssh secret on cluster
echo -n "Deleting ssh secret on cluster..."
kubectl delete secret $ssh_secret_name -n $k8s_namespace &>/dev/null
echo "OK"
fi
#Remove 'kod/viewer' node label
clean_cluster
echo "Process completed"
trap - INT TERM KILL EXIT QUIT HUP
exit $1
}
#Print usage function
function print_usage_and_exit {
echo "Run application in Cloud using Kubernetes as orchestrator."
echo "Usage: ./cloudify [-h] [-s] [-t timeout] [-p protocol] [-q quality] [-c compression] [-v] [-r runmode] app_name"
echo "|-> -h: start the helper menu"
echo "|-> -s: specify that the connection must be secured by using encryption (0/1, default 0 disabled)"
echo "|-> -t: connection/wait timeout in seconds (positive number, default $timeout)"
echo "|-> -p: connection protocol to be used (vnc/novnc, default $protocol)"
echo "|-> -q: specify the quality of the connection (0-9, default $quality)"
echo "|-> -c: specify the compression of the connection (0-6, default $compression)"
echo "|-> -v: specify to use the persistent volume claim (default $default_use_pvc)"
echo "|-> -r: vncviewer run mode ($(IFS='/'; echo "${supported_run_modes[*]}"), default ${supported_run_modes[$default_run_mode]})."
echo "| Warning: this option can't be used if '-p novnc' is set"
echo "|"
echo "|-> app_name: application to run. Supported applications: $(IFS=','; echo "${supported_apps[*]}")"
echo "|"
echo "|->Example: ./cloudify firefox"
echo "|->Example: ./cloudify -q 7 -t 10 -s firefox"
echo "|->Example: ./cloudify -q 7 -t 10 -s -r docker firefox"
exit $1
}
#Function to check options conflicts
function check_opts_conflicts {
if [[ $run_mode =~ ^[1-2]$ ]] && [[ $protocol == "novnc" ]]; then
printf "ERROR: Can't be used 1 or 2 as parameter in -r option when \"novlc\" protocol is set\n\n"
print_usage_and_exit 1
fi
}
function main() {
#Retrieving the name of the application passed as last argument
for application_name in $@; do :; done
asd=1
if [ $# -lt 1 ] || ! ( IFS=$'\n'; echo "${supported_apps[*]}" ) | grep -qFx "$application_name" &>/dev/null; then
print_usage_and_exit 1
fi
#Variable containing random alphanumeric string used as PID
app_pid=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 10 | head -n 1)
#Variable containing the ssh secret name
ssh_secret_name="rar-ssh-secret-"$app_pid
#Generating app names
echo -n "Generating VNC AppName and PID..."
vnc_srv_app_pid="$application_name-$app_pid"
vnc_cli_app_pid="$tigervnc_container_name-$app_pid"
viewer_ns_label="kod/vncviewer-$app_pid"
echo "OK"
while getopts "hsc:q:t:p:vr:" opt; do
case $opt in
s)
enc=1
echo "Parameter ENCRYPTION...OK activated"
;;
t)
if [[ ! "$OPTARG" =~ ^[0-9]+$ ]]; then
print_usage_and_exit 1
fi
timeout=$OPTARG
echo "Parameter TIMEOUT...OK ${timeout}"
;;
q)
if [[ ! "$OPTARG" =~ ^[0-9]$ ]]; then
print_usage_and_exit 1
fi
quality=$OPTARG
echo "Parameter QUALITY...OK ${quality}"
;;
c)
if [[ ! "$OPTARG" =~ ^[0-6]$ ]]; then
print_usage_and_exit 1
fi
compression=$OPTARG
echo "Parameter COMPRESSION...OK ${compression}"
;;
h)
print_usage_and_exit 0
;;
p)
if ! ( IFS=$'\n'; echo "${supported_protocols[*]}" ) | grep -qFx "$OPTARG" &>/dev/null; then
print_usage_and_exit 1
fi
protocol="$OPTARG"
echo "Parameter PROTOCOL...OK $OPTARG"
;;
v)
use_pvc=1
echo "Parameter USE PVC...OK"
;;
r)
case $OPTARG in
native)
run_mode=0
;;
docker)
run_mode=1
;;
pod)
run_mode=2
;;
*)
print_usage_and_exit 1
;;
esac
echo "Parameter RUN MODE...OK => Run as ${supported_run_modes[$run_mode]}"
;;
\?)
print_usage_and_exit 1
;;
esac
done
#Check options conflicts
check_opts_conflicts
#Starting the deploy
start_deploy
#Clear and exit
clear_and_exit 0
}
main $@