291 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env bash
 | |
| 
 | |
| # |source| this file
 | |
| #
 | |
| # Utilities for working with Colo instances
 | |
| #
 | |
| 
 | |
| # COLO_PARALLELIZE is not ready for use, disable it
 | |
| declare COLO_PARALLELIZE=false
 | |
| 
 | |
| __cloud_colo_here="$(dirname "${BASH_SOURCE[0]}")"
 | |
| # shellcheck source=net/scripts/colo-utils.sh
 | |
| source "${__cloud_colo_here}/colo-utils.sh"
 | |
| 
 | |
| # Default zone
 | |
| cloud_DefaultZone() {
 | |
|   echo "Denver"
 | |
| }
 | |
| 
 | |
| cloud_RestartPreemptedInstances() {
 | |
|   : # Not implemented
 | |
| }
 | |
| 
 | |
| #
 | |
| # __cloud_FindInstances
 | |
| #
 | |
| # Find instances matching the specified pattern.
 | |
| #
 | |
| # For each matching instance, an entry in the `instances` array will be added with the
 | |
| # following information about the instance:
 | |
| #   "name:zone:public IP:private IP"
 | |
| #
 | |
| # filter   - The instances to filter on
 | |
| #
 | |
| # examples:
 | |
| #   $ __cloud_FindInstances "name=exact-machine-name"
 | |
| #   $ __cloud_FindInstances "name~^all-machines-with-a-common-machine-prefix"
 | |
| #
 | |
| __cloud_FindInstances() {
 | |
|   declare HOST_NAME IP PRIV_IP STATUS ZONE LOCK_USER INSTNAME INSTANCES_TEXT
 | |
|   declare filter=${1}
 | |
|   declare onlyPreemptible=${2}
 | |
|   instances=()
 | |
| 
 | |
|   if ! ${COLO_PARALLELIZE}; then
 | |
|     colo_load_resources
 | |
|     colo_load_availability false
 | |
|   fi
 | |
|   INSTANCES_TEXT="$(
 | |
|     for AVAIL in "${COLO_RES_AVAILABILITY[@]}"; do
 | |
|       IFS=$'\x1f' read -r HOST_NAME IP PRIV_IP STATUS ZONE LOCK_USER INSTNAME PREEMPTIBLE <<<"${AVAIL}"
 | |
|       if [[ ${INSTNAME} =~ ${filter} ]]; then
 | |
|         if [[ -n $onlyPreemptible && $PREEMPTIBLE == "false" ]]; then
 | |
|           continue
 | |
|         else
 | |
|           printf "%-40s | publicIp=%-16s privateIp=%s zone=%s preemptible=%s\n" "${INSTNAME}" "${IP}" "${PRIV_IP}" "${ZONE}" "${PREEMPTIBLE}" 1>&2
 | |
|           echo -e "${INSTNAME}:${IP}:${PRIV_IP}:${ZONE}"
 | |
|         fi
 | |
|       fi
 | |
|     done | sort -t $'\x1f' -k1
 | |
|   )"
 | |
|   if [[ -n "${INSTANCES_TEXT}" ]]; then
 | |
|     while read -r LINE; do
 | |
|       instances+=( "${LINE}" )
 | |
|     done <<<"${INSTANCES_TEXT}"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| #
 | |
| # cloud_FindInstances [namePrefix]
 | |
| #
 | |
| # Find instances with names matching the specified prefix
 | |
| #
 | |
| # For each matching instance, an entry in the `instances` array will be added with the
 | |
| # following information about the instance:
 | |
| #   "name:public IP:private IP"
 | |
| #
 | |
| # namePrefix - The instance name prefix to look for
 | |
| #
 | |
| # examples:
 | |
| #   $ cloud_FindInstances all-machines-with-a-common-machine-prefix
 | |
| #
 | |
| cloud_FindInstances() {
 | |
|   declare filter="^${1}.*"
 | |
|   declare onlyPreemptible="${2}"
 | |
|   __cloud_FindInstances "${filter}" "${onlyPreemptible}"
 | |
| }
 | |
| 
 | |
| #
 | |
| # cloud_FindInstance [name]
 | |
| #
 | |
| # Find an instance with a name matching the exact pattern.
 | |
| #
 | |
| # For each matching instance, an entry in the `instances` array will be added with the
 | |
| # following information about the instance:
 | |
| #   "name:public IP:private IP"
 | |
| #
 | |
| # name - The instance name to look for
 | |
| #
 | |
| # examples:
 | |
| #   $ cloud_FindInstance exact-machine-name
 | |
| #
 | |
| cloud_FindInstance() {
 | |
|   declare name="^${1}$"
 | |
|   declare onlyPreemptible="${2}"
 | |
|   __cloud_FindInstances "${name}" "${onlyPreemptible}"
 | |
| }
 | |
| 
 | |
| #
 | |
