Skip to content

Better handling of concurrency#63

Draft
amarao wants to merge 33 commits into
masterfrom
better_handling_of_concurrency
Draft

Better handling of concurrency#63
amarao wants to merge 33 commits into
masterfrom
better_handling_of_concurrency

Conversation

@amarao

@amarao amarao commented May 15, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Servers.com dynamic inventory plugin (sc_inventory) with extensive unit/integration test coverage and adds reusable GitHub Actions workflows to better serialize bare-metal-dependent integration runs (concurrency handling).

Changes:

  • Added serverscom.sc_api.sc_inventory inventory plugin supporting multi-kind discovery, filtering, grouping, and ${VAR} substitution.
  • Added unit + integration tests (including new integration target for the inventory plugin) plus example inventory configs.
  • Refactored CI workflow structure for baremetal/L2 integration tests using reusable workflows and shared concurrency control.

Reviewed changes

Copilot reviewed 41 out of 43 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
examples/dynamic_inventory/01_everything.sc_api.yml Example config: fetch all kinds with no resources: blocks.
examples/dynamic_inventory/02_all_cloud_grouped_by_region.sc_api.yml Example config: cloud instances grouped by region_code.
examples/dynamic_inventory/03_all_baremetal_grouped_by_location.sc_api.yml Example config: bare-metal grouped by location_code.
examples/dynamic_inventory/04_baremetal_in_specific_regions.sc_api.yml Example config: bare-metal filtered to specific regions.
examples/dynamic_inventory/05_cloud_labels_private_ip.sc_api.yml Example config: cloud filtered by labels, using private IPv4.
examples/dynamic_inventory/06_exclusion_ams1_except_production.sc_api.yml Example config: exclusion rules (drop a labeled subset).
examples/dynamic_inventory/07_multi_rule_exclusion.sc_api.yml Example config: multiple exclusion rules (OR).
examples/dynamic_inventory/08_env_var_substitution.sc_api.yml Example config: ${VAR} substitution usage.
examples/dynamic_inventory/09_k8s_nodes.sc_api.yml Example config: k8s bare-metal nodes with private IP addressing.
examples/dynamic_inventory/10_multi_kind_inventory.sc_api.yml Example config: multiple resources blocks with per-kind settings.
examples/dynamic_inventory/11_name_regex_two_kinds_same_group.sc_api.yml Example config: name regex across kinds into same group.
ansible_collections/serverscom/sc_api/plugins/inventory/sc_inventory.py New inventory plugin implementation (filtering/grouping/env subst/strict key validation).
ansible_collections/serverscom/sc_api/plugins/inventory/init.py Inventory package marker for the collection.
ansible_collections/serverscom/sc_api/tests/unit/plugins/inventory/test_sc_inventory.py Unit tests for inventory plugin behavior and validation.
ansible_collections/serverscom/sc_api/tests/unit/plugins/inventory/init.py Unit test package marker.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/tasks/main.yaml Integration test scenario provisioning + assertions for inventory output.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/tasks/_run.yaml Helper task file to render inventory config and run ansible-inventory.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/unknown_top_key.sc_api.yml.j2 Template: unknown top-level key validation test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/unknown_sub_key.sc_api.yml.j2 Template: unknown resources[] key validation test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/unknown_exclude_key.sc_api.yml.j2 Template: unknown exclude-rule key validation test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/same_group_combine.sc_api.yml.j2 Template: merge behavior for same group across multiple blocks.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/region_nomatch.sc_api.yml.j2 Template: region filter negative case.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/region_match.sc_api.yml.j2 Template: region filter positive case.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/name_regex.sc_api.yml.j2 Template: name regex filtering.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/missing_kind.sc_api.yml.j2 Template: missing kind contract validation.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/label_match.sc_api.yml.j2 Template: label filter test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/group_static.sc_api.yml.j2 Template: assign_inventory_group test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/group_by_location.sc_api.yml.j2 Template: group_by test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/exclude_label.sc_api.yml.j2 Template: exclusion by label test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/env_subst.sc_api.yml.j2 Template: ${VAR} substitution success test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/env_subst_missing.sc_api.yml.j2 Template: ${VAR} substitution missing-var failure test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/default_no_resources.sc_api.yml.j2 Template: default “no resources” behavior test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/cross_kind_group.sc_api.yml.j2 Template: same group across kinds (baremetal+cloud).
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/cloud_ipv6_host.sc_api.yml.j2 Template: cloud ansible_host selection using IPv6.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/cloud_filter.sc_api.yml.j2 Template: cloud filtering by label.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/baremetal_basic.sc_api.yml.j2 Template: baremetal-only selection.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/ansible_host_public.sc_api.yml.j2 Template: baremetal ansible_host mapping test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sc_inventory/templates/all_kinds.sc_api.yml.j2 Template: explicit multi-kind inventory test.
ansible_collections/serverscom/sc_api/tests/integration/targets/sbm_server_lifecycle/tasks/test_inventory_discovery.yaml New integration test verifying SBM discovery via sc_inventory.
ansible_collections/serverscom/sc_api/tests/integration/targets/sbm_server_lifecycle/tasks/main.yaml Adds inventory discovery test into SBM lifecycle integration flow.
.github/workflows/tests.yaml Switches to reusable workflows for baremetal/L2 and updates job dependencies.
.github/workflows/baremetal.yaml New reusable workflow for baremetal integration tests + inventory target.
.github/workflows/l2.yaml New reusable workflow for L2 integration tests, sharing baremetal concurrency group.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +316 to +336
def verify_file(self, path):
if not super(InventoryModule, self).verify_file(path):
return False
if not path.endswith(
(
".sc_api.yml",
".sc_api.yaml",
".sc_inventory.yml",
".sc_inventory.yaml",
)
):
return False
# Must also contain `plugin: serverscom.sc_api.sc_inventory` so we
# don't claim foreign files that just happen to share the suffix.
try:
with open(path, "r") as f:
content = f.read()
except (OSError, IOError):
return False
return "plugin: %s" % self.NAME in content

)
extra_vars = config.get("extra_vars") or {}

