Skip to content

Commit cdea2f1

Browse files
committed
.github: add build and unit test workflow
Adds a Github Actions workflow to build and run unit tests for the mshv crate. It calls teh infra setup workflow to provision then logs in via SSH and runs the tests. It ensures tests run in a consistent VM setup. Signed-off-by: Aastha Rawat <[email protected]>
1 parent 06cbeb6 commit cdea2f1

File tree

2 files changed

+234
-93
lines changed

2 files changed

+234
-93
lines changed

.github/workflows/mshv-infra.yaml

Lines changed: 137 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,58 @@ name: MSHV Infra Setup
22
on:
33
workflow_call:
44
inputs:
5+
ARCH:
6+
description: 'Architecture for the VM'
7+
required: true
8+
type: string
9+
KEY:
10+
description: 'SSH Key Name'
11+
required: true
12+
type: string
13+
OS_DISK_SIZE:
14+
description: 'OS Disk Size in GB'
15+
required: true
16+
type: string
517
RG:
618
description: 'Resource Group Name'
719
required: true
820
type: string
9-
SKU:
21+
VM_SKU:
1022
description: 'VM SKU'
1123
required: true
1224
type: string
1325
secrets:
14-
MSHV_MI_CLIENT_ID:
15-
required: true
16-
MSHV_STORAGE_ACCOUNT_PATHS:
26+
MI_CLIENT_ID:
1727
required: true
18-
MSHV_USERNAME:
28+
RUNNER_RG:
1929
required: true
20-
MSHV_PASSWORD:
30+
RUNNER:
2131
required: true
22-
MSHV_X86_SOURCE_PATH:
32+
STORAGE_ACCOUNT_PATHS:
2333
required: true
24-
MSHV_RUNNER_RG:
34+
X86_SOURCE_PATH:
2535
required: true
26-
MSHV_RUNNER:
36+
USERNAME:
2737
required: true
38+
outputs:
39+
PRIVATE_IP:
40+
description: 'Private IP of the VM'
41+
value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }}
2842
concurrency:
2943
group: ${{ github.workflow }}-${{ github.ref }}
3044
cancel-in-progress: true
3145
jobs:
32-
infrasetup:
33-
name: MSHV Infra Setup
46+
infra-setup:
47+
name: ${{ inputs.ARCH }} VM Provision
3448
runs-on:
3549
- self-hosted
3650
- Linux
51+
outputs:
52+
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
3753
steps:
3854
- name: Install & login to AZ CLI
3955
env:
40-
MI_CLIENT_ID: ${{ secrets.MSHV_MI_CLIENT_ID }}
56+
MI_CLIENT_ID: ${{ secrets.MI_CLIENT_ID }}
4157
run: |
4258
set -e
4359
echo "Installing Azure CLI if not already installed"
@@ -50,140 +66,168 @@ jobs:
5066
echo "Logging into Azure CLI using Managed Identity"
5167
az login --identity --client-id ${MI_CLIENT_ID}
5268
69+
- name: Get Location
70+
id: get-location
71+
env:
72+
SKU: ${{ inputs.VM_SKU }}
73+
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
74+
run: |
75+
set -e
76+
# Extract vCPU count from SKU (e.g., "Standard_D2s_v3" => 2)
77+
vcpu=$(echo "$SKU" | sed -n 's/^Standard_[A-Za-z]\+\([0-9]\+\).*/\1/p')
78+
if [[ -z "$vcpu" ]]; then
79+
echo "Cannot extract vCPU count from SKU: $SKU"
80+
exit 1
81+
fi
82+
83+
SUPPORTED_LOCATIONS=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[] | .key')
84+
85+
for location in $SUPPORTED_LOCATIONS; do
86+
family=$(az vm list-skus --size "$SKU" --location "$location" --resource-type "virtualMachines" --query '[0].family' -o tsv)
87+
if [[ -z "$family" ]]; then
88+
echo "Cannot determine VM family for SKU: $SKU in $location"
89+
continue
90+
fi
91+
92+
usage=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json)
93+
current=$(echo "$usage" | jq -r '.currentValue')
94+
limit=$(echo "$usage" | jq -r '.limit')
95+
96+
if [[ $((limit - current)) -ge $vcpu ]]; then
97+
echo "Sufficient quota found in $location"
98+
echo "location=$location" >> "$GITHUB_OUTPUT"
99+
exit 0
100+
fi
101+
done
102+
103+
echo "No location found with sufficient vCPU quota for SKU: $SKU"
104+
exit 1
105+
53106
- name: Create Resource Group
54107
id: rg-setup
55108
env:
109+
LOCATION: ${{ steps.get-location.outputs.location }}
56110
RG: ${{ inputs.RG }}
57-
STORAGE_ACCOUNT_PATHS: ${{ secrets.MSHV_STORAGE_ACCOUNT_PATHS }}
111+
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
58112
run: |
59113
set -e
60114
echo "Creating Resource Group: $RG"
61-
62-
# Extract the location from the storage account paths
63-
LOCATION=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[0].key')
64-
if [[ -z "$LOCATION" ]]; then
65-
echo "ERROR: Failed to extract location from storage account paths."
66-
exit 1
67-
fi
68-
echo "LOCATION=$LOCATION" >> $GITHUB_ENV
69-
70115
# Create the resource group
71-
echo "Creating resource group in location: $LOCATION"
116+
echo "Creating resource group in location: ${LOCATION}"
72117
az group create --name ${RG} --location ${LOCATION}
73118
echo "Resource group created successfully."
74119
75-
- name: Copy/Upload VHD to Azure Storage Account
120+
- name: Generate SSH Key
121+
id: generate-ssh-key
76122
env:
77-
IMAGE_NAME: "vhd-image"
78-
MI_CLIENT_ID: ${{ secrets.MSHV_MI_CLIENT_ID }}
79-
RG: ${{ inputs.RG }}
80-
LOCATION: ${{ env.LOCATION }}
81-
SOURCE_PATH_X86: ${{ secrets.MSHV_X86_SOURCE_PATH }}
82-
STORAGE_ACCOUNT_PATHS: ${{ secrets.MSHV_STORAGE_ACCOUNT_PATHS }}
123+
KEY: ${{ inputs.KEY }}
83124
run: |
84125
set -e
85-
echo "Installing AzCopy if not already installed"
86-
if ! command -v azcopy &> /dev/null; then
87-
wget -O azcopy.tar.gz https://aka.ms/downloadazcopy-v10-linux
88-
tar -xvf azcopy.tar.gz
89-
sudo mv azcopy*/azcopy /usr/local/bin/
90-
sudo chmod +x /usr/local/bin/azcopy
91-
fi
92-
azcopy --version
93-
94-
echo "Logging into AzCopy"
95-
azcopy login --identity --identity-client-id ${MI_CLIENT_ID}
126+
echo "Generating SSH key: $KEY"
127+
mkdir -p ~/.ssh
128+
ssh-keygen -t rsa -b 4096 -f ~/.ssh/${KEY} -N ""
96129
97-
echo "Uploading VHD to Azure Storage Account"
98-
STORAGE_ACCOUNT_PATH=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[0].value')
99-
BLOB_URL="${STORAGE_ACCOUNT_PATH}/${GITHUB_RUN_ID}/x86.vhd"
100-
azcopy copy ${SOURCE_PATH_X86} ${BLOB_URL}
101-
echo "VHD upload complete"
102-
103-
# Create Image from the VHD
104-
echo "Creating Image"
105-
az image create \
106-
--resource-group ${RG} \
107-
--name ${IMAGE_NAME} \
108-
--source $BLOB_URL \
109-
--os-type Linux \
110-
--location ${LOCATION} \
111-
--hyper-v-generation V2 || true
112-
echo "Image creation complete"
113130
114131
- name: Create VM
132+
id: vm-setup
115133
env:
116-
LOCATION: ${{ env.LOCATION }}
117-
OS_DISK_SIZE: 512
134+
KEY: ${{ inputs.KEY }}
135+
LOCATION: ${{ steps.get-location.outputs.location }}
136+
OS_DISK_SIZE: ${{ inputs.OS_DISK_SIZE }}
118137
RG: ${{ inputs.RG }}
119-
RUNNER_RG: ${{ secrets.MSHV_RUNNER_RG }}
120-
RUNNER: ${{ secrets.MSHV_RUNNER }}
121-
SKU: ${{ inputs.SKU }}
122-
VM_NAME: x86_${{ github.run_id }}
123-
USERNAME: ${{ secrets.MSHV_USERNAME }}
124-
PASSWORD: ${{ secrets.MSHV_PASSWORD }}
138+
RUNNER_RG: ${{ secrets.RUNNER_RG }}
139+
RUNNER: ${{ secrets.RUNNER }}
140+
USERNAME: ${{ secrets.USERNAME }}
141+
VM_SKU: ${{ inputs.VM_SKU }}
142+
VM_IMAGE_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_image
143+
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
125144
run: |
126145
set -e
127-
echo "Starting VM creation process..."
146+
echo "Creating $VM_SKU VM: $VM_NAME"
128147
129148
# Extract subnet ID from the runner VM
130149
echo "Retrieving subnet ID..."
131150
SUBNET_ID=$(az network vnet list --resource-group ${RUNNER_RG} --query "[?contains(location, '${LOCATION}')].{SUBNETS:subnets}" | jq -r ".[0].SUBNETS[0].id")
132-
if [[ -z "$SUBNET_ID" ]]; then
151+
if [[ -z "${SUBNET_ID}" ]]; then
133152
echo "ERROR: Failed to retrieve Subnet ID."
134153
exit 1
135154
fi
136-
echo "Successfully retrieved Subnet ID: $SUBNET_ID."
155+
156+
# Extract image ID from the runner VM
157+
echo "Retrieving image ID..."
158+
IMAGE_ID=$(az image show --resource-group ${RUNNER_RG} --name ${VM_IMAGE_NAME} --query "id" -o tsv)
159+
if [[ -z "${IMAGE_ID}" ]]; then
160+
echo "ERROR: Failed to retrieve Image ID."
161+
exit 1
162+
fi
137163
138164
# Create VM
139-
echo "Creating VM: $VM_NAME"
140-
141165
az vm create \
142166
--resource-group ${RG} \
143167
--name ${VM_NAME} \
144-
--subnet $SUBNET_ID \
145-
--size ${SKU} \
168+
--subnet ${SUBNET_ID} \
169+
--size ${VM_SKU} \
146170
--location ${LOCATION} \
147-
--image vhd-image \
171+
--image ${IMAGE_ID} \
148172
--os-disk-size-gb ${OS_DISK_SIZE} \
149173
--public-ip-sku Standard \
150174
--storage-sku Premium_LRS \
151175
--public-ip-address "" \
152176
--admin-username ${USERNAME} \
153-
--admin-password ${PASSWORD} \
177+
--ssh-key-value ~/.ssh/${KEY}.pub \
154178
--security-type Standard \
155179
--output json
156-
echo "VM creation process completed successfully."
157180
158-
# SSH into the VM via sshpass
159-
echo "Installing sshpass"
160-
sudo apt install sshpass -y
181+
echo "VM creation process completed successfully."
161182
162-
# Retrieve VM Private IP address
183+
- name: Get VM Private IP
184+
id: get-vm-ip
185+
env:
186+
RG: ${{ inputs.RG }}
187+
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
188+
run: |
189+
set -e
163190
echo "Retrieving VM Private IP address..."
191+
# Retrieve VM Private IP address
164192
PRIVATE_IP=$(az vm show -g ${RG} -n ${VM_NAME} -d --query privateIps -o tsv)
165193
if [[ -z "$PRIVATE_IP" ]]; then
166194
echo "ERROR: Failed to retrieve private IP address."
167195
exit 1
168196
fi
169-
echo "Successfully retrieved Private IP: $PRIVATE_IP"
197+
echo "PRIVATE_IP=$PRIVATE_IP" >> $GITHUB_OUTPUT
198+
199+
- name: Wait for SSH availability
200+
env:
201+
KEY: ${{ inputs.KEY }}
202+
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
203+
USERNAME: ${{ secrets.USERNAME }}
204+
run: |
205+
echo "Waiting for SSH to be accessible..."
206+
timeout 120 bash -c 'until ssh -o StrictHostKeyChecking=no -i ~/.ssh/${KEY} ${USERNAME}@${PRIVATE_IP} "exit" 2>/dev/null; do sleep 5; done'
207+
echo "VM is accessible!"
170208
171-
# Remove old host key
209+
- name: Remove Old Host Key
210+
env:
211+
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
212+
run: |
213+
set -e
172214
echo "Removing the old host key"
173215
ssh-keygen -R $PRIVATE_IP
174216
175-
sleep 30
176-
echo "SSH is now accessible. Connecting..."
177-
sshpass -p ${PASSWORD} ssh -o StrictHostKeyChecking=no ${USERNAME}@$PRIVATE_IP << EOF
217+
- name: SSH into VM and Install Dependencies
218+
env:
219+
KEY: ${{ inputs.KEY }}
220+
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
221+
USERNAME: ${{ secrets.USERNAME }}
222+
run: |
223+
set -e
224+
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
178225
set -e
179226
echo "Logged in successfully."
180-
181-
# Install Dependencies
182227
echo "Installing dependencies..."
183-
sudo tdnf install -y git clang llvm pkg-config make gcc glibc-devel
184-
185-
# Clone mshv repo
186-
echo "Cloning mshv repo..."
187-
git clone https://github.com/rust-vmm/mshv.git
188-
echo "Clone succeeded for mshv"
189-
EOF
228+
sudo tdnf install -y git moby-engine moby-cli clang llvm pkg-config make gcc glibc-devel
229+
echo "Installing Rust..."
230+
curl -sSf https://sh.rustup.rs | sh -s -- --default-toolchain stable --profile default -y
231+
export PATH="\$HOME/.cargo/bin:\$PATH"
232+
cargo --version
233+
EOF

0 commit comments

Comments
 (0)