| # cloud_Initialize [networkName]
 | |
| #
 | |
| # Perform one-time initialization that may be required for the given testnet.
 | |
| #
 | |
| # networkName   - unique name of this testnet
 | |
| #
 | |
| # This function will be called before |cloud_CreateInstances|
 | |
| cloud_Initialize() {
 | |
|   # networkName=${1} # unused
 | |
|   # zone=${2} #unused
 | |
|   colo_load_resources
 | |
|   if ${COLO_PARALLELIZE}; then
 | |
|     colo_load_availability
 | |
|   fi
 | |
| }
 | |
| 
 | |
| #
 | |
| # cloud_CreateInstances [networkName] [namePrefix] [numNodes] [imageName]
 | |
| #                       [machineType] [bootDiskSize] [enableGpu]
 | |
| #                       [startupScript] [address]
 | |
| #
 | |
| # Creates one more identical instances.
 | |
| #
 | |
| # networkName   - unique name of this testnet
 | |
| # namePrefix    - unique string to prefix all the instance names with
 | |
| # numNodes      - number of instances to create
 | |
| # imageName     - Disk image for the instances
 | |
| # machineType   - GCE machine type.  Note that this may also include an
 | |
| #                 `--accelerator=` or other |gcloud compute instances create|
 | |
| #                 options
 | |
| # bootDiskSize  - Optional size of the boot disk in GB
 | |
| # enableGpu     - Optionally enable GPU, use the value "true" to enable
 | |
| #                 eg, request 4 K80 GPUs with "count=4,type=nvidia-tesla-k80"
 | |
| # startupScript - Optional startup script to execute when the instance boots
 | |
| # address       - Optional name of the GCE static IP address to attach to the
 | |
| #                 instance.  Requires that |numNodes| = 1 and that addressName
 | |
| #                 has been provisioned in the GCE region that is hosting `${zone}`
 | |
| # bootDiskType  - Optional specify SSD or HDD boot disk
 | |
| # additionalDiskSize - Optional specify size of additional storage volume
 | |
| # preemptible - Optionally request a preemptible instance ("true")
 | |
| #
 | |
| # Tip: use cloud_FindInstances to locate the instances once this function
 | |
| #      returns
 | |
| cloud_CreateInstances() {
 | |
|   #declare networkName="${1}" # unused
 | |
|   declare namePrefix="${2}"
 | |
|   declare numNodes="${3}"
 | |
|   #declare enableGpu="${4}" # unused
 | |
|   declare machineType="${5}"
 | |
|   # declare zone="${6}" # unused
 | |
|   #declare optionalBootDiskSize="${7}" # unused
 | |
|   #declare optionalStartupScript="${8}" # unused
 | |
|   #declare optionalAddress="${9}" # unused
 | |
|   #declare optionalBootDiskType="${10}" # unused
 | |
|   #declare optionalAdditionalDiskSize="${11}" # unused
 | |
|   declare optionalPreemptible="${12}"
 | |
|   declare sshPrivateKey="${13}"
 | |
| 
 | |
|   declare -a nodes
 | |
|   if [[ ${numNodes} = 1 ]]; then
 | |
|     nodes=("${namePrefix}")
 | |
|   else
 | |
|     for node in $(seq -f "${namePrefix}%0${#numNodes}g" 1 "${numNodes}"); do
 | |
|       nodes+=("${node}")
 | |
|     done
 | |
|   fi
 | |
| 
 | |
|   if ${COLO_PARALLELIZE}; then
 | |
|     declare HOST_NAME IP PRIV_IP STATUS ZONE LOCK_USER INSTNAME INDEX RES LINE
 | |
|     declare -a AVAILABLE
 | |
|     declare AVAILABLE_TEXT
 | |
|     AVAILABLE_TEXT="$(
 | |
|       for RES in "${COLO_RES_AVAILABILITY[@]}"; do
 | |
|         IFS=$'\x1f' read -r HOST_NAME IP PRIV_IP STATUS ZONE LOCK_USER INSTNAME <<<"${RES}"
 | |
|         if [[ "FREE" = "${STATUS}" ]]; then
 | |
|           INDEX=$(colo_res_index_from_ip "${IP}")
 | |
|           RES_MACH="${COLO_RES_MACHINE[${INDEX}]}"
 | |
|           if colo_machine_types_compatible "${RES_MACH}" "${machineType}"; then
 | |
|             if ! colo_node_is_requisitioned "${INDEX}" "${COLO_RES_REQUISITIONED[*]}"; then
 | |
|               echo -e "${RES_MACH}\x1f${IP}"
 | |
|             fi
 | |
|           fi
 | |
|         fi
 | |
|       done | sort -nt $'\x1f' -k1,1
 | |
|     )"
 | |
| 
 | |
|     if [[ -n "${AVAILABLE_TEXT}" ]]; then
 | |
|       while read -r LINE; do
 | |