compiled_regex = re.compile(name_regex) if name_regex else None
Comment on lines +110 to +120
baremetal:
needs:
- unit_and_sanity
- lint
concurrency:
group: BM1
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Configure integration tests
run: |
envsubst < integration_config.yml.template > ansible_collections/serverscom/sc_api/tests/integration/integration_config.yml
env:
SC_TOKEN: "${{ secrets.SC_TOKEN }}"
- name: Integration tests (server1)
run: ansible-test integration --requirements --python 3.13 --color yes
ssh_key
baremetal_locations_info
dedicated_server_reinstall_quick
dedicated_server_reinstall_1
dedicated_server_power
uses: ./.github/workflows/baremetal.yaml
secrets: inherit

working-directory: ansible_collections/serverscom/sc_api
- name: Cleanup secrets
if: always()
run: |
dd if=/dev/zero bs=4k count=4 of=ansible_collections/serverscom/sc_api/tests/integration/integration_config.yml
sync
sync

integration_tests_bm_2:
runs-on: ubuntu-24.04
needs:
- unit_and_sanity
- lint
concurrency:
group: BM2
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Configure integration tests
run: |
envsubst < integration_config.yml.template > ansible_collections/serverscom/sc_api/tests/integration/integration_config.yml
env:
SC_TOKEN: "${{ secrets.SC_TOKEN }}"
- name: Integration tests (server2)
run: ansible-test integration --requirements --python 3.13 --color yes
baremetal_servers_info
dedicated_server_reinstall_2
dedicated_server_rescue
dedicated_server_reinstall_raid0

working-directory: ansible_collections/serverscom/sc_api
- name: Cleanup secrets
if: always()
run: |
dd if=/dev/zero bs=4k count=4 of=ansible_collections/serverscom/sc_api/tests/integration/integration_config.yml
sync
sync

