Loading .gitlab-ci.yml +12 −0 Changes for .gitlab-ci.yml: 12 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -65,3 +65,15 @@ python-smoke: stage: test script: - python3 -m py_compile ansible/roles/oop-platform/files/fm_partners.py ansible/tests/r2_e2e/*.py # Pure gate/observation checks; no inventory, cluster access or deployment secrets. sdk-acceptance-checks: stage: test variables: TMPDIR: "$CI_PROJECT_DIR/.cache/sdk-tmp" PYTHONDONTWRITEBYTECODE: "1" script: - mkdir -p "$TMPDIR" - pip install pytest==8.3.2 requests==2.32.5 - python -m pytest --confcutdir=ansible/tests/unit -q -p no:cacheprovider ansible/tests/unit/test_sdk_acceptance.py - python -m pytest --collect-only -q -p no:cacheprovider ansible/tests/r2_e2e ansible/inventory/r2-sdk-lab.example.yml 0 → 100644 +65 −0 Changes for ansible/inventory/r2-sdk-lab.example.yml: 65 added lines, 0 removed lines. Original line number Diff line number Diff line --- # Separate, single-operator lab. Copy to r2-sdk-lab.local.yml and fill the pins. # No production inventory, federation, shared workload namespace or floating SDK install. all: children: oop_platform_operators: hosts: sdk_lab: ansible_connection: local ansible_python_interpreter: "{{ ansible_playbook_python }}" oop_platform_kubeconfig: ~/.kube/oop-sdk-lab.yaml oop_platform_kube_context: oop-sdk-lab oop_platform_namespace: oop-e2e-platform oop_platform_public_host: sdk-lab.example.test oop_platform_federation_id: sdk-lab oop_platform_country_code: GR oop_platform_mcc: "202" oop_platform_mncs: ["01"] oop_platform_federation_clients: [] oop_platform_portal_enabled: false oop_platform_chart_version: 36dda75882352216ced21475786160ee2e669a6b oop_platform_fm_image_tag: REPLACE_WITH_REVIEWED_FM_TAG oop_platform_oeg_image_tag: REPLACE_WITH_REVIEWED_OEG_TAG oop_platform_srm_image_tag: REPLACE_WITH_REVIEWED_SRM_TAG e2e_temp_dir: "{{ playbook_dir }}/../../../.cache/sdk-e2e-tmp" oop_platform_extra_values: srm: srmcontroller: workloads: namespaces: [oop-e2e-workloads] controlPaths: - control_path_ref: sdk-lab-k8s adapter: kubernetes config: namespace: oop-e2e-workloads topology: | version: openop.srm.bootstrap/v1 platform_instance_id: sdk-lab control_paths: - ref: sdk-lab-k8s adapter_family: compute adapter_type: kubernetes adapter_contract_version: v1 infrastructure: namespace: oop-e2e-workloads zones: - ref: sdk-lab-zone name: SDK lab kind: resource state: active domains: - ref: sdk-lab-compute name: SDK lab compute kind: compute state: active capabilities: - ref: sdk-lab-workload name: SDK lab workload kind: workload state: active control_path_bindings: - ref: sdk-lab-workload-binding control_path_ref: sdk-lab-k8s is_default: true state: active ansible/playbooks/scenarios/r2_existing_cluster/e2e.yml +11 −0 Changes for ansible/playbooks/scenarios/r2_existing_cluster/e2e.yml: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -12,6 +12,15 @@ hosts: oop_platform_operators gather_facts: true tasks: - name: Prepare an explicitly configured controller temporary directory ansible.builtin.file: path: "{{ e2e_temp_dir }}" state: directory mode: "0700" when: e2e_temp_dir is defined delegate_to: localhost run_once: true - name: Export the settings ansible.builtin.include_role: name: oop-platform Loading @@ -33,6 +42,7 @@ - name: Make a file for it on the controller ansible.builtin.tempfile: path: "{{ e2e_temp_dir | default(omit) }}" suffix: .kubeconfig register: e2e_kubeconfig_file delegate_to: localhost Loading Loading @@ -65,6 +75,7 @@ tasks: - name: Write the settings for the tests ansible.builtin.tempfile: path: "{{ e2e_temp_dir | default(omit) }}" suffix: .json register: e2e_settings_file Loading ansible/tests/r2_e2e/SDK_ACCEPTANCE.md 0 → 100644 +96 −0 Changes for ansible/tests/r2_e2e/SDK_ACCEPTANCE.md: 96 added lines, 0 removed lines. Original line number Diff line number Diff line # OEG deployment-to-TI acceptance (TI-E2E-01) Run this scenario in a separate single-operator lab. Default `e2e.yml` runs keep the existing tests; the two `sdk_acceptance` cases are deselected. Explicit SDK selection runs only the requested checkpoint, leaving federation/auth tests out of this run. No existing test is marked xfail or weakened. ## Prepare the lab 1. Copy `inventory/r2-sdk-lab.example.yml` to `inventory/r2-sdk-lab.local.yml`. Supply a dedicated cluster/context and published component image tags. The reviewed chart is pinned. This example deliberately contains placeholders. 2. Deploy that inventory with `r2_existing_cluster/deploy.yml`. Do not federate it or reuse another test suite's workload namespace. Configure DNS/routing so the advertised workload host is reachable from the test controller. 3. Build SRM with the reviewed TF-SDK wheel installed during image build. Pin its dependencies and record the image digest; do not pip-install over a running/shared SRM. Include the required Helm client and backend credentials. 4. Copy `profiles/deploy_to_ti.example.json` into an ignored `.cache/` directory and fill real image digests, deployment/container names and lab identities. Use a digest-pinned [traefik/whoami](https://github.com/traefik/whoami) fixture: it listens on port 80 and returns its hostname, allowing the test to prove that HTTP reached this run's pod rather than a different HTTP 200 endpoint. Compute `sdk.sha256` from the independently reviewed wheel: ```sh python tests/r2_e2e/sdk_artifact.py /disk/artifacts/openop_tfsdk-1.1.1-py3-none-any.whl ``` The same script runs read-only in the selected SRM pod. Python/chart contents and package version must match; version 1.1.1 alone cannot identify a commit. Record the reviewed source SHA alongside the wheel fingerprint. The test verifies both SRM/OEG running image digests before creating a test client. Use disk-backed `TMPDIR` and `ANSIBLE_LOCAL_TEMP`. The lab inventory sets `e2e_temp_dir` for controller settings/kubeconfig copies; no /tmp is required. Keep a JUnit report and the generated `.cache/sdk-e2e/<run-id>/run.json`. ## Run the first checkpoint From `ansible/`, using this lab only: ```sh ansible-playbook -i inventory/r2-sdk-lab.local.yml \ playbooks/scenarios/r2_existing_cluster/e2e.yml \ -e '{"e2e_pytest_args":"--sdk-checkpoint=endpoints --sdk-profile=.cache/sdk-profile.json --junitxml=.cache/sdk-endpoints.xml"}' ``` The endpoint case registers one digest-pinned HTTP identity container, deploys it through OEG and polls the public filtered instance read until ready. It requires an `http` endpoint, checks its NodePort against the live Service, checks the same deployment's image and makes a request from the controller to the returned host and port. The response must identify the selected ready pod. It does not inject endpoints or query/write platform databases. Before the run, the workload namespace must contain no Service/Deployment. The scenario uses a fresh application ID and ephemeral scoped Keycloak client. Cleanup uses public OEG deletion, only for this run's application/instances/TI, then removes that client. Cleanup failures are reported alongside the test failure, and no namespace, platform release or unrelated resource is deleted. The current SDK baseline returns no endpoints, so this assertion is expected to fail once a compatible lab is available. That is not a live failure already reproduced by this change. Capture the red baseline before changing the SDK. ## Complete the TI checkpoint separately Select `--sdk-checkpoint=ti` only after the NEF owner agrees and enables the application-wide profile and supplies its subscriptions URL and actual DNAI. Configure the network control path in the lab; the example inventory covers only workload. Never fabricate a device default to satisfy this public request. Set `ti.token_env` to an environment variable name if NEF reads need a bearer token; no token belongs in the profile or report. The TI case repeats the real deployment/endpoint checks, creates public TI by appId/appInstanceId and destination TCP/port, waits for public `active`, then reads the corresponding NEF policy. This bounded profile requires `anyUeInd`, no individual-user selector, one TCP filter to the observed IPv4/port, and the agreed DNAI. It refuses a broader IP-only flow. This is a control-plane test; packet forwarding and notification delivery require separate acceptance. The current app-wide/provider-mode, SRM enrichment and SDK filter gaps can make TI fail independently of SDK endpoint output. Keep those failures owned by their modules rather than changing the SDK to perform SRM inventory selection. ## Fast verification without a lab ```sh python -m pytest tests/unit/test_sdk_acceptance.py python -m pytest --collect-only tests/r2_e2e ``` The first checks the new gate/observation helpers with controlled inputs. The second must retain the same 23 default cases and deselect the two SDK cases. Neither is evidence of a live OEG/SRM/SDK/backend run. No live test is added to the existing CI jobs by this change. ansible/tests/r2_e2e/conftest.py +38 −0 Changes for ansible/tests/r2_e2e/conftest.py: 38 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -17,6 +17,44 @@ from pathlib import Path import pytest import requests def pytest_addoption(parser): group = parser.getgroup("SDK acceptance") group.addoption("--sdk-checkpoint", choices=("endpoints", "ti"), default=None) group.addoption("--sdk-profile", help="JSON profile for a dedicated SDK acceptance lab") def pytest_configure(config): config.addinivalue_line("markers", "sdk_acceptance(checkpoint): opt-in real SDK lab scenario") if config.getoption("sdk_checkpoint"): from sdk_scenario import load_profile if not config.getoption("sdk_profile"): raise pytest.UsageError("--sdk-checkpoint requires --sdk-profile") try: config.sdk_profile = load_profile( config.getoption("sdk_profile"), config.getoption("sdk_checkpoint") ) except (OSError, ValueError, KeyError, TypeError) as error: raise pytest.UsageError(f"Invalid SDK acceptance profile: {error}") from error elif config.getoption("sdk_profile"): raise pytest.UsageError("--sdk-profile requires --sdk-checkpoint") def pytest_collection_modifyitems(config, items): checkpoint = config.getoption("sdk_checkpoint") selected, deselected = [], [] for item in items: mark = item.get_closest_marker("sdk_acceptance") keep = (mark is None) if checkpoint is None else ( mark is not None and mark.args == (checkpoint,) ) (selected if keep else deselected).append(item) if deselected: config.hook.pytest_deselected(items=deselected) items[:] = selected TIMEOUT = 30 EWBI = "/operatorplatform/federation/v1" FM_CALL = """ Loading Loading
.gitlab-ci.yml +12 −0 Changes for .gitlab-ci.yml: 12 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -65,3 +65,15 @@ python-smoke: stage: test script: - python3 -m py_compile ansible/roles/oop-platform/files/fm_partners.py ansible/tests/r2_e2e/*.py # Pure gate/observation checks; no inventory, cluster access or deployment secrets. sdk-acceptance-checks: stage: test variables: TMPDIR: "$CI_PROJECT_DIR/.cache/sdk-tmp" PYTHONDONTWRITEBYTECODE: "1" script: - mkdir -p "$TMPDIR" - pip install pytest==8.3.2 requests==2.32.5 - python -m pytest --confcutdir=ansible/tests/unit -q -p no:cacheprovider ansible/tests/unit/test_sdk_acceptance.py - python -m pytest --collect-only -q -p no:cacheprovider ansible/tests/r2_e2e
ansible/inventory/r2-sdk-lab.example.yml 0 → 100644 +65 −0 Changes for ansible/inventory/r2-sdk-lab.example.yml: 65 added lines, 0 removed lines. Original line number Diff line number Diff line --- # Separate, single-operator lab. Copy to r2-sdk-lab.local.yml and fill the pins. # No production inventory, federation, shared workload namespace or floating SDK install. all: children: oop_platform_operators: hosts: sdk_lab: ansible_connection: local ansible_python_interpreter: "{{ ansible_playbook_python }}" oop_platform_kubeconfig: ~/.kube/oop-sdk-lab.yaml oop_platform_kube_context: oop-sdk-lab oop_platform_namespace: oop-e2e-platform oop_platform_public_host: sdk-lab.example.test oop_platform_federation_id: sdk-lab oop_platform_country_code: GR oop_platform_mcc: "202" oop_platform_mncs: ["01"] oop_platform_federation_clients: [] oop_platform_portal_enabled: false oop_platform_chart_version: 36dda75882352216ced21475786160ee2e669a6b oop_platform_fm_image_tag: REPLACE_WITH_REVIEWED_FM_TAG oop_platform_oeg_image_tag: REPLACE_WITH_REVIEWED_OEG_TAG oop_platform_srm_image_tag: REPLACE_WITH_REVIEWED_SRM_TAG e2e_temp_dir: "{{ playbook_dir }}/../../../.cache/sdk-e2e-tmp" oop_platform_extra_values: srm: srmcontroller: workloads: namespaces: [oop-e2e-workloads] controlPaths: - control_path_ref: sdk-lab-k8s adapter: kubernetes config: namespace: oop-e2e-workloads topology: | version: openop.srm.bootstrap/v1 platform_instance_id: sdk-lab control_paths: - ref: sdk-lab-k8s adapter_family: compute adapter_type: kubernetes adapter_contract_version: v1 infrastructure: namespace: oop-e2e-workloads zones: - ref: sdk-lab-zone name: SDK lab kind: resource state: active domains: - ref: sdk-lab-compute name: SDK lab compute kind: compute state: active capabilities: - ref: sdk-lab-workload name: SDK lab workload kind: workload state: active control_path_bindings: - ref: sdk-lab-workload-binding control_path_ref: sdk-lab-k8s is_default: true state: active
ansible/playbooks/scenarios/r2_existing_cluster/e2e.yml +11 −0 Changes for ansible/playbooks/scenarios/r2_existing_cluster/e2e.yml: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -12,6 +12,15 @@ hosts: oop_platform_operators gather_facts: true tasks: - name: Prepare an explicitly configured controller temporary directory ansible.builtin.file: path: "{{ e2e_temp_dir }}" state: directory mode: "0700" when: e2e_temp_dir is defined delegate_to: localhost run_once: true - name: Export the settings ansible.builtin.include_role: name: oop-platform Loading @@ -33,6 +42,7 @@ - name: Make a file for it on the controller ansible.builtin.tempfile: path: "{{ e2e_temp_dir | default(omit) }}" suffix: .kubeconfig register: e2e_kubeconfig_file delegate_to: localhost Loading Loading @@ -65,6 +75,7 @@ tasks: - name: Write the settings for the tests ansible.builtin.tempfile: path: "{{ e2e_temp_dir | default(omit) }}" suffix: .json register: e2e_settings_file Loading
ansible/tests/r2_e2e/SDK_ACCEPTANCE.md 0 → 100644 +96 −0 Changes for ansible/tests/r2_e2e/SDK_ACCEPTANCE.md: 96 added lines, 0 removed lines. Original line number Diff line number Diff line # OEG deployment-to-TI acceptance (TI-E2E-01) Run this scenario in a separate single-operator lab. Default `e2e.yml` runs keep the existing tests; the two `sdk_acceptance` cases are deselected. Explicit SDK selection runs only the requested checkpoint, leaving federation/auth tests out of this run. No existing test is marked xfail or weakened. ## Prepare the lab 1. Copy `inventory/r2-sdk-lab.example.yml` to `inventory/r2-sdk-lab.local.yml`. Supply a dedicated cluster/context and published component image tags. The reviewed chart is pinned. This example deliberately contains placeholders. 2. Deploy that inventory with `r2_existing_cluster/deploy.yml`. Do not federate it or reuse another test suite's workload namespace. Configure DNS/routing so the advertised workload host is reachable from the test controller. 3. Build SRM with the reviewed TF-SDK wheel installed during image build. Pin its dependencies and record the image digest; do not pip-install over a running/shared SRM. Include the required Helm client and backend credentials. 4. Copy `profiles/deploy_to_ti.example.json` into an ignored `.cache/` directory and fill real image digests, deployment/container names and lab identities. Use a digest-pinned [traefik/whoami](https://github.com/traefik/whoami) fixture: it listens on port 80 and returns its hostname, allowing the test to prove that HTTP reached this run's pod rather than a different HTTP 200 endpoint. Compute `sdk.sha256` from the independently reviewed wheel: ```sh python tests/r2_e2e/sdk_artifact.py /disk/artifacts/openop_tfsdk-1.1.1-py3-none-any.whl ``` The same script runs read-only in the selected SRM pod. Python/chart contents and package version must match; version 1.1.1 alone cannot identify a commit. Record the reviewed source SHA alongside the wheel fingerprint. The test verifies both SRM/OEG running image digests before creating a test client. Use disk-backed `TMPDIR` and `ANSIBLE_LOCAL_TEMP`. The lab inventory sets `e2e_temp_dir` for controller settings/kubeconfig copies; no /tmp is required. Keep a JUnit report and the generated `.cache/sdk-e2e/<run-id>/run.json`. ## Run the first checkpoint From `ansible/`, using this lab only: ```sh ansible-playbook -i inventory/r2-sdk-lab.local.yml \ playbooks/scenarios/r2_existing_cluster/e2e.yml \ -e '{"e2e_pytest_args":"--sdk-checkpoint=endpoints --sdk-profile=.cache/sdk-profile.json --junitxml=.cache/sdk-endpoints.xml"}' ``` The endpoint case registers one digest-pinned HTTP identity container, deploys it through OEG and polls the public filtered instance read until ready. It requires an `http` endpoint, checks its NodePort against the live Service, checks the same deployment's image and makes a request from the controller to the returned host and port. The response must identify the selected ready pod. It does not inject endpoints or query/write platform databases. Before the run, the workload namespace must contain no Service/Deployment. The scenario uses a fresh application ID and ephemeral scoped Keycloak client. Cleanup uses public OEG deletion, only for this run's application/instances/TI, then removes that client. Cleanup failures are reported alongside the test failure, and no namespace, platform release or unrelated resource is deleted. The current SDK baseline returns no endpoints, so this assertion is expected to fail once a compatible lab is available. That is not a live failure already reproduced by this change. Capture the red baseline before changing the SDK. ## Complete the TI checkpoint separately Select `--sdk-checkpoint=ti` only after the NEF owner agrees and enables the application-wide profile and supplies its subscriptions URL and actual DNAI. Configure the network control path in the lab; the example inventory covers only workload. Never fabricate a device default to satisfy this public request. Set `ti.token_env` to an environment variable name if NEF reads need a bearer token; no token belongs in the profile or report. The TI case repeats the real deployment/endpoint checks, creates public TI by appId/appInstanceId and destination TCP/port, waits for public `active`, then reads the corresponding NEF policy. This bounded profile requires `anyUeInd`, no individual-user selector, one TCP filter to the observed IPv4/port, and the agreed DNAI. It refuses a broader IP-only flow. This is a control-plane test; packet forwarding and notification delivery require separate acceptance. The current app-wide/provider-mode, SRM enrichment and SDK filter gaps can make TI fail independently of SDK endpoint output. Keep those failures owned by their modules rather than changing the SDK to perform SRM inventory selection. ## Fast verification without a lab ```sh python -m pytest tests/unit/test_sdk_acceptance.py python -m pytest --collect-only tests/r2_e2e ``` The first checks the new gate/observation helpers with controlled inputs. The second must retain the same 23 default cases and deselect the two SDK cases. Neither is evidence of a live OEG/SRM/SDK/backend run. No live test is added to the existing CI jobs by this change.
ansible/tests/r2_e2e/conftest.py +38 −0 Changes for ansible/tests/r2_e2e/conftest.py: 38 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -17,6 +17,44 @@ from pathlib import Path import pytest import requests def pytest_addoption(parser): group = parser.getgroup("SDK acceptance") group.addoption("--sdk-checkpoint", choices=("endpoints", "ti"), default=None) group.addoption("--sdk-profile", help="JSON profile for a dedicated SDK acceptance lab") def pytest_configure(config): config.addinivalue_line("markers", "sdk_acceptance(checkpoint): opt-in real SDK lab scenario") if config.getoption("sdk_checkpoint"): from sdk_scenario import load_profile if not config.getoption("sdk_profile"): raise pytest.UsageError("--sdk-checkpoint requires --sdk-profile") try: config.sdk_profile = load_profile( config.getoption("sdk_profile"), config.getoption("sdk_checkpoint") ) except (OSError, ValueError, KeyError, TypeError) as error: raise pytest.UsageError(f"Invalid SDK acceptance profile: {error}") from error elif config.getoption("sdk_profile"): raise pytest.UsageError("--sdk-profile requires --sdk-checkpoint") def pytest_collection_modifyitems(config, items): checkpoint = config.getoption("sdk_checkpoint") selected, deselected = [], [] for item in items: mark = item.get_closest_marker("sdk_acceptance") keep = (mark is None) if checkpoint is None else ( mark is not None and mark.args == (checkpoint,) ) (selected if keep else deselected).append(item) if deselected: config.hook.pytest_deselected(items=deselected) items[:] = selected TIMEOUT = 30 EWBI = "/operatorplatform/federation/v1" FM_CALL = """ Loading