|         AVAILABLE+=("${LINE}")
 | |
|       done <<<"${AVAILABLE_TEXT}"
 | |
|     fi
 | |
| 
 | |
|     if [[ ${#AVAILABLE[@]} -lt ${numNodes} ]]; then
 | |
|       echo "Insufficient resources available to allocate ${numNodes} ${namePrefix}" 1>&2
 | |
|       exit 1
 | |
|     fi
 | |
| 
 | |
|     declare node
 | |
|     declare AI=0
 | |
|     for node in "${nodes[@]}"; do
 | |
|       IFS=$'\x1f' read -r _ IP <<<"${AVAILABLE[${AI}]}"
 | |
|       colo_node_requisition "${IP}" "${node}" >/dev/null
 | |
|       AI=$((AI+1))
 | |
|     done
 | |
|   else
 | |
|     declare RES_MACH node
 | |
|     declare RI=0
 | |
|     declare NI=0
 | |
|     while [[ ${NI} -lt ${numNodes} && ${RI} -lt ${COLO_RES_N} ]]; do
 | |
|       node="${nodes[${NI}]}"
 | |
|       RES_MACH="${COLO_RES_MACHINE[${RI}]}"
 | |
|       IP="${COLO_RES_IP[${RI}]}"
 | |
|       if colo_machine_types_compatible "${RES_MACH}" "${machineType}"; then
 | |
|         if colo_node_requisition "${IP}" "${node}" "${sshPrivateKey}" "${optionalPreemptible}" >/dev/null; then
 | |
|           NI=$((NI+1))
 | |
|         fi
 | |
|       fi
 | |
|       RI=$((RI+1))
 | |
|     done
 | |
|   fi
 | |
| }
 | |
| 
 | |
| #
 | |
| # cloud_DeleteInstances
 | |
| #
 | |
| # Deletes all the instances listed in the `instances` array
 | |
| #
 | |
| cloud_DeleteInstances() {
 | |
|   declare forceDelete="${1}"
 | |
|   declare _ IP _ _
 | |
|   for instance in "${instances[@]}"; do
 | |
|     IFS=':' read -r _ IP _ _ <<< "${instance}"
 | |
|     colo_node_free "${IP}" "${forceDelete}" >/dev/null
 | |
|   done
 | |
| }
 | |
| 
 | |
| #
 | |
| # cloud_WaitForInstanceReady [instanceName] [instanceIp] [instanceZone] [timeout]
 | |
| #
 | |
| # Return once the newly created VM instance is responding.  This function is cloud-provider specific.
 | |
| #
 | |
| cloud_WaitForInstanceReady() {
 | |
|   #declare instanceName="${1}" # unused
 | |
|   #declare instanceIp="${2}" # unused
 | |
|   #declare timeout="${4}" # unused
 | |
| 
 | |
|   true
 | |
| }
 | |
| 
 | |
| #
 | |
| # cloud_FetchFile [instanceName] [publicIp] [remoteFile] [localFile]
 | |
| #
 | |
| # Fetch a file from the given instance.  This function uses a cloud-specific
 | |
| # mechanism to fetch the file
 | |
| #
 | |
| cloud_FetchFile() {
 | |
|   #declare instanceName="${1}" # unused
 | |
|   declare publicIp="${2}"
 | |
|   declare remoteFile="${3}"
 | |
|   declare localFile="${4}"
 | |
|   #declare zone="${5}" # unused
 | |
|   scp \
 | |
|     -o "StrictHostKeyChecking=no" \
 | |
|     -o "UserKnownHostsFile=/dev/null" \
 | |
|     -o "User=solana" \
 | |
|     -o "LogLevel=ERROR" \
 | |
|     -F /dev/null \
 | |
|     "solana@${publicIp}:${remoteFile}" "${localFile}"
 | |
| }
 | |
| 
 | |
| cloud_StatusAll() {
 | |
|   declare HOST_NAME IP PRIV_IP STATUS ZONE LOCK_USER INSTNAME PREEMPTIBLE
 | |
|   if ! ${COLO_PARALLELIZE}; then
 | |
|     colo_load_resources
 | |
|     colo_load_availability false
 | |
|   fi
 | |
|   for AVAIL in "${COLO_RES_AVAILABILITY[@]}"; do
 | |
|     IFS=$'\x1f' read -r HOST_NAME IP PRIV_IP STATUS ZONE LOCK_USER INSTNAME PREEMPTIBLE <<<"${AVAIL}"
 | |
|     printf "%-30s | publicIp=%-16s privateIp=%s status=%s who=%s zone=%s inst=%s preemptible=%s\n" "${HOST_NAME}" "${IP}" "${PRIV_IP}" "${STATUS}" "${LOCK_USER}" "${ZONE}" "${INSTNAME}" "${PREEMPTIBLE}"
 | |
|   done
 | |
| }
 |