integration_tests_bm_3:
runs-on: ubuntu-24.04
needs:
- unit_and_sanity
- lint
concurrency:
group: BM3
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Configure integration tests
run: |
envsubst < integration_config.yml.template > ansible_collections/serverscom/sc_api/tests/integration/integration_config.yml
env:
SC_TOKEN: "${{ secrets.SC_TOKEN }}"
- name: Integration tests (server3)
run: ansible-test integration --requirements --python 3.13 --color yes
baremetal_os_list
dedicated_server_info
dedicated_server_reinstall_3
dedicated_server_ipxe
dedicated_server_reinstall_noraid

working-directory: ansible_collections/serverscom/sc_api
- name: Cleanup secrets
if: always()
run: |
dd if=/dev/zero bs=4k count=4 of=ansible_collections/serverscom/sc_api/tests/integration/integration_config.yml
sync
sync

integration_tests_l2:
runs-on: ubuntu-24.04
needs:
- integration_tests_bm_1
- integration_tests_bm_2
- integration_tests_bm_3
concurrency:
group: BM
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Configure integration tests
run: |
envsubst < integration_config.yml.template > ansible_collections/serverscom/sc_api/tests/integration/integration_config.yml
env:
SC_TOKEN: "${{ secrets.SC_TOKEN }}"
- name: Integration tests
run: ansible-test integration --requirements --python 3.13 --color yes
l2_segment
l2_segment_aliases
l2_segments_info

working-directory: ansible_collections/serverscom/sc_api
- name: Cleanup secrets
if: always()
run: |
dd if=/dev/zero bs=4k count=4 of=ansible_collections/serverscom/sc_api/tests/integration/integration_config.yml
sync
sync
l2:
needs: baremetal
uses: ./.github/workflows/l2.yaml
secrets: inherit
Comment on lines +11 to +59
- name: Render sc_inventory config (kind=sbm)
copy:
content: |
plugin: serverscom.sc_api.sc_inventory
resources:
- kind: sbm
dest: "{{ inv_tmp.path }}/sbm.sc_api.yml"
mode: "0600"

- name: Run ansible-inventory against the sc_inventory config
command:
argv:
- ansible-inventory
- -i
- "{{ inv_tmp.path }}/sbm.sc_api.yml"
- --list
environment:
SERVERSCOM_API_TOKEN: "{{ sc_token }}"
SERVERSCOM_API_URL: "{{ sc_endpoint }}"
register: inv_run
changed_when: false

- name: Stash inventory JSON to a file
copy:
content: "{{ inv_run.stdout }}"
dest: "{{ inv_tmp.path }}/inventory.json"
mode: "0600"
changed_when: false

- name: Load inventory JSON
include_vars:
file: "{{ inv_tmp.path }}/inventory.json"
name: inv_json

- name: Created SBM server is discovered by sc_inventory
assert:
that:
- test_hostname in inv_json._meta.hostvars
- inv_json._meta.hostvars[test_hostname].sc_kind == "sbm"
- inv_json._meta.hostvars[test_hostname].type == "sbm_server"
fail_msg: >-
Expected to find SBM host {{ test_hostname }} in sc_inventory output.
hostvars seen: {{ inv_json._meta.hostvars | list }}

- name: Cleanup inventory temp dir
file:
path: "{{ inv_tmp.path }}"
state: absent
changed_when: false
@amarao amarao self-assigned this Jun 15, 2026
silentirk and others added 26 commits June 15, 2026 16:13
Implements a new Ansible module for managing iPXE boot configuration
on dedicated servers. Supports public/private iPXE modes with automatic
deactivation of the opposite feature. Includes unit tests, integration
tests, and API layer extensions.
Extract environment-specific RBS (Block Storage) values from integration
tests into integration_config.yml.template for portability across
test environments.

New variables: rbs_test_location_id, rbs_test_location_code,
rbs_test_flavor_id, rbs_test_flavor_name.

Affected test targets: rbs_flavors_info, rbs_volume, rbs_volume_info,
rbs_volume_credentials_reset.

Co-Authored-By: Claude Opus 4.6
Extract environment-specific Load Balancer values from integration
tests into integration_config.yml.template for portability across
test environments.

