diff --git a/binarylane/lib/common.sh b/binarylane/lib/common.sh index f35d95cb..f79c1d70 100644 --- a/binarylane/lib/common.sh +++ b/binarylane/lib/common.sh @@ -139,6 +139,34 @@ get_server_name() { # get_cloud_init_userdata is now defined in shared/common.sh +# Poll the BinaryLane API until the server becomes active and has an IP +# Sets BINARYLANE_SERVER_IP on success +_binarylane_wait_for_active() { + log_warn "Waiting for server to become active..." + local max_attempts=60 + local attempt=1 + while [[ "$attempt" -le "$max_attempts" ]]; do + local status_response + status_response=$(binarylane_api GET "/servers/$BINARYLANE_SERVER_ID") + local status + status=$(echo "$status_response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['server']['status'])") + + if [[ "$status" == "active" ]]; then + BINARYLANE_SERVER_IP=$(echo "$status_response" | python3 -c "import json,sys; networks = json.loads(sys.stdin.read())['server']['networks']['v4']; print([n['ip_address'] for n in networks if n['type'] == 'public'][0])") + export BINARYLANE_SERVER_IP + log_info "Server active: IP=$BINARYLANE_SERVER_IP" + return 0 + fi + + log_warn "Server status: $status ($attempt/$max_attempts)" + sleep "${INSTANCE_STATUS_POLL_DELAY}" + attempt=$((attempt + 1)) + done + + log_error "Server did not become active in time" + return 1 +} + create_server() { local name="$1" local size="${BINARYLANE_SIZE:-std-1vcpu}" @@ -205,30 +233,8 @@ print(json.dumps(body)) return 1 fi - # Wait for server to get an IP - log_warn "Waiting for server to become active..." - local max_attempts=60 - local attempt=1 - while [[ "$attempt" -le "$max_attempts" ]]; do - local status_response - status_response=$(binarylane_api GET "/servers/$BINARYLANE_SERVER_ID") - local status - status=$(echo "$status_response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['server']['status'])") - - if [[ "$status" == "active" ]]; then - BINARYLANE_SERVER_IP=$(echo "$status_response" | python3 -c "import json,sys; networks = json.loads(sys.stdin.read())['server']['networks']['v4']; print([n['ip_address'] for n in networks if n['type'] == 'public'][0])") - export BINARYLANE_SERVER_IP - log_info "Server active: IP=$BINARYLANE_SERVER_IP" - return 0 - fi - - log_warn "Server status: $status ($attempt/$max_attempts)" - sleep "${INSTANCE_STATUS_POLL_DELAY}" - attempt=$((attempt + 1)) - done - - log_error "Server did not become active in time" - return 1 + # Wait for server to become active with IP + _binarylane_wait_for_active } verify_server_connectivity() { diff --git a/cherry/lib/common.sh b/cherry/lib/common.sh index 22870929..df43709f 100755 --- a/cherry/lib/common.sh +++ b/cherry/lib/common.sh @@ -166,6 +166,36 @@ get_server_name() { # Create server # Sets CHERRY_SERVER_ID and CHERRY_SERVER_IP as exports +# Poll the Cherry Servers API until the server has an IP address +# Sets CHERRY_SERVER_IP on success +_cherry_wait_for_ip() { + local server_id="$1" + log_info "Waiting for IP address assignment..." + local ip_address="" + local attempts=0 + local max_attempts=60 + + while [[ -z "$ip_address" ]] && [[ $attempts -lt $max_attempts ]]; do + sleep "${POLL_INTERVAL}" + + local server_info + server_info=$(cherry_api GET "/servers/${server_id}") + + ip_address=$(printf '%s' "$server_info" | _cherry_extract_primary_ip) + + attempts=$((attempts + 1)) + done + + if [[ -z "$ip_address" ]]; then + log_error "Failed to get server IP address" + return 1 + fi + + log_info "Server IP: $ip_address" + CHERRY_SERVER_IP="$ip_address" + export CHERRY_SERVER_IP +} + create_server() { local hostname="$1" local plan="${CHERRY_DEFAULT_PLAN}" @@ -215,30 +245,7 @@ print(json.dumps(data)) export CHERRY_SERVER_ID # Wait for IP assignment - log_info "Waiting for IP address assignment..." - local ip_address="" - local attempts=0 - local max_attempts=60 - - while [[ -z "$ip_address" ]] && [[ $attempts -lt $max_attempts ]]; do - sleep "${POLL_INTERVAL}" - - local server_info - server_info=$(cherry_api GET "/servers/${server_id}") - - ip_address=$(printf '%s' "$server_info" | _cherry_extract_primary_ip) - - attempts=$((attempts + 1)) - done - - if [[ -z "$ip_address" ]]; then - log_error "Failed to get server IP address" - return 1 - fi - - log_info "Server IP: $ip_address" - CHERRY_SERVER_IP="$ip_address" - export CHERRY_SERVER_IP + _cherry_wait_for_ip "$server_id" } # ============================================================ diff --git a/netcup/lib/common.sh b/netcup/lib/common.sh index 5abf27ce..e8fa0f9e 100644 --- a/netcup/lib/common.sh +++ b/netcup/lib/common.sh @@ -310,28 +310,11 @@ _pick_datacenter() { fi } -# Create a Netcup VPS with cloud-init -create_server() { - local name="$1" - - # Interactive selections - local datacenter - datacenter=$(_pick_datacenter) - - local product - product=$(_pick_vps_product) - - local image="ubuntu-24.04" - - log_warn "Creating Netcup VPS '$name' (product: $product, datacenter: $datacenter)..." - - # Get cloud-init userdata - local userdata - userdata=$(get_cloud_init_userdata) - - # Build request param - local param - param=$(echo "$userdata" | python3 -c " +# Build JSON request body for Netcup VPS creation +# Reads cloud-init userdata from stdin +# Usage: get_cloud_init_userdata | _netcup_build_create_body NAME PRODUCT DATACENTER IMAGE +_netcup_build_create_body() { + python3 -c " import json, sys userdata = sys.stdin.read() name, product, datacenter, image = sys.argv[1:5] @@ -344,37 +327,12 @@ param = { 'userdata': userdata } print(json.dumps(param)) -" "$name" "$product" "$datacenter" "$image") +" "$@" +} - local response - response=$(netcup_api "createVServer" "$param") - - # Check for errors - local status - status=$(echo "$response" | python3 -c "import json, sys; print(json.loads(sys.stdin.read()).get('status', 'error'))") - - if [[ "$status" != "success" ]]; then - log_error "Failed to create Netcup VPS" - local error_msg - error_msg=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('longmessage','Unknown error'))" 2>/dev/null || echo "$response") - log_error "API Error: $error_msg" - log_error "" - log_error "Common issues:" - log_error " - Insufficient account balance" - log_error " - Product not available in selected datacenter" - log_error " - Account limits reached" - log_error "" - log_error "Check your account: https://ccp.netcup.net/" - return 1 - fi - - # Extract server ID - NETCUP_SERVER_ID=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['responsedata']['vserverid'])") - export NETCUP_SERVER_ID - - log_info "VPS created: ID=$NETCUP_SERVER_ID" - - # Wait for IP assignment (Netcup assigns IPs asynchronously) +# Poll the Netcup API until the VPS has an IPv4 address +# Sets NETCUP_SERVER_IP on success +_netcup_wait_for_ip() { log_info "Waiting for IP assignment..." local ip="" local attempts=0 @@ -401,10 +359,59 @@ except: NETCUP_SERVER_IP="$ip" export NETCUP_SERVER_IP - log_info "Server IP: $NETCUP_SERVER_IP" } +# Create a Netcup VPS with cloud-init +create_server() { + local name="$1" + + # Interactive selections + local datacenter + datacenter=$(_pick_datacenter) + + local product + product=$(_pick_vps_product) + + local image="ubuntu-24.04" + + log_warn "Creating Netcup VPS '$name' (product: $product, datacenter: $datacenter)..." + + # Get cloud-init userdata and build request body + local param + param=$(get_cloud_init_userdata | _netcup_build_create_body "$name" "$product" "$datacenter" "$image") + + local response + response=$(netcup_api "createVServer" "$param") + + # Check for errors + local status + status=$(echo "$response" | python3 -c "import json, sys; print(json.loads(sys.stdin.read()).get('status', 'error'))") + + if [[ "$status" != "success" ]]; then + log_error "Failed to create Netcup VPS" + local error_msg + error_msg=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('longmessage','Unknown error'))" 2>/dev/null || echo "$response") + log_error "API Error: $error_msg" + log_error "" + log_error "Common issues:" + log_error " - Insufficient account balance" + log_error " - Product not available in selected datacenter" + log_error " - Account limits reached" + log_error "" + log_error "Check your account: https://ccp.netcup.net/" + return 1 + fi + + # Extract server ID + NETCUP_SERVER_ID=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['responsedata']['vserverid'])") + export NETCUP_SERVER_ID + log_info "VPS created: ID=$NETCUP_SERVER_ID" + + # Wait for IP assignment + _netcup_wait_for_ip +} + # Wait for SSH connectivity verify_server_connectivity() { local ip="$1" diff --git a/ramnode/lib/common.sh b/ramnode/lib/common.sh index 7b79d1d1..c278b801 100755 --- a/ramnode/lib/common.sh +++ b/ramnode/lib/common.sh @@ -348,6 +348,62 @@ if networks: " } +# Build JSON request body for RamNode server creation +# Usage: _ramnode_build_server_body NAME FLAVOR IMAGE_ID KEY_NAME USERDATA [NETWORK_ID] +_ramnode_build_server_body() { + python3 -c " +import json, sys +name, flavor, image_id, key_name, userdata, network_id = sys.argv[1:7] +body = { + 'server': { + 'name': name, + 'flavorRef': flavor, + 'imageRef': image_id, + 'key_name': key_name, + 'user_data': userdata + } +} +if network_id: + body['server']['networks'] = [{'uuid': network_id}] +print(json.dumps(body)) +" "$@" +} + +# Poll the RamNode API until the server has an IPv4 address +# Sets RAMNODE_SERVER_IP on success +_ramnode_wait_for_ip() { + log_info "Waiting for IP address..." + local max_attempts=30 + local attempt=0 + while [[ $attempt -lt $max_attempts ]]; do + sleep 2 + local server_info + server_info=$(ramnode_compute_api GET "/servers/$RAMNODE_SERVER_ID") + + RAMNODE_SERVER_IP=$(echo "$server_info" | python3 -c " +import json, sys +data = json.loads(sys.stdin.read()) +addresses = data.get('server', {}).get('addresses', {}) +for net_name, addrs in addresses.items(): + for addr in addrs: + if addr.get('version') == 4: + print(addr['addr']) + sys.exit(0) +" 2>/dev/null || echo "") + + if [[ -n "$RAMNODE_SERVER_IP" ]]; then + export RAMNODE_SERVER_IP + log_info "IP address assigned: $RAMNODE_SERVER_IP" + return 0 + fi + + attempt=$((attempt + 1)) + done + + log_error "Timeout waiting for IP address" + return 1 +} + # Create a RamNode server create_server() { local name="$1" @@ -379,24 +435,8 @@ create_server() { log_warn "Creating RamNode instance '$name' (flavor: $flavor)..." - # Build request body local body - body=$(python3 -c " -import json, sys -name, flavor, image_id, key_name, userdata, network_id = sys.argv[1:7] -body = { - 'server': { - 'name': name, - 'flavorRef': flavor, - 'imageRef': image_id, - 'key_name': key_name, - 'user_data': userdata - } -} -if network_id: - body['server']['networks'] = [{'uuid': network_id}] -print(json.dumps(body)) -" "$name" "$flavor" "$image_id" "$key_name" "$userdata" "${network_id:-}") + body=$(_ramnode_build_server_body "$name" "$flavor" "$image_id" "$key_name" "$userdata" "${network_id:-}") local response response=$(ramnode_compute_api POST "/servers" "$body") @@ -417,40 +457,10 @@ print(json.dumps(body)) # Extract server ID RAMNODE_SERVER_ID=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['server']['id'])") export RAMNODE_SERVER_ID - log_info "Server created: ID=$RAMNODE_SERVER_ID" - # Wait for server to get IP address - log_info "Waiting for IP address..." - local max_attempts=30 - local attempt=0 - while [[ $attempt -lt $max_attempts ]]; do - sleep 2 - local server_info - server_info=$(ramnode_compute_api GET "/servers/$RAMNODE_SERVER_ID") - - RAMNODE_SERVER_IP=$(echo "$server_info" | python3 -c " -import json, sys -data = json.loads(sys.stdin.read()) -addresses = data.get('server', {}).get('addresses', {}) -for net_name, addrs in addresses.items(): - for addr in addrs: - if addr.get('version') == 4: - print(addr['addr']) - sys.exit(0) -" 2>/dev/null || echo "") - - if [[ -n "$RAMNODE_SERVER_IP" ]]; then - export RAMNODE_SERVER_IP - log_info "IP address assigned: $RAMNODE_SERVER_IP" - return 0 - fi - - attempt=$((attempt + 1)) - done - - log_error "Timeout waiting for IP address" - return 1 + # Wait for IP assignment + _ramnode_wait_for_ip } # Wait for SSH connectivity