You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/10/23 08:34:14 UTC
(skywalking-horizon-ui) branch main updated: ci: run CI and e2e on ubuntu-26.04, with infra-e2e importing kind images per platform (#206)
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
The following commit(s) were added to refs/heads/main by this push:
new 8756ee18 ci: run CI and e2e on ubuntu-26.04, with infra-e2e importing kind images per platform (#206)
8756ee18 is described below
commit 8756ee18cd1ef299721953256874932a92a2df44
Author: 吴晟 Wu Sheng <wu...@foxmail.com>
AuthorDate: Fri Oct 23 16:34:09 2026 +0800
ci: run CI and e2e on ubuntu-26.04, with infra-e2e importing kind images per platform (#206)
Docker 29 (ubuntu-26.04 runners) uses the containerd image store, where a
pulled multi-platform image holds only the host platform; importing every
platform into kind failed for the OAP image ("content digest ... not
found"). Saved for one platform, the archive also lists the image's
referrers, such as attestations, at its top level, and the containerd 1.7
kindest/node:v1.28.15 these cases pin imports each one as an image, which
failed for the Rover image ("mismatched image rootfs and manifest layers").
infra-e2e now saves only the node platform and leaves the referrers out of
the archive (apache/skywalking-infra-e2e#150, #151), so the e2e cases leave
the ubuntu-24.04 pin #205 added.
The CI and e2e jobs name ubuntu-26.04 rather than ubuntu-latest:
ubuntu-latest jobs sat queued with no runner while jobs naming ubuntu-24.04
or ubuntu-26.04 started within seconds.
The istio mesh fixture is fully known, so its specs now require all of it
and accept only `UNKNOWN` beyond it. The mesh spec asserts the service
picker lists exactly the five workloads (bookinfo's four and the ingress
gateway) plus, optionally, `UNKNOWN`; the topology spec requires a node for
each workload by name; and the roster readiness check waits until Horizon
lists all five. MESH_PEERS, which left `ratings` out, is gone.
`UNKNOWN` is accepted as the service the MESH header opens on
(MESH_HEADER_SERVICES, in the mesh and eBPF profiling specs). Since the e2e
OAP pin moved past apache/skywalking#14135 (#204), OAP reads the Kubernetes
metadata of a new pod address in the background, so bookinfo's first calls
count under `UNKNOWN`; in the first minute of traffic that burst can rank
first by calls per minute, and the header then picks it. It failed so on
main at ed6171da (ubuntu-24.04) as well as here.
---
.github/workflows/ci.yaml | 8 ++---
.github/workflows/e2e.yaml | 9 ++----
.../cases/istio/expected/horizon-mesh-roster.yml | 2 +-
test/e2e/cases/istio/verify-mesh.yaml | 7 +++--
test/e2e/playwright/specs/fixture.ts | 34 ++++++++++------------
.../playwright/specs/istio-profiling/ebpf.spec.ts | 4 +--
test/e2e/playwright/specs/istio/mesh.spec.ts | 27 +++++++++--------
7 files changed, 45 insertions(+), 46 deletions(-)
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 2d945e2d..75a3f842 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -32,7 +32,7 @@ jobs:
# container action that would otherwise sit behind the toolchain setup the
# build job spends its first half-minute on.
name: License header
- runs-on: ubuntu-latest
+ runs-on: ubuntu-26.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
@@ -48,7 +48,7 @@ jobs:
# the setup cost eight times over. Steps run cheapest-first so a lint or
# type error reports before the package build starts.
name: Build and check
- runs-on: ubuntu-latest
+ runs-on: ubuntu-26.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
@@ -252,7 +252,7 @@ jobs:
# `test/e2e/script/env` pins, on an ephemeral port, and stops it after. No
# service container to declare and no port to collide with.
name: BanyanDB client IT
- runs-on: ubuntu-latest
+ runs-on: ubuntu-26.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
@@ -278,7 +278,7 @@ jobs:
if: always() && !cancelled()
name: Required
needs: [license-header, build-and-check, banyandb-client-it]
- runs-on: ubuntu-latest
+ runs-on: ubuntu-26.04
timeout-minutes: 5
steps:
- name: Check upstream jobs
diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 79c13f22..931b2ae6 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -225,10 +225,7 @@ concurrency:
jobs:
e2e:
name: ${{ matrix.case.name }}
- # Not ubuntu-latest: on ubuntu-26.04, kind cannot load the multi-platform
- # OAP image into its node (`ctr images import --all-platforms`: content
- # digest not found), so the kind cases fail before any test runs.
- runs-on: ubuntu-24.04
+ runs-on: ubuntu-26.04
timeout-minutes: 45
strategy:
# One case failing must not cancel the others: when several go red at
@@ -292,7 +289,7 @@ jobs:
run: make -C test/e2e images
- name: ${{ matrix.case.name }}
- uses: apache/skywalking-infra-e2e@68bbdec303cf2d3df9f2353f970f91938b52f674
+ uses: apache/skywalking-infra-e2e@56ac8befeb5f910f2dc9339e65d07cd18953f90e
with:
e2e-file: $GITHUB_WORKSPACE/${{ matrix.case.config }}
# Container logs land here. On a red run they answer the first
@@ -330,7 +327,7 @@ jobs:
name: E2E Required
if: always() && !cancelled()
needs: [e2e]
- runs-on: ubuntu-latest
+ runs-on: ubuntu-26.04
steps:
- name: Assert every case passed
run: |
diff --git a/test/e2e/cases/istio/expected/horizon-mesh-roster.yml b/test/e2e/cases/istio/expected/horizon-mesh-roster.yml
index 6f7c77fd..5396b19d 100644
--- a/test/e2e/cases/istio/expected/horizon-mesh-roster.yml
+++ b/test/e2e/cases/istio/expected/horizon-mesh-roster.yml
@@ -16,4 +16,4 @@
# under the License.
reachable: true
-meshServices: 1
+meshServices: 5
diff --git a/test/e2e/cases/istio/verify-mesh.yaml b/test/e2e/cases/istio/verify-mesh.yaml
index 1b9b2253..86511951 100644
--- a/test/e2e/cases/istio/verify-mesh.yaml
+++ b/test/e2e/cases/istio/verify-mesh.yaml
@@ -43,12 +43,13 @@ cases:
)
expected: expected/metrics-has-value.yml
- # 3. READINESS: Horizon can see the mesh roster too, absorbing the catalog TTL
- # before the browser project pays for it.
+ # 3. READINESS: Horizon's mesh roster holds every workload the fixture
+ # produces (MESH_SERVICES in the specs), which the mesh spec asserts
+ # whole, absorbing the catalog TTL before the browser project pays for it.
- query: |
bash test/e2e/script/prepare/horizon-get.sh \
"http://${service_horizon_host}:${service_horizon_8081}" /api/layer/mesh/services \
- | yq -P '{"reachable": .reachable, "meshServices": [.services[] | select(.name == "productpage")] | length}'
+ | yq -P '{"reachable": .reachable, "meshServices": [.services[] | select(.name == "productpage" or .name == "reviews" or .name == "details" or .name == "ratings" or .name == "istio-ingressgateway")] | length}'
expected: expected/horizon-mesh-roster.yml
# 3b. READINESS: the mesh topology has EDGES. The service roster above proves
diff --git a/test/e2e/playwright/specs/fixture.ts b/test/e2e/playwright/specs/fixture.ts
index 48e13d65..4d510a9c 100644
--- a/test/e2e/playwright/specs/fixture.ts
+++ b/test/e2e/playwright/specs/fixture.ts
@@ -124,29 +124,27 @@ export const WINDOW_MINUTES = 30;
export const MESH_LAYER = 'mesh';
/**
- * bookinfo's workloads as MESH services. Matched as a SET, never pinned to
- * one: which service the layer header auto-resolves follows OAP's ordering,
- * so naming a single one fails on a healthy mesh whenever traffic shifts.
- *
- * `ratings` is deliberately absent. Only reviews-v2 and v3 call it, so
- * whether it has appeared yet depends on which reviews version the traffic
- * happened to reach — true of a healthy mesh, and not something to assert.
+ * EVERY service this fixture's mesh produces: all four bookinfo workloads
+ * plus the ingress gateway bookinfo is reached through, which ALS reports
+ * like any other mesh workload. The mesh is fully known, so the mesh spec
+ * asserts the roster against this whole and the topology must draw every one;
+ * the istio readiness check waits until Horizon lists all of them.
*/
-export const MESH_PEERS = ['productpage', 'reviews', 'details'];
+export const MESH_SERVICES = ['productpage', 'reviews', 'details', 'ratings', 'istio-ingressgateway'];
/**
- * EVERY service this fixture's mesh can produce: all four bookinfo workloads
- * plus the ingress gateway bookinfo is reached through, which ALS reports
- * like any other mesh workload.
+ * The services the MESH layer header may open on: MESH_SERVICES plus OAP's
+ * `UNKNOWN`. Which one it picks follows OAP's ordering, so it is matched as a
+ * set. OAP reads the Kubernetes metadata of a new pod address in the
+ * background rather than waiting for it (apache/skywalking#14135), so
+ * bookinfo's first calls count under `UNKNOWN`, and in the first minute of
+ * traffic that burst can rank above every workload.
*
- * Distinct from MESH_PEERS on purpose. `ratings` is reached only via
- * reviews-v2/v3, so whether it exists in a given window depends on which
- * review version the traffic hit — it cannot be REQUIRED to be listed, but it
- * can certainly be the one the header resolves to. Which service that is
- * follows OAP's ordering and changes between runs, so anything asserting on
- * the SELECTED service must accept the full set.
+ * Only for the header's pick. `UNKNOWN` is the one service outside
+ * MESH_SERVICES the roster may hold, and a roster missing any workload still
+ * fails the mesh spec.
*/
-export const MESH_SERVICES = [...MESH_PEERS, 'ratings', 'istio-ingressgateway'];
+export const MESH_HEADER_SERVICES = [...MESH_SERVICES, 'UNKNOWN'];
/**
* The SAME workload in the MESH_DP layer, which names services
diff --git a/test/e2e/playwright/specs/istio-profiling/ebpf.spec.ts b/test/e2e/playwright/specs/istio-profiling/ebpf.spec.ts
index cfca988e..aea6c004 100644
--- a/test/e2e/playwright/specs/istio-profiling/ebpf.spec.ts
+++ b/test/e2e/playwright/specs/istio-profiling/ebpf.spec.ts
@@ -16,7 +16,7 @@
*/
import { test, expect } from '../support/diagnostics.js';
-import { MESH_LAYER, MESH_SERVICES } from '../fixture.js';
+import { MESH_LAYER, MESH_HEADER_SERVICES } from '../fixture.js';
// Profiling pages, which exist only where rover does.
//
@@ -43,7 +43,7 @@ test('the eBPF profiling page mounts and resolves a service', async ({ page, pag
// ours.
const selected = page.locator('.service-row .svc-name');
await expect(selected).toBeVisible({ timeout: 60_000 });
- await expect(selected).toHaveText(new RegExp(`^(${MESH_SERVICES.join('|')})$`));
+ await expect(selected).toHaveText(new RegExp(`^(${MESH_HEADER_SERVICES.join('|')})$`));
expect(pageErrors, 'an uncaught error during mount blanks the page').toEqual([]);
});
diff --git a/test/e2e/playwright/specs/istio/mesh.spec.ts b/test/e2e/playwright/specs/istio/mesh.spec.ts
index c43eb404..b7bcea3d 100644
--- a/test/e2e/playwright/specs/istio/mesh.spec.ts
+++ b/test/e2e/playwright/specs/istio/mesh.spec.ts
@@ -18,8 +18,8 @@
import { test, expect } from '../support/diagnostics.js';
import {
MESH_LAYER,
- MESH_PEERS,
MESH_SERVICES,
+ MESH_HEADER_SERVICES,
MESH_DP_LAYER,
MESH_DP_SUFFIX,
} from '../fixture.js';
@@ -41,17 +41,19 @@ test('the mesh layer lists services built from Envoy access logs', async ({ page
// member of the known set instead.
const selected = page.locator('.service-row .svc-name');
await expect(selected).toBeVisible({ timeout: 45_000 });
- await expect(selected).toHaveText(new RegExp(`^(${MESH_SERVICES.join('|')})$`));
+ await expect(selected).toHaveText(new RegExp(`^(${MESH_HEADER_SERVICES.join('|')})$`));
// The roster itself is the real assertion: every one of these exists only
// because OAP analysed Envoy access logs into mesh entities. No agent
- // reported any of them.
+ // reported any of them. The fixture's mesh is fully known, so the roster is
+ // asserted whole: every workload, and nothing else but `UNKNOWN`.
await page.locator('.service-row button.switch').click();
const picker = page.locator('.picker-table');
await expect(picker).toBeVisible();
- for (const peer of MESH_PEERS) {
- await expect(picker.locator('tr.row', { hasText: peer }).first()).toBeVisible();
- }
+ const names = picker.locator('tr.row .name-text');
+ await expect
+ .poll(async () => (await names.allTextContents()).map((n) => n.trim()).filter((n) => n !== 'UNKNOWN').sort())
+ .toEqual([...MESH_SERVICES].sort());
expect(pageErrors, 'an uncaught error during mount blanks the page').toEqual([]);
});
@@ -62,12 +64,13 @@ test('the mesh topology draws the bookinfo call graph', async ({ page, pageError
const nodes = page.locator('.sm-node');
await expect(nodes.first()).toBeVisible({ timeout: 60_000 });
- // bookinfo is a four-service graph on purpose: with two services a single
- // edge drawn twice is indistinguishable from a real topology. Requiring
- // more than one peer is what makes this assert a GRAPH.
- await expect
- .poll(async () => nodes.count(), { timeout: 60_000 })
- .toBeGreaterThanOrEqual(MESH_PEERS.length);
+ // bookinfo is a multi-service graph on purpose: with two services a single
+ // edge drawn twice is indistinguishable from a real topology. The mesh is
+ // fully known, so every workload must be drawn, by name — a count would let
+ // `UNKNOWN` stand in for a missing one.
+ for (const name of MESH_SERVICES) {
+ await expect(nodes.filter({ hasText: name }).first()).toBeVisible({ timeout: 60_000 });
+ }
await expect(page.locator('.sm-edge').first()).toBeVisible({ timeout: 60_000 });
expect(pageErrors).toEqual([]);