New variables: lb_test_location_id, lb_test_upstream_ip1,
lb_test_upstream_ip2, lb_test_network.

Affected test targets: load_balancer_instance_l4,
load_balancer_instance_l7, load_balancer_instance_info,
load_balancer_instances_list.

Co-Authored-By: Claude Opus 4.6
…fig.yml

Extract environment-specific Cloud Computing values from integration
tests into integration_config.yml.template for portability across
test environments.

New variables: cloud_test_region_id, cloud_test_flavor_id,
cloud_test_flavor_name, cloud_test_ssh_key_fingerprint,
cloud_test_region_search_pattern, cloud_test_region_search_match,
cloud_test_region_search_nomatch.

Affected test targets: cloud_computing_instance,
cloud_computing_instance_info, cloud_computing_instance_ptr,
cloud_computing_instance_state, cloud_computing_instance_reinstall,
cloud_computing_instances_info, cloud_computing_regions_info.

Co-Authored-By: Claude Opus 4.6
…_config.yml

Extract environment-specific Baremetal and Dedicated Server values
from integration tests into integration_config.yml.template.

New variables: baremetal_test_location_search_pattern,
baremetal_test_location_search_match,
baremetal_test_location_search_nomatch,
baremetal_test_os_location_id, baremetal_test_os_location_code,
baremetal_test_os_server_model_id, baremetal_test_os_server_model_name,
dedicated_test_reinstall_os_id,
dedicated_test_reinstall_ssh_key_fingerprint,
dedicated_test_reinstall_ssh_key_name.

Also fix hardcoded non_existing_id value in
dedicated_server_reinstall_quick assertion.

Affected test targets: baremetal_locations_info, baremetal_os_list,
dedicated_server_reinstall_long, dedicated_server_reinstall_quick.

Co-Authored-By: Claude Opus 4.6
Extract environment-specific SBM (Scalable Baremetal) values from
integration tests into integration_config.yml.template.

New variables: sbm_test_location_code, sbm_test_flavor_name,
sbm_test_os_regex, sbm_test_reinstall_os_name.

Affected test targets: sbm_server_lifecycle, sbm_flavor_models_info,
sbm_os_list, sbm_servers_info.

Co-Authored-By: Claude Opus 4.6
amarao added 7 commits June 15, 2026 16:13
Also, add a test for that.

Also, a test for proper joining of the same group kinds for the same
group.
  - verify_file content check (sc_inventory.py): now reads the file and
  confirms plugin: serverscom.sc_api.sc_inventory is present in the body.
  Foreign .sc_api.yml files with a different plugin: key, or no plugin: line
  at all, are rejected at discovery time rather than reaching parse().
  - Unit tests for verify_file rewritten to use real temp files + 3 new cases
  (wrong-plugin-key, missing-plugin-key, unreadable-path).
  - Integration cloud test: added public_ipv6 == cloud_vm.public_ipv6_address
  assertion on the cloud-filter check, so the IPv6 hostvar exposure is
  verified end-to-end (not just ansible_host: public_ipv6 in the v6 follow-up
  test).
The problem:

PXE-boot tests and L2-segment tests share the same baremetal fleet, and PXE is incompatible with a server being in an L2 segment. Within one workflow run, needs: correctly ordered BM before L2, but the per-job concurrency groups (BM1/BM2/BM3/BM) didn't overlap across parallel workflow runs.

Example failure: push #1's integration_tests_l2 is mid-run (servers attached to an L2 segment); push #2 lands and its integration_tests_bm_1 immediately starts (group BM1 is free), attempting a PXE boot on a server still in run #1's L2 segment — PXE fails or produces undefined state.

Fix: BM jobs and L2 are moved into two reusable workflows that share workflow-level concurrency: baremetal, so the entire BM phase of run #2 queues until run #1's BM and L2 phases have both released the fleet, while server1/2/3 still run in parallel within a run.
@amarao amarao force-pushed the better_handling_of_concurrency branch from 78f4701 to e6b9fb8 Compare June 15, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants