Skip to content

GCP Image Test

GCP Image Test #21

Workflow file for this run

name: GCP cloud-image-tests
on:
workflow_dispatch:
inputs:
version_major:
description: 'AlmaLinux major version'
required: true
default: '10'
type: choice
options:
- 10-kitten
- 10
- 9
- 8
arch:
description: 'Architecture to test'
required: true
default: 'ALL'
type: choice
options:
- ALL
- x86_64
- aarch64
image_override:
description: 'Image to test, overrides version_major to test a direct image instead. Architecture must be set properly for the image being passed. This must be a full path to a GCP image, for example, projects/almalinux-dev-images-469421/global/images/almalinux-9-v20230920'
required: false
default: ''
# notify_mattermost:
# description: "Send notification to Mattermost"
# required: true
# type: boolean
# default: false
jobs:
init-data:
runs-on: ubuntu-latest
outputs:
image_path: ${{ steps.determine_image.outputs.image_path }}
steps:
- name: Determine image to test
id: determine_image
run: |
if [ -n "${{ inputs.image_override }}" ]; then
echo "Using image override: ${{ inputs.image_override }}"
image_path="${{ inputs.image_override }}"
elif [ "${{ inputs.arch == 'ALL' }}" ]; then
echo "Using version major: ${{ inputs.version_major }}"
echo "Using all architectures"
image_path="projects/almalinux-dev-images-469421/global/images/family/almalinux-${{ inputs.version_major }}"
else
echo "Using version major: ${{ inputs.version_major }}"
echo "Using arch: ${{ inputs.arch }}"
image_path="projects/almalinux-dev-images-469421/global/images/family/almalinux-${{ inputs.version_major }}"
fi
echo "Determined image path: ${image_path}"
echo "image_path=${image_path}" >> $GITHUB_OUTPUT
- name: Build randomized per-arch shape lists
id: rand-shapes
shell: bash
run: |
python3 - <<'PY' > /tmp/generate_shapes.py
import json, random, heapq, sys
from collections import deque, defaultdict
# change these lists to match the shapes you're using
shapes_by_arch = {
"x86_64": [
"n4-standard-2","n4-standard-80",
"n2-standard-2","n2-standard-128",
"n2d-standard-2","n2d-standard-224",
"n1-standard-1","n1-standard-96",
"c4-standard-2","c4-standard-288","c4-standard-288-metal","c4-standard-4-lssd","c4-standard-288-lssd",
"c4d-standard-2","c4d-standard-384","c4d-standard-384-metal","c4d-standard-8-lssd","c4d-standard-384-lssd",
"c3-standard-4","c3-standard-176","c3-standard-192-metal","c3-standard-4-lssd","c3-standard-176-lssd",
"c3d-standard-4","c3d-standard-360","c3d-standard-8-lssd","c3d-standard-360-lssd",
"e2-standard-2","e2-standard-32","e2-medium",
"t2d-standard-1","t2d-standard-60"
],
"aarch64": [
"c4a-standard-1","c4a-standard-72","c4a-standard-4-lssd","c4a-standard-72-lssd",
"t2a-standard-1","t2a-standard-48"
]
}
# parameters: k-distance -> no repeat within next k elements (k=2 as you requested)
K = 3
def family_of(shape):
return shape.split('-', 1)[0]
# greedy scheduler with cooldown
def randomize_list(shapes, k=K, max_attempts=200):
if not shapes:
return []
# group by family
buckets = defaultdict(list)
for s in shapes:
buckets[family_of(s)].append(s)
families = list(buckets.keys())
def attempt():
# create max-heap by remaining count
heap = [(-len(buckets[f]), f) for f in families if buckets[f]]
heapq.heapify(heap)
# local copy of buckets' lists
local = {f: list(buckets[f]) for f in buckets}
result = []
cooldown = deque() # stores families in the last k chosen
while heap:
popped = []
chosen = None
while heap:
cnt, fam = heapq.heappop(heap)
cnt = -cnt
if fam in cooldown:
popped.append((-cnt, fam))
continue
# choose this family
lst = local[fam]
# pick a random shape from the family
s = random.choice(lst)
lst.remove(s)
result.append(s)
# update counts and reinsert
if len(lst) > 0:
heapq.heappush(heap, (-len(lst), fam))
# push back popped families
for item in popped:
heapq.heappush(heap, item)
# update cooldown
cooldown.append(fam)
if len(cooldown) > k:
cooldown.popleft()
chosen = True
break
if not chosen:
# dead end
return None
return result
for _ in range(max_attempts):
out = attempt()
if out is not None:
return out
return None
out = {}
for arch, shapes in shapes_by_arch.items():
randomized = randomize_list(shapes, K)
if randomized is None:
print(f"ERROR: failed to generate sequence for {arch}", file=sys.stderr)
sys.exit(1)
out[arch] = randomized
print(json.dumps(out))
PY
# run generator and capture result
result=$(python3 /tmp/generate_shapes.py)
echo "randomized=$result" >&2
# extract per-arch arrays and export to GITHUB_OUTPUT
# set outputs like matrix_shapes_x86_64 and matrix_shapes_aarch64
x86=$(python3 - <<'PY'
import json,sys
d=json.loads(sys.stdin.read())
print(json.dumps(d.get('x86_64', [])))
PY
<<<"$result")
aarch=$(python3 - <<'PY'
import json,sys
d=json.loads(sys.stdin.read())
print(json.dumps(d.get('aarch64', [])))
PY
<<<"$result")
echo "matrix_shapes_x86_64=$x86" >> $GITHUB_OUTPUT
echo "matrix_shapes_aarch64=$aarch" >> $GITHUB_OUTPUT
# this initial test does the generic suite of tests not assigned to any specific shape - letting the test system
# choose its own shapes and sizes. This is run first to catch any major issues before running the per-shape tests
# which take a long time and use a lot of resources. Think of this as a smoke test to catch major issues early.
test-gcp-initialtest:
name: AlmaLinux ${{ inputs.image_override || format('{0} {1}', inputs.version_major, matrix.arch) }} Generic Full Run
needs: init-data
permissions:
id-token: write
contents: read
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
# this sets the arch matrix based on the input
# if input is ALL, then set to both x86_64 and aarch64
# otherwise set to the selected arch
arch: ${{ fromJSON(inputs.arch == 'ALL' && '["x86_64","aarch64"]' || format('["{0}"]', inputs.arch)) }}
steps:
# we don't need the checked out files, but this is required for the google auth action to work
- uses: 'actions/checkout@v5'
- id: 'google-auth-image-testing'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: 'projects/527193872801/locations/global/workloadIdentityPools/github-actions/providers/github'
service_account: 'github-actions-image-testing@almalinux-image-testing-469421.iam.gserviceaccount.com'
- name: 'Set up Google Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v3.0.0'
- name: 'Run Google cloud-image-testing tests which are hard-coded to specific shapes'
shell: bash
run: |
docker run \
-v ${{ env.GOOGLE_GHA_CREDS_PATH }}:/creds/auth.json \
-e GOOGLE_APPLICATION_CREDENTIALS=/creds/auth.json \
gcr.io/compute-image-tools/cloud-image-tests:latest \
-project almalinux-image-testing-469421 \
-parallel_stagger 10s \
-parallel_count 20 \
-filter '^(cvm|livemigrate|suspendresume|loadbalancer|guestagent|hostnamevalidation|imageboot|licensevalidation|network|security|hotattach|packagevalidation|ssh|metadata|disk|lssd|vmspec)$' \
-images '${{ needs.init-data.outputs.image_path }}${{ inputs.image_override == '' && matrix.arch == 'aarch64' && '-arm64' || ''}}'
test-gcp-pershape-x86_64:
name: AlmaLinux ${{ inputs.image_override || format('{0}', inputs.version_major) }} x86_64 ${{ matrix.shape }}
needs: [init-data, test-gcp-initialtest]
permissions:
id-token: write
contents: read
runs-on: ubuntu-24.04
if: inputs.arch == 'ALL' || inputs.arch == 'x86_64'
strategy:
fail-fast: false
max-parallel: 2
matrix:
shape: ${{ fromJSON(needs.init-data.outputs.matrix_shapes_x86_64) }}
steps:
# we don't need the checked out files, but this is required for the google auth action to work
- uses: 'actions/checkout@v5'
- id: 'google-auth-image-testing'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: 'projects/527193872801/locations/global/workloadIdentityPools/github-actions/providers/github'
service_account: 'github-actions-image-testing@almalinux-image-testing-469421.iam.gserviceaccount.com'
- name: 'Set up Google Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v3.0.0'
- name: 'Run Google cloud-image-testing tests on ${{ matrix.shape }}'
shell: bash
run: |
docker run \
-v ${{ env.GOOGLE_GHA_CREDS_PATH }}:/creds/auth.json \
-e GOOGLE_APPLICATION_CREDENTIALS=/creds/auth.json \
gcr.io/compute-image-tools/cloud-image-tests:latest \
-project almalinux-image-testing-469421 \
-parallel_stagger 10s \
-parallel_count 20 \
-x86_shape ${{ matrix.shape }} \
-filter '^(cvm|livemigrate|suspendresume|loadbalancer|guestagent|hostnamevalidation|imageboot|licensevalidation|network|security|hotattach|packagevalidation|ssh|metadata)$' \
-images '${{ needs.init-data.outputs.image_path || inputs.image_override }}${{ inputs.image_override == '' && matrix.arch == 'aarch64' && '-arm64' || ''}}'
test-gcp-pershape-aarch64:
name: AlmaLinux ${{ inputs.image_override || format('{0}', inputs.version_major) }} aarch64 ${{ matrix.shape }}
needs: [init-data, test-gcp-initialtest]
permissions:
id-token: write
contents: read
runs-on: ubuntu-24.04
if: inputs.arch == 'ALL' || inputs.arch == 'aarch64'
strategy:
fail-fast: false
# the arm sizes are smaller so we can do more concurrently
max-parallel: 3
matrix:
shape: ${{ fromJSON(needs.init-data.outputs.matrix_shapes_aarch64) }}
steps:
# we don't need the checked out files, but this is required for the google auth action to work
- uses: 'actions/checkout@v5'
- id: 'google-auth-image-testing'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: 'projects/527193872801/locations/global/workloadIdentityPools/github-actions/providers/github'
service_account: 'github-actions-image-testing@almalinux-image-testing-469421.iam.gserviceaccount.com'
- name: 'Set up Google Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v3.0.0'
- name: 'Run Google cloud-image-testing tests on ${{ matrix.shape }}'
shell: bash
run: |
docker run \
-v ${{ env.GOOGLE_GHA_CREDS_PATH }}:/creds/auth.json \
-e GOOGLE_APPLICATION_CREDENTIALS=/creds/auth.json \
gcr.io/compute-image-tools/cloud-image-tests:latest \
-project almalinux-image-testing-469421 \
-parallel_stagger 10s \
-parallel_count 20 \
-arm64_shape ${{ matrix.shape }} \
-filter '^(cvm|livemigrate|suspendresume|loadbalancer|guestagent|hostnamevalidation|imageboot|licensevalidation|network|security|hotattach|packagevalidation|ssh|metadata)$' \
-images '${{ needs.init-data.outputs.image_path || inputs.image_override }}${{ inputs.image_override == '' && matrix.arch == 'aarch64' && '-arm64' || ''}}'