Skip to content

Commit 00bf288

Browse files
authored
Merge pull request #31 from dhslove/codex/n2k-cloud-rootdisksize-fix
N2K Cloud root disk size 전달 보완
2 parents 2c966a5 + 07073fb commit 00bf288

3 files changed

Lines changed: 255 additions & 11 deletions

File tree

lib/n2k/cloudstack_api.sh

Lines changed: 123 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,36 +102,148 @@ n2k_cloud_signed_query() {
102102
printf '%s&signature=%s' "${query}" "${signature_enc}"
103103
}
104104

105+
n2k_cloud_api_prefers_post() {
106+
local command="$1"
107+
case "${command}" in
108+
createDiskOffering|importVolume|deployVirtualMachineForVolume|updateVolume|attachVolume|startVirtualMachine)
109+
return 0
110+
;;
111+
*)
112+
return 1
113+
;;
114+
esac
115+
}
116+
117+
n2k_cloud_api_method() {
118+
local command="$1" query_len="$2"
119+
local requested threshold
120+
requested="$(printf '%s' "${N2K_CLOUD_API_METHOD:-auto}" | tr '[:lower:]' '[:upper:]')"
121+
threshold="${N2K_CLOUD_POST_THRESHOLD:-1800}"
122+
[[ "${threshold}" =~ ^[0-9]+$ ]] || threshold=1800
123+
124+
case "${requested}" in
125+
GET|POST)
126+
printf '%s' "${requested}"
127+
return 0
128+
;;
129+
esac
130+
131+
if n2k_cloud_api_prefers_post "${command}" || (( query_len >= threshold )); then
132+
printf '%s' "POST"
133+
else
134+
printf '%s' "GET"
135+
fi
136+
}
137+
105138
n2k_cloud_command_params_json() {
106139
local command="$1" api_key="$2" params_json="${3:-}"
107-
if [[ -z "${params_json}" ]]; then
108-
params_json="{}"
109-
fi
140+
[[ -n "${params_json}" ]] || params_json="{}"
110141
printf '%s' "${params_json}" | jq -c \
111142
--arg command "${command}" \
112143
--arg api_key "${api_key}" \
113144
'. + {command:$command, apiKey:$api_key, response:"json"}'
114145
}
115146

