gce-provider.sh 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. # |source| this file
  2. #
  3. # Utilities for working with GCE instances
  4. #
  5. # Default zone
  6. cloud_DefaultZone() {
  7. echo "us-west1-b"
  8. }
  9. cloud_DefaultCustomMemoryGB() {
  10. echo 64
  11. }
  12. #
  13. # cloud_RestartPreemptedInstances [namePrefix]
  14. #
  15. # Restart any preempted instances matching the specified prefix
  16. #
  17. # namePrefix - The instance name prefix of the preempted instances
  18. #
  19. cloud_RestartPreemptedInstances() {
  20. declare filter="$1"
  21. declare name status zone
  22. while read -r name status zone; do
  23. echo "Starting $status instance: $name"
  24. (
  25. set -x
  26. gcloud compute instances start --zone "$zone" "$name"
  27. )
  28. done < <(gcloud compute instances list \
  29. --filter "$filter" \
  30. --format 'value(name,status,zone)' \
  31. | grep TERMINATED)
  32. }
  33. #
  34. # __cloud_FindInstances
  35. #
  36. # Find instances matching the specified pattern.
  37. #
  38. # For each matching instance, an entry in the `instances` array will be added with the
  39. # following information about the instance:
  40. # "name:zone:public IP:private IP"
  41. #
  42. # filter - The instances to filter on
  43. #
  44. # examples:
  45. # $ __cloud_FindInstances "name=exact-machine-name"
  46. # $ __cloud_FindInstances "name~^all-machines-with-a-common-machine-prefix"
  47. #
  48. __cloud_FindInstances() {
  49. declare filter="$1"
  50. instances=()
  51. declare name zone publicIp privateIp status
  52. while read -r name publicIp privateIp status zone; do
  53. printf "%-30s | publicIp=%-16s privateIp=%s status=%s zone=%s\n" "$name" "$publicIp" "$privateIp" "$status" "$zone"
  54. instances+=("$name:$publicIp:$privateIp:$zone")
  55. done < <(gcloud compute instances list \
  56. --filter "$filter" \
  57. --format 'value(name,networkInterfaces[0].accessConfigs[0].natIP,networkInterfaces[0].networkIP,status,zone)' \
  58. | grep RUNNING)
  59. while read -r name status zone; do
  60. privateIp=TERMINATED
  61. publicIp=TERMINATED
  62. printf "%-30s | publicIp=%-16s privateIp=%s status=%s zone=%s\n" "$name" "$publicIp" "$privateIp" "$status" "$zone"
  63. instances+=("$name:$publicIp:$privateIp:$zone")
  64. done < <(gcloud compute instances list \
  65. --filter "$filter" \
  66. --format 'value(name,status,zone)' \
  67. | grep TERMINATED)
  68. }
  69. #
  70. # cloud_FindInstances [namePrefix]
  71. #
  72. # Find instances with names matching the specified prefix
  73. #
  74. # For each matching instance, an entry in the `instances` array will be added with the
  75. # following information about the instance:
  76. # "name:public IP:private IP"
  77. #
  78. # namePrefix - The instance name prefix to look for
  79. #
  80. # examples:
  81. # $ cloud_FindInstances all-machines-with-a-common-machine-prefix
  82. #
  83. cloud_FindInstances() {
  84. declare namePrefix="$1"
  85. __cloud_FindInstances "name~^$namePrefix"
  86. }
  87. #
  88. # cloud_FindInstance [name]
  89. #
  90. # Find an instance with a name matching the exact pattern.
  91. #
  92. # For each matching instance, an entry in the `instances` array will be added with the
  93. # following information about the instance:
  94. # "name:public IP:private IP"
  95. #
  96. # name - The instance name to look for
  97. #
  98. # examples:
  99. # $ cloud_FindInstance exact-machine-name
  100. #
  101. cloud_FindInstance() {
  102. declare name="$1"
  103. __cloud_FindInstances "name=$name"
  104. }
  105. #
  106. # cloud_Initialize [networkName]
  107. #
  108. # Perform one-time initialization that may be required for the given testnet.
  109. #
  110. # networkName - unique name of this testnet
  111. #
  112. # This function will be called before |cloud_CreateInstances|
  113. cloud_Initialize() {
  114. declare networkName="$1"
  115. # ec2-provider.sh creates firewall rules programmatically, should do the same
  116. # here.
  117. echo "Note: one day create $networkName firewall rules programmatically instead of assuming the 'testnet' tag exists"
  118. }
  119. #
  120. # cloud_CreateInstances [networkName] [namePrefix] [numNodes]
  121. # [machineType] [zone]
  122. # [bootDiskSize] [startupScript] [address]
  123. # [bootDiskType] [additionalDiskSize] [preemptible]
  124. #
  125. # Creates one more identical instances.
  126. #
  127. # networkName - unique name of this testnet
  128. # namePrefix - unique string to prefix all the instance names with
  129. # numNodes - number of instances to create
  130. # machineType - GCE machine type. Note that this may also include an
  131. # `--accelerator=` or other |gcloud compute instances create|
  132. # options
  133. # zone - cloud zone
  134. # bootDiskSize - Optional size of the boot disk in GB
  135. # startupScript - Optional startup script to execute when the instance boots
  136. # address - Optional name of the GCE static IP address to attach to the
  137. # instance. Requires that |numNodes| = 1 and that addressName
  138. # has been provisioned in the GCE region that is hosting `$zone`
  139. # bootDiskType - Optional specify SSD or HDD boot disk
  140. # additionalDiskSize - Optional specify size of additional storage volume
  141. # preemptible - Optionally request a preemptible instance ("true")
  142. #
  143. # Tip: use cloud_FindInstances to locate the instances once this function
  144. # returns
  145. cloud_CreateInstances() {
  146. declare networkName="$1"
  147. declare namePrefix="$2"
  148. declare numNodes="$3"
  149. declare machineType="$4"
  150. declare zone="$5"
  151. declare optionalBootDiskSize="$6"
  152. declare optionalStartupScript="$7"
  153. declare optionalAddress="$8"
  154. declare optionalBootDiskType="${9:-pd-ssd}"
  155. declare optionalAdditionalDiskSize="${10}"
  156. declare optionalPreemptible="${11}"
  157. imageName="ubuntu-2404-noble-amd64-v20250709 --image-project ubuntu-os-cloud"
  158. declare -a nodes
  159. if [[ $numNodes = 1 ]]; then
  160. nodes=("$namePrefix")
  161. else
  162. for node in $(seq -f "${namePrefix}%0${#numNodes}g" 1 "$numNodes"); do
  163. nodes+=("$node")
  164. done
  165. fi
  166. declare -a args
  167. args=(
  168. --zone "$zone"
  169. --tags testnet
  170. --metadata "testnet=$networkName"
  171. --maintenance-policy TERMINATE
  172. --restart-on-failure
  173. --scopes compute-rw
  174. )
  175. # shellcheck disable=SC2206 # Do not want to quote $imageName as it may contain extra args
  176. args+=(--image $imageName)
  177. if [[ $optionalPreemptible = true ]]; then
  178. args+=(--preemptible)
  179. fi
  180. # shellcheck disable=SC2206 # Do not want to quote $machineType as it may contain extra args
  181. for word in $machineType; do
  182. # Special handling for the "--min-cpu-platform" argument which may contain a
  183. # space (escaped as '%20')...
  184. args+=("${word//%20/ }")
  185. done
  186. if [[ -n $optionalBootDiskSize ]]; then
  187. args+=(
  188. --boot-disk-size "${optionalBootDiskSize}GB"
  189. )
  190. fi
  191. if [[ -n $optionalStartupScript ]]; then
  192. args+=(
  193. --metadata-from-file "startup-script=$optionalStartupScript"
  194. )
  195. fi
  196. if [[ -n $optionalBootDiskType ]]; then
  197. args+=(
  198. --boot-disk-type "${optionalBootDiskType}"
  199. )
  200. fi
  201. if [[ -n $optionalAddress ]]; then
  202. [[ $numNodes = 1 ]] || {
  203. echo "Error: address may not be supplied when provisioning multiple nodes: $optionalAddress"
  204. exit 1
  205. }
  206. args+=(
  207. --address "$optionalAddress"
  208. )
  209. fi
  210. (
  211. set -x
  212. gcloud beta compute instances create "${nodes[@]}" "${args[@]}"
  213. )
  214. if [[ -n $optionalAdditionalDiskSize ]]; then
  215. if [[ $numNodes = 1 ]]; then
  216. (
  217. set -x
  218. cloud_CreateAndAttachPersistentDisk "${namePrefix}" "$optionalAdditionalDiskSize" "pd-ssd" "$zone"
  219. )
  220. else
  221. for node in $(seq -f "${namePrefix}%0${#numNodes}g" 1 "$numNodes"); do
  222. (
  223. set -x
  224. cloud_CreateAndAttachPersistentDisk "${node}" "$optionalAdditionalDiskSize" "pd-ssd" "$zone"
  225. )
  226. done
  227. fi
  228. fi
  229. }
  230. #
  231. # cloud_DeleteInstances
  232. #
  233. # Deletes all the instances listed in the `instances` array
  234. #
  235. cloud_DeleteInstances() {
  236. if [[ ${#instances[0]} -eq 0 ]]; then
  237. echo No instances to delete
  238. return
  239. fi
  240. declare names=("${instances[@]/:*/}")
  241. declare zones=("${instances[@]/*:/}")
  242. declare unique_zones=()
  243. read -r -a unique_zones <<< "$(echo "${zones[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')"
  244. for zone in "${unique_zones[@]}"; do
  245. set -x
  246. # Try deleting instances in all zones
  247. gcloud beta compute instances delete --zone "$zone" --quiet "${names[@]}" || true
  248. done
  249. }
  250. #
  251. # cloud_WaitForInstanceReady [instanceName] [instanceIp] [instanceZone] [timeout]
  252. #
  253. # Return once the newly created VM instance is responding. This function is cloud-provider specific.
  254. #
  255. cloud_WaitForInstanceReady() {
  256. declare instanceName="$1"
  257. declare instanceIp="$2"
  258. # declare instanceZone="$3"
  259. declare timeout="$4"
  260. if [[ $instanceIp = "TERMINATED" ]]; then
  261. return 1
  262. fi
  263. timeout "${timeout}"s bash -c "set -o pipefail; until ping -c 3 $instanceIp | tr - _; do echo .; done"
  264. }
  265. #
  266. # cloud_FetchFile [instanceName] [publicIp] [remoteFile] [localFile]
  267. #
  268. # Fetch a file from the given instance. This function uses a cloud-specific
  269. # mechanism to fetch the file
  270. #
  271. cloud_FetchFile() {
  272. declare instanceName="$1"
  273. # shellcheck disable=SC2034 # publicIp is unused
  274. declare publicIp="$2"
  275. declare remoteFile="$3"
  276. declare localFile="$4"
  277. declare zone="$5"
  278. if [[ $publicIp = "TERMINATED" ]]; then
  279. return 1
  280. fi
  281. (
  282. set -x
  283. gcloud compute scp --zone "$zone" "$instanceName:$remoteFile" "$localFile"
  284. )
  285. }
  286. #
  287. # cloud_CreateAndAttachPersistentDisk [instanceName] [diskSize] [diskType]
  288. #
  289. # Create a persistent disk and attach it to a pre-existing VM instance.
  290. # Set disk to auto-delete upon instance deletion
  291. #
  292. cloud_CreateAndAttachPersistentDisk() {
  293. declare instanceName="$1"
  294. declare diskSize="$2"
  295. declare diskType="$3"
  296. declare zone="$4"
  297. diskName="${instanceName}-pd"
  298. gcloud beta compute disks create "$diskName" \
  299. --size "$diskSize" \
  300. --type "$diskType" \
  301. --zone "$zone"
  302. gcloud compute instances attach-disk "$instanceName" \
  303. --disk "$diskName" \
  304. --zone "$zone"
  305. gcloud compute instances set-disk-auto-delete "$instanceName" \
  306. --disk "$diskName" \
  307. --zone "$zone" \
  308. --auto-delete
  309. }
  310. #
  311. # cloud_StatusAll
  312. #
  313. # Not yet implemented for this cloud provider
  314. cloud_StatusAll() {
  315. echo "ERROR: cloud_StatusAll is not yet implemented for GCE"
  316. }