--- apiVersion: v1 kind: ConfigMap metadata: annotations: {{- if .Values.global.commonAnnotations }} {{- include "render-value" ( dict "value" .Values.global.commonAnnotations "context" .) | nindent 4 }} {{- end }} labels: {{- if .Values.global.commonLabels }} {{- include "render-value" ( dict "value" .Values.global.commonLabels "context" .) | nindent 4 }} {{- end }} app.kubernetes.io/part-of: federatorai app: alameda name: auto-provision-script-template namespace: {{ .Release.Namespace }} data: planning-util.sh.template: |- #!/usr/bin/env bash # Creation Time: {{`{{.CreateTime}}`}} Version: {{ .Chart.AppVersion }} Build: {{ .Chart.Name }}-{{ .Chart.Version }} #=========================== target config info start ========================= target_config_info='{{`{{.TargetConfigInfo}}`}}' {{`{{.TfComment}}`}}#=========================== target config info end =========================== if [ "$BASH_VERSION" = "" ]; then err_code="6" /bin/echo -e "{\n \"reason\": \"Please use bash to run the script.\",\n \"error_code\": $err_code\n}" exit $err_code fi set -o pipefail awk_egrep () { local pattern_string=$1 gawk '{ while ($0) { start=match($0, pattern); token=substr($0, start, RLENGTH); print token; $0=substr($0, start+RLENGTH); } }' pattern="$pattern_string" } tokenize_json () { input="$1" local GREP local ESCAPE local CHAR if echo "test string" | egrep -ao --color=never "test" >/dev/null 2>&1 then GREP='egrep -ao --color=never' else GREP='egrep -ao' fi if echo "test string" | egrep -o "test" >/dev/null 2>&1 then ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' CHAR='[^[:cntrl:]"\\]' else GREP=awk_egrep ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' CHAR='[^[:cntrl:]"\\\\]' fi local STRING="\"$CHAR*($ESCAPE$CHAR*)*\"" local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' local KEYWORD='null|false|true' local SPACE='[[:space:]]+' # Force zsh to expand $A into multiple words local is_wordsplit_disabled=$(unsetopt 2>/dev/null | grep -c '^shwordsplit$') if [ $is_wordsplit_disabled != 0 ]; then setopt shwordsplit; fi echo "$input"|$GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$" if [ $is_wordsplit_disabled != 0 ]; then unsetopt shwordsplit; fi } parse_json_array () { local index=0 local ary='' read -r token case "$token" in ']') ;; *) while : do parse_json_value "$1" "$index" index=$((index+1)) ary="$ary""$value" read -r token case "$token" in ']') break ;; ',') ary="$ary," ;; *) exit 71;; esac read -r token done ;; esac [ "$BRIEF" -eq 0 ] && value=$(printf '[%s]' "$ary") || value= : } parse_json_object () { local key local obj='' read -r token case "$token" in '}') ;; *) while : do case "$token" in '"'*'"') key=$token ;; *) exit 70;; esac read -r token case "$token" in ':') ;; *) exit 73;; esac read -r token parse_json_value "$1" "$key" obj="$obj$key:$value" read -r token case "$token" in '}') break ;; ',') obj="$obj," ;; *) exit 74;; esac read -r token done ;; esac [ "$BRIEF" -eq 0 ] && value=$(printf '{%s}' "$obj") || value= : } parse_json_value () { local jpath="${1:+$1,}$2" isleaf=0 isempty=0 print=0 case "$token" in '{') parse_json_object "$jpath" ;; '[') parse_json_array "$jpath" ;; # At this point, the only valid single-character tokens are digits. ''|[!0-9]) exit 75;; *) value=$token # if asked, replace solidus ("\/") in json strings with normalized value: "/" [ "$NORMALIZE_SOLIDUS" -eq 1 ] && value=$(echo "$value" | sed 's#\\/#/#g') isleaf=1 [ "$value" = '""' ] && isempty=1 ;; esac [ "$value" = '' ] && return [ "$NO_HEAD" -eq 1 ] && [ -z "$jpath" ] && return [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 0 ] && print=1 [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && [ $PRUNE -eq 0 ] && print=1 [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 1 ] && [ "$isempty" -eq 0 ] && print=1 [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && \ [ $PRUNE -eq 1 ] && [ $isempty -eq 0 ] && print=1 [ "$print" -eq 1 ] && printf "[%s]\t%s\n" "$jpath" "$value" : } parse_json_begin () { read -r token parse_json_value read -r token case "$token" in '') ;; *) exit 72;; esac } check_target_config() { if [ -z "$target_config_info" ]; then err_code="2" show_error "target_config_info variable is not defined." $err_code exit $err_code else echo "-------------- config info ----------------" >> $debug_log # Hide password echo "$target_config_info" |sed 's/"login_password.*/"login_password": *****/g' >> $debug_log echo "-----------------------------------------------------" >> $debug_log fi } show_usage() { cat << __EOF__ Usage: Requirement: Modify "target_config_info" variable at the beginning of this script to specify target's info Run the script: bash $(basename $BASH_SOURCE) Standalone options: --test-connection-only --dry-run-only --verbose --log-name [/] [e.g., --log-name mycluster.log] --terraform-path [e.g., --terraform-path /var/test/output] __EOF__ } show_info() { if [ "$verbose_mode" = "y" ]; then tee -a $debug_log 1>&2 << __EOF__ $* __EOF__ else echo "$*" >> $debug_log fi return 0 } show_error() { echo -e "{\n \"reason\": \"$1\",\n \"error_code\": $2,\n \"log_file\": \"$debug_log\"\n}" | tee -a $debug_log } show_detail_to_stderr() { echo "$*" >> $debug_log if [ "$detail_to_stderr" = "y" ]; then echo "$*" 1>&2 fi } check_user_token() { if [ "$access_token" = "null" ] || [ "$access_token" = "" ]; then err_code="2" show_error "Failed to get login token from REST API." $err_code show_detail_to_stderr "Please check login account and login password." exit $err_code fi } parse_value_from_target_var() { target_string="$1" if [ -z "$target_string" ]; then err_code="2" show_error "parse_value_from_target_var() target_string parameter can't be empty." $err_code exit $err_code fi echo "$target_config_info"|tr -d '\n'|grep -o "\"$target_string\":[^\"]*\"[^\"]*\""|sed -E 's/".*".*"(.*)"/\1/' } check_rest_api_url() { show_info "Getting REST API URL..." api_url=$(parse_value_from_target_var "rest_api_url") if [ "$api_url" = "" ]; then err_code="2" show_error "Failed to get REST API URL from target_config_info." $err_code exit $err_code fi show_info "REST API URL = $api_url" show_info "Done." } rest_api_login() { show_info "Logging into REST API..." if [ "$FEDERATORAI_ACCESS_TOKEN" = "" ]; then login_account=$(parse_value_from_target_var "login_account") if [ "$login_account" = "" ]; then err_code="2" show_error "Failed to get login account from target_config_info." $err_code exit $err_code fi login_password=$(parse_value_from_target_var "login_password") if [ "$login_password" = "" ]; then err_code="2" show_error "Failed to get login password from target_config_info." $err_code exit $err_code fi auth_string="${login_account}:${login_password}" auth_cipher=$(echo -n "$auth_string"|base64) if [ "$auth_cipher" = "" ]; then err_code="2" show_error "Failed to encode login string using base64 command." $err_code exit $err_code fi rest_output=$(curl -sS -k -X POST "$api_url/apis/v1/users/login" -H "accept: application/json" -H "authorization: Basic ${auth_cipher}") if [ "$?" != "0" ]; then err_code="3" show_error "Failed to connect to REST API service ($api_url/apis/v1/users/login)" $err_code show_detail_to_stderr "Please check REST API IP/login account/login password" exit $err_code fi access_token="$(echo $rest_output|tr -d '\n'|grep -o "\"accessToken\":[^\"]*\"[^\"]*\""|sed -E 's/".*".*"(.*)"/\1/')" else access_token="$FEDERATORAI_ACCESS_TOKEN" # Examine http response code token_test_http_response="$(curl -o /dev/null -sS -k -X GET "$api_url/apis/v1/resources/clusters" -w "%{http_code}" -H "accept: application/json" -H "Authorization: Bearer $access_token")" if [ "$token_test_http_response" != "200" ]; then err_code="3" show_error "The access_token can't access the REST API service." $err_code exit $err_code fi fi check_user_token show_info "Done." } do_application_way(){ show_info "Getting the information of controller under application ..." exec_cmd="curl -sS -k -X GET \"$api_url/apis/v1/configs/scaler\" -H \"accept: application/json\" -H \"Authorization: Bearer $access_token\"" rest_output=$(eval $exec_cmd) if [ "$?" != "0" ]; then err_code="3" show_error "Failed to get clusters info using REST API (Command: $exec_cmd)" $err_code exit $err_code fi scaler_all="$rest_output" # check if return is '"plannings":[]}' scaler_count=${#scaler_all} if [ "$scaler_all" = "" ] || [ "$scaler_count" -le "18" ]; then err_code="1" show_error "Scaler info is empty." $err_code show_detail_to_stderr "REST API output: ${rest_output}" exit $err_code fi data=$(tokenize_json "$scaler_all"| parse_json_begin) err_code="$?" if [ "$err_code" != "0" ]; then show_error "Failed to parse json output (scaler result)." $err_code exit $err_code fi #echo "$data" scaler_record_no=$(echo "$data"|cut -d ',' -f2|sort -n -r|head -1) scaler_found="n" controller_name_array=() controller_namespace_array=() controller_kind_array=() controller_profile_array=() profile_array=() for scaler_index in `seq 0 $scaler_record_no` do obj_name=$(get_valued_from_parsed_json "$data" "\[\"data\",$scaler_index,\"object_meta\",\"name\"\]") target_cluster_name=$(get_valued_from_parsed_json "$data" "\[\"data\",$scaler_index,\"target_cluster_name\"\]") if [ "$obj_name" = "$resource_name" ] && [ "$target_cluster_name" = "$cluster_name" ]; then scaler_found="y" controller_record_no=$(echo "$data"|grep "\[\"data\",$scaler_index,\"controllers\","|cut -d ',' -f4|sort -n -r|head -1) doable_controller_found="n" for controller_index in `seq 0 $controller_record_no` do application_type=$(get_valued_from_parsed_json "$data" "\[\"data\",$scaler_index,\"controllers\",$controller_index,\"application_type\"\]") #echo "application_type=$controller_index $application_type" scaling_type=$(get_valued_from_parsed_json "$data" "\[\"data\",$scaler_index,\"controllers\",$controller_index,\"scaling_type\"\]") #echo "scaling_type=$controller_index $scaling_type" if [ "$application_type" = "generic" ] && [ "$scaling_type" = "1" ]; then doable_controller_found="y" controller_name=$(get_valued_from_parsed_json "$data" "\[\"data\",$scaler_index,\"controllers\",$controller_index,\"generic\",\"target\",\"name\"\]") #echo "controller_name=$controller_index $controller_name" controller_namespace=$(get_valued_from_parsed_json "$data" "\[\"data\",$scaler_index,\"controllers\",$controller_index,\"generic\",\"target\",\"namespace\"\]") controller_kind=$(get_valued_from_parsed_json "$data" "\[\"data\",$scaler_index,\"controllers\",$controller_index,\"generic\",\"target\",\"controller_kind\"\]") [ "$controller_kind" = "1" ] && controller_kind="deployment" [ "$controller_kind" = "2" ] && controller_kind="statefulset" [ "$controller_kind" = "3" ] && controller_kind="deploymentconfig" #echo "$controller_name $controller_namespace $controller_kind" controller_name_array+=( "$controller_name" ) controller_namespace_array+=( "$controller_namespace" ) controller_kind_array+=( "$controller_kind" ) fi done break fi done if [ "$scaler_found" != "y" ]; then err_code="1" show_error "Failed to locate application info in scaler settings." $err_code exit $err_code fi if [ "$doable_controller_found" != "y" ]; then err_code="1" show_error "No suitable controller (Generic, Non HPA) found under application." $err_code exit $err_code fi # Get controller allocation (profile) info exec_cmd="curl -sS -k -X GET \"$api_url/apis/v1/autoprovision/allocations/controllers\" -H \"accept: application/json\" -H \"Authorization: Bearer $access_token\"" rest_output=$(eval $exec_cmd) if [ "$?" != "0" ]; then err_code="3" show_error "Failed to get autoprovision allocation info using REST API (Command: $exec_cmd)" $err_code exit $err_code fi allocation_all="$rest_output" data=$(tokenize_json "$allocation_all"| parse_json_begin) err_code="$?" if [ "$err_code" != "0" ]; then show_error "Failed to parse json output (allocation result)." $err_code exit $err_code fi allocation_record_no=$(echo "$data"|cut -d ',' -f2|sort -n -r|head -1) con_array_len=${#controller_name_array[@]} if [ "$allocation_record_no" = "" ]; then for con_index in `seq 0 $(( con_array_len - 1 ))` do controller_profile_array+=( "noprofilematch" ) done else for con_index in `seq 0 $(( con_array_len - 1 ))` do profile_found="n" con_name=${controller_name_array[con_index]} con_namespace=${controller_namespace_array[con_index]} con_kind=${controller_kind_array[con_index]} for allocation_index in `seq 0 $allocation_record_no` do #echo "allocation_index = $allocation_index" tmp_c_name=$(get_valued_from_parsed_json "$data" "\[\"data\",$allocation_index,\"name\"\]") tmp_c_namespace=$(get_valued_from_parsed_json "$data" "\[\"data\",$allocation_index,\"namespace\"\]") tmp_cluster_name=$(get_valued_from_parsed_json "$data" "\[\"data\",$allocation_index,\"cluster_name\"\]") tmp_profile_name=$(get_valued_from_parsed_json "$data" "\[\"data\",$allocation_index,\"profile_name\"\]") tmp_profile_type=$(get_valued_from_parsed_json "$data" "\[\"data\",$allocation_index,\"profile_type\"\]") tmp_c_kind=$(get_valued_from_parsed_json "$data" "\[\"data\",$allocation_index,\"kind\"\]") tmp_c_kind="$(echo "$tmp_c_kind" | tr '[:upper:]' '[:lower:]')" if [ "$tmp_cluster_name" = "$cluster_name" ] && [ "$tmp_c_name" = "$con_name" ] && [ "$tmp_c_namespace" = "$con_namespace" ] && [ "$tmp_profile_type" = "k8s" ] && [ "$tmp_c_kind" = "$con_kind" ]; then profile_found="y" controller_profile_array+=( "$tmp_profile_name" ) fi done if [ "$profile_found" != "y" ]; then controller_profile_array+=( "noprofilematch" ) fi done fi get_profile_detail for con_index in `seq 0 $(( con_array_len - 1 ))` do export resource_type=controller export resource_name=${controller_name_array[con_index]} export target_namespace=${controller_namespace_array[con_index]} export owner_reference_kind=${controller_kind_array[con_index]} show_info "Cluster name = $cluster_name" show_info "Resource type = $resource_type" show_info "Resource name = $resource_name" show_info "Kind = $owner_reference_kind" show_info "Namespace = $target_namespace" c_profile_name=${controller_profile_array[con_index]} if [ "$c_profile_name" != "noprofilematch" ]; then for profile in "${profile_array[@]}" do p_name=$(echo "$profile"|cut -d ',' -f1) if [ "$c_profile_name" = "$p_name" ]; then export readable_granularity=$(echo "$profile"|cut -d ',' -f2) export cpu_headroom=$(echo "$profile"|cut -d ',' -f3) export min_cpu=$(echo "$profile"|cut -d ',' -f4) export max_cpu=$(echo "$profile"|cut -d ',' -f5) export memory_headroom=$(echo "$profile"|cut -d ',' -f6) export min_memory=$(echo "$profile"|cut -d ',' -f7) export max_memory=$(echo "$profile"|cut -d ',' -f8) export trigger_condition=$(echo "$profile"|cut -d ',' -f9) break fi done else # reset export readable_granularity=$app_granularity unset cpu_headroom unset cpu_headroom_mode unset min_cpu unset max_cpu unset memory_headroom unset memory_headroom_mode unset min_memory unset max_memory unset trigger_condition fi parse_condition normal_way done } get_profile_detail(){ # Get profile detail exec_cmd="curl -sS -k -X GET \"$api_url/apis/v1/autoprovision/profiles?profile_type=k8s&task_type=auto_provision\" -H \"accept: application/json\" -H \"Authorization: Bearer $access_token\"" rest_output=$(eval $exec_cmd) if [ "$?" != "0" ]; then err_code="3" show_error "Failed to get profile info using REST API (Command: $exec_cmd)" $err_code exit $err_code fi profile_all="$rest_output" data=$(tokenize_json "$profile_all"| parse_json_begin) err_code="$?" if [ "$err_code" != "0" ]; then show_error "Failed to parse json output (profile result)." $err_code exit $err_code fi profile_record_no=$(echo "$data"|cut -d ',' -f2|sort -n -r|head -1) if [ "$profile_record_no" != "" ]; then for profile_index in `seq 0 $profile_record_no` do tmp_p_name=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"name\"\]") tmp_p_recommendation_window=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"recommendation_window\"\]") tmp_p_cpu_headroom=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"cpu_headroom\"\]") if [[ $tmp_p_cpu_headroom =~ ^[0-9]+m$ ]]; then # Remove last 'm' if [ "$machine_type" = "Linux" ]; then tmp_p_cpu_headroom=`echo ${tmp_p_cpu_headroom::-1}` else # Mac tmp_p_cpu_headroom=`echo "${tmp_p_cpu_headroom%?}"` fi fi tmp_p_cpu_minimum=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"cpu_minimum\"\]") if [[ $tmp_p_cpu_minimum =~ ^[0-9]+$ ]]; then # Convert to mCore tmp_p_cpu_minimum=$(( tmp_p_cpu_minimum * 1000 )) elif [[ $tmp_p_cpu_minimum =~ ^[0-9]+m$ ]]; then # Remove last 'm' if [ "$machine_type" = "Linux" ]; then tmp_p_cpu_minimum=`echo ${tmp_p_cpu_minimum::-1}` else # Mac tmp_p_cpu_minimum=`echo "${tmp_p_cpu_minimum%?}"` fi fi tmp_p_cpu_maximum=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"cpu_maximum\"\]") if [[ $tmp_p_cpu_maximum =~ ^[0-9]+$ ]]; then # Convert to mCore tmp_p_cpu_maximum=$(( tmp_p_cpu_maximum * 1000 )) elif [[ $tmp_p_cpu_maximum =~ ^[0-9]+m$ ]]; then # Remove last 'm' if [ "$machine_type" = "Linux" ]; then tmp_p_cpu_maximum=`echo ${tmp_p_cpu_maximum::-1}` else # Mac tmp_p_cpu_maximum=`echo "${tmp_p_cpu_maximum%?}"` fi fi tmp_p_memory_headroom=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"memory_headroom\"\]") if [[ $tmp_p_memory_headroom =~ ^[0-9]+Mi$ ]] || [[ $tmp_p_memory_headroom =~ ^[0-9]+Gi$ ]]; then if [ "$machine_type" = "Linux" ]; then tmp_h=`echo ${tmp_p_memory_headroom::-2}` else # Mac tmp_h=`echo "${tmp_p_memory_headroom%??}"` fi fi if [[ $tmp_p_memory_headroom =~ ^[0-9]+Mi$ ]]; then # Convert Mi to byte tmp_p_memory_headroom=$(( tmp_h * 1024 * 1024 )) elif [[ $tmp_p_memory_headroom =~ ^[0-9]+Gi$ ]]; then # Convert Gi to byte tmp_p_memory_headroom=$(( tmp_h * 1024 * 1024 * 1024 )) fi tmp_p_memory_minimum=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"memory_minimum\"\]") if [[ $tmp_p_memory_minimum =~ ^[0-9]+Mi$ ]] || [[ $tmp_p_memory_minimum =~ ^[0-9]+Gi$ ]]; then if [ "$machine_type" = "Linux" ]; then tmp_h=`echo ${tmp_p_memory_minimum::-2}` else # Mac tmp_h=`echo "${tmp_p_memory_minimum%??}"` fi fi if [[ $tmp_p_memory_minimum =~ ^[0-9]+Mi$ ]]; then # Convert Mi to byte tmp_p_memory_minimum=$(( tmp_h * 1024 * 1024 )) elif [[ $tmp_p_memory_minimum =~ ^[0-9]+Gi$ ]]; then # Convert Gi to byte tmp_p_memory_minimum=$(( tmp_h * 1024 * 1024 * 1024 )) fi tmp_p_memory_maximum=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"memory_maximum\"\]") if [[ $tmp_p_memory_maximum =~ ^[0-9]+Mi$ ]] || [[ $tmp_p_memory_maximum =~ ^[0-9]+Gi$ ]]; then if [ "$machine_type" = "Linux" ]; then tmp_h=`echo ${tmp_p_memory_maximum::-2}` else # Mac tmp_h=`echo "${tmp_p_memory_maximum%??}"` fi fi if [[ $tmp_p_memory_maximum =~ ^[0-9]+Mi$ ]]; then # Convert Mi to byte tmp_p_memory_maximum=$(( tmp_h * 1024 * 1024 )) elif [[ $tmp_p_memory_maximum =~ ^[0-9]+Gi$ ]]; then # Convert Gi to byte tmp_p_memory_maximum=$(( tmp_h * 1024 * 1024 * 1024 )) fi tmp_p_trigger_condition=$(get_valued_from_parsed_json "$data" "\[\"data\",$profile_index,\"trigger_condition\"\]") if [ "$tmp_p_trigger_condition" = "-1" ]; then tmp_p_trigger_condition="" fi profile_array+=( "$tmp_p_name,$tmp_p_recommendation_window,$tmp_p_cpu_headroom,$tmp_p_cpu_minimum,$tmp_p_cpu_maximum,$tmp_p_memory_headroom,$tmp_p_memory_minimum,$tmp_p_memory_maximum,$tmp_p_trigger_condition" ) done fi } rest_api_check_cluster_name() { show_info "Getting the cluster name of the planning target ..." cluster_name=$(parse_value_from_target_var "cluster_name") if [ "$cluster_name" = "" ]; then err_code="2" show_error "Failed to get cluster name of the planning target from target_config_info." $err_code exit $err_code fi exec_cmd="curl -sS -k -X GET \"$api_url/apis/v1/resources/clusters\" -H \"accept: application/json\" -H \"Authorization: Bearer $access_token\"" rest_output=$(eval $exec_cmd) if [ "$?" != "0" ]; then err_code="3" show_error "Failed to get clusters info using REST API (Command: $exec_cmd)" $err_code exit $err_code fi echo "$rest_output" |grep -q "\"name\":\"$cluster_name\"" if [ "$?" != "0" ]; then err_code="3" show_error "The cluster name ($cluster_name) is not found in REST API return." $err_code show_detail_to_stderr "REST API output: $rest_output" exit $err_code fi show_info "cluster_name = $cluster_name" show_info "Done." } parse_condition() { if [[ ! $min_cpu =~ ^[0-9]+$ ]]; then min_cpu=""; fi if [[ ! $max_cpu =~ ^[0-9]+$ ]]; then max_cpu=""; fi if [[ $cpu_headroom =~ ^[0-9]+[%]$ ]]; then # Percentage mode cpu_headroom_mode="%" # Remove last character as value if [ "$machine_type" = "Linux" ]; then cpu_headroom=`echo ${cpu_headroom::-1}` else # Mac cpu_headroom=`echo "${cpu_headroom%?}"` fi elif [[ $cpu_headroom =~ ^[0-9]+$ ]]; then # Absolute value (mCore) mode cpu_headroom_mode="m" else # No valid value or mode, set inactive value and mode cpu_headroom="0" cpu_headroom_mode="m" fi if [[ ! $min_memory =~ ^[0-9]+$ ]]; then min_memory=""; fi if [[ ! $max_memory =~ ^[0-9]+$ ]]; then max_memory=""; fi if [[ $memory_headroom =~ ^[0-9]+[%]$ ]]; then # Percentage mode memory_headroom_mode="%" # Remove last character as value if [ "$machine_type" = "Linux" ]; then memory_headroom=`echo ${memory_headroom::-1}` else # Mac memory_headroom=`echo "${memory_headroom%?}"` fi elif [[ $memory_headroom =~ ^[0-9]+$ ]]; then # Absolute value (byte) mode memory_headroom_mode="b" else # No valid value, set inactive value and mode memory_headroom="0" memory_headroom_mode="b" fi if [[ ! $trigger_condition =~ ^[0-9]+$ ]]; then trigger_condition=""; fi [ "$trigger_condition" = "0" ] && trigger_condition="" if [ "$readable_granularity" = "daily" ]; then granularity="3600" elif [ "$readable_granularity" = "weekly" ]; then granularity="21600" elif [ "$readable_granularity" = "monthly" ]; then granularity="86400" else err_code="2" show_error "Only support planning time interval equals daily/weekly/monthly." $err_code exit $err_code fi show_info "Time interval = $readable_granularity" show_info "min_cpu = $min_cpu" show_info "max_cpu = $max_cpu" show_info "cpu_headroom = $cpu_headroom" show_info "cpu_headroom_mode = $cpu_headroom_mode" show_info "min_memory = $min_memory" show_info "max_memory = $max_memory" show_info "memory_headroom = $memory_headroom" show_info "memory_headroom_mode = $memory_headroom_mode" show_info "trigger_condition = $trigger_condition" } get_info_from_config() { show_info "Getting the $resource_type info of the planning target..." resource_name=$(parse_value_from_target_var "resource_name") if [ "$resource_name" = "" ]; then err_code="2" show_error "Failed to get resource name of the planning target from target_config_info." $err_code exit $err_code fi iac_command=$(parse_value_from_target_var "iac_command") iac_command="$(echo "$iac_command" | tr '[:upper:]' '[:lower:]')" if [ "$iac_command" = "" ]; then err_code="2" show_error "Failed to get iac_command from target_config_info." $err_code exit $err_code elif [ "$iac_command" != "script" ] && [ "$iac_command" != "terraform" ]; then err_code="2" show_error "Only support iac_command equals 'script' or 'terraform'." $err_code exit $err_code fi if [ "$resource_type" = "controller" ]; then owner_reference_kind=$(parse_value_from_target_var "kind") if [ "$owner_reference_kind" = "" ]; then err_code="2" show_error "Failed to get controller kind of the planning target from target_config_info." $err_code exit $err_code fi owner_reference_kind="$(echo "$owner_reference_kind" | tr '[:upper:]' '[:lower:]')" if [ "$owner_reference_kind" = "statefulset" ] && [ "$owner_reference_kind" = "deployment" ] && [ "$owner_reference_kind" = "deploymentconfig" ]; then err_code="2" show_error "Only support controller type equals Statefulset/Deployment/DeploymentConfig." $err_code exit $err_code fi target_namespace=$(parse_value_from_target_var "namespace") if [ "$target_namespace" = "" ]; then err_code="2" show_error "Failed to get namespace of the planning target from target_config_info." $err_code exit $err_code fi elif [ "$resource_type" = "namespace" ]; then # resource_type = namespace # target_namespace is resource_name target_namespace=$resource_name elif [ "$resource_type" = "application" ]; then if [ "$iac_command" = "terraform" ]; then err_code="3" show_error "terraform in resource_type (application mode) is not yet supported." $err_code exit $err_code fi else err_code="2" show_error "Only support resource type equals controller/namespace/application." $err_code exit $err_code fi readable_granularity=$(parse_value_from_target_var "time_interval") readable_granularity="$(echo "$readable_granularity" | tr '[:upper:]' '[:lower:]')" if [ "$resource_type" = "application" ]; then if [ "$readable_granularity" = "" ]; then readable_granularity="daily" app_granularity="daily" else app_granularity=$readable_granularity fi elif [ "$resource_type" != "application" ] && [ "$readable_granularity" = "" ]; then err_code="2" show_error "Failed to get time interval of the planning target from target_config_info." $err_code exit $err_code fi min_cpu=$(parse_value_from_target_var "min_cpu") max_cpu=$(parse_value_from_target_var "max_cpu") cpu_headroom=$(parse_value_from_target_var "cpu_headroom") min_memory=$(parse_value_from_target_var "min_memory") max_memory=$(parse_value_from_target_var "max_memory") memory_headroom=$(parse_value_from_target_var "memory_headroom") trigger_condition=$(parse_value_from_target_var "trigger_condition") limits_only=$(parse_value_from_target_var "limits_only") requests_only=$(parse_value_from_target_var "requests_only") if [ "$limits_only" != 'y' ] && [ "$limits_only" != 'n' ]; then limits_only="n" fi if [ "$requests_only" != 'y' ] && [ "$requests_only" != 'n' ]; then requests_only="n" fi show_info "Cluster name = $cluster_name" show_info "Resource type = $resource_type" show_info "Resource name = $resource_name" if [ "$resource_type" = "controller" ]; then show_info "Kind = $owner_reference_kind" show_info "Namespace = $target_namespace" fi parse_condition show_info "Done." } get_valued_from_parsed_json(){ all_data="$1" target_string="$2" echo "$all_data"|grep "$target_string"|awk '{print $2}'|sed 's/"//g' } parse_value_from_planning_for_multiple_conatiners(){ for container_name in "${container_name_keys[@]}" do data=$(tokenize_json "$planning_all"| parse_json_begin) err_code="$?" if [ "$err_code" != "0" ]; then show_error "Failed to parse json output (multiple conatiners)." $err_code exit $err_code fi limit_con_cpu=$(get_valued_from_parsed_json "$data" "\[\"plannings\",0,\"plannings\",0,\"containerPlannings\",\"$container_name\",\"limitPlannings\",\"CPU_MILLICORES_USAGE\",0,\"numValue\"\]") limit_con_memory=$(get_valued_from_parsed_json "$data" "\[\"plannings\",0,\"plannings\",0,\"containerPlannings\",\"$container_name\",\"limitPlannings\",\"MEMORY_BYTES_USAGE\",0,\"numValue\"\]") request_con_cpu=$(get_valued_from_parsed_json "$data" "\[\"plannings\",0,\"plannings\",0,\"containerPlannings\",\"$container_name\",\"requestPlannings\",\"CPU_MILLICORES_USAGE\",0,\"numValue\"\]") request_con_memory=$(get_valued_from_parsed_json "$data" "\[\"plannings\",0,\"plannings\",0,\"containerPlannings\",\"$container_name\",\"requestPlannings\",\"MEMORY_BYTES_USAGE\",0,\"numValue\"\]") if [ "$limit_con_cpu" = "" ]; then err_code="3" show_error "Failed to parse limit cpu planning value of container ($container_name)." $err_code exit $err_code fi if [ "$limit_con_memory" = "" ]; then err_code="3" show_error "Failed to parse limit memory planning value of container ($container_name)." $err_code exit $err_code fi if [ "$request_con_cpu" = "" ]; then err_code="3" show_error "Failed to parse request cpu planning value of container ($container_name)." $err_code exit $err_code fi if [ "$request_con_memory" = "" ]; then err_code="3" show_error "Failed to parse request memory planning value of container ($container_name)." $err_code exit $err_code fi limit_con_cpu=$(( ($limit_con_cpu + $replica_number - 1)/$replica_number )) limit_con_memory=$(( ($limit_con_memory + $replica_number - 1)/$replica_number )) request_con_cpu=$(( ($request_con_cpu + $replica_number - 1)/$replica_number )) request_con_memory=$(( ($request_con_memory + $replica_number - 1)/$replica_number )) container_planning_array+=( "$container_name.limitPlannings.CPU_MILLICORES_USAGE:$limit_con_cpu" ) container_planning_array+=( "$container_name.limitPlannings.MEMORY_BYTES_USAGE:$limit_con_memory" ) container_planning_array+=( "$container_name.requestPlannings.CPU_MILLICORES_USAGE:$request_con_cpu" ) container_planning_array+=( "$container_name.requestPlannings.MEMORY_BYTES_USAGE:$request_con_memory" ) show_info "-------------- Planning for container ($container_name) --------------" show_info "resources.limits.cpu = $limit_con_cpu(m)" show_info "resources.limits.momory = $limit_con_memory(byte)" show_info "resources.requests.cpu = $request_con_cpu(m)" show_info "resources.requests.memory = $request_con_memory(byte)" show_info "-----------------------------------------------------" done } parse_value_from_planning() { target_field="$1" target_resource="$2" if [ -z "$target_field" ]; then err_code="3" show_error "parse_value_from_planning() target_field parameter can't be empty." $err_code exit $err_code elif [ -z "$target_resource" ]; then err_code="3" show_error "parse_value_from_planning() target_resource parameter can't be empty." $err_code exit $err_code fi if [ "$target_field" != "limitPlannings" ] && [ "$target_field" != "requestPlannings" ]; then err_code="3" show_error "parse_value_from_planning() target_field can only be either 'limitPlannings' and 'requestPlannings'." $err_code exit $err_code fi if [ "$target_resource" != "CPU_MILLICORES_USAGE" ] && [ "$target_resource" != "MEMORY_BYTES_USAGE" ]; then err_code="3" show_error "parse_value_from_planning() target_field can only be either 'CPU_MILLICORES_USAGE' and 'MEMORY_BYTES_USAGE'." $err_code exit $err_code fi if [ "$resource_type" = "controller" ]; then data=$(tokenize_json "$planning_all"| parse_json_begin) err_code="$?" if [ "$err_code" != "0" ]; then show_error "Failed to parse json output (single conatiner)." $err_code exit $err_code fi echo $(get_valued_from_parsed_json "$data" "\[\"plannings\",0,\"plannings\",0,\"$target_field\",\"$target_resource\",0,\"numValue\"\]") else echo "$planning_all"|grep -o "\"$target_field\":[^{]*{[^}]*}[^}]*}"|grep -o "\"$target_resource\":[^\[]*\[[^]]*"|grep -o '"numValue":[^"]*"[^"]*"'|cut -d '"' -f4 fi } get_container_number_and_name_list(){ if [ "$DEMO_MODE" != "y" ]; then query_type="${owner_reference_kind}s" container_str=$($kube_cmd -n $target_namespace get $query_type ${resource_name} -o jsonpath='{.spec.template.spec.containers[*].name}') if [ "$container_str" = "" ]; then err_code="3" show_error "Failed to get container list (namespace:$target_namespace, kind:$query_type, name:${resource_name})" $err_code exit $err_code fi container_array=(`echo "$container_str"`) container_number=${#container_array[@]} fi } get_planning_from_api() { show_info "Getting planning values for the $resource_type through REST API..." show_info "Cluster name = $cluster_name" if [ "$resource_type" = "controller" ]; then show_info "Kind = $owner_reference_kind" show_info "Namespace = $target_namespace" else # namespace show_info "Namespace = $target_namespace" fi show_info "Resource name = $resource_name" show_info "Time interval = $readable_granularity" # Use 0 as 'now' interval_start_time="0" interval_end_time=$(($interval_start_time + $granularity - 1)) show_info "Query interval (start) = 0" show_info "Query interval (end) = $interval_end_time" # Use planning here type="planning" if [ "$resource_type" = "controller" ]; then query_type="${owner_reference_kind}s" exec_cmd="curl -sS -k -X GET \"$api_url/apis/v1/plannings/clusters/$cluster_name/namespaces/$target_namespace/$query_type/${resource_name}?granularity=$granularity&type=$type&limit=1&order=asc&startTime=$interval_start_time&endTime=$interval_end_time\" -H \"accept: application/json\" -H \"Authorization: Bearer $access_token\"" else # resource_type = namespace # Check if namespace is in monitoring state first exec_cmd="curl -sS -k -X GET \"$api_url/apis/v1/resources/clusters/$cluster_name/namespaces?names=$target_namespace\" -H \"accept: application/json\" -H \"Authorization: Bearer $access_token\"" rest_output=$(eval $exec_cmd) if [ "$?" != "0" ]; then err_code="3" show_error "Failed to get namespace $target_namespace resource info using REST API (Command: $exec_cmd)" $err_code exit $err_code fi namespace_state="$(echo $rest_output|tr -d '\n'|grep -o "\"name\":.*\"${target_namespace}.*"|grep -o "\"state\":.*\".*\""|cut -d '"' -f4)" if [ "$namespace_state" != "monitoring" ]; then err_code="1" show_error "Namespace $target_namespace is not in 'monitoring' state." $err_code show_detail_to_stderr "REST API output: $rest_output" exit $err_code fi exec_cmd="curl -sS -k -X GET \"$api_url/apis/v1/plannings/clusters/$cluster_name/namespaces/$target_namespace?granularity=$granularity&type=$type&limit=1&order=asc&startTime=$interval_start_time&endTime=$interval_end_time\" -H \"accept: application/json\" -H \"Authorization: Bearer $access_token\"" fi for repeat in `seq 1 10` do rest_output=$(eval $exec_cmd) if [ "$?" != "0" ]; then err_code="3" show_error "Failed to get planning value of $resource_type using REST API (Command: $exec_cmd)" $err_code exit $err_code fi planning_all="$rest_output" # check if return is '"plannings":[]}' planning_count=${#planning_all} if [ "$planning_all" = "" ] || [ "$planning_count" -le "15" ]; then err_code="1" show_error "Planning value ($readable_granularity) is empty." $err_code show_detail_to_stderr "REST API output: ${rest_output}" exit $err_code fi if [ "$resource_type" = "controller" ]; then data=$(tokenize_json "$planning_all"| parse_json_begin) err_code="$?" if [ "$err_code" != "0" ]; then show_error "Failed to parse json output (API result)." $err_code exit $err_code fi records=$(echo "$data" |grep "\[\"plannings\",0,\"plannings\",0,\"containerPlannings\"") container_records_num="$(echo "$records" | cut -d ',' -f6|sort|uniq|wc -l|xargs)" if [ "$DEMO_MODE" != "y" ]; then if [ "0$container_records_num" -ne "0$container_number" ]; then err_code="3" show_error "Container number doesn't match (system:planning=$container_number:$container_records_num)" $err_code exit $err_code fi fi container_name_keys=($(echo "$records" | cut -d ',' -f6|sort|uniq|sed 's/"//g')) if [ "$DEMO_MODE" != "y" ]; then # verify all container name (system) do have key in planning need_repeat="" for name in "${container_array[@]}" do matched="n" for id in "${container_name_keys[@]}" do if [ "$id" = "$name" ]; then matched="y" break fi done if [ "$matched" != "y" ]; then need_repeat="y" break fi done if [ "$need_repeat" = "y" ]; then if [ "$repeat" = "10" ]; then err_code="3" show_error "Can't locate container name ($name) as key inside Federator.ai planning output after repeat $repeat times." $err_code exit $err_code fi sleep 1 continue else break fi else # Demo break fi fi done limits_pod_cpu=$(parse_value_from_planning "limitPlannings" "CPU_MILLICORES_USAGE") requests_pod_cpu=$(parse_value_from_planning "requestPlannings" "CPU_MILLICORES_USAGE") limits_pod_memory=$(parse_value_from_planning "limitPlannings" "MEMORY_BYTES_USAGE") requests_pod_memory=$(parse_value_from_planning "requestPlannings" "MEMORY_BYTES_USAGE") if [ "$resource_type" = "controller" ]; then if [ "$DEMO_MODE" != "y" ]; then replica_number="$($kube_cmd get $owner_reference_kind $resource_name -n $target_namespace -o json|tr -d '\n'|grep -o "\"spec\":.*"|grep -o "\"replicas\":[^,]*[0-9]*"|head -1|cut -d ':' -f2|xargs)" if [ "$replica_number" = "" ]; then err_code="4" show_error "Failed to get replica number from controller ($resource_name) in ns $target_namespace" $err_code exit $err_code fi case $replica_number in ''|*[!0-9]*) err_code="4" && show_error "Replica number needs to be an integer." $err_code && exit $err_code ;; *) ;; esac show_info "Controller replica number = $replica_number" if [ "$replica_number" = "0" ]; then err_code="4" show_error "Replica number is zero." $err_code exit $err_code fi else replica_number="1" fi # Round up the result (planning / replica) limits_pod_cpu=$(( ($limits_pod_cpu + $replica_number - 1)/$replica_number )) requests_pod_cpu=$(( ($requests_pod_cpu + $replica_number - 1)/$replica_number )) limits_pod_memory=$(( ($limits_pod_memory + $replica_number - 1)/$replica_number )) requests_pod_memory=$(( ($requests_pod_memory + $replica_number - 1)/$replica_number )) fi if [ "$resource_type" = "controller" ] && [ "0$container_records_num" -ge "2" ]; then parse_value_from_planning_for_multiple_conatiners fi show_info "-------------- Planning for $resource_type --------------" show_info "resources.limits.cpu = $limits_pod_cpu(m)" show_info "resources.limits.momory = $limits_pod_memory(byte)" show_info "resources.requests.cpu = $requests_pod_cpu(m)" show_info "resources.requests.memory = $requests_pod_memory(byte)" show_info "-----------------------------------------------------" if [ "$limits_pod_cpu" = "" ] || [ "$requests_pod_cpu" = "" ] || [ "$limits_pod_memory" = "" ] || [ "$requests_pod_memory" = "" ]; then err_code="3" if [ "$resource_type" = "controller" ]; then show_error "Failed to get controller ($resource_name) planning. Missing value." $err_code else # namespace show_error "Failed to get namespace ($target_namespace) planning. Missing value." $err_code fi exit $err_code fi show_info "Done." } apply_min_max_margin() { planning_name="$1" mode_name="$2" headroom_name="$3" min_name="$4" max_name="$5" original_value="${!planning_name}" if [ "${!mode_name}" = "%" ]; then # Percentage mode export $planning_name=$(( (${!planning_name}*(100+${!headroom_name})+99)/100 )) else # Absolute value mode export $planning_name=$(( ${!planning_name} + ${!headroom_name} )) fi if [ "${!min_name}" != "" ] && [ "${!min_name}" -gt "${!planning_name}" ]; then # Assign minimum value export $planning_name="${!min_name}" fi if [ "${!max_name}" != "" ] && [ "${!planning_name}" -gt "${!max_name}" ]; then # Assign maximum value export $planning_name="${!max_name}" fi show_info "-------------- Calculate min/max/headroom/default -------------" show_info "${mode_name} = ${!mode_name}" show_info "${headroom_name} = ${!headroom_name}" show_info "${min_name} = ${!min_name}" show_info "${max_name} = ${!max_name}" show_info "${planning_name} (before)= ${original_value}" show_info "${planning_name} (after)= ${!planning_name}" show_info "---------------------------------------------------------------" } check_default_value_satisfied() { void_mode="x" void_value="0" show_info "Verifying default value satisfied..." apply_min_max_margin "requests_pod_cpu" "void_mode" "void_value" "default_min_cpu" "void" apply_min_max_margin "requests_pod_memory" "void_mode" "void_value" "default_min_memory" "void" apply_min_max_margin "limits_pod_cpu" "void_mode" "void_value" "default_min_cpu" "void" apply_min_max_margin "limits_pod_memory" "void_mode" "void_value" "default_min_memory" "void" show_info "Done" } compare_trigger_condition_with_difference() { before=$1 after=$2 trigger_result="" result=$(awk -v t1="${!before}" -v t2="${!after}" 'BEGIN{printf "%.0f", (t2-t1)/t1 * 100}') show_info "Comparing current value ($before=${!before}) and planning value ($after=${!after})..." if [ "$result" -gt "0" ]; then # planning > current, ignore checking trigger_result="y" show_info "planning value is bigger than current value, ignore trigger condition check." return fi # Get absolute value result=$(echo ${result#-}) show_info "comp_result=${result}%, trigger_condition=${trigger_condition}%" if [ "$trigger_condition" -le "$result" ]; then trigger_result="y" show_info "$(echo "$before"|awk -F'_' '{print $1"_"$2}') meet the trigger condition." else trigger_result="n" show_info "$(echo "$before"|awk -F'_' '{print $1"_"$2}') will be skipped." fi } check_trigger_condition_on_all_metrics() { show_info "Verifying trigger condition..." if [ "$trigger_condition" != "" ]; then if [ "$limit_cpu_before" != "N/A" ]; then compare_trigger_condition_with_difference "limit_cpu_before_converted" "limits_pod_cpu" do_limit_cpu="$trigger_result" else do_limit_cpu="y" fi if [ "$limit_memory_before" != "N/A" ]; then compare_trigger_condition_with_difference "limit_memory_before_converted" "limits_pod_memory" do_limit_memory="$trigger_result" else do_limit_memory="y" fi if [ "$request_cpu_before" != "N/A" ]; then compare_trigger_condition_with_difference "request_cpu_before_converted" "requests_pod_cpu" do_request_cpu="$trigger_result" else do_request_cpu="y" fi if [ "$request_memory_before" != "N/A" ]; then compare_trigger_condition_with_difference "request_memory_before_converted" "requests_pod_memory" do_request_memory="$trigger_result" else do_request_memory="y" fi else show_info "'trigger_condition' is disabled." do_limit_cpu="y" do_limit_memory="y" do_request_cpu="y" do_request_memory="y" fi # New rule, if one of action is 'y', let every action become 'y' # Still reserve limits_only and requests_only if [ "$do_limit_cpu" = "y" ] || [ "$do_limit_memory" = "y" ] || [ "$do_request_cpu" = "y" ] || [ "$do_request_memory" = "y" ]; then show_info "One of do action is 'y', set all do actions to 'y'. Still reserve limits_only or requests_only settings." do_limit_cpu="y" do_limit_memory="y" do_request_cpu="y" do_request_memory="y" fi if [ "$limits_only" = "y" ]; then show_info "'Limits only' is enabled." do_request_cpu="n" do_request_memory="n" fi if [ "$requests_only" = "y" ]; then show_info "'Requests only' is enabled." do_limit_cpu="n" do_limit_memory="n" fi show_info "----- Final results -----" show_info "do_limit_cpu : $do_limit_cpu" show_info "do_limit_memory : $do_limit_memory" show_info "do_request_cpu : $do_request_cpu" show_info "do_request_memory: $do_request_memory" show_info "-------------------------" show_info "Done." } get_value_from_planning_array(){ container_name="$1" type="$2" metric="$3" ret="" for record in "${container_planning_array[@]}" do record_key="${record%%:*}" record_value=${record#*:} if [ "$record_key" = "$container_name.$type.$metric" ]; then # Found record ret=$record_value break fi done echo $ret } get_value_from_resource_array(){ #container_resource_array+=( "$container_name.before.limits.cpu.original:$limit_cpu" ) container_name="$1" time="$2" type="$3" metric="$4" modified="$5" ret="" for record in "${container_resource_array[@]}" do record_key="${record%%:*}" record_value=${record#*:} if [ "$record_key" = "$container_name.$time.$type.$metric.$modified" ]; then # Found record ret=$record_value break fi done echo $ret } do_filter_min_max_headroom(){ apply_min_max_margin "limits_pod_cpu" "cpu_headroom_mode" "cpu_headroom" "min_cpu" "max_cpu" apply_min_max_margin "limits_pod_memory" "memory_headroom_mode" "memory_headroom" "min_memory" "max_memory" apply_min_max_margin "requests_pod_cpu" "cpu_headroom_mode" "cpu_headroom" "min_cpu" "max_cpu" apply_min_max_margin "requests_pod_memory" "memory_headroom_mode" "memory_headroom" "min_memory" "max_memory" } generate_controller_set_cmd(){ set_cmd="" if [ "$do_limit_cpu" = "y" ]; then if [ "$do_limit_memory" = "y" ]; then set_cmd="--limits cpu=${limits_pod_cpu}m,memory=${limits_pod_memory}" else set_cmd="--limits cpu=${limits_pod_cpu}m" fi else # do_limit_cpu = n if [ "$do_limit_memory" = "y" ]; then set_cmd="--limits memory=${limits_pod_memory}" fi fi if [ "$do_request_cpu" = "y" ]; then if [ "$do_request_memory" = "y" ]; then set_cmd="$set_cmd --requests cpu=${requests_pod_cpu}m,memory=${requests_pod_memory}" else set_cmd="$set_cmd --requests cpu=${requests_pod_cpu}m" fi else # do_request_cpu = n if [ "$do_request_memory" = "y" ]; then set_cmd="$set_cmd --requests memory=${requests_pod_memory}" fi fi # For get_controller_resources_from_kubecmd 'after' mode if [ "$machine_type" = "Linux" ]; then [ "$do_limit_cpu" = "n" ] && limits_pod_cpu="${limit_cpu_before::-1}" [ "$do_request_cpu" = "n" ] && requests_pod_cpu="${request_cpu_before::-1}" else # Mac [ "$do_limit_cpu" = "n" ] && limits_pod_cpu="${limit_cpu_before%?}" [ "$do_request_cpu" = "n" ] && requests_pod_cpu="${request_cpu_before%?}" fi [ "$do_limit_memory" = "n" ] && limits_pod_memory=$limit_memory_before [ "$do_request_memory" = "n" ] && requests_pod_memory=$request_memory_before container_resource_array+=( "$container_name.after.limits.cpu.original:$limits_pod_cpu" ) container_resource_array+=( "$container_name.after.limits.memory.original:$limits_pod_memory" ) container_resource_array+=( "$container_name.after.requests.cpu.original:$requests_pod_cpu" ) container_resource_array+=( "$container_name.after.requests.memory.original:$requests_pod_memory" ) if [ "$set_cmd" = "" ]; then exec_cmd="N/A, execution skipped due to trigger condition is not met." execution_skipped="y" else execution_skipped="n" if [ "0$container_records_num" -ge "2" ]; then exec_cmd="$kube_cmd -n $target_namespace set resources $owner_reference_kind $resource_name -c=$container_name $set_cmd" else exec_cmd="$kube_cmd -n $target_namespace set resources $owner_reference_kind $resource_name $set_cmd" fi fi container_resource_array+=( "$container_name.after.issue.execute.cmd:$exec_cmd" ) } execute_command(){ # Optional parameter local con_name=$1 show_info "Issuing cmd:" show_info "$exec_cmd" if [ "$execution_skipped" = "y" ]; then execution_time="N/A, execution skipped due to trigger condition is not met." if [ "$con_name" != "" ]; then container_resource_array+=( "$con_name.after.issue.execute.time:$execution_time" ) fi else if [ "$mode" = "dry_run" ]; then execution_time="N/A, execution skipped due to --dry-run-only is specified." show_info "Dry run is enabled, skip execution." show_info "Done. Dry run is done." if [ "$con_name" != "" ]; then container_resource_array+=( "$con_name.after.issue.execute.time:$execution_time" ) fi return fi execution_time="$(date -u)" if [ "$con_name" != "" ]; then container_resource_array+=( "$con_name.after.issue.execute.time:$execution_time" ) fi if [ "$resource_type" = "namespace" ]; then # Clean other quotas all_quotas=$($kube_cmd -n $target_namespace get quota -o name|cut -d '/' -f2) for quota in $(echo "$all_quotas") do $kube_cmd -n $target_namespace patch quota $quota --type json --patch "[ { \"op\" : \"remove\" , \"path\" : \"/spec/hard/limits.cpu\"}]" >/dev/null 2>&1 $kube_cmd -n $target_namespace patch quota $quota --type json --patch "[ { \"op\" : \"remove\" , \"path\" : \"/spec/hard/limits.memory\"}]" >/dev/null 2>&1 $kube_cmd -n $target_namespace patch quota $quota --type json --patch "[ { \"op\" : \"remove\" , \"path\" : \"/spec/hard/requests.cpu\"}]" >/dev/null 2>&1 $kube_cmd -n $target_namespace patch quota $quota --type json --patch "[ { \"op\" : \"remove\" , \"path\" : \"/spec/hard/requests.memory\"}]" >/dev/null 2>&1 done # Delete previous federator.ai quota $kube_cmd -n $target_namespace delete quota $quota_name > /dev/null 2>&1 fi eval $exec_cmd 3>&1 1>&2 2>&3 1>>$debug_log | tee -a $debug_log if [ "${PIPESTATUS[0]}" != "0" ]; then err_code="5" if [ "$resource_type" = "controller" ]; then show_error "Failed to update resources for $owner_reference_kind $resource_name" $err_code else show_error "Failed to update quota for namespace $target_namespace" $err_code fi exit $err_code fi fi } update_target_resources() { mode=$1 if [ "$mode" = "" ]; then err_code="3" show_error "update_target_resources() mode parameter can't be empty." $err_code exit $err_code fi show_info "Updateing $resource_type resources..." if [ "$resource_type" = "controller" ] && [ "0$container_records_num" -ge "2" ]; then for container_name in "${container_name_keys[@]}" do show_info "Calculating min/max/headroom for container ($container_name)..." limits_pod_cpu=$(get_value_from_planning_array $container_name limitPlannings CPU_MILLICORES_USAGE) limits_pod_memory=$(get_value_from_planning_array $container_name limitPlannings MEMORY_BYTES_USAGE) requests_pod_cpu=$(get_value_from_planning_array $container_name requestPlannings CPU_MILLICORES_USAGE) requests_pod_memory=$(get_value_from_planning_array $container_name requestPlannings MEMORY_BYTES_USAGE) if [ "$limits_pod_cpu" = "" ] || [ "$requests_pod_cpu" = "" ] || [ "$limits_pod_memory" = "" ] || [ "$requests_pod_memory" = "" ]; then err_code="3" show_error "Missing planning values." $err_code exit $err_code fi do_filter_min_max_headroom show_info "Done" # Make sure default cpu & memory value above existing one check_default_value_satisfied # Make sure trigger condition is met limit_cpu_before_converted=$(get_value_from_resource_array $container_name before limits cpu converted) limit_memory_before_converted=$(get_value_from_resource_array $container_name before limits memory converted) request_cpu_before_converted=$(get_value_from_resource_array $container_name before requests cpu converted) request_memory_before_converted=$(get_value_from_resource_array $container_name before requests memory converted) limit_cpu_before=$(get_value_from_resource_array $container_name before limits cpu original) limit_memory_before=$(get_value_from_resource_array $container_name before limits memory original) request_cpu_before=$(get_value_from_resource_array $container_name before requests cpu original) request_memory_before=$(get_value_from_resource_array $container_name before requests memory original) # Make sure trigger condition is met check_trigger_condition_on_all_metrics if [ "$iac_command" = "script" ]; then generate_controller_set_cmd execute_command $container_name else # terraform err_code="3" show_error "terraform with multiple container is not yet supported." $err_code exit $err_code fi done else if [ "$limits_pod_cpu" = "" ] || [ "$requests_pod_cpu" = "" ] || [ "$limits_pod_memory" = "" ] || [ "$requests_pod_memory" = "" ]; then err_code="3" show_error "Missing planning values." $err_code exit $err_code fi show_info "Calculating min/max/headroom ..." do_filter_min_max_headroom show_info "Done" # Make sure default cpu & memory value above existing one check_default_value_satisfied # Make sure trigger condition is met check_trigger_condition_on_all_metrics if [ "$iac_command" = "script" ]; then if [ "$resource_type" = "controller" ]; then generate_controller_set_cmd else # namespace quota execution_skipped="y" if [ "$do_limit_cpu" = "n" ]; then limits_pod_cpu="${limit_cpu_before::-1}" else execution_skipped="n" fi if [ "$do_limit_memory" = "n" ]; then limits_pod_memory=$limit_memory_before else execution_skipped="n" fi if [ "$do_request_cpu" = "n" ]; then requests_pod_cpu="${request_cpu_before::-1}" else execution_skipped="n" fi if [ "$do_request_memory" = "n" ]; then requests_pod_memory=$request_memory_before else execution_skipped="n" fi if [ "$execution_skipped" = "y" ]; then exec_cmd="N/A, execution skipped due to trigger condition is not met." else exec_cmd="$kube_cmd -n $target_namespace create quota $quota_name --hard=limits.cpu=${limits_pod_cpu}m,limits.memory=${limits_pod_memory},requests.cpu=${requests_pod_cpu}m,requests.memory=${requests_pod_memory}" fi fi execute_command else # iac_command = terraform # dry_run = normal variable_tf_name="${terraform_path}/federatorai_variables.tf" auto_tfvars_name="${terraform_path}/federatorai_recommendations.auto.tfvars" auto_tfvars_previous_name="${terraform_path}/federatorai_recommendations.auto.tfvars.previous" create_auto_tfvars create_variable_tf # Print final json output if [ "$resource_type" = "controller" ]; then echo -e "{\n \"info\": {\n \"cluster_name\": \"$cluster_name\",\n \"resource_type\": \"$resource_type\",\n \"namespace\": \"$target_namespace\",\n \"resource_name\": \"$resource_name\",\n \"kind\": \"$owner_reference_kind\",\n \"time_interval\": \"$readable_granularity\",\n \"execute_cmd\": \"N/A\",\n \"execution_time\": \"N/A\"\n },\n \"log_file\": \"$debug_log\",\n \"before_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_before\",\n \"memory\": \"$limit_memory_before\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_before\",\n \"memory\": \"$request_memory_before\"\n }\n },\n \"recommended_values\": {\n \"tf_file\": \"$variable_tf_name\",\n \"tfvars_file\": \"$auto_tfvars_name\",\n \"limits\": {\n \"cpu\": \"${limits_pod_cpu}m\",\n \"memory\": \"$limits_pod_memory\"\n },\n \"requests\": {\n \"cpu\": \"${requests_pod_cpu}m\",\n \"memory\": \"$requests_pod_memory\"\n }\n }\n}" | tee -a $debug_log else #resource_type = namespace echo -e "{\n \"info\": {\n \"cluster_name\": \"$cluster_name\",\n \"resource_type\": \"$resource_type\",\n \"resource_name\": \"$target_namespace\",\n \"time_interval\": \"$readable_granularity\",\n \"execute_cmd\": \"N/A\",\n \"execution_time\": \"N/A\"\n },\n \"log_file\": \"$debug_log\",\n \"before_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_before\",\n \"memory\": \"$limit_memory_before\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_before\",\n \"memory\": \"$request_memory_before\"\n }\n },\n \"recommended_values\": {\n \"tf_file\": \"$variable_tf_name\",\n \"tfvars_file\": \"$auto_tfvars_name\",\n \"limits\": {\n \"cpu\": \"${limits_pod_cpu}m\",\n \"memory\": \"$limits_pod_memory\"\n },\n \"requests\": {\n \"cpu\": \"${requests_pod_cpu}m\",\n \"memory\": \"$requests_pod_memory\"\n }\n }\n}" | tee -a $debug_log fi fi fi show_info "Done" } create_variable_tf() { if [ ! -f "$variable_tf_name" ]; then echo "variable \"federatorai_recommendations\" {" >> $variable_tf_name echo " description = \"Recommendations given by Federator.ai\"" >> $variable_tf_name echo " type = map(map(map(string)))" >> $variable_tf_name echo "}" >> $variable_tf_name fi module_name="${resource_id}_${cluster_name}" cat $variable_tf_name |grep -q "module \"$module_name\" {" if [ "$?" != "0" ]; then echo "" >> $variable_tf_name echo "module \"$module_name\" {" >> $variable_tf_name echo " source = \"prophetstor-ai/resource-provision/federatorai\"" >> $variable_tf_name echo " version = \"5.0.0\"" >> $variable_tf_name echo " federatorai_resource_id = \"${resource_id}\"" >> $variable_tf_name echo " federatorai_cluster_name = \"${cluster_name}\"" >> $variable_tf_name echo " federatorai_recommendations = var.federatorai_recommendations" >> $variable_tf_name echo "}" >> $variable_tf_name fi show_info "tf file ($variable_tf_name) is generated." } create_auto_tfvars() { if [ -f "$auto_tfvars_name" ]; then mv $auto_tfvars_name $auto_tfvars_previous_name fi if [ "$resource_type" = "controller" ]; then resource_id="federatorai_${owner_reference_kind}_${resource_name}_${target_namespace}" else resource_id="federatorai_namespace_${resource_name}" fi declare -A cluster_resource_map # Merge previous into map first if [ -f "$auto_tfvars_previous_name" ]; then rec_num="0" merge_tfvars "" < $auto_tfvars_previous_name rm -f $auto_tfvars_previous_name > /dev/null 2>&1 fi # Add Current entry current_time="$(date)" current_rec="#UpdateTime=\"$current_time\",recommended_cpu_request=\"${requests_pod_cpu}m\",recommended_memory_request=\"$requests_pod_memory\",recommended_cpu_limit=\"${limits_pod_cpu}m\",recommended_memory_limit=\"$limits_pod_memory\"" cluster_resource_map["federatorai_recommendations,$cluster_name,$resource_id"]="$current_rec" # Do export export_final_tfvars show_info "tfvars file ($auto_tfvars_name) is generated." } export_final_tfvars() { > $auto_tfvars_name cluster_list=$(echo "${!cluster_resource_map[@]}"|tr ' ' '\n'|awk -F',' '{print $2}'|sort|uniq) # Generate final tfvars echo "federatorai_recommendations = {" >> $auto_tfvars_name for cluster in $(echo $cluster_list) do echo " $cluster = {" >> $auto_tfvars_name for key in "${!cluster_resource_map[@]}" do target_cluster=$(echo "$key"|cut -d',' -f2) if [ "$target_cluster" = "$cluster" ]; then resource=$(echo "$key"|cut -d',' -f3) echo " $resource = {" >> $auto_tfvars_name rec_string=${cluster_resource_map[$key]} update_time=$(echo $rec_string|grep -o "#UpdateTime=[^\"]*\"[^\"]*\"") reccpureq=$(echo $rec_string|grep -o "recommended_cpu_request=[^\"]*\"[^\"]*\"") recmemreq=$(echo $rec_string|grep -o "recommended_memory_request=[^\"]*\"[^\"]*\"") reccpulim=$(echo $rec_string|grep -o "recommended_cpu_limit=[^\"]*\"[^\"]*\"") recmemlim=$(echo $rec_string|grep -o "recommended_memory_limit=[^\"]*\"[^\"]*\"") echo " $update_time" >> $auto_tfvars_name echo " $reccpureq" >> $auto_tfvars_name echo " $recmemreq" >> $auto_tfvars_name echo " $reccpulim" >> $auto_tfvars_name echo " $recmemlim" >> $auto_tfvars_name echo " }" >> $auto_tfvars_name fi done echo " }" >> $auto_tfvars_name done echo "}" >> $auto_tfvars_name } merge_tfvars() { local KEYS local K local V while true do read LINE if [ "${LINE}" = "" ] then return fi if [[ "$LINE" =~ "#UpdateTime" ]]; then K=`echo ${LINE} | awk -F'=' '{print $1}'` V=`echo ${LINE} | awk -F'=' '{print $2}'` else K=`echo ${LINE} | awk -F'=' '{print $1}' | tr -d '[:space:]'` V=`echo ${LINE} | awk -F'=' '{print $2}' | tr -d '[:space:]'` fi if [ "${K}" = "}" ] then return else if [ "$1" = "" ] then KEYS="${K}" else KEYS="$1,${K}" fi if [ "${V}" = "{" ] then merge_tfvars ${KEYS} else rec_key=$(echo ${KEYS##*,}) final_rec_value_string="${final_rec_value_string}${final_rec_value_string:+,}$rec_key=$V" rec_num=$(($rec_num + 1)) if [ "$rec_num" = "5" ]; then final_key=$(echo $KEYS|rev|cut -d',' -f2-|rev) cluster_resource_map[${final_key}]="$final_rec_value_string" rec_num="0" final_rec_value_string="" fi fi fi done } convert_cpu_unit_to_bytes() { unit="$1" result=$(echo "$unit"|awk ' /^[0-9.]+$/ { print $0 * 1000 exit } /^[0-9.]+m$/ { match($0, /^[0-9.]+/) mynumber = substr($0,RSTART,RLENGTH) print mynumber exit } // { print -1 } ') echo $result } convert_memory_unit_to_bytes() { unit="$1" result=$(echo "$unit"|awk ' /^[0-9.]+e$/ { print -1 exit } /^[0-9]+$/ { print $0 exit } /^[0-9.]+[EPTGMkKi]+$/ { match($0, /^[0-9.]+/) myunit = substr($0,RLENGTH+1,length($0)) mynumber = substr($0,RSTART,RLENGTH) #print mynumber,myunit if (myunit == "E") print mynumber * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 else if (myunit == "P") print mynumber * 1000 * 1000 * 1000 * 1000 * 1000 else if (myunit == "T") print mynumber * 1000 * 1000 * 1000 * 1000 else if (myunit == "G") print mynumber * 1000 * 1000 * 1000 else if (myunit == "M") print mynumber * 1000 * 1000 else if (myunit == "k") print mynumber * 1000 else if (myunit == "Ei"||myunit == "EiB") print mynumber * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 else if (myunit == "Pi"||myunit == "PiB") print mynumber * 1024 * 1024 * 1024 * 1024 * 1024 else if (myunit == "Ti"||myunit == "TiB") print mynumber * 1024 * 1024 * 1024 * 1024 else if (myunit == "Gi"||myunit == "GiB") print mynumber * 1024 * 1024 * 1024 else if (myunit == "Mi"||myunit == "MiB") print mynumber * 1024 * 1024 else if (myunit == "Ki"||myunit == "KiB") print mynumber * 1024 else print -1 exit } /^[0-9.]+[eE][0-9]+$/ { IGNORECASE = 1; match($0, /^[0-9.]+/) myexponumber = substr($0,RLENGTH+2,length($0)) mynumber = substr($0,RSTART,RLENGTH) print mynumber * 10 ^ myexponumber exit } /^[0-9.]+[m]+$/ { match($0, /^[0-9.]+/) myunit = substr($0,RLENGTH+1,length($0)) mynumber = substr($0,RSTART,RLENGTH) printf "%.3f",mynumber /1000 exit } // { print -1 } ') echo $result } parse_value_from_resource() { target_field="$1" target_resource="$2" if [ -z "$target_field" ]; then err_code="3" show_error "parse_value_from_resource() target_field parameter can't be empty." $err_code exit $err_code elif [ -z "$target_resource" ]; then err_code="3" show_error "parse_value_from_resource() target_resource parameter can't be empty." $err_code exit $err_code fi if [ "$target_field" != "limits" ] && [ "$target_field" != "requests" ]; then err_code="3" show_error "parse_value_from_resource() target_field can only be either 'limits' and 'requests'." $err_code exit $err_code fi if [ "$target_resource" != "cpu" ] && [ "$target_resource" != "memory" ]; then err_code="3" show_error "parse_value_from_resource() target_field can only be either 'cpu' and 'memory'." $err_code exit $err_code fi echo "$resources"|grep -o "\"$target_field\":[^{]*{[^}]*}"|grep -o "\"$target_resource\":[^\"]*\"[^\"]*\""|cut -d '"' -f4|head -1 } parse_value_from_quota() { target_field="$1" target_resource="$2" if [ -z "$target_field" ]; then err_code="3" show_error "parse_value_from_quota() target_field parameter can't be empty." $err_code exit $err_code elif [ -z "$target_resource" ]; then err_code="3" show_error "parse_value_from_quota() target_resource parameter can't be empty." $err_code exit $err_code fi if [ "$target_field" != "limits" ] && [ "$target_field" != "requests" ]; then err_code="3" show_error "parse_value_from_quota() target_field can only be either 'limits' and 'requests'." $err_code exit $err_code fi if [ "$target_resource" != "cpu" ] && [ "$target_resource" != "memory" ]; then err_code="3" show_error "parse_value_from_quota() target_field can only be either 'cpu' and 'memory'." $err_code exit $err_code fi echo "$quotas"|grep -o "\"$target_field.$target_resource\":[^\"]*\"[^\"]*\""|cut -d '"' -f4 } get_namespace_quota_from_kubecmd() { mode=$1 if [ "$mode" = "" ]; then err_code="4" show_error "get_namespace_quota_from_kubecmd() mode parameter can't be empty." $err_code exit $err_code fi quota_name="${target_namespace}.federator.ai" show_info "Getting current namespace quota..." show_info "Namespace = $target_namespace" show_info "Quota name = $quota_name" if [ "$DEMO_MODE" != "y" ]; then quotas=$($kube_cmd get quota $quota_name -n $target_namespace -o json 2>/dev/null|tr -d '\n'|grep -o "\"spec\":.*"|grep -o "\"hard\":[^}]*}"|head -1) limit_cpu=$(parse_value_from_quota "limits" "cpu") limit_memory=$(parse_value_from_quota "limits" "memory") request_cpu=$(parse_value_from_quota "requests" "cpu") request_memory=$(parse_value_from_quota "requests" "memory") else limit_cpu="" limit_memory="" request_cpu="" request_memory="" fi if [ "$mode" = "before" ]; then if [ "$limit_cpu" = "" ]; then limit_cpu_before="N/A" else limit_cpu_before=$limit_cpu fi if [ "$limit_memory" = "" ]; then limit_memory_before="N/A" else limit_memory_before=$limit_memory fi if [ "$request_cpu" = "" ]; then request_cpu_before="N/A" else request_cpu_before=$request_cpu fi if [ "$request_memory" = "" ]; then request_memory_before="N/A" else request_memory_before=$request_memory fi show_info "--------- Namespace Quota: Before execution ---------" show_info "limits:" show_info " cpu: $limit_cpu_before" show_info " memory: $limit_memory_before" show_info "Requests:" show_info " cpu: $request_cpu_before" show_info " memory: $request_memory_before" show_info "-----------------------------------------------------" else # mode = "after" if [ "$do_dry_run" = "y" ]; then show_info "--------------------- Dry run -----------------------" # dry run - set resource values from planning results to display limit_cpu_after="${limits_pod_cpu}m" limit_memory_after="$limits_pod_memory" request_cpu_after="${requests_pod_cpu}m" request_memory_after="$requests_pod_memory" else # patch is done if [ "$limit_cpu" = "" ]; then limit_cpu_after="N/A" else limit_cpu_after=$limit_cpu fi if [ "$limit_memory" = "" ]; then limit_memory_after="N/A" else limit_memory_after=$limit_memory fi if [ "$request_cpu" = "" ]; then request_cpu_after="N/A" else request_cpu_after=$request_cpu fi if [ "$request_memory" = "" ]; then request_memory_after="N/A" else request_memory_after=$request_memory fi show_info "--------- Namespace Quota: After execution ----------" fi show_info "limits:" show_info " cpu: $limit_cpu_before -> $limit_cpu_after" show_info " memory: $limit_memory_before -> $limit_memory_after" show_info "Requests:" show_info " cpu: $request_cpu_before -> $request_cpu_after" show_info " memory: $request_memory_before -> $request_memory_after" show_info "-----------------------------------------------------" echo -e "{\n \"info\": {\n \"cluster_name\": \"$cluster_name\",\n \"resource_type\": \"$resource_type\",\n \"resource_name\": \"$target_namespace\",\n \"time_interval\": \"$readable_granularity\",\n \"execute_cmd\": \"$exec_cmd\",\n \"execution_time\": \"$execution_time\"\n },\n \"log_file\": \"$debug_log\",\n \"before_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_before\",\n \"memory\": \"$limit_memory_before\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_before\",\n \"memory\": \"$request_memory_before\"\n }\n },\n \"after_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_after\",\n \"memory\": \"$limit_memory_after\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_after\",\n \"memory\": \"$request_memory_after\"\n }\n }\n}" | tee -a $debug_log fi show_info "Done." } get_controller_resources_from_kubecmd() { mode=$1 if [ "$mode" = "" ]; then err_code="4" show_error "get_controller_resources_from_kubecmd() mode parameter can't be empty." $err_code exit $err_code fi get_container_number_and_name_list if [ "$DEMO_MODE" = "y" ]; then container_number=$container_records_num container_array=("${container_name_keys[@]}") fi if [ "0$container_number" -ge "2" ]; then show_info "Getting current controller resources (per container)..." show_info "Namespace = $target_namespace" show_info "Resource name = $resource_name" show_info "Kind = $owner_reference_kind" if [ "$DEMO_MODE" != "y" ]; then controller_resources="$($kube_cmd get $owner_reference_kind $resource_name -n $target_namespace -o json)" data=$(tokenize_json "$controller_resources"| parse_json_begin) err_code="$?" if [ "$err_code" != "0" ]; then show_error "Failed to parse json output (controller_resources)." $err_code exit $err_code fi fi for container_name in "${container_array[@]}" do for index in `seq 0 $(( container_number - 1 ))` do name=$(get_valued_from_parsed_json "$data" "\[\"spec\",\"template\",\"spec\",\"containers\",$index,\"name\"") if [ "$name" = "$container_name" ]; then limit_cpu=$(get_valued_from_parsed_json "$data" "\[\"spec\",\"template\",\"spec\",\"containers\",$index,\"resources\",\"limits\",\"cpu\"\]") limit_memory=$(get_valued_from_parsed_json "$data" "\[\"spec\",\"template\",\"spec\",\"containers\",$index,\"resources\",\"limits\",\"memory\"\]") request_cpu=$(get_valued_from_parsed_json "$data" "\[\"spec\",\"template\",\"spec\",\"containers\",$index,\"resources\",\"requests\",\"cpu\"\]") request_memory=$(get_valued_from_parsed_json "$data" "\[\"spec\",\"template\",\"spec\",\"containers\",$index,\"resources\",\"requests\",\"memory\"\]") fi done if [ "$limit_cpu" = "" ]; then limit_cpu="N/A" else limit_cpu_converted=$(convert_cpu_unit_to_bytes "$limit_cpu") fi if [ "$limit_memory" = "" ]; then limit_memory="N/A" else limit_memory_converted=$(convert_memory_unit_to_bytes "$limit_memory") fi if [ "$request_cpu" = "" ]; then request_cpu="N/A" else request_cpu_converted=$(convert_cpu_unit_to_bytes "$request_cpu") fi if [ "$request_memory" = "" ]; then request_memory="N/A" else request_memory_converted=$(convert_memory_unit_to_bytes "$request_memory") fi show_info "container ($container_name) resources" show_info "limits:" show_info " cpu: $limit_cpu" show_info " memory: $limit_memory" show_info "Requests:" show_info " cpu: $request_cpu" show_info " memory: $request_memory" show_info "-----------------------------------------------------" if [ "$mode" = "before" ]; then container_resource_array+=( "$container_name.before.limits.cpu.original:$limit_cpu" ) container_resource_array+=( "$container_name.before.limits.memory.original:$limit_memory" ) container_resource_array+=( "$container_name.before.requests.cpu.original:$request_cpu" ) container_resource_array+=( "$container_name.before.requests.memory.original:$request_memory" ) container_resource_array+=( "$container_name.before.limits.cpu.converted:$limit_cpu_converted" ) container_resource_array+=( "$container_name.before.limits.memory.converted:$limit_memory_converted" ) container_resource_array+=( "$container_name.before.requests.cpu.converted:$request_cpu_converted" ) container_resource_array+=( "$container_name.before.requests.memory.converted:$request_memory_converted" ) else # mode = "after" if [ "$do_dry_run" = "y" ]; then show_info "--------------------- Dry run -----------------------" # dry run - set resource values from planning results to display limits_pod_cpu=$(get_value_from_resource_array $container_name after limits cpu original) limits_pod_memory=$(get_value_from_resource_array $container_name after limits memory original) requests_pod_cpu=$(get_value_from_resource_array $container_name after requests cpu original) requests_pod_memory=$(get_value_from_resource_array $container_name after requests memory original) limit_cpu_after="${limits_pod_cpu}m" limit_memory_after="$limits_pod_memory" request_cpu_after="${requests_pod_cpu}m" request_memory_after="$requests_pod_memory" else # patch is done show_info "------------------ After execution ------------------" limit_cpu_after=$limit_cpu limit_memory_after=$limit_memory request_cpu_after=$request_cpu request_memory_after=$request_memory fi limit_cpu_before=$(get_value_from_resource_array $container_name before limits cpu original) limit_memory_before=$(get_value_from_resource_array $container_name before limits memory original) request_cpu_before=$(get_value_from_resource_array $container_name before requests cpu original) request_memory_before=$(get_value_from_resource_array $container_name before requests memory original) show_info "container ($container_name)" show_info "limits:" show_info " cpu: $limit_cpu_before -> $limit_cpu_after" show_info " memory: $limit_memory_before -> $limit_memory_after" show_info "Requests:" show_info " cpu: $request_cpu_before -> $request_cpu_after" show_info " memory: $request_memory_before -> $request_memory_after" show_info "-----------------------------------------------------" if [ "$do_dry_run" = "y" ]; then # For dry run mode, print every json result exec_cmd=$(get_value_from_resource_array $container_name after issue execute cmd) execution_time=$(get_value_from_resource_array $container_name after issue execute time) echo -e "{\n \"info\": {\n \"cluster_name\": \"$cluster_name\",\n \"resource_type\": \"$resource_type\",\n \"namespace\": \"$target_namespace\",\n \"resource_name\": \"$resource_name\",\n \"kind\": \"$owner_reference_kind\",\n \"time_interval\": \"$readable_granularity\",\n \"execute_cmd\": \"$exec_cmd\",\n \"execution_time\": \"$execution_time\"\n },\n \"log_file\": \"$debug_log\",\n \"before_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_before\",\n \"memory\": \"$limit_memory_before\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_before\",\n \"memory\": \"$request_memory_before\"\n }\n },\n \"after_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_after\",\n \"memory\": \"$limit_memory_after\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_after\",\n \"memory\": \"$request_memory_after\"\n }\n }\n}" | tee -a $debug_log fi fi done if [ "$mode" = "after" ] && [ "$do_dry_run" != "y" ]; then # Only display last container execution result for now echo -e "{\n \"info\": {\n \"cluster_name\": \"$cluster_name\",\n \"resource_type\": \"$resource_type\",\n \"namespace\": \"$target_namespace\",\n \"resource_name\": \"$resource_name\",\n \"kind\": \"$owner_reference_kind\",\n \"time_interval\": \"$readable_granularity\",\n \"execute_cmd\": \"$exec_cmd\",\n \"execution_time\": \"$execution_time\"\n },\n \"log_file\": \"$debug_log\",\n \"before_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_before\",\n \"memory\": \"$limit_memory_before\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_before\",\n \"memory\": \"$request_memory_before\"\n }\n },\n \"after_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_after\",\n \"memory\": \"$limit_memory_after\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_after\",\n \"memory\": \"$request_memory_after\"\n }\n }\n}" | tee -a $debug_log fi else # only one container show_info "Getting current controller resources..." show_info "Namespace = $target_namespace" show_info "Resource name = $resource_name" show_info "Kind = $owner_reference_kind" if [ "$DEMO_MODE" != "y" ]; then resources=$($kube_cmd get $owner_reference_kind $resource_name -n $target_namespace -o json |tr -d '\n'|grep -o "\"spec\":.*"|grep -o "\"template\":.*"|grep -o "\"spec\":.*"|grep -o "\"containers\":.*"|grep -o "\"resources\":.*") fi if [ "$mode" = "before" ]; then show_info "----------------- Before execution ------------------" limit_cpu_before=$(parse_value_from_resource "limits" "cpu") if [ "$limit_cpu_before" = "" ]; then limit_cpu_before="N/A" else limit_cpu_before_converted=$(convert_cpu_unit_to_bytes "$limit_cpu_before") fi limit_memory_before=$(parse_value_from_resource "limits" "memory") if [ "$limit_memory_before" = "" ]; then limit_memory_before="N/A" else limit_memory_before_converted=$(convert_memory_unit_to_bytes "$limit_memory_before") fi request_cpu_before=$(parse_value_from_resource "requests" "cpu") if [ "$request_cpu_before" = "" ]; then request_cpu_before="N/A" else request_cpu_before_converted=$(convert_cpu_unit_to_bytes "$request_cpu_before") fi request_memory_before=$(parse_value_from_resource "requests" "memory") if [ "$request_memory_before" = "" ]; then request_memory_before="N/A" else request_memory_before_converted=$(convert_memory_unit_to_bytes "$request_memory_before") fi show_info "limits:" show_info " cpu: $limit_cpu_before" show_info " memory: $limit_memory_before" show_info "Requests:" show_info " cpu: $request_cpu_before" show_info " memory: $request_memory_before" show_info "-----------------------------------------------------" else # mode = "after" if [ "$do_dry_run" = "y" ]; then show_info "--------------------- Dry run -----------------------" # dry run - set resource values from planning results to display limit_cpu_after="${limits_pod_cpu}m" limit_memory_after="$limits_pod_memory" request_cpu_after="${requests_pod_cpu}m" request_memory_after="$requests_pod_memory" else # patch is done show_info "------------------ After execution ------------------" limit_cpu_after=$(parse_value_from_resource "limits" "cpu") [ "$limit_cpu_after" = "" ] && limit_cpu_after="N/A" limit_memory_after=$(parse_value_from_resource "limits" "memory") [ "$limit_memory_after" = "" ] && limit_memory_after="N/A" request_cpu_after=$(parse_value_from_resource "requests" "cpu") [ "$request_cpu_after" = "" ] && request_cpu_after="N/A" request_memory_after=$(parse_value_from_resource "requests" "memory") [ "$request_memory_after" = "" ] && request_memory_after="N/A" fi show_info "limits:" show_info " cpu: $limit_cpu_before -> $limit_cpu_after" show_info " memory: $limit_memory_before -> $limit_memory_after" show_info "Requests:" show_info " cpu: $request_cpu_before -> $request_cpu_after" show_info " memory: $request_memory_before -> $request_memory_after" show_info "-----------------------------------------------------" echo -e "{\n \"info\": {\n \"cluster_name\": \"$cluster_name\",\n \"resource_type\": \"$resource_type\",\n \"namespace\": \"$target_namespace\",\n \"resource_name\": \"$resource_name\",\n \"kind\": \"$owner_reference_kind\",\n \"time_interval\": \"$readable_granularity\",\n \"execute_cmd\": \"$exec_cmd\",\n \"execution_time\": \"$execution_time\"\n },\n \"log_file\": \"$debug_log\",\n \"before_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_before\",\n \"memory\": \"$limit_memory_before\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_before\",\n \"memory\": \"$request_memory_before\"\n }\n },\n \"after_execution\": {\n \"limits\": {\n \"cpu\": \"$limit_cpu_after\",\n \"memory\": \"$limit_memory_after\"\n },\n \"requests\": {\n \"cpu\": \"$request_cpu_after\",\n \"memory\": \"$request_memory_after\"\n }\n }\n}" | tee -a $debug_log fi fi show_info "Done." } connection_test() { check_rest_api_url rest_api_login } normal_way(){ if [ "$resource_type" = "controller" ];then if [ "$DEMO_MODE" != "y" ]; then get_controller_resources_from_kubecmd "before" get_planning_from_api else get_planning_from_api get_controller_resources_from_kubecmd "before" fi elif [ "$resource_type" = "namespace" ]; then get_namespace_quota_from_kubecmd "before" get_planning_from_api else err_code="3" show_error "Only support resource_type equals 'controller' or 'namespace'." $err_code exit $err_code fi if [ "$do_dry_run" = "y" ]; then update_target_resources "dry_run" else update_target_resources "normal" fi if [ "$iac_command" = "script" ]; then if [ "$resource_type" = "controller" ];then get_controller_resources_from_kubecmd "after" else # resource_type = namespace get_namespace_quota_from_kubecmd "after" fi fi } # json parser parameter NO_HEAD=0 NORMALIZE_SOLIDUS=0 BRIEF=1 LEAFONLY=1 PRUNE=1 while getopts "h-:" o; do case "${o}" in -) case "${OPTARG}" in dry-run-only) do_dry_run="y" ;; test-connection-only) do_test_connection="y" ;; verbose) verbose_mode="y" ;; log-name) log_name="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) if [ "$log_name" = "" ]; then err_code="6" show_error "Missing --${OPTARG} value" $err_code exit $err_code fi ;; terraform-path) terraform_path="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) if [ "$terraform_path" = "" ]; then err_code="6" show_error "Missing --${OPTARG} value" $err_code exit $err_code fi ;; detail-to-stderr) detail_to_stderr="y" ;; help) show_usage exit 0 ;; *) err_code="6" show_error "Unknown option --${OPTARG}" $err_code exit $err_code ;; esac;; h) show_usage exit 0 ;; *) err_code="6" show_error "Wrong parameter." $err_code exit $err_code ;; esac done if [ "$DEMO_MODE" = "y" ] && [ "$do_dry_run" != "y" ]; then err_code="6" show_error "DEMO_MODE env enabled. It can only run with dry run mode" $err_code exit $err_code fi unameOut="$(uname -s)" case "${unameOut}" in Linux*) machine_type=Linux;; Darwin*) machine_type=Mac;; *) err_code="6" show_error "Unsupported machine type (${unameOut})." $err_code exit $err_code ;; esac if [ "$FEDERATORAI_FILE_PATH" = "" ]; then save_path="/opt/federatorai" else save_path="$FEDERATORAI_FILE_PATH" fi file_folder="$save_path/auto-provisioning" realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" } if [ "$log_name" = "" ]; then log_name="output.log" debug_log="${file_folder}/${log_name}" else if [[ "$log_name" = /* ]]; then # Absolute path file_folder="$(dirname "$log_name")" debug_log="$log_name" else # Relative path if [ "$machine_type" = "Linux" ]; then file_folder="${file_folder}/$(dirname "$log_name")" debug_log="${file_folder}/$(basename "$log_name")" else parent_folder=$(dirname $log_name) file_folder="$file_folder/$parent_folder" debug_log="${file_folder}/$(basename "$log_name")" fi fi fi mkdir -p $file_folder if [ ! -d "$file_folder" ]; then err_code="6" show_error "Failed to create folder ($file_folder) to save Federator.ai planning-util files. Consider exporting the env variable \$FEDERATORAI_FILE_PATH to specify the folder path." exit $err_code fi if [ "$machine_type" = "Linux" ]; then script_located_path=$(dirname $(readlink -f "$0")) else # Mac script_located_path=$(dirname $(realpath "$0")) fi if [ "$terraform_path" = "" ]; then terraform_path="$script_located_path" fi mkdir -p $terraform_path if [ ! -d "$terraform_path" ]; then err_code="6" show_error "Failed to create terraform folder ($terraform_path) to save Federator.ai planning-util files." $err_code exit $err_code fi current_location=`pwd` # mCore default_min_cpu="50" # Byte default_min_memory="10485760" echo "================================== New Round ======================================" >> $debug_log echo "Receiving command: '$0 $@'" >> $debug_log echo "Receiving time: `date -u`" >> $debug_log # Check target_config_info variable check_target_config # Get resource type resource_type=$(parse_value_from_target_var "resource_type") resource_type="$(echo "$resource_type" | tr '[:upper:]' '[:lower:]')" # Parse config info get_info_from_config # Get kubeconfig path kubeconfig_path=$(parse_value_from_target_var "kubeconfig_path") if [ "$DEMO_MODE" != "y" ]; then if [ "$resource_type" = "controller" ] && [ "$owner_reference_kind" = "deploymentconfig" ]; then if [ "$kubeconfig_path" = "" ]; then kube_cmd="oc" verify_cmd="kubectl" else kube_cmd="oc --kubeconfig $kubeconfig_path" verify_cmd="kubectl --kubeconfig $kubeconfig_path" fi cmd_type="oc" else if [ "$kubeconfig_path" = "" ]; then kube_cmd="kubectl" else kube_cmd="kubectl --kubeconfig $kubeconfig_path" fi cmd_type="kubectl" fi type $cmd_type > /dev/null 2>&1 if [ "$?" != "0" ];then err_code="6" show_error "$cmd_type command is needed for this tool." $err_code exit $err_code fi if [ "$cmd_type" = "oc" ]; then # kubectl must exist too type kubectl > /dev/null 2>&1 if [ "$?" != "0" ];then err_code="6" show_error "kubectl command is needed for this tool." $err_code exit $err_code fi # Still use kubectl version to verify server connection $verify_cmd version|grep -q "^Server" else $kube_cmd version|grep -q "^Server" fi if [ "$?" != "0" ];then err_code="6" show_error "Failed to get Kubernetes server info through $cmd_type cmd. Please login first or check your kubeconfig_path config value." $err_code exit $err_code fi else # Demo mode kube_cmd="kubectl" fi type curl > /dev/null 2>&1 if [ "$?" != "0" ];then err_code="6" show_error "curl command is needed for this tool." $err_code exit $err_code fi type awk > /dev/null 2>&1 if [ "$?" != "0" ];then err_code="6" show_error "awk command is needed for this tool." $err_code exit $err_code fi type base64 > /dev/null 2>&1 if [ "$?" != "0" ];then err_code="6" show_error "base64 command is needed for this tool." $err_code exit $err_code fi connection_test if [ "$do_test_connection" = "y" ]; then echo -e "{\n \"connection_test\": \"passed\",\n \"log_file\": \"$debug_log\"\n}" | tee -a $debug_log exit 0 fi rest_api_check_cluster_name container_resource_array=() container_planning_array=() if [ "$resource_type" = "application" ];then do_application_way else normal_way fi planning-util.sh.template.desc: |- { "rest_api_url": "", "login_account": " # required # Federator.ai GUI login account", "login_password": " # required # Federator.ai GUI login password", "resource_type": " # required # controller|application|namespace", "iac_command": " # required # script|terraform", "kubeconfig_path": " # optional # kubeconfig path", "planning_target": { "cluster_name": " # required", "namespace": " # required if resource_type=controller", "time_interval": " # required # daily|weekly|monthly", "resource_name": " # required", "kind": " # required if resource_type=controller # StatefulSet|Deployment|DeploymentConfig", "min_cpu": " # optional # mCore", "max_cpu": " # optional # mCore", "cpu_headroom": " # optional # 200|20% # Absolute value (mCore) e.g. 200 or Percentage e.g. 20%", "min_memory": " # optional # byte", "max_memory": " # optional # byte", "memory_headroom": " # optional # 209715200|20% # Absolute value (byte) e.g. 209715200 or Percentage e.g. 20%", "trigger_condition": " # optional # 20 # 20 means 20%" } }