147+
n2k_cloud_response_error_summary() {
148+
local body_file="$1" header_file="$2"
149+
local json_summary="" header_desc=""
150+
151+
if [[ -s "${body_file}" ]]; then
152+
json_summary="$(jq -r '
153+
to_entries[0].value as $v
154+
| [
155+
(if ($v.errorcode // null) != null then "errorcode=" + ($v.errorcode | tostring) else empty end),
156+
(if ($v.cserrorcode // null) != null then "cserrorcode=" + ($v.cserrorcode | tostring) else empty end),
157+
(if (($v.errortext // "") | tostring | length) > 0 then "errortext=" + ($v.errortext | tostring) else empty end)
158+
]
159+
| join(" ")
160+
' "${body_file}" 2>/dev/null || true)"
161+
fi
162+
163+
if [[ -s "${header_file}" ]]; then
164+
header_desc="$(awk -F': *' 'tolower($1) == "x-description" {print substr($0, index($0, $2))}' "${header_file}" | tail -n 1 | tr -d '\r' || true)"
165+
fi
166+
167+
if [[ -n "${json_summary}" && -n "${header_desc}" && "${json_summary}" != *"${header_desc}"* ]]; then
168+
printf '%s x-description=%s' "${json_summary}" "${header_desc}"
169+
elif [[ -n "${json_summary}" ]]; then
170+
printf '%s' "${json_summary}"
171+
elif [[ -n "${header_desc}" ]]; then
172+
printf 'x-description=%s' "${header_desc}"
173+
elif [[ -s "${body_file}" ]]; then
174+
head -c 512 "${body_file}" | tr '\n' ' '
175+
fi
176+
}
177+
178+
n2k_cloud_api_report_failure() {
179+
local command="$1" method="$2" query_len="$3" http_status="$4" body_file="$5" header_file="$6" curl_err_file="$7"
180+
local summary curl_err
181+
182+
echo "Cloud API request failed: command=${command} method=${method} http_status=${http_status:-000} query_length=${query_len}" >&2
183+
summary="$(n2k_cloud_response_error_summary "${body_file}" "${header_file}")"
184+
if [[ -n "${summary}" ]]; then
185+
echo "Cloud API error: ${summary}" >&2
186+
fi
187+
if [[ -s "${curl_err_file}" ]]; then
188+
curl_err="$(tr '\n' ' ' <"${curl_err_file}")"
189+
[[ -n "${curl_err}" ]] && echo "Cloud API curl error: ${curl_err}" >&2
190+
fi
191+
}
192+
116193
n2k_cloud_api_get() {
117194
local endpoint="$1" api_key="$2" secret_key="$3" command="$4" params_json="${5:-}"
118-
local connect_timeout max_time body_params query url
119-
if [[ -z "${params_json}" ]]; then
120-
params_json="{}"
121-
fi
195+
local connect_timeout max_time body_params query url method query_len
196+
local body_file header_file curl_err_file http_status curl_rc
197+
[[ -n "${params_json}" ]] || params_json="{}"
122198
endpoint="$(n2k_cloud_normalize_endpoint "${endpoint}")"
123199
n2k_cloud_require_credentials "${endpoint}" "${api_key}" "${secret_key}"
124200
connect_timeout="${N2K_CLOUD_CONNECT_TIMEOUT:-10}"
125201
max_time="${N2K_CLOUD_MAX_TIME:-120}"
126202

127203
body_params="$(n2k_cloud_command_params_json "${command}" "${api_key}" "${params_json}")"
128204
query="$(n2k_cloud_signed_query "${body_params}" "${secret_key}")"
205+
query_len="${#query}"
206+
method="$(n2k_cloud_api_method "${command}" "${query_len}")"
129207
url="${endpoint}?${query}"
130208

131-
curl --globoff --silent --show-error --fail \
132-
--connect-timeout "${connect_timeout}" \
133-
--max-time "${max_time}" \
134-
"${url}"
209+
body_file="$(mktemp "${TMPDIR:-/tmp}/n2k-cloud-body.XXXXXX")"
210+
header_file="$(mktemp "${TMPDIR:-/tmp}/n2k-cloud-headers.XXXXXX")"
211+
curl_err_file="$(mktemp "${TMPDIR:-/tmp}/n2k-cloud-curl.XXXXXX")"
212+
213+
if [[ "${method}" == "POST" ]]; then
214+
http_status="$(curl --globoff --silent --show-error \
215+
--request POST \
216+
--header "Content-Type: application/x-www-form-urlencoded" \
217+
--data-binary "${query}" \
218+
--connect-timeout "${connect_timeout}" \
219+
--max-time "${max_time}" \
220+
--dump-header "${header_file}" \
221+
--output "${body_file}" \
222+
--write-out "%{http_code}" \
223+
"${endpoint}" 2>"${curl_err_file}")"
224+
curl_rc=$?
225+
if (( curl_rc != 0 )) || ! [[ "${http_status}" =~ ^[0-9]{3}$ ]] || (( http_status >= 400 )); then
226+
n2k_cloud_api_report_failure "${command}" "POST" "${query_len}" "${http_status}" "${body_file}" "${header_file}" "${curl_err_file}"
227+
rm -f "${body_file}" "${header_file}" "${curl_err_file}"
228+
return 1
229+
fi
230+
else
231+
http_status="$(curl --globoff --silent --show-error \
232+
--connect-timeout "${connect_timeout}" \
233+
--max-time "${max_time}" \
234+
--dump-header "${header_file}" \
235+
--output "${body_file}" \
236+
--write-out "%{http_code}" \
237+
"${url}" 2>"${curl_err_file}")"
238+
curl_rc=$?
239+
if (( curl_rc != 0 )) || ! [[ "${http_status}" =~ ^[0-9]{3}$ ]] || (( http_status >= 400 )); then
240+
n2k_cloud_api_report_failure "${command}" "GET" "${query_len}" "${http_status}" "${body_file}" "${header_file}" "${curl_err_file}"
241+
rm -f "${body_file}" "${header_file}" "${curl_err_file}"
242+
return 1
243+
fi
244+
fi
245+
cat "${body_file}"
246+
rm -f "${body_file}" "${header_file}" "${curl_err_file}"
135247
}
136248

137249
n2k_cloud_response_body() {

lib/n2k/target_cloud.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ n2k_cloud_target_source_deploy_params_json() {
235235
(.source.vm // {}) as $vm
236236
| (($vm.cpu // 0) | tonumber? // 0) as $cpu
237237
| (($vm.memory_mb // 0) | tonumber? // 0) as $memory_mb
238+
| (((.disks[0].size_bytes // .disks[0].disk_size_bytes // .disks[0].capacity_bytes // .disks[0].size // 0) | tonumber? // 0)) as $root_size_bytes
239+
| (if $root_size_bytes > 0 then ((($root_size_bytes + 1073741823) / 1073741824) | floor) else 0 end) as $root_size_gib
238240
| ((.target.cloud.cpu_speed // "1000") | tostring) as $cpu_speed
239241
| (($vm.firmware // "") | tostring | ascii_downcase) as $firmware
240242
| (($vm.secure_boot // false) == true) as $secure_boot
@@ -245,6 +247,7 @@ n2k_cloud_target_source_deploy_params_json() {
245247
+ (if $cpu > 0 then {"details[0].cpuNumber": ($cpu | floor | tostring)} else {} end)
246248
+ {"details[0].cpuSpeed": $cpu_speed}
247249
+ (if $memory_mb > 0 then {"details[0].memory": ($memory_mb | floor | tostring)} else {} end)
250+
+ (if $root_size_gib > 0 then {"details[0].rootdisksize": ($root_size_gib | tostring)} else {} end)
248251
+ (if ($source_mac | length) > 0 then {macaddress:$source_mac} else {} end)
249252
+ (if ($root_controller | length) > 0 then {"details[0].rootDiskController": $root_controller} else {} end)
250253
+ (if ($data_controller | length) > 0 then {"details[0].dataDiskController": $data_controller} else {} end)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
WORK_DIR="${TMPDIR:-/tmp}/n2k_cloud_target_runtime_smoke"
6+
7+
require_cmd() {
8+
command -v "$1" >/dev/null 2>&1 || {
9+
echo "[ERR] Missing command: $1" >&2
10+
exit 2
11+
}
12+
}
13+
14+
cleanup() {
15+
rm -rf "${WORK_DIR}"
16+
}
17+
18+
require_cmd jq
19+
trap cleanup EXIT
20+
rm -rf "${WORK_DIR}"
21+
mkdir -p "${WORK_DIR}"
22+
23+
# shellcheck source=/dev/null
24+
source "${ROOT_DIR}/lib/n2k/cloudstack_api.sh"
25+
# shellcheck source=/dev/null
26+
source "${ROOT_DIR}/lib/n2k/target_cloud.sh"
27+
28+
manifest="${WORK_DIR}/manifest.json"
29+
cat > "${manifest}" <<'JSON'
30+
{
31+
"source": {
32+
"vm": {
33+
"cpu": 2,
34+
"memory_mb": 4096,
35+
"firmware": "bios",
36+
"nics": [
37+
{"mac": "52:54:00:12:34:56"}
38+
]
39+
}
40+
},
41+
"target": {
42+
"cloud": {
43+
"cpu_speed": "1000"
44+
}
45+
},
46+
"disks": [
47+
{
48+
"disk_id": "disk0",
49+
"size_bytes": 32212254720,
50+
"controller": {"type": "scsi"}
51+
},
52+
{
53+
"disk_id": "disk1",
54+
"capacity_bytes": 10737418240,
55+
"controller": {"type": "scsi"}
56+
}
57+
]
58+
}
59+
JSON
60+
61+
params="$(n2k_cloud_target_source_deploy_params_json "${manifest}")"
62+
jq -e '
63+
.["details[0].cpuNumber"] == "2"
64+
and .["details[0].cpuSpeed"] == "1000"
65+
and .["details[0].memory"] == "4096"
66+
and .["details[0].rootdisksize"] == "30"
67+
and .["details[0].rootDiskController"] == "scsi"
68+
and .["details[0].dataDiskController"] == "scsi"
69+
and .macaddress == "52:54:00:12:34:56"
70+
' <<<"${params}" >/dev/null || {
71+
echo "[ERR] n2k Cloud deploy params did not include expected rootdisksize/details" >&2
72+
printf '%s\n' "${params}" >&2
73+
exit 1
74+
}
75+
76+
manifest_ceil="${WORK_DIR}/manifest-ceil.json"
77+
jq '.disks[0].size_bytes = 32212254721' "${manifest}" > "${manifest_ceil}"
78+
params_ceil="$(n2k_cloud_target_source_deploy_params_json "${manifest_ceil}")"
79+
jq -e '.["details[0].rootdisksize"] == "31"' <<<"${params_ceil}" >/dev/null || {
80+
echo "[ERR] n2k Cloud deploy rootdisksize was not rounded up to GiB" >&2
81+
printf '%s\n' "${params_ceil}" >&2
82+
exit 1
83+
}
84+
85+
manifest_capacity="${WORK_DIR}/manifest-capacity.json"
86+
jq 'del(.disks[0].size_bytes) | .disks[0].capacity_bytes = 21474836480' "${manifest}" > "${manifest_capacity}"
87+
params_capacity="$(n2k_cloud_target_source_deploy_params_json "${manifest_capacity}")"
88+
jq -e '.["details[0].rootdisksize"] == "20"' <<<"${params_capacity}" >/dev/null || {
89+
echo "[ERR] n2k Cloud deploy rootdisksize did not fall back to capacity_bytes" >&2
90+
printf '%s\n' "${params_capacity}" >&2
91+
exit 1
92+
}
93+
94+
[[ "$(n2k_cloud_api_method deployVirtualMachineForVolume 100)" == "POST" ]] || {
95+
echo "[ERR] deployVirtualMachineForVolume should prefer POST" >&2
96+
exit 1
97+
}
98+
N2K_CLOUD_POST_THRESHOLD=10
99+
[[ "$(n2k_cloud_api_method listApis 100)" == "POST" ]] || {
100+
echo "[ERR] long Cloud API queries should use POST in auto mode" >&2
101+
exit 1
102+
}
103+
unset N2K_CLOUD_POST_THRESHOLD
104+
105+
query="$(n2k_cloud_params_query "$(jq -nc '{"details[0].rootdisksize":"30"}')")"
106+
[[ "${query}" == "details[0].rootdisksize=30" ]] || {
107+
echo "[ERR] Cloud API parameter keys should remain literal while values are encoded" >&2
108+
printf '%s\n' "${query}" >&2
109+
exit 1
110+
}
111+
112+
body_file="${WORK_DIR}/body.json"
113+
header_file="${WORK_DIR}/headers.txt"
114+
cat > "${body_file}" <<'JSON'
115+
{"deployvirtualmachineforvolumeresponse":{"uuidList":[],"errorcode":431,"cserrorcode":4350,"errortext":"This disk offering requires a custom size specified"}}
116+
JSON
117+
cat > "${header_file}" <<'EOF'
118+
HTTP/1.1 431 Request Header Fields Too Large
119+
Content-Type: application/json;charset=utf-8
120+
X-Description: This disk offering requires a custom size specified
121+
EOF
122+
summary="$(n2k_cloud_response_error_summary "${body_file}" "${header_file}")"
123+
[[ "${summary}" == *"errorcode=431"* && "${summary}" == *"cserrorcode=4350"* && "${summary}" == *"errortext=This disk offering requires a custom size specified"* ]] || {
124+
echo "[ERR] n2k Cloud API error summary did not preserve 431 details" >&2
125+
printf '%s\n' "${summary}" >&2
126+
exit 1
127+
}
128+
129+
echo "[OK] n2k Cloud target rootdisksize and API method/error helpers passed"

0 commit comments

Comments
 (0)