This is an automated email from the ASF dual-hosted git repository.
mfordjody pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git
The following commit(s) were added to refs/heads/master by this push:
new 8b15fb9f Remove outdated product (#919)
8b15fb9f is described below
commit 8b15fb9f1fec22df4b1425958ede35d8bdc87acc
Author: mfordjody <[email protected]>
AuthorDate: Thu Jun 18 22:32:22 2026 +0800
Remove outdated product (#919)
* delete logo
* Update moviereview sample
* add dubbo mesh cni
* Remove outdated product
---
dubbocni/main.go | 139 +++++++
tests/e2e/dubbod-observability.sh | 100 -----
tests/e2e/dubbod-xds-dxload.sh | 84 -----
tests/e2e/multicluster-primary-remote.sh | 122 -------
tools/release_notes/generate_release_notes.py | 401 ---------------------
tools/release_notes/generate_release_notes_test.py | 86 -----
6 files changed, 139 insertions(+), 793 deletions(-)
diff --git a/dubbocni/main.go b/dubbocni/main.go
new file mode 100644
index 00000000..7e512f4e
--- /dev/null
+++ b/dubbocni/main.go
@@ -0,0 +1,139 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "github.com/apache/dubbo-kubernetes/pkg/cni"
+)
+
+func main() {
+ if len(os.Args) > 1 {
+ switch os.Args[1] {
+ case "install":
+ if err := runInstall(os.Args[2:]); err != nil {
+ exitErr(err)
+ }
+ return
+ case "uninstall":
+ if err := runUninstall(os.Args[2:]); err != nil {
+ exitErr(err)
+ }
+ return
+ }
+ }
+
+ stdin, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ exitErr(fmt.Errorf("read stdin: %w", err))
+ }
+
+ conf, err := cni.ParseNetConf(stdin)
+ if err != nil {
+ exitErr(err)
+ }
+ env := cni.EnvFromOS()
+ if env.Command == "VERSION" {
+ out, err := cni.Plugin{}.Run(context.Background(), env, conf)
+ if err != nil {
+ exitErr(err)
+ }
+ if _, err := os.Stdout.Write(out); err != nil {
+ exitErr(fmt.Errorf("write stdout: %w", err))
+ }
+ return
+ }
+
+ plugin := cni.Plugin{
+ RuleManager: cni.NewIPTablesRuleManager(conf),
+ StateStore: cni.NewFileStateStore(conf.StateDirectory()),
+ }
+ if (env.Command == "ADD" || env.Command == "CHECK") &&
cni.EnvHasKubernetesPod(env) {
+ provider, err :=
cni.NewKubernetesPodInfoProvider(conf.KubeConfigPath())
+ if err != nil {
+ exitErr(err)
+ }
+ plugin.PodInfoProvider = provider
+ }
+ out, err := plugin.Run(context.Background(), env, conf)
+ if err != nil {
+ exitErr(err)
+ }
+ if len(out) > 0 {
+ if _, err := os.Stdout.Write(out); err != nil {
+ exitErr(fmt.Errorf("write stdout: %w", err))
+ }
+ }
+}
+
+func runInstall(args []string) error {
+ opts := cni.DefaultInstallerOptions()
+ var watch bool
+ var interval time.Duration
+ flags := installerFlagSet("install", &opts)
+ flags.BoolVar(&watch, "watch", false, "keep installer running and
refresh service account credentials")
+ flags.DurationVar(&interval, "refresh-interval",
cni.DefaultInstallerInterval(), "credential refresh interval")
+ if err := flags.Parse(args); err != nil {
+ return err
+ }
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt,
syscall.SIGTERM)
+ defer stop()
+ if watch {
+ return cni.InstallLoop(ctx, opts, interval)
+ }
+ return cni.Install(ctx, opts)
+}
+
+func runUninstall(args []string) error {
+ opts := cni.DefaultInstallerOptions()
+ flags := installerFlagSet("uninstall", &opts)
+ if err := flags.Parse(args); err != nil {
+ return err
+ }
+ return cni.Uninstall(opts)
+}
+
+func installerFlagSet(name string, opts *cni.InstallerOptions) *flag.FlagSet {
+ flags := flag.NewFlagSet(name, flag.ContinueOnError)
+ flags.StringVar(&opts.BinDir, "bin-dir", opts.BinDir, "host CNI binary
directory")
+ flags.StringVar(&opts.ConfDir, "conf-dir", opts.ConfDir, "host CNI
config directory")
+ flags.StringVar(&opts.StateDir, "state-dir", opts.StateDir, "host state
directory")
+ flags.StringVar(&opts.KubeConfigPath, "kubeconfig",
opts.KubeConfigPath, "host kubeconfig path used by the CNI plugin")
+ flags.StringVar(&opts.TokenFile, "token-file", opts.TokenFile, "host
service account token file")
+ flags.StringVar(&opts.CAFile, "ca-file", opts.CAFile, "host Kubernetes
CA file")
+ flags.StringVar(&opts.ServiceAccountTokenPath, "service-account-token",
opts.ServiceAccountTokenPath, "mounted service account token source")
+ flags.StringVar(&opts.ServiceAccountCAPath, "service-account-ca",
opts.ServiceAccountCAPath, "mounted service account CA source")
+ flags.StringVar(&opts.APIServer, "api-server", opts.APIServer,
"Kubernetes API server URL")
+ flags.StringVar(&opts.ManagedLabel, "managed-label", opts.ManagedLabel,
"label key that marks mesh-managed Pods")
+ flags.StringVar(&opts.ManagedLabelValue, "managed-label-value",
opts.ManagedLabelValue, "label value that marks mesh-managed Pods")
+ flags.StringVar(&opts.IPTablesPath, "iptables-path", opts.IPTablesPath,
"iptables binary used by the CNI plugin")
+ flags.StringVar(&opts.IPSetPath, "ipset-path", opts.IPSetPath, "ipset
binary used by the CNI plugin")
+ flags.IntVar(&opts.XServerPort, "xserver-port", opts.XServerPort,
"xserver inbound port")
+ return flags
+}
+
+func exitErr(err error) {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+}
diff --git a/tests/e2e/dubbod-observability.sh
b/tests/e2e/dubbod-observability.sh
deleted file mode 100755
index d3ef0b1d..00000000
--- a/tests/e2e/dubbod-observability.sh
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/env bash
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-KUBECTL="${KUBECTL:-kubectl}"
-DUBBOD_NAMESPACE="${DUBBOD_NAMESPACE:-dubbo-system}"
-DUBBOD_SERVICE="${DUBBOD_SERVICE:-dubbod}"
-DUBBOD_DEPLOYMENT="${DUBBOD_DEPLOYMENT:-dubbod}"
-ADDONS_DIR="${ADDONS_DIR:-samples/addons}"
-PROMETHEUS_SERVICE="${PROMETHEUS_SERVICE:-prometheus}"
-GRAFANA_SERVICE="${GRAFANA_SERVICE:-grafana}"
-PROMETHEUS_LOCAL_PORT="${PROMETHEUS_LOCAL_PORT:-19090}"
-PROMETHEUS_QUERY="${PROMETHEUS_QUERY:-up{job=\"dubbod\"}}"
-DRY_RUN_ONLY="${DUBBO_OBSERVABILITY_DRY_RUN_ONLY:-false}"
-
-if [[ ! -d "${ADDONS_DIR}" ]]; then
- echo "addons directory not found: ${ADDONS_DIR}" >&2
- exit 1
-fi
-
-"${KUBECTL}" apply --dry-run=client -f "${ADDONS_DIR}" >/dev/null
-
-if [[ "${DRY_RUN_ONLY}" == "true" ]]; then
- exit 0
-fi
-
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" rollout status
"deploy/${DUBBOD_DEPLOYMENT}" --timeout=180s
-"${KUBECTL}" apply -f "${ADDONS_DIR}"
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" rollout status
"deploy/${PROMETHEUS_SERVICE}" --timeout=180s
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" rollout status
"deploy/${GRAFANA_SERVICE}" --timeout=180s
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" get "svc/${PROMETHEUS_SERVICE}"
"svc/${GRAFANA_SERVICE}" >/dev/null
-
-scrape_annotation="$("${KUBECTL}" -n "${DUBBOD_NAMESPACE}" get
"svc/${DUBBOD_SERVICE}" -o
jsonpath='{.metadata.annotations.prometheus\.io/scrape}')"
-metrics_port="$("${KUBECTL}" -n "${DUBBOD_NAMESPACE}" get
"svc/${DUBBOD_SERVICE}" -o
jsonpath='{.metadata.annotations.prometheus\.io/port}')"
-if [[ "${scrape_annotation}" != "true" || "${metrics_port}" != "8080" ]]; then
- echo "dubbod service prometheus annotations invalid:
scrape=${scrape_annotation} port=${metrics_port}" >&2
- exit 1
-fi
-
-pf_log="$(mktemp -t dubbod-observability-prometheus.XXXXXX.log)"
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" port-forward "svc/${PROMETHEUS_SERVICE}"
\
- "${PROMETHEUS_LOCAL_PORT}:9090" >"${pf_log}" 2>&1 &
-pf_pid="$!"
-
-cleanup() {
- if kill -0 "${pf_pid}" >/dev/null 2>&1; then
- kill "${pf_pid}" >/dev/null 2>&1 || true
- wait "${pf_pid}" >/dev/null 2>&1 || true
- fi
- rm -f "${pf_log}"
-}
-trap cleanup EXIT
-
-for _ in $(seq 1 100); do
- if nc -z 127.0.0.1 "${PROMETHEUS_LOCAL_PORT}" >/dev/null 2>&1; then
- break
- fi
- if ! kill -0 "${pf_pid}" >/dev/null 2>&1; then
- cat "${pf_log}" >&2
- exit 1
- fi
- sleep 0.1
-done
-
-if ! nc -z 127.0.0.1 "${PROMETHEUS_LOCAL_PORT}" >/dev/null 2>&1; then
- cat "${pf_log}" >&2
- echo "prometheus port-forward did not become ready" >&2
- exit 1
-fi
-
-python3 - "${PROMETHEUS_LOCAL_PORT}" "${PROMETHEUS_QUERY}" <<'PY'
-import json
-import sys
-import urllib.parse
-import urllib.request
-
-port, query = sys.argv[1], sys.argv[2]
-url = f"http://127.0.0.1:{port}/api/v1/query?" +
urllib.parse.urlencode({"query": query})
-with urllib.request.urlopen(url, timeout=10) as response:
- payload = json.load(response)
-
-if payload.get("status") != "success":
- raise SystemExit(f"prometheus query failed: {payload}")
-if not payload.get("data", {}).get("result"):
- raise SystemExit(f"prometheus query returned no series: {query}")
-PY
diff --git a/tests/e2e/dubbod-xds-dxload.sh b/tests/e2e/dubbod-xds-dxload.sh
deleted file mode 100755
index 7f232f65..00000000
--- a/tests/e2e/dubbod-xds-dxload.sh
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/usr/bin/env bash
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-KUBECTL="${KUBECTL:-kubectl}"
-DUBBOD_NAMESPACE="${DUBBOD_NAMESPACE:-dubbo-system}"
-DUBBOD_SERVICE="${DUBBOD_SERVICE:-dubbod}"
-DUBBOD_DEPLOYMENT="${DUBBOD_DEPLOYMENT:-dubbod}"
-DUBBOD_XDS_PORT="${DUBBOD_XDS_PORT:-26010}"
-DUBBOD_LOCAL_PORT="${DUBBOD_LOCAL_PORT:-26010}"
-DXLOAD_DIR="${DXLOAD_DIR:-/Users/mfordjody/laboratory/opensource/dxload}"
-DXLOAD_CONFIG="${DXLOAD_CONFIG:-${DXLOAD_DIR}/examples/basic.yaml}"
-DXLOAD_DURATION="${DXLOAD_DURATION:-20s}"
-DXLOAD_METRICS_PORT="${DXLOAD_METRICS_PORT:-127.0.0.1:0}"
-
-if [[ ! -d "${DXLOAD_DIR}" ]]; then
- echo "dxload directory not found: ${DXLOAD_DIR}" >&2
- exit 1
-fi
-if [[ ! -f "${DXLOAD_CONFIG}" ]]; then
- echo "dxload config not found: ${DXLOAD_CONFIG}" >&2
- exit 1
-fi
-
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" rollout status
"deploy/${DUBBOD_DEPLOYMENT}" --timeout=120s
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" get "svc/${DUBBOD_SERVICE}" >/dev/null
-
-pf_log="$(mktemp -t dubbod-xds-port-forward.XXXXXX.log)"
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" port-forward "svc/${DUBBOD_SERVICE}" \
- "${DUBBOD_LOCAL_PORT}:${DUBBOD_XDS_PORT}" >"${pf_log}" 2>&1 &
-pf_pid="$!"
-
-cleanup() {
- if kill -0 "${pf_pid}" >/dev/null 2>&1; then
- kill "${pf_pid}" >/dev/null 2>&1 || true
- wait "${pf_pid}" >/dev/null 2>&1 || true
- fi
- rm -f "${pf_log}"
-}
-trap cleanup EXIT
-
-for _ in $(seq 1 100); do
- if nc -z 127.0.0.1 "${DUBBOD_LOCAL_PORT}" >/dev/null 2>&1; then
- break
- fi
- if ! kill -0 "${pf_pid}" >/dev/null 2>&1; then
- cat "${pf_log}" >&2
- exit 1
- fi
- sleep 0.1
-done
-
-if ! nc -z 127.0.0.1 "${DUBBOD_LOCAL_PORT}" >/dev/null 2>&1; then
- cat "${pf_log}" >&2
- echo "dubbod xDS port-forward did not become ready" >&2
- exit 1
-fi
-
-(
- cd "${DXLOAD_DIR}"
- go run . cluster \
- --address "127.0.0.1:${DUBBOD_LOCAL_PORT}" \
- --config "${DXLOAD_CONFIG}" \
- --duration "${DXLOAD_DURATION}" \
- --metrics-port "${DXLOAD_METRICS_PORT}" \
- --fail-on-errors=true \
- --require-responses=true
-)
-
-"${KUBECTL}" -n "${DUBBOD_NAMESPACE}" logs "deploy/${DUBBOD_DEPLOYMENT}"
--since=2m | grep -E "new connection for node|XDS: Pushing|Push Status"
>/dev/null
diff --git a/tests/e2e/multicluster-primary-remote.sh
b/tests/e2e/multicluster-primary-remote.sh
deleted file mode 100755
index 5622ce1b..00000000
--- a/tests/e2e/multicluster-primary-remote.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env bash
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-KUBECTL="${KUBECTL:-kubectl}"
-DUBBOCTL="${DUBBOCTL:-go run ./dubboctl}"
-PRIMARY_CONTEXT="${PRIMARY_CONTEXT:?PRIMARY_CONTEXT is required}"
-REMOTE_CONTEXT="${REMOTE_CONTEXT:?REMOTE_CONTEXT is required}"
-PRIMARY_KUBECONFIG="${PRIMARY_KUBECONFIG:-${KUBECONFIG:-${HOME}/.kube/config}}"
-REMOTE_CLUSTER_NAME="${REMOTE_CLUSTER_NAME:?REMOTE_CLUSTER_NAME is required}"
-REMOTE_WEBHOOK_URL="${REMOTE_WEBHOOK_URL:?REMOTE_WEBHOOK_URL is required}"
-REMOTE_XDS_ADDRESS="${REMOTE_XDS_ADDRESS:?REMOTE_XDS_ADDRESS is required}"
-REMOTE_CA_ADDRESS="${REMOTE_CA_ADDRESS:?REMOTE_CA_ADDRESS is required}"
-CA_BUNDLE_FILE="${CA_BUNDLE_FILE:?CA_BUNDLE_FILE is required}"
-REMOTE_KUBECONFIG="${REMOTE_KUBECONFIG:-${KUBECONFIG:-${HOME}/.kube/config}}"
-MULTICLUSTER_NETWORK_MODE="${MULTICLUSTER_NETWORK_MODE:-cross-network}"
-PRIMARY_CLUSTER_NAME="${PRIMARY_CLUSTER_NAME:-Kubernetes}"
-EASTWEST_GATEWAY_PORT="${EASTWEST_GATEWAY_PORT:-15443}"
-EASTWEST_SERVICE_TYPE="${EASTWEST_SERVICE_TYPE:-NodePort}"
-EASTWEST_NODE_PORT="${EASTWEST_NODE_PORT:-32443}"
-REMOTE_EASTWEST_XDS_ADDRESS="${REMOTE_EASTWEST_XDS_ADDRESS:-}"
-
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" cluster-info >/dev/null
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
cluster-info >/dev/null
-
-if [[ "${MULTICLUSTER_NETWORK_MODE}" == "same-network" ]]; then
- primary_pod_cidrs="$("${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}"
--context "${PRIMARY_CONTEXT}" get nodes \
- -o jsonpath='{range .items[*]}{.spec.podCIDR}{"\n"}{end}' | sort -u)"
- remote_pod_cidrs="$("${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}"
--context "${REMOTE_CONTEXT}" get nodes \
- -o jsonpath='{range .items[*]}{.spec.podCIDR}{"\n"}{end}' | sort -u)"
- if comm -12 <(printf '%s\n' "${primary_pod_cidrs}") <(printf '%s\n'
"${remote_pod_cidrs}") | grep -q .; then
- echo "primary and remote pod CIDRs overlap; same-network multicluster
requires disjoint, routable Pod CIDRs" >&2
- exit 1
- fi
-else
- primary_gateways="$("${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}"
--context "${PRIMARY_CONTEXT}" -n dubbo-system get deploy dubbod \
- -o
jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="DUBBO_EASTWEST_GATEWAYS")].value}')"
- if [[ "${primary_gateways}" != *"${REMOTE_CLUSTER_NAME}="* ||
"${primary_gateways}" != *"${PRIMARY_CLUSTER_NAME}="* ]]; then
- echo "dubbod DUBBO_EASTWEST_GATEWAYS=${primary_gateways}; cross-network
requires ${REMOTE_CLUSTER_NAME}=... and ${PRIMARY_CLUSTER_NAME}=..." >&2
- exit 1
- fi
- if [[ -z "${REMOTE_EASTWEST_XDS_ADDRESS}" ]]; then
- echo "REMOTE_EASTWEST_XDS_ADDRESS is required for cross-network remote
dxgate bootstrap" >&2
- exit 1
- fi
-fi
-
-${DUBBOCTL} multicluster create-remote-secret \
- --cluster-name "${REMOTE_CLUSTER_NAME}" \
- --kubeconfig "${REMOTE_KUBECONFIG}" \
- --context "${REMOTE_CONTEXT}" | "${KUBECTL}" --kubeconfig
"${PRIMARY_KUBECONFIG}" --context "${PRIMARY_CONTEXT}" apply -f -
-
-${DUBBOCTL} multicluster generate-remote-manifest \
- --cluster-name "${REMOTE_CLUSTER_NAME}" \
- --webhook-url "${REMOTE_WEBHOOK_URL}" \
- --xds-address "${REMOTE_XDS_ADDRESS}" \
- --ca-address "${REMOTE_CA_ADDRESS}" \
- --ca-bundle-file "${CA_BUNDLE_FILE}" | "${KUBECTL}" --kubeconfig
"${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}" apply -f -
-
-if [[ "${MULTICLUSTER_NETWORK_MODE}" != "same-network" ]]; then
- gateway_args=(
- multicluster generate-eastwest-gateway
- --xds-address "${REMOTE_EASTWEST_XDS_ADDRESS}"
- --service-type "${EASTWEST_SERVICE_TYPE}"
- --port "${EASTWEST_GATEWAY_PORT}"
- )
- if [[ "${EASTWEST_NODE_PORT}" != "0" ]]; then
- gateway_args+=(--node-port "${EASTWEST_NODE_PORT}")
- fi
- ${DUBBOCTL} "${gateway_args[@]}" | "${KUBECTL}" --kubeconfig
"${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}" apply -f -
-fi
-
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" create namespace app --dry-run=client -o yaml | \
- "${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" apply -f -
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" label namespace app dubbo-injection=enabled --overwrite
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" apply -f samples/app/deployment.yaml
-
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
create namespace app --dry-run=client -o yaml | \
- "${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context
"${REMOTE_CONTEXT}" apply -f -
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
label namespace app dubbo-injection=enabled --overwrite
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
apply -f samples/app/deployment.yaml
-
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" -n app scale deploy/nginx-v2 --replicas=0
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
-n app scale deploy/nginx-v1 --replicas=0
-
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" -n app rollout status deploy/nginx-v1 --timeout=180s
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" -n app rollout status deploy/nginx-v2 --timeout=180s
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" -n app rollout status deploy/nginx-consumer --timeout=180s
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
-n app rollout status deploy/nginx-v1 --timeout=180s
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
-n app rollout status deploy/nginx-v2 --timeout=180s
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
-n app rollout status deploy/nginx-consumer --timeout=180s
-
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" apply -f samples/app/meshservice.yaml
-if [[ "${MULTICLUSTER_NETWORK_MODE}" != "same-network" ]]; then
- "${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" apply -f samples/multicluster/eastwest-nginx-httproute.yaml
-fi
-
-remote_cluster_id="$("${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}"
--context "${REMOTE_CONTEXT}" -n app get pod -l app=nginx-consumer \
- -o
jsonpath='{.items[0].spec.containers[0].env[?(@.name=="DUBBO_META_CLUSTER_ID")].value}')"
-if [[ "${remote_cluster_id}" != "${REMOTE_CLUSTER_NAME}" ]]; then
- echo "remote consumer cluster id = ${remote_cluster_id}, want
${REMOTE_CLUSTER_NAME}" >&2
- exit 1
-fi
-
-"${KUBECTL}" --kubeconfig "${PRIMARY_KUBECONFIG}" --context
"${PRIMARY_CONTEXT}" -n app exec deploy/nginx-consumer -- \
- dubbod xclient --expect v1=50,v2=50 100 | sort | uniq -c
-"${KUBECTL}" --kubeconfig "${REMOTE_KUBECONFIG}" --context "${REMOTE_CONTEXT}"
-n app exec deploy/nginx-consumer -- \
- dubbod xclient --expect v1=50,v2=50 100 | sort | uniq -c
diff --git a/tools/release_notes/generate_release_notes.py
b/tools/release_notes/generate_release_notes.py
deleted file mode 100644
index 1db7afbd..00000000
--- a/tools/release_notes/generate_release_notes.py
+++ /dev/null
@@ -1,401 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from __future__ import annotations
-
-import argparse
-import dataclasses
-import datetime as dt
-import os
-import re
-import subprocess
-from pathlib import Path
-from typing import Iterable
-
-
-VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$")
-PR_RE = re.compile(r"\(#(?P<num>\d+)\)")
-
-
[email protected](frozen=True)
-class Commit:
- subject: str
- sha: str
- author: str
-
-
[email protected](frozen=True)
-class ReleaseContext:
- tag: str
- version: str
- previous_tag: str | None
- repo: str
- date: dt.date
-
-
-CATEGORY_ORDER = [
- ("security", "Security", "安全"),
- ("features", "Features", "新增功能"),
- ("traffic", "Traffic Management", "流量管理"),
- ("installation", "Installation and CLI", "安装与命令行"),
- ("fixes", "Bug Fixes", "问题修复"),
- ("tests", "Tests", "测试"),
- ("docs", "Documentation", "文档"),
- ("other", "Other Changes", "其他变更"),
-]
-
-
-def run_git(args: list[str], cwd: Path) -> str:
- return subprocess.check_output(["git", *args], cwd=cwd, text=True).strip()
-
-
-def parse_version(tag: str) -> tuple[int, int, int]:
- match = VERSION_RE.match(strip_ref(tag))
- if not match:
- raise ValueError(f"tag must be a semantic version such as 0.4.0 or
v0.4.0: {tag}")
- return tuple(int(part) for part in match.groups())
-
-
-def strip_ref(tag: str) -> str:
- return tag.removeprefix("refs/tags/")
-
-
-def display_version(tag: str) -> str:
- return strip_ref(tag).removeprefix("v")
-
-
-def minor_series(version: str) -> str:
- major, minor, _ = parse_version(version)
- return f"{major}.{minor}.x"
-
-
-def english_date(value: dt.date) -> str:
- months = [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ]
- return f"{months[value.month - 1]} {value.day}, {value.year}"
-
-
-def release_kind(version: str) -> tuple[str, str]:
- _, _, patch = parse_version(version)
- if patch == 0:
- return "minor release", "小版本发布"
- return "patch release", "补丁版本发布"
-
-
-def source_changes_url(ctx: ReleaseContext) -> str:
- base = f"https://github.com/{ctx.repo}"
- if ctx.previous_tag:
- return f"{base}/compare/{ctx.previous_tag}...{ctx.tag}"
- return f"{base}/commits/{ctx.tag}"
-
-
-def github_release_url(ctx: ReleaseContext) -> str:
- return f"https://github.com/{ctx.repo}/releases/tag/{ctx.tag}"
-
-
-def previous_tag_for(tag: str, cwd: Path) -> str | None:
- current = parse_version(tag)
- tags = []
- for candidate in run_git(["tag", "--list"], cwd).splitlines():
- try:
- parsed = parse_version(candidate)
- except ValueError:
- continue
- if parsed < current:
- tags.append((parsed, candidate))
- if not tags:
- return None
- tags.sort()
- return tags[-1][1]
-
-
-def git_log_range(ctx: ReleaseContext) -> str:
- if ctx.previous_tag:
- return f"{ctx.previous_tag}..{ctx.tag}"
- return ctx.tag
-
-
-def collect_commits(ctx: ReleaseContext, cwd: Path) -> list[Commit]:
- fmt = "%s%x1f%h%x1f%an"
- range_ref = git_log_range(ctx)
- output = run_git(["log", "--no-merges", f"--pretty=format:{fmt}",
range_ref], cwd)
- if not output:
- output = run_git(["log", f"--pretty=format:{fmt}", range_ref], cwd)
- commits = []
- for line in output.splitlines():
- parts = line.split("\x1f")
- if len(parts) == 3:
- commits.append(Commit(subject=parts[0], sha=parts[1],
author=parts[2]))
- return [commit for commit in commits if not
is_release_marker(commit.subject)]
-
-
-def is_release_marker(subject: str) -> bool:
- normalized = subject.strip().lower()
- return bool(
- re.fullmatch(r"release[-: ]?v?\d+\.\d+\.\d+", normalized)
- or re.fullmatch(r"version v?\d+\.\d+\.\d+.*", normalized)
- or normalized in {"fix version", "bump version", "version bump"}
- )
-
-
-def classify(subject: str) -> str:
- value = subject.lower()
- if any(token in value for token in ["cve", "security", "auth", "jwt",
"tls", "mtls", "rbac"]):
- return "security"
- if any(token in value for token in ["feat", "feature", "add ", "added",
"new "]):
- return "features"
- if any(
- token in value
- for token in [
- "gateway",
- "xds",
- "route",
- "traffic",
- "meshservice",
- "multicluster",
- "east-west",
- "proxyless",
- "eds",
- "lds",
- "rds",
- ]
- ):
- return "traffic"
- if any(token in value for token in ["helm", "chart", "install",
"dubboctl", "operator", "manifest"]):
- return "installation"
- if any(token in value for token in ["fix", "bug", "repair", "resolve",
"correct"]):
- return "fixes"
- if any(token in value for token in ["test", "e2e", "coverage"]):
- return "tests"
- if any(token in value for token in ["doc", "readme", "website"]):
- return "docs"
- return "other"
-
-
-def clean_subject(subject: str) -> str:
- value = subject.strip()
- value =
re.sub(r"^(feat|fix|docs|test|chore|refactor|perf)(\([^)]+\))?:\s*", "", value,
flags=re.I)
- return value[:1].upper() + value[1:] if value else subject
-
-
-def format_commit(commit: Commit, repo: str) -> str:
- subject = clean_subject(commit.subject)
- match = PR_RE.search(subject)
- if match:
- pr = match.group("num")
- subject = PR_RE.sub("", subject).strip()
- return f"{subject} ([#{pr}](https://github.com/{repo}/pull/{pr}))"
- return f"{subject} ({commit.sha})"
-
-
-def grouped_changes(commits: Iterable[Commit], repo: str) -> dict[str,
list[str]]:
- groups: dict[str, list[str]] = {key: [] for key, _, _ in CATEGORY_ORDER}
- seen: set[str] = set()
- for commit in commits:
- item = format_commit(commit, repo)
- if item in seen:
- continue
- seen.add(item)
- groups[classify(commit.subject)].append(item)
- return {key: values for key, values in groups.items() if values}
-
-
-def render_changes(groups: dict[str, list[str]], language: str) -> str:
- if not groups:
- return "- No user-facing changes were detected in the commit range.\n"
if language == "en" else "- 未检测到面向用户的变更。\n"
- lines: list[str] = []
- for key, en_title, zh_title in CATEGORY_ORDER:
- items = groups.get(key)
- if not items:
- continue
- title = en_title if language == "en" else zh_title
- lines.append(f"### {title}")
- lines.append("")
- for item in items:
- lines.append(f"- {item}")
- lines.append("")
- return "\n".join(lines).rstrip() + "\n"
-
-
-def render_release_body(ctx: ReleaseContext, commits: list[Commit]) -> str:
- en_kind, _ = release_kind(ctx.version)
- groups = grouped_changes(commits, ctx.repo)
- previous = f"Kdubbo {display_version(ctx.previous_tag)}" if
ctx.previous_tag else "the initial release"
- return f"""# Announcing Kdubbo {ctx.version}
-
-Kdubbo {ctx.version} {en_kind}.
-
-{english_date(ctx.date)}
-
-This release note describes what is different between {previous} and Kdubbo
{ctx.version}.
-
-[Before you upgrade](https://kdubbo.github.io/operation/) |
[Download]({github_release_url(ctx)}) | [Docs](https://kdubbo.github.io/) |
[Source Changes]({source_changes_url(ctx)})
-
-## Changes
-
-{render_changes(groups, "en")}"""
-
-
-def render_docs_page(ctx: ReleaseContext, commits: list[Commit], language:
str) -> str:
- en_kind, zh_kind = release_kind(ctx.version)
- groups = grouped_changes(commits, ctx.repo)
- if language == "en":
- previous = f"Kdubbo {display_version(ctx.previous_tag)}" if
ctx.previous_tag else "the initial release"
- return f"""# Announcing Kdubbo {ctx.version}
-
-Kdubbo {ctx.version} {en_kind}.
-
-{english_date(ctx.date)}
-
-This release note describes what is different between {previous} and Kdubbo
{ctx.version}.
-
-[Before you upgrade](../../../operation/) |
[Download]({github_release_url(ctx)}) | [Docs](https://kdubbo.github.io/) |
[Source Changes]({source_changes_url(ctx)})
-
-## Changes
-
-{render_changes(groups, "en")}"""
-
- previous_zh = f"Kdubbo {display_version(ctx.previous_tag)}" if
ctx.previous_tag else "初始版本"
- return f"""# Kdubbo {ctx.version} 发布公告
-
-Kdubbo {ctx.version} {zh_kind}。
-
-发布日期:{ctx.date.isoformat()}
-
-本发布说明描述 {previous_zh} 和 Kdubbo {ctx.version} 之间的差异。
-
-[升级前注意事项](../../../operation/) | [下载]({github_release_url(ctx)}) |
[文档](https://kdubbo.github.io/) | [源码变更]({source_changes_url(ctx)})
-
-## 变更
-
-{render_changes(groups, "zh")}"""
-
-
-def docs_paths(docs_root: Path, version: str) -> tuple[Path, Path]:
- base = docs_root / "latest" / "release" / minor_series(version)
- return base / f"announcing-{version}.md", base /
f"announcing-{version}.en.md"
-
-
-def render_index_entry(ctx: ReleaseContext, commits: list[Commit]) -> str:
- groups = grouped_changes(commits, ctx.repo)
- first_items = []
- for key, _, zh_title in CATEGORY_ORDER:
- if key in groups:
- first_items.append(f"{zh_title}:{groups[key][0]}")
- if len(first_items) == 3:
- break
- summary = "\n".join(f" - {item}" for item in first_items) or " -
查看完整发布说明"
- page = f"{minor_series(ctx.version)}/announcing-{ctx.version}.md"
- return f""" ### [{ctx.version}]({page})
-
- 发布日期:{ctx.date.isoformat()}
-
-{summary}
-
- [查看完整发布说明]({page})
- [查看 GitHub Release]({github_release_url(ctx)})
-
- ---"""
-
-
-def update_docs_index(docs_root: Path, ctx: ReleaseContext, commits:
list[Commit]) -> None:
- index = docs_root / "latest" / "release" / "index.md"
- start = " <!-- KDUBBO_RELEASE_NOTES:START -->"
- end = " <!-- KDUBBO_RELEASE_NOTES:END -->"
- entry = render_index_entry(ctx, commits)
- content = index.read_text(encoding="utf-8")
- if start in content and end in content:
- before, rest = content.split(start, 1)
- block, after = rest.split(end, 1)
- old_entries = [part.strip("\n") for part in block.strip().split("\n\n
---\n\n") if part.strip()]
- old_entries = [part for part in old_entries if
f"announcing-{ctx.version}.md" not in part]
- normalized = [entry.rstrip()]
- for part in old_entries:
- normalized.append(part.rstrip() + "\n\n ---")
- new_block = "\n\n" + "\n\n".join(normalized).rstrip() + "\n\n"
- index.write_text(before + start + new_block + end + after,
encoding="utf-8")
- return
-
- anchor = '=== "发布公告"'
- if anchor not in content:
- raise ValueError(f"{index} does not contain the release announcement
tab")
- replacement = f'{anchor}\n\n{start}\n\n{entry}\n\n{end}\n'
- index.write_text(content.replace(anchor, replacement, 1), encoding="utf-8")
-
-
-def write_docs(docs_root: Path, ctx: ReleaseContext, commits: list[Commit]) ->
None:
- zh_path, en_path = docs_paths(docs_root, ctx.version)
- zh_path.parent.mkdir(parents=True, exist_ok=True)
- zh_path.write_text(render_docs_page(ctx, commits, "zh"), encoding="utf-8")
- en_path.write_text(render_docs_page(ctx, commits, "en"), encoding="utf-8")
- update_docs_index(docs_root, ctx, commits)
-
-
-def build_context(args: argparse.Namespace, cwd: Path) -> ReleaseContext:
- tag = strip_ref(args.tag or os.environ.get("GITHUB_REF_NAME", ""))
- if not tag:
- tag = strip_ref(run_git(["describe", "--tags", "--abbrev=0"], cwd))
- version = display_version(tag)
- parse_version(version)
- previous_tag = args.previous_tag if args.previous_tag is not None else
previous_tag_for(tag, cwd)
- date_value = dt.date.fromisoformat(args.date) if args.date else
dt.date.today()
- repo = args.repo or os.environ.get("GITHUB_REPOSITORY",
"apache/dubbo-kubernetes")
- return ReleaseContext(tag=tag, version=version, previous_tag=previous_tag,
repo=repo, date=date_value)
-
-
-def parse_args() -> argparse.Namespace:
- parser = argparse.ArgumentParser(description="Generate Kdubbo release
notes and kdubbo.github.io pages.")
- parser.add_argument("--tag", help="Release tag, for example 0.4.0 or
v0.4.0.")
- parser.add_argument("--previous-tag", help="Previous release tag.
Auto-detected from git tags when omitted.")
- parser.add_argument("--repo", help="GitHub repository, for example
apache/dubbo-kubernetes.")
- parser.add_argument("--date", help="Release date in YYYY-MM-DD format.
Defaults to today.")
- parser.add_argument("--release-body", type=Path, help="Write the GitHub
Release body markdown to this file.")
- parser.add_argument("--docs-root", type=Path, help="Path to
kdubbo.github.io/docs to update.")
- parser.add_argument("--print", action="store_true", help="Print the GitHub
Release body to stdout.")
- return parser.parse_args()
-
-
-def main() -> int:
- args = parse_args()
- cwd = Path.cwd()
- ctx = build_context(args, cwd)
- commits = collect_commits(ctx, cwd)
- body = render_release_body(ctx, commits)
- if args.release_body:
- args.release_body.parent.mkdir(parents=True, exist_ok=True)
- args.release_body.write_text(body, encoding="utf-8")
- if args.docs_root:
- write_docs(args.docs_root, ctx, commits)
- if args.print:
- print(body)
- return 0
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/tools/release_notes/generate_release_notes_test.py
b/tools/release_notes/generate_release_notes_test.py
deleted file mode 100644
index 8e5a2d7b..00000000
--- a/tools/release_notes/generate_release_notes_test.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from __future__ import annotations
-
-import datetime as dt
-import importlib.util
-from pathlib import Path
-import sys
-import tempfile
-import unittest
-
-
-MODULE_PATH = Path(__file__).with_name("generate_release_notes.py")
-SPEC = importlib.util.spec_from_file_location("generate_release_notes",
MODULE_PATH)
-assert SPEC and SPEC.loader
-release_notes = importlib.util.module_from_spec(SPEC)
-sys.modules[SPEC.name] = release_notes
-SPEC.loader.exec_module(release_notes)
-
-
-class ReleaseNotesTest(unittest.TestCase):
- def context(self):
- return release_notes.ReleaseContext(
- tag="0.4.1",
- version="0.4.1",
- previous_tag="0.4.0",
- repo="apache/dubbo-kubernetes",
- date=dt.date(2026, 5, 18),
- )
-
- def commits(self):
- return [
- release_notes.Commit("feat: Add multicluster east-west gateway
(#910)", "abc1234", "dev"),
- release_notes.Commit("fix: Repair remote webhook CA bundle
(#911)", "def5678", "dev"),
- release_notes.Commit("docs: Update install guide", "abcd999",
"dev"),
- ]
-
- def test_release_body_uses_istio_style_sections(self):
- body = release_notes.render_release_body(self.context(),
self.commits())
-
- self.assertIn("# Announcing Kdubbo 0.4.1", body)
- self.assertIn("Kdubbo 0.4.1 patch release.", body)
- self.assertIn("May 18, 2026", body)
- self.assertIn("[Before you upgrade]", body)
- self.assertIn("[Source
Changes](https://github.com/apache/dubbo-kubernetes/compare/0.4.0...0.4.1)",
body)
- self.assertIn("### Features", body)
-
self.assertIn("[#910](https://github.com/apache/dubbo-kubernetes/pull/910)",
body)
-
- def test_writes_bilingual_docs_and_index_entry(self):
- with tempfile.TemporaryDirectory() as temp:
- docs = Path(temp) / "docs"
- release_dir = docs / "latest" / "release"
- release_dir.mkdir(parents=True)
- (release_dir / "index.md").write_text('# 公告栏\n\n=== "发布公告"\n\n
## 0.3.x\n', encoding="utf-8")
-
- release_notes.write_docs(docs, self.context(), self.commits())
-
- zh = release_dir / "0.4.x" / "announcing-0.4.1.md"
- en = release_dir / "0.4.x" / "announcing-0.4.1.en.md"
- index = release_dir / "index.md"
-
- self.assertTrue(zh.exists())
- self.assertTrue(en.exists())
- self.assertIn("# Kdubbo 0.4.1 发布公告",
zh.read_text(encoding="utf-8"))
- self.assertIn("# Announcing Kdubbo 0.4.1",
en.read_text(encoding="utf-8"))
- self.assertIn("KDUBBO_RELEASE_NOTES:START",
index.read_text(encoding="utf-8"))
- self.assertIn("[0.4.1](0.4.x/announcing-0.4.1.md)",
index.read_text(encoding="utf-8"))
-
-
-if __name__ == "__main__":
- unittest.main()