diff --git a/.github/workflows/_backend.yml b/.github/workflows/_backend.yml index a73c405244..de4d8b0681 100644 --- a/.github/workflows/_backend.yml +++ b/.github/workflows/_backend.yml @@ -36,6 +36,42 @@ jobs: - name: Build with Maven run: cd services && ./mvnw compile -B + - name: Verify reproducible patched Nacos generation + run: cd services && ./mvnw -B -pl platform/nacos-api-patched -Pverify-nacos-generation verify + + - name: Verify upstream Nacos SBOM identity + run: | + python3 -m unittest discover -s scripts/test -p test_nacos_sbom.py -v + python3 scripts/ci/check-nacos-sbom.py services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/bom.json + + - name: Scan upstream Nacos SBOM identity + id: nacos-sbom-scan + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 (Trivy v0.74.0) + with: + scan-type: sbom + list-all-pkgs: 'true' + scan-ref: services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/bom.json + scanners: vuln + vuln-type: library + severity: HIGH,CRITICAL + exit-code: '1' + format: json + output: ${{ runner.temp }}/nacos-api-pedigree.trivy.json + version: v0.74.0 + + - name: Verify Trivy recognized upstream Nacos + if: ${{ always() && steps.nacos-sbom-scan.outcome != 'skipped' && steps.nacos-sbom-scan.outcome != '' }} + run: python3 scripts/ci/check-nacos-sbom.py services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/bom.json --report "$RUNNER_TEMP/nacos-api-pedigree.trivy.json" + + - name: Upload upstream Nacos scan inventory + if: ${{ always() && steps.nacos-sbom-scan.outcome != 'skipped' && steps.nacos-sbom-scan.outcome != '' }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: nacos-api-pedigree-scan + path: ${{ runner.temp }}/nacos-api-pedigree.trivy.json + if-no-files-found: error + retention-days: 30 + backend-test: name: Backend Test needs: backend-build @@ -96,6 +132,11 @@ jobs: - name: Run tests with CI profile run: cd services && ./mvnw verify -Dspring.profiles.active=ci -Dtest='!*IT' -B + - name: Verify patched client against disposable authenticated Nacos + run: | + python3 -m unittest discover -s scripts/test -p test_nacos_client_integration.py -v + bash scripts/test/nacos-client-integration.sh + - name: Upload test results on failure if: failure() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 351e318907..28de8c947a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -385,6 +385,12 @@ WebSocket 只从 handshake 的 `access_token` cookie 获取 token,不接受 qu Redis ACL deny-by-default,按 Owner 限制命令、key 和 channel;运行时 ACL 在 ignored directory 原子物化,轮换使用 overlap/finalize/rollback。命令白名单授予 `+scan` 而不授予 `+keys`,因此 Spring Cache 的 Redis writer 必须使用 SCAN 批量策略(共享 `RedisCacheWritePolicy`);默认的 `KEYS` writer 会让 `@CacheEvict(allEntries = true)` 与 `Cache#clear()` 以 `NOPERM` 失败。生产 Compose 不挂载 Docker socket;Judge 使用部署拥有的 remote/rootless Docker TLS endpoint、只读证书和共享 workspace。真实证书、私钥、密码、token、生产 endpoint 和 secret-store 状态不得提交或写入文档。 +### 注册中心协议解析边界 + +应用使用独立坐标的 Nacos API 衍生包修复客户端 protobuf 生成代码,仍保留上游来源和版本血缘;构建及兼容门禁见 [开发指南](DEVELOPMENT.md#nacos-客户端协议构建与兼容验证)。客户端包与独立 Nacos 服务端镜像是不同安全边界。服务端 gRPC 解码发生在请求鉴权之前,开启鉴权不能代替解析器修复;私有网络和开发 loopback 限制暴露面,但不能保护同网络或本机可达路径。服务端开放问题单独记录在 [Services issue registry](../services/docs/SERVICES_ISSUES.md),不得把客户端测试成功表述成服务端漏洞已修复。 + +配置监听的协议兼容与 workload 最小权限属于不同验证边界。独占合成数据 namespace 的测试账号可以按服务端接口要求拥有该 namespace 的配置读取权限,但不得把这一测试授权推广为生产权限或生产兼容证据;原 workload 监听限制见 [SVC-030](../services/docs/SERVICES_ISSUES.md)。 + ### 证据入口 - [Security invariants](../AGENTS.md#security-invariants) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 65f5c0f50e..7b3069ffae 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -671,6 +671,29 @@ CI/CD 契约的 provenance 测试数据必须由测试在临时目录生成或 这些检查验证流水线实现与离线失败路径,不代表已运行远程 GitHub Actions、签名发布或真实环境部署。Environment 审批、分支保护和部署凭据的启用要求见 [`OPERATIONS.md`](OPERATIONS.md#cicd-入口)。 +### Nacos 客户端协议构建与兼容验证 + +`services/platform/nacos-api-patched` 以独立的 `com.ulticode:nacos-api-patched` 坐标构建客户端 API 衍生包,不发布到上游坐标或覆盖上游 artifact。构建校验锁定的原始 JAR、POM、协议和生成源码 SHA-256,仅替换 protobuf 生成类,保留其他上游字节;包内保留原始来源、许可证和 CycloneDX 血缘;CI 额外扫描上游普通组件并核对 Trivy 实际识别结果,不能因为改用自有坐标而丢失扫描覆盖。普通构建编译已提交的生成源码,不需要本机 protoc。更改协议或工具链时必须同时更新来源、生成结果和校验值,不得把不匹配改为警告。 + +```bash +# Linux x86_64 上再生成并逐字节校验;其他已锁定平台用 nacos.protoc.classifier 选择。 +(cd services && ./mvnw -B -pl platform/nacos-api-patched -Pverify-nacos-generation verify) +# 严格解析与类来源单测;不启动基础设施。 +(cd services && ./mvnw -B -pl platform/rpc-resilience -am -Punit verify) +# Docker Compose 本机隔离集成;无需已有 .env,不访问现有实例。 +bash scripts/test/nacos-client-integration.sh +# 编排失败与清理路径的零基础设施回归。 +python3 scripts/test/test_nacos_client_integration.py +``` + +再生成使用锁定的 protoc 版本,避免破坏已编译 Nacos/gRPC 调用者的方法签名;不能只按运行时版本号选择最新生成器。单测与真实客户端均启用 `com.google.protobuf.error_on_unsafe_pre22_gencode`,禁止用 `use_unsafe_pre22_gencode` 隐藏旧代码。集成脚本生成临时凭据、独立 Compose project 与 loopback 端口,验证注册、发现、配置/元数据回调、错误及缺失凭据拒绝、断线检测与自动重连/重订阅,并清理所属容器、卷和私有日志。它拒绝远端 Docker daemon;容器或工具不可用时失败,不跳过。后端 CI 同时要求再生成校验和真实注册中心集成通过。 + +配置监听验证分为两个不可替代的对照:原 AUTH workload ACL 不变,继续验证 naming 与直接配置读写,并要求其标准批量监听请求明确返回 403;另一个随机凭据的测试账号只在独占、仅含合成数据的 namespace 内拥有配置读取权限和固定 dataId 写权限,使用普通 SDK listener 验证变更回调与重连。该读取权限覆盖测试 namespace 的所有 group,不是“仅监听”权限;不得复制到生产。跨 namespace 的直接读取和范围外写入仍须拒绝,测试账号、数据与资源随 Compose project 销毁。 + +生命周期检查在关闭前捕获原 RPC、通道和 SDK 声明关闭的执行器,验证其终止及 factory 移除;关闭后调用配置客户端的 health getter 可能重新创建连接,不能用作无副作用的关闭断言。上游 listener 同步线程池的保留须单独记录,不能把临时 JVM 退出说成 SDK 完整释放。 + +此补丁只覆盖应用进程加载的 Nacos 客户端 API,不改变独立 Nacos 服务端镜像、生产 workload ACL 或 SDK 生命周期实现。测试专用授权下的回调通过不代表原生产角色的监听兼容;服务端解析器风险、生产监听限制与 listener 线程池保留分别见 [SVC-029 / SVC-030 / SVC-031](../services/docs/SERVICES_ISSUES.md)。 + ### Core 与 distributed 验证边界 | 层级 | 环境/副作用 | 能证明什么 | 明确不能证明 | diff --git a/scripts/ci/check-nacos-sbom.py b/scripts/ci/check-nacos-sbom.py new file mode 100644 index 0000000000..f5072e5966 --- /dev/null +++ b/scripts/ci/check-nacos-sbom.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Fail closed when the upstream Nacos identity is absent from SBOM/scan inventory.""" +import argparse +import json +from pathlib import Path +import sys + +UPSTREAM = "pkg:maven/com.alibaba.nacos/nacos-api@2.5.1" +PATCHED = "pkg:maven/com.ulticode/nacos-api-patched@2.5.1-ulticode-1" + + +def validate(sbom, report=None): + component = next((item for item in sbom.get("components", []) if item.get("purl") == UPSTREAM), None) + if component is None or (component.get("group"), component.get("name"), component.get("version")) != ( + "com.alibaba.nacos", "nacos-api", "2.5.1"): + raise ValueError("Ordinary SBOM components must identify upstream com.alibaba.nacos:nacos-api:2.5.1") + if not any(item.get("ref") == PATCHED and component.get("bom-ref") in item.get("dependsOn", []) + for item in sbom.get("dependencies", [])): + raise ValueError("Patched SBOM component must depend on the upstream identity") + if report is not None: + packages = [package for result in report.get("Results", []) for package in result.get("Packages", [])] + if not any(package.get("Identifier", {}).get("PURL") == UPSTREAM or ( + package.get("Name") == "com.alibaba.nacos:nacos-api" and package.get("Version") == "2.5.1") + for package in packages): + raise ValueError("Trivy package inventory did not identify upstream Nacos; no findings is not sufficient") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("sbom", type=Path) + parser.add_argument("--report", type=Path) + args = parser.parse_args() + try: + validate(json.loads(args.sbom.read_text()), json.loads(args.report.read_text()) if args.report else None) + except (OSError, ValueError, TypeError, AttributeError) as error: + print(str(error), file=sys.stderr) + return 1 + print("Nacos upstream SBOM identity" + (" and Trivy recognition" if args.report else "") + ": PASS") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/ci/check-workflows.py b/scripts/ci/check-workflows.py index a619300f32..94dfda9c1b 100644 --- a/scripts/ci/check-workflows.py +++ b/scripts/ci/check-workflows.py @@ -136,6 +136,34 @@ def check_steps(steps, context): steps = jobs.get("ci-ok", {}).get("steps", []) if not any('needs.changes.outputs.acceptance' in str(step.get('env', {}).get('SELECTED', '')) for step in steps): reject("ci-ok must validate the acceptance selection") + if path.name == "_backend.yml": + build = data.get("jobs", {}).get("backend-build", {}) + steps = build.get("steps", []) + scans = [step for step in steps if step.get("id") == "nacos-sbom-scan"] + expected_scan = { + "scan-type": "sbom", "list-all-pkgs": "true", + "scan-ref": "services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/bom.json", + "scanners": "vuln", "vuln-type": "library", "severity": "HIGH,CRITICAL", + "exit-code": "1", "format": "json", "version": "v0.74.0", + "output": "${{ runner.temp }}/nacos-api-pedigree.trivy.json", + } + if (len(scans) != 1 or build.get("if") is not None + or scans[0].get("if") is not None + or scans[0].get("uses") != "aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25" + or any(scans[0].get("with", {}).get(key) != value for key, value in expected_scan.items()) + or scans[0].get("with", {}).get("ignore-unfixed") not in (None, False, "false")): + reject("upstream Nacos SBOM scan must remain pinned, unconditional and fail closed") + scan_ran = "${{ always() && steps.nacos-sbom-scan.outcome != 'skipped' && steps.nacos-sbom-scan.outcome != '' }}" + if not any(step.get("if") == scan_ran and + 'check-nacos-sbom.py' in step.get("run", "") and + '--report "$RUNNER_TEMP/nacos-api-pedigree.trivy.json"' in step.get("run", "") + for step in steps): + reject("upstream Nacos scan must verify actual scanner package recognition") + if not any(step.get("if") == scan_ran and + step.get("uses", "").startswith("actions/upload-artifact@") and + step.get("with", {}).get("path") == "${{ runner.temp }}/nacos-api-pedigree.trivy.json" and + step.get("with", {}).get("if-no-files-found") == "error" for step in steps): + reject("upstream Nacos scan inventory must be retained on success and failure") for name, job in data.get("jobs", {}).items(): if "permissions" in job: check_permissions(job["permissions"], WRITE_PERMISSIONS.get((path.name, name), set()), f"job {name} permissions") diff --git a/scripts/test/fixtures/nacos-client.compose.yml b/scripts/test/fixtures/nacos-client.compose.yml new file mode 100644 index 0000000000..01a06973fa --- /dev/null +++ b/scripts/test/fixtures/nacos-client.compose.yml @@ -0,0 +1,55 @@ +# Test-only project. Never load application .env or attach application volumes. +services: + mysql: + image: mysql@sha256:0255b469f0135a0236d672d60e3154ae2f4538b146744966d96440318cc822c6 + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?disposable password required} + volumes: + - ${NACOS_FIXTURE_SCHEMA:?schema path required}:/docker-entrypoint-initdb.d/01-nacos-init.sql:ro + healthcheck: + test: ["CMD-SHELL", 'MYSQL_PWD="$${MYSQL_ROOT_PASSWORD}" mysql -uroot nacos_config -e "SELECT 1 FROM users LIMIT 1" >/dev/null 2>&1'] + interval: 2s + timeout: 5s + retries: 60 + networks: [fixture] + + nacos: + # Same v2.3.2 server artifact as production. This gate validates CLIENT + # compatibility only; it does not remediate the server's embedded libraries. + image: nacos/nacos-server@sha256:d70b20546fe59aeb86add0754db1dbc3a3067363e18599329c5163d0d8cd9cd8 + platform: linux/amd64 + environment: + MODE: standalone + SPRING_DATASOURCE_PLATFORM: mysql + MYSQL_SERVICE_HOST: mysql + MYSQL_SERVICE_PORT: 3306 + MYSQL_SERVICE_DB_NAME: nacos_config + MYSQL_SERVICE_USER: root + MYSQL_SERVICE_PASSWORD: ${MYSQL_ROOT_PASSWORD:?disposable password required} + MYSQL_SERVICE_DB_PARAM: "characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true" + JVM_XMS: 256m + JVM_XMX: 512m + JVM_XMN: 128m + NACOS_AUTH_ENABLE: "true" + NACOS_AUTH_TOKEN: ${NACOS_AUTH_TOKEN:?disposable token required} + NACOS_AUTH_IDENTITY_KEY: ${NACOS_AUTH_IDENTITY_KEY:?disposable identity required} + NACOS_AUTH_IDENTITY_VALUE: ${NACOS_AUTH_IDENTITY_VALUE:?disposable identity required} + ports: + - "127.0.0.1:${NACOS_FIXTURE_HTTP_PORT:?loopback port required}:8848" + - "127.0.0.1:${NACOS_FIXTURE_GRPC_PORT:?loopback port required}:9848" + depends_on: + mysql: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8848/nacos/"] + interval: 2s + timeout: 5s + retries: 60 + networks: [fixture] + +networks: + fixture: + # The Java client runs on the host. Like docker-compose.dev.yml, published + # loopback ports require a non-internal bridge. This network belongs only + # to the random disposable project; MySQL still publishes no host port. + internal: false diff --git a/scripts/test/nacos-client-integration.sh b/scripts/test/nacos-client-integration.sh new file mode 100755 index 0000000000..c66faf8372 --- /dev/null +++ b/scripts/test/nacos-client-integration.sh @@ -0,0 +1,216 @@ +#!/usr/bin/env bash +# Required disposable authenticated-registry compatibility gate. No .env, +# existing containers/accounts, privileged networking or remote daemon is used. +set +x +set -euo pipefail +umask 077 +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +for command in docker python3 openssl java timeout; do + command -v "$command" >/dev/null || { echo "Required integration tool unavailable: $command" >&2; exit 1; } +done +if [[ -n "${DOCKER_HOST:-}" && "${DOCKER_HOST}" != unix://* ]]; then + echo 'Nacos integration requires a local Unix-socket Docker daemon' >&2 + exit 1 +fi +endpoint="$(docker context inspect --format '{{(index .Endpoints "docker").Host}}')" +[[ "$endpoint" == unix://* ]] || { echo 'Remote Docker contexts are not permitted' >&2; exit 1; } +docker compose version >/dev/null + +tmp="$(mktemp -d "${TMPDIR:-/tmp}/ulticode-nacos-client.XXXXXX")" +readonly tmp +project="ulticode-nacos-client-$(openssl rand -hex 8)" +readonly project +compose=(docker compose --project-directory "$ROOT_DIR" --project-name "$project" + --env-file "$tmp/fixture.env" -f "$ROOT_DIR/scripts/test/fixtures/nacos-client.compose.yml") +client_pid='' +phase='prepare' +cleanup() { + local rc=$? + trap - EXIT INT TERM + if [[ -n "$client_pid" ]] && kill -0 "$client_pid" 2>/dev/null; then + kill -TERM -- "-$client_pid" 2>/dev/null || kill -TERM "$client_pid" 2>/dev/null || true + for _ in {1..10}; do + kill -0 "$client_pid" 2>/dev/null || break + sleep 1 + done + if kill -0 "$client_pid" 2>/dev/null; then kill -KILL -- "-$client_pid" 2>/dev/null || kill -KILL "$client_pid" 2>/dev/null || true; fi + wait "$client_pid" 2>/dev/null || true + fi + if [[ -f "$tmp/fixture.env" ]] && ! "${compose[@]}" down --volumes --remove-orphans >"$tmp/cleanup.log" 2>&1; then + printf 'Failed to remove disposable Nacos project %s\n' "$project" >&2 + rc=1 + fi + if (( rc != 0 )); then + # Never emit raw client/server/build logs: those can contain auth tokens. + printf 'Nacos integration failed during %s (exit %s).\n' "$phase" "$rc" >&2 + fi + # Only the typed numeric/static evidence is public, including on success. + for report in evidence failure; do + if [[ -f "$tmp/control/$report" ]]; then + python3 - "$tmp/control/$report" <<'PY_DIAGNOSTIC' +from pathlib import Path +import re +import sys +for line in Path(sys.argv[1]).read_text().splitlines(): + if re.fullmatch(r'(?:phase=[a-z-]+|exception=[A-Za-z0-9_.$]+|line=[0-9]+|(?:nacos-error-code|workload-batch-result-code|workload-batch-error-code)=-?[0-9]+|config-retained-listener-pools=[0-9]+|config-declared-resources-closed=true|callback-server-readback=(?:not-run|matches|differs|request-failed)|classification=(?:client-disconnected|authorization-rejected|server-not-ready|resource-not-found|nacos-request-failed))', line): + print('Client diagnostic: ' + line, file=sys.stderr) +PY_DIAGNOSTIC + fi + done + if ! rm -rf -- "$tmp"; then rc=1; fi + if (( rc == 0 )); then + echo 'PASS: isolated Nacos client compatibility, naming/discovery, dedicated-namespace config callbacks, auth/scope rejection, reconnect/resubscribe, declared-resource closure and single patched API provenance; original workload batch-listen remains denied (403), unresolved; upstream listener-pool retention remains unresolved' + fi + exit "$rc" +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +MYSQL_ROOT_PASSWORD="$(openssl rand -hex 32)" +NACOS_AUTH_TOKEN="$(openssl rand -base64 48 | tr -d '\n')" +NACOS_AUTH_IDENTITY_KEY="fixture-$(openssl rand -hex 16)" +NACOS_AUTH_IDENTITY_VALUE="$(openssl rand -hex 32)" +NACOS_USERNAME="fixture-admin-$(openssl rand -hex 8)" +NACOS_PASSWORD="$(openssl rand -hex 32)" +NACOS_FIXTURE_WRONG_PASSWORD="$(openssl rand -hex 32)" +export MYSQL_ROOT_PASSWORD NACOS_AUTH_TOKEN NACOS_AUTH_IDENTITY_KEY NACOS_AUTH_IDENTITY_VALUE +export NACOS_USERNAME NACOS_PASSWORD NACOS_FIXTURE_WRONG_PASSWORD +export DUBBO_NAMESPACE='protobuf-fixture' +export NACOS_NAMESPACE="$DUBBO_NAMESPACE" +export NACOS_FIXTURE_NAMESPACE="$DUBBO_NAMESPACE" +export NACOS_FIXTURE_CONFIG_NAMESPACE='protobuf-config-fixture' +NACOS_FIXTURE_CONFIG_USERNAME="fixture-config-$(openssl rand -hex 8)" +NACOS_FIXTURE_CONFIG_PASSWORD="$(openssl rand -hex 32)" +export NACOS_FIXTURE_CONFIG_USERNAME NACOS_FIXTURE_CONFIG_PASSWORD +export NACOS_EXPECTED_DOCKER_PROJECT="$project" +export NACOS_FIXTURE_SCHEMA="$ROOT_DIR/docker/initdb/01-nacos-init.sql" +for prefix in AUTH ADMIN APP SUBMISSION NOTIFICATION JUDGE; do + user_suffix="$(openssl rand -hex 6)" + user_password="$(openssl rand -hex 32)" + printf -v "${prefix}_NACOS_USERNAME" 'fixture-%s-%s' "${prefix,,}" "$user_suffix" + printf -v "${prefix}_NACOS_PASSWORD" '%s' "$user_password" + export "${prefix}_NACOS_USERNAME" "${prefix}_NACOS_PASSWORD" +done +NACOS_FIXTURE_HTTP_PORT="$(python3 - <<'PY' +import secrets +import socket +for _ in range(64): + port = 20000 + secrets.randbelow(30000) + with socket.socket() as http, socket.socket() as grpc: + try: + http.bind(('127.0.0.1', port)) + grpc.bind(('127.0.0.1', port + 1000)) + except OSError: + continue + print(port) + break +else: + raise SystemExit('No free loopback Nacos port pair') +PY +)" +export NACOS_FIXTURE_HTTP_PORT +export NACOS_FIXTURE_GRPC_PORT="$((NACOS_FIXTURE_HTTP_PORT + 1000))" +export NACOS_FIXTURE_SERVER="127.0.0.1:$NACOS_FIXTURE_HTTP_PORT" +export NACOS_FIXTURE_CONTROL="$tmp/control" +mkdir "$NACOS_FIXTURE_CONTROL" "$tmp/home" +# Compose's explicit env file suppresses implicit .env discovery. All values +# are generated above or an owned fixture path, not sourced as shell code. +python3 - "$tmp/fixture.env" <<'PY' +import os +import pathlib +import sys +keys = ('MYSQL_ROOT_PASSWORD', 'NACOS_AUTH_TOKEN', 'NACOS_AUTH_IDENTITY_KEY', + 'NACOS_AUTH_IDENTITY_VALUE', 'NACOS_FIXTURE_SCHEMA', + 'NACOS_FIXTURE_HTTP_PORT', 'NACOS_FIXTURE_GRPC_PORT') +with pathlib.Path(sys.argv[1]).open('w') as output: + for key in keys: + value = os.environ[key] + if '\n' in value or '\r' in value or "'" in value: + raise SystemExit('Unsafe fixture environment value') + output.write(f"{key}='{value}'\n") +PY + +phase='compile isolated client' +( + cd "$ROOT_DIR/services" + ./mvnw -B -pl platform/rpc-resilience -am -DskipTests install + ./mvnw -B -pl platform/rpc-resilience dependency:build-classpath \ + -Dmdep.includeScope=test -Dmdep.outputFile="$tmp/classpath" +) >"$tmp/build.log" 2>&1 +[[ -s "$tmp/classpath" ]] || { echo 'Missing client test classpath' >&2; exit 1; } + +phase='start fixture database' +"${compose[@]}" up -d --wait --wait-timeout 120 mysql >"$tmp/mysql.log" 2>&1 +MYSQL_CONTAINER="$("${compose[@]}" ps -q mysql)" +export MYSQL_CONTAINER +phase='bootstrap disposable principals' +# Reuse the production bootstrap's validated Compose ownership, password +# hashing, disabled built-in user and per-service least-privilege ACLs. +bash "$ROOT_DIR/scripts/security/bootstrap-nacos-user.sh" >"$tmp/bootstrap.log" 2>&1 +# Test-only principal, with the same pinned bcrypt tool as the unchanged +# bootstrap. No password in argv/logs, and no global/admin or workload grants. +config_password_hash="$(printf '%s\n' "$NACOS_FIXTURE_CONFIG_PASSWORD" \ + | docker run --rm --network none -i \ + httpd@sha256:1b766f17b84026429b7cb243317b142921b24432336e798bc881c43f45ed9567 \ + htpasswd -niBC 12 "$NACOS_FIXTURE_CONFIG_USERNAME" 2>"$tmp/config-principal.log" \ + | sed 's/^[^:]*://' | tr -d '\r\n')" +[[ "$config_password_hash" =~ ^\$2[aby]\$12\$[./A-Za-z0-9]{53}$ ]] || { + echo 'Invalid fixture principal password hash' >&2 + exit 1 +} +{ + printf '%s\n' "$MYSQL_ROOT_PASSWORD" + # Server 2.3.2 batch auth requires namespace-wide config read. This new + # namespace contains synthetic fixture data only; this is NOT a production + # permission fix. The exact write identifiers contain no regex metacharacters. + cat <"$tmp/namespace.log" 2>&1 +phase='start authenticated registry' +"${compose[@]}" up -d --wait --wait-timeout 120 nacos >"$tmp/nacos.log" 2>&1 + +phase='authenticated client and negative auth checks' +classpath="$ROOT_DIR/services/platform/rpc-resilience/target/test-classes:$ROOT_DIR/services/platform/rpc-resilience/target/classes:$(cat "$tmp/classpath")" +# HOME and Java's user.home keep SDK logs/snapshots away from developer state. +# The timeout is a failing outer bound, never a success/skip fallback. +HOME="$tmp/home" timeout --signal=TERM --kill-after=10s 360s java \ + -Duser.home="$tmp/home" \ + -Dcom.google.protobuf.error_on_unsafe_pre22_gencode=true \ + -cp "$classpath" com.ulticode.rpc.resilience.NacosRegistryIT >"$tmp/client.log" 2>&1 & +client_pid=$! +wait_for_marker() { + local marker="$1" + for _ in {1..120}; do + [[ -f "$NACOS_FIXTURE_CONTROL/$marker" ]] && return 0 + if ! kill -0 "$client_pid" 2>/dev/null; then + wait "$client_pid" + echo 'Client exited before completing the registry drill' >&2 + return 1 + fi + sleep 1 + done + echo "Client did not reach required fixture phase: $marker" >&2 + return 1 +} +wait_for_marker ready-for-outage +phase='disconnect detection' +"${compose[@]}" stop --timeout 10 nacos >"$tmp/stop.log" 2>&1 +touch "$NACOS_FIXTURE_CONTROL/server-stopped" +wait_for_marker observed-disconnect +phase='reconnect and resubscribe' +"${compose[@]}" up -d --wait --wait-timeout 120 nacos >"$tmp/restart.log" 2>&1 +touch "$NACOS_FIXTURE_CONTROL/server-restarted" +wait "$client_pid" +client_pid='' diff --git a/scripts/test/supply-chain-contract.sh b/scripts/test/supply-chain-contract.sh index e48b95241d..9dac872f0b 100755 --- a/scripts/test/supply-chain-contract.sh +++ b/scripts/test/supply-chain-contract.sh @@ -177,7 +177,7 @@ for service in owner_services: owner_pom = repo / 'services' / service['module'].split('/', 1)[0] / 'pom.xml' owner_properties = ET.parse(owner_pom).getroot().find('m:properties', ns) owner_version = owner_properties.findtext(f'm:{prop}', namespaces=ns) - expected_version = '1.0.7' + expected_version = '1.0.8' assert root_version == owner_version == expected_version, f'{service["name"]}: release version mismatch (root={root_version!r}, owner={owner_version!r}, expected={expected_version!r})' assert 'actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333' in workflow print('digest-only candidates, common normalized repo and all-service promote gate: PASS') diff --git a/scripts/test/test_nacos_client_integration.py b/scripts/test/test_nacos_client_integration.py new file mode 100644 index 0000000000..a7aa6b1787 --- /dev/null +++ b/scripts/test/test_nacos_client_integration.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +"""No Docker required: verify fixture ownership, fail-closed orchestration and cleanup. + +Doubles test the launcher only; they are not real Nacos integration evidence. +""" +import json +import os +import re +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] + + +class NacosIntegrationLauncherTest(unittest.TestCase): + def run_fixture(self, failure=""): + with tempfile.TemporaryDirectory(prefix="nacos-launcher-test-") as directory: + root = Path(directory) + repo = root / "repo with spaces" + for relative in ("scripts/test", "scripts/security", "services", "tmp", "bin"): + (repo / relative).mkdir(parents=True) + shutil.copy2(ROOT / "scripts/test/nacos-client-integration.sh", repo / "scripts/test") + (repo / ".env").write_text("touch MUST_NOT_EXECUTE\n") + calls = root / "calls.jsonl" + bootstrap = repo / "scripts/security/bootstrap-nacos-user.sh" + bootstrap.write_text('''#!/usr/bin/env bash +set -euo pipefail +[[ "$NACOS_EXPECTED_DOCKER_PROJECT" == ulticode-nacos-client-* ]] +[[ "$MYSQL_CONTAINER" == fixture-mysql ]] +[[ "$NACOS_NAMESPACE" == protobuf-fixture ]] +[[ ${#AUTH_NACOS_PASSWORD} -ge 32 ]] +[[ "$NACOS_PASSWORD" != "$AUTH_NACOS_PASSWORD" ]] +[[ "$AUTH_NACOS_PASSWORD" != "$NACOS_FIXTURE_WRONG_PASSWORD" ]] +[[ "$NACOS_FIXTURE_CONFIG_NAMESPACE" == protobuf-config-fixture ]] +[[ "$NACOS_FIXTURE_CONFIG_USERNAME" == fixture-config-* ]] +[[ "$NACOS_FIXTURE_CONFIG_PASSWORD" != "$AUTH_NACOS_PASSWORD" ]] +[[ "$NACOS_FIXTURE_CONFIG_PASSWORD" != "$NACOS_PASSWORD" ]] +[[ "$NACOS_FIXTURE_CONFIG_PASSWORD" != "$NACOS_FIXTURE_WRONG_PASSWORD" ]] +[[ ${#NACOS_FIXTURE_CONFIG_PASSWORD} -eq 64 ]] +''') + maven = repo / "services/mvnw" + maven.write_text('''#!/usr/bin/env bash +set -euo pipefail +[[ "$*" != *"use_unsafe"* ]] +[[ "$FAILURE" != build ]] || exit 23 +for argument in "$@"; do + if [[ "$argument" == -Dmdep.outputFile=* ]]; then + printf '/fixture/dependencies.jar' > "${argument#*=}" + fi +done +''') + maven.chmod(0o755) + docker = repo / "bin/docker" + docker.write_text('''#!/usr/bin/env python3 +import json, os, pathlib, re, sys +args = sys.argv[1:] +with open(os.environ['CALLS'], 'a') as output: + output.write(json.dumps(args) + '\\n') +if args[:2] == ['context', 'inspect']: + print('ssh://forbidden' if os.environ['FAILURE'] == 'remote' else 'unix:///fixture.sock') +elif args == ['compose', 'version']: + pass +elif args[0] == 'compose': + assert '--project-name' in args and '--env-file' in args, args + project = args[args.index('--project-name') + 1] + assert project.startswith('ulticode-nacos-client-'), project + env_file = pathlib.Path(args[args.index('--env-file') + 1]) + assert env_file.name == 'fixture.env' and env_file.stat().st_mode & 0o077 == 0 + assert 'NACOS_AUTH_TOKEN=' in env_file.read_text() + if 'ps' in args: + print('fixture-mysql') + elif 'up' in args: + print(os.environ['NACOS_AUTH_TOKEN']) # must remain in private log + if os.environ['FAILURE'] == 'start': sys.exit(29) + elif 'stop' in args: + assert args[-1] == 'nacos' + elif 'down' in args: + assert '--volumes' in args and '--remove-orphans' in args + if os.environ['FAILURE'] == 'cleanup': sys.exit(31) + else: + raise SystemExit('Unexpected compose action') +elif args[0] == 'run': + assert args[1:5] == ['--rm', '--network', 'none', '-i'] + assert args[5] == 'httpd@sha256:1b766f17b84026429b7cb243317b142921b24432336e798bc881c43f45ed9567' + assert args[6:9] == ['htpasswd', '-niBC', '12'] + assert args[9:] == [os.environ['NACOS_FIXTURE_CONFIG_USERNAME']] + assert sys.stdin.read().strip() == os.environ['NACOS_FIXTURE_CONFIG_PASSWORD'] + password_hash = 'invalid' if os.environ['FAILURE'] == 'config-hash' else '$2y$12$' + 'A' * 53 + print(args[9] + ':' + password_hash) +elif args[:3] == ['exec', '-i', 'fixture-mysql']: + lines = sys.stdin.read().splitlines() + assert lines[0] == os.environ['MYSQL_ROOT_PASSWORD'] + sql = '\\n'.join(lines[1:]) + assert sql.startswith('START TRANSACTION;') and sql.endswith('COMMIT;') + assert "('1','protobuf-fixture','protobuf-fixture'," in sql + assert "('1','protobuf-config-fixture','protobuf-config-fixture'," in sql + assert os.environ['NACOS_FIXTURE_CONFIG_USERNAME'] in sql + assert '$2y$12$' + 'A' * 53 in sql + assert os.environ['NACOS_FIXTURE_CONFIG_PASSWORD'] not in sql + assert 'ROLE_ADMIN' not in sql and 'ROLE_ULTICODE_' not in sql + grants = re.findall(r"\\('ROLE_PROTOBUF_CONFIG_FIXTURE','([^']+)','([rw])'\\)", sql) + assert grants == [ + ('protobuf-config-fixture:*:config/*', 'r'), + ('protobuf-config-fixture:FIXTURE_CONFIG:config/protobuf-fixture-metadata', 'w'), + ], grants + assert sql.count('INSERT INTO users') == 1 and sql.count('INSERT INTO roles') == 1 + assert sql.count('INSERT INTO permissions') == 1 + if os.environ['FAILURE'] == 'fixture-sql': sys.exit(38) +else: + raise SystemExit('Unexpected Docker command') +''') + docker.chmod(0o755) + java = repo / "bin/java" + java.write_text('''#!/usr/bin/env python3 +import os, pathlib, sys, time +assert '-Dcom.google.protobuf.error_on_unsafe_pre22_gencode=true' in sys.argv +assert not any('use_unsafe' in arg for arg in sys.argv) +assert os.environ['HOME'] != os.environ['ORIGINAL_HOME'] +control = pathlib.Path(os.environ['NACOS_FIXTURE_CONTROL']) +print(os.environ['AUTH_NACOS_PASSWORD']) # must remain in private log +print(os.environ['NACOS_FIXTURE_CONFIG_PASSWORD']) +(control / 'evidence').write_text('workload-batch-result-code=500\\nworkload-batch-error-code=403\\n' + + 'config-retained-listener-pools=' + os.environ['NACOS_FIXTURE_CONFIG_PASSWORD'] + '\\n' + + 'workload-batch-error-code=' + os.environ['NACOS_FIXTURE_CONFIG_PASSWORD'] + '\\n') +if os.environ['FAILURE'] in ('client', 'transport'): + code, classification = (-401, 'client-disconnected') if os.environ['FAILURE'] == 'transport' else (403, 'authorization-rejected') + (control / 'failure').write_text('phase=wrong-auth-rejection\\nexception=java.lang.AssertionError\\nline=123\\n' + + f'nacos-error-code={code}\\nclassification={classification}\\n' + + 'callback-server-readback=matches\\ncallback-server-readback=' + os.environ['NACOS_FIXTURE_CONFIG_PASSWORD'] + '\\n' + + 'classification=' + os.environ['AUTH_NACOS_PASSWORD'] + '\\n' + os.environ['AUTH_NACOS_PASSWORD']) + sys.exit(37) +(control / 'ready-for-outage').touch() +for requested, observed in [('server-stopped', 'observed-disconnect'), ('server-restarted', 'finished')]: + deadline = time.monotonic() + 15 + while not (control / requested).exists(): + assert time.monotonic() < deadline + time.sleep(0.01) + (control / observed).touch() +with (control / 'evidence').open('a') as evidence: + evidence.write('config-retained-listener-pools=1\\nconfig-declared-resources-closed=true\\n') +''') + java.chmod(0o755) + env = {**os.environ, "PATH": f"{repo / 'bin'}:{os.environ['PATH']}", + "TMPDIR": str(repo / "tmp"), "CALLS": str(calls), "FAILURE": failure, + "ORIGINAL_HOME": os.environ.get("HOME", ""), "DOCKER_HOST": ""} + result = subprocess.run(["bash", str(repo / "scripts/test/nacos-client-integration.sh")], + cwd=repo, env=env, capture_output=True, text=True, timeout=30) + events = [json.loads(line) for line in calls.read_text().splitlines()] + self.assertFalse((repo / "MUST_NOT_EXECUTE").exists()) + self.assertFalse(list((repo / "tmp").iterdir()), "temporary secrets/logs leaked") + self.assertNotRegex(result.stdout + result.stderr, r"[a-f0-9]{64}", "secret printed") + self.assertNotIn("dependencies.jar", result.stdout + result.stderr) + return result, events + + def test_fixture_supports_host_clients_without_exposing_mysql(self): + # This small fixture deliberately has one project-local bridge. Docker's + # internal bridge does not support this host-published topology (same + # constraint as docker-compose.dev.yml); pin that contract without a + # Docker/PyYAML dependency in this infrastructure-free regression. + compose = (ROOT / "scripts/test/fixtures/nacos-client.compose.yml").read_text() + services, networks = compose.split("\nnetworks:\n", 1) + network_settings = [line.strip() for line in networks.splitlines() + if line.strip() and not line.lstrip().startswith("#")] + self.assertEqual(network_settings, ["fixture:", "internal: false"]) + mysql, nacos = services.split("\n nacos:\n", 1) + self.assertNotIn("ports:", mysql) + self.assertEqual(services.count("networks: [fixture]"), 2) + ports = re.search(r"(?ms)^ ports:\n(.*?)(?=^ \S|\Z)", nacos) + self.assertIsNotNone(ports) + publications = [line.strip() for line in ports.group(1).splitlines() + if line.strip() and not line.lstrip().startswith("#")] + self.assertEqual(publications, [ + '- "127.0.0.1:${NACOS_FIXTURE_HTTP_PORT:?loopback port required}:8848"', + '- "127.0.0.1:${NACOS_FIXTURE_GRPC_PORT:?loopback port required}:9848"', + ]) + self.assertNotIn("network_mode:", compose) + self.assertNotIn("external:", compose) + + def test_success_restarts_only_owned_fixture_and_cleans(self): + result, events = self.run_fixture() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("PASS:", result.stdout) + self.assertIn("original workload batch-listen remains denied (403), unresolved", result.stdout) + self.assertIn("Client diagnostic: workload-batch-error-code=403", result.stderr) + self.assertIn("Client diagnostic: config-retained-listener-pools=1", result.stderr) + self.assertIn("Client diagnostic: config-declared-resources-closed=true", result.stderr) + self.assertIn("upstream listener-pool retention remains unresolved", result.stdout) + self.assertEqual(sum("stop" in event for event in events), 1) + self.assertIn("down", events[-1]) + projects = {event[event.index('--project-name') + 1] for event in events if '--project-name' in event} + self.assertEqual(len(projects), 1) + + def test_failures_are_not_skips_or_success(self): + for failure in ("build", "start", "config-hash", "fixture-sql", "client", "transport", "cleanup"): + with self.subTest(failure=failure): + result, events = self.run_fixture(failure) + self.assertNotEqual(result.returncode, 0) + self.assertNotIn("PASS:", result.stdout) + self.assertIn("down", events[-1]) + if failure in ("client", "transport"): + self.assertIn("Client diagnostic: phase=wrong-auth-rejection", result.stderr) + self.assertIn("Client diagnostic: exception=java.lang.AssertionError", result.stderr) + code, classification = (-401, "client-disconnected") if failure == "transport" else (403, "authorization-rejected") + self.assertIn(f"Client diagnostic: nacos-error-code={code}", result.stderr) + self.assertIn(f"Client diagnostic: classification={classification}", result.stderr) + self.assertIn("Client diagnostic: callback-server-readback=matches", result.stderr) + self.assertIn("Client diagnostic: workload-batch-error-code=403", result.stderr) + if failure in ("build", "start", "config-hash", "fixture-sql", "client", "transport"): + self.assertFalse(any("stop" in event for event in events)) + + def test_remote_context_is_rejected_before_mutation(self): + result, events = self.run_fixture("remote") + self.assertNotEqual(result.returncode, 0) + self.assertEqual(len(events), 1) + self.assertIn("Remote Docker contexts are not permitted", result.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test/test_nacos_sbom.py b/scripts/test/test_nacos_sbom.py new file mode 100644 index 0000000000..e555f815b8 --- /dev/null +++ b/scripts/test/test_nacos_sbom.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Scanner-identity contract, independent of network/database availability.""" +import copy +import importlib.util +import json +from pathlib import Path +import unittest + +ROOT = Path(__file__).resolve().parents[2] +SPEC = importlib.util.spec_from_file_location("nacos_sbom", ROOT / "scripts/ci/check-nacos-sbom.py") +CHECK = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(CHECK) + + +class NacosSbomTest(unittest.TestCase): + def setUp(self): + self.sbom = json.loads((ROOT / "services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/bom.json").read_text()) + self.report = {"Results": [{"Packages": [{"Name": "com.alibaba.nacos:nacos-api", "Version": "2.5.1", + "Identifier": {"PURL": CHECK.UPSTREAM}}]}]} + + def test_actual_sbom_and_recognized_package(self): + CHECK.validate(self.sbom, self.report) + + def test_pedigree_alone_does_not_count(self): + self.sbom["components"] = [] + with self.assertRaises(ValueError): + CHECK.validate(self.sbom) + + def test_missing_dependency_edge_fails(self): + self.sbom["dependencies"] = [] + with self.assertRaises(ValueError): + CHECK.validate(self.sbom) + + def test_empty_or_wrong_scan_inventory_is_not_a_pass(self): + for report in ({}, {"Results": []}, {"Results": [{"Vulnerabilities": []}]}, + {"Results": [{"Packages": [{"Name": "unrelated", "Version": "2.5.1"}]}]}): + with self.subTest(report=report), self.assertRaises(ValueError): + CHECK.validate(self.sbom, copy.deepcopy(report)) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test/workflow-policy-contract.py b/scripts/test/workflow-policy-contract.py index 0661932d23..720babef0e 100644 --- a/scripts/test/workflow-policy-contract.py +++ b/scripts/test/workflow-policy-contract.py @@ -58,6 +58,30 @@ def test_real_acceptance_cannot_be_removed_from_required_summary(self): path.write_text(json.dumps(data)) self.assertTrue(any('acceptance' in error for error in policy.check_repository(self.root))) + def test_nacos_upstream_scan_cannot_be_removed_or_weakened(self): + import shutil + shutil.copytree(ROOT / '.github', self.root / '.github', dirs_exist_ok=True) + self.assertEqual(policy.check_repository(self.root), []) + path = self.root / '.github/workflows/_backend.yml' + original = policy.yaml.load(path.read_text(), Loader=policy.WorkflowLoader) + for mutation in ('remove', 'conditional', 'scan-ref', 'severity', 'exit-code', 'ignore-unfixed', + 'inventory', 'recognition', 'artifact'): + with self.subTest(mutation=mutation): + data = copy.deepcopy(original) + steps = data['jobs']['backend-build']['steps'] + scan = next(step for step in steps if step.get('id') == 'nacos-sbom-scan') + if mutation == 'remove': steps.remove(scan) + elif mutation == 'conditional': scan['if'] = '${{ false }}' + elif mutation in ('scan-ref', 'severity', 'exit-code'): scan['with'][mutation] = 'weakened' + elif mutation == 'ignore-unfixed': scan['with'][mutation] = True + elif mutation == 'inventory': scan['with']['list-all-pkgs'] = 'false' + elif mutation == 'recognition': + steps[:] = [step for step in steps if '--report' not in step.get('run', '')] + elif mutation == 'artifact': + steps[:] = [step for step in steps if step.get('with', {}).get('name') != 'nacos-api-pedigree-scan'] + path.write_text(json.dumps(data)) + self.assertTrue(any('Nacos' in error for error in policy.check_repository(self.root))) + def test_safe_workflow_and_yaml_on_key(self): self.assertEqual(self.check(WORKFLOW), []) parsed = policy.yaml.load("on:\n push:\npermissions: {}\nenabled: false\n", Loader=policy.WorkflowLoader) diff --git a/services/admin/pom.xml b/services/admin/pom.xml index f9c4f11acf..a53cea1b5b 100644 --- a/services/admin/pom.xml +++ b/services/admin/pom.xml @@ -13,7 +13,7 @@ - 1.0.7 + 1.0.8 backend-admin ${service.version.admin} diff --git a/services/agent/data/repository_corpus_manifest.json b/services/agent/data/repository_corpus_manifest.json index b38e18671f..3b95453d45 100644 --- a/services/agent/data/repository_corpus_manifest.json +++ b/services/agent/data/repository_corpus_manifest.json @@ -15,8 +15,8 @@ }, { "doc_id": "repository-architecture", - "version": "sha256-cd35f7993d889c5c1d9b8ed488e7f10d9aded3ed56cc024ba7f809c922d69dae", - "chunk_id": "repository-architecture:sha256-cd35f7993d889c5c1d9b8ed488e7f10d9aded3ed56cc024ba7f809c922d69dae:1", + "version": "sha256-6f3f89a3cf140ab093fb8d87e5a1843c936ffeebd3f367e5a760171b4041cb08", + "chunk_id": "repository-architecture:sha256-6f3f89a3cf140ab093fb8d87e5a1843c936ffeebd3f367e5a760171b4041cb08:1", "source_path": "docs/ARCHITECTURE.md", "access_scope": "repository-public", "sample_kind": "real", @@ -29,8 +29,8 @@ }, { "doc_id": "repository-development", - "version": "sha256-b82d7a1f1b140850d8cc7ea6364bfe705876b20be69ba7013a3c499fd69b654f", - "chunk_id": "repository-development:sha256-b82d7a1f1b140850d8cc7ea6364bfe705876b20be69ba7013a3c499fd69b654f:1", + "version": "sha256-0c5793206db6d637843f30e51530fbf273fd65ebc47692e79215ba2e63ba6c20", + "chunk_id": "repository-development:sha256-0c5793206db6d637843f30e51530fbf273fd65ebc47692e79215ba2e63ba6c20:1", "source_path": "docs/DEVELOPMENT.md", "access_scope": "repository-public", "sample_kind": "real", diff --git a/services/app/pom.xml b/services/app/pom.xml index 5fa18ef2f7..b4dc6999ef 100644 --- a/services/app/pom.xml +++ b/services/app/pom.xml @@ -13,7 +13,7 @@ - 1.0.7 + 1.0.8 backend-app ${service.version.app} diff --git a/services/auth/pom.xml b/services/auth/pom.xml index c95429cab8..52fb5e7a0e 100644 --- a/services/auth/pom.xml +++ b/services/auth/pom.xml @@ -13,7 +13,7 @@ - 1.0.7 + 1.0.8 backend-auth ${service.version.auth} diff --git a/services/docs/SERVICES_ISSUES.md b/services/docs/SERVICES_ISSUES.md index eabcbf9f76..b87b2ba1fc 100644 --- a/services/docs/SERVICES_ISSUES.md +++ b/services/docs/SERVICES_ISSUES.md @@ -1,6 +1,6 @@ # `services/` 问题清单 -更新时间:2026-09-06 +更新时间:2026-11-06 本文件是 `services/` 微服务架构问题、评审 Finding、修复状态与可选外部运行触发条件的唯一入口。其他文档只能链接到本文件,不得复制问题正文或维护第二份状态。 @@ -30,10 +30,72 @@ App interface locality 已闭环;Core 的边界与验证范围见 SVC-025。 当前拓扑与 Contract 边界以 [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md)、源码、配置和门禁为准。本文件是当前 issue 状态和 SVC-025 关闭条件的唯一入口;已完成的执行计划、台账和检查点不作为当前证据。 +Core Auth/Admin bounded testbed 已取得 local Adapter parity 和真实 HTTP +disposable journey 证据;本项不改变默认拓扑或 SVC-025 的状态。 + ## OPEN -本文件当前无 OPEN 项;Core Auth/Admin bounded testbed 已取得 local Adapter -parity 和真实 HTTP disposable journey 证据,不得切换默认拓扑。 +### SVC-029 Nacos registry server protobuf generated code(OPEN) + +Base Compose 的 `nacos/nacos-server:v2.3.2` 与 prod/HA 固定的 +`sha256:d70b20546fe59aeb86add0754db1dbc3a3067363e18599329c5163d0d8cd9cd8` +指向同一镜像。该镜像包含 `nacos-api:2.3.2`、`protobuf-java:3.22.3`, +并在 `nacos-client:2.3.2` 内再次包含协议类。上游同版本的 Metadata/Payload +仍使用旧生成解析器;[GHSA-h4h5-3hr4-j3g2](https://github.com/protocolbuffers/protobuf/security/advisories/GHSA-h4h5-3hr4-j3g2) +不能仅靠更新 protobuf runtime 修复。Owner/Worker 的 patched API 只作用于应用 +依赖,不修改这个独立的注册中心镜像。 + +Nacos gRPC 在连接有效性与请求鉴权之前解析 Payload,不能把开启认证当作 +解析器修复。base/prod 不发布 Nacos 端口,dev 只绑定 loopback,缩小了入口, +但不能排除同网络服务或本机进程。提取依赖的有界本地解析已观察到资源放大; +这不是镜像 JDK 或运行中服务器的完整验证。替换镜像尚未验收;后续修复须 +检查所有协议类来源,并验证数据、认证、SDK/集群连接与重连兼容性。不得通过 +静音旧生成代码警告、取消认证或放宽门禁关闭本项。 + +### SVC-030 Nacos 批量配置监听与 workload ACL(OPEN) + +现有 bootstrap 按 namespace、明确的 group 和数据范围授予 workload 配置权限。 +Nacos 2.3.2 对标准 SDK 的批量监听请求使用 namespace 级配置读取权限;空 group/name +会形成 `:*:config/*`,而不是逐个匹配请求中的配置条目。 +[资源解析](https://github.com/alibaba/nacos/blob/2.3.2/auth/src/main/java/com/alibaba/nacos/auth/parser/grpc/ConfigGrpcResourceParser.java#L34-L72) +与[权限键拼接](https://github.com/alibaba/nacos/blob/2.3.2/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java#L263-L288)、 +[权限匹配](https://github.com/alibaba/nacos/blob/2.3.2/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java#L146-L160) +使现有 group 级读取权限不足以允许该批量请求。直接配置读写成功不能证明监听回调可用。 +所核对的 2.5.5、3.3.0 同一路径也未提供逐配置授权,不能仅以升级版本关闭本项。 + +客户端协议门禁保留原 AUTH workload ACL,明确要求其批量监听请求返回 403; +正向配置回调使用另外的测试账号和独占、仅含合成数据的测试 namespace。 +该测试账号拥有整个测试 namespace 的配置读取权限、仅固定 dataId 的写权限, +没有管理员角色,随独立测试环境销毁。此权限宽度只用于客户端协议、回调和重连 +兼容验证,不证明原 workload ACL 的生产监听兼容性。不得把该 namespace 全读 +权限加入生产 bootstrap、使用管理员账号替代 workload,或把回调超时当成鉴权通过。 + +本项需独立解决服务端批量请求的逐配置授权与隔离边界,并重新验收真实角色的 +订阅和恢复行为。客户端 protobuf 修复与扩大测试授权范围均不能关闭本项; +现有生产 profile、bootstrap 权限和已有凭据保持不变。 + +### SVC-031 Nacos 配置客户端 listener 线程池释放(OPEN) + +Nacos pure SDK 2.5.1 的配置客户端在首次同步监听时创建按 task 分组的 daemon +线程池;`shutDown()` 会关闭 RPC、通道和主调度器,但未关闭 `multiTaskExecutor` +内的监听同步线程池,移除 listener 也不释放它们。 +[创建路径](https://github.com/alibaba/nacos/blob/2.5.1/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java#L911-L920) +与[关闭路径](https://github.com/alibaba/nacos/blob/2.5.1/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java#L614-L645) +属于未修改的上游 SDK;客户端 protobuf API 衍生包不替换这段实现。 +单个稳定客户端按 task 复用线程池,保留量跟随曾用到的 task 分区, +不是每次回调都新增。长期存活的 JVM 若反复创建、监听、销毁配置客户端, +会累积保留的线程及关联对象;daemon 线程不阻止 JVM 退出, +但不能据此宣称进程内资源已经完整释放。 + +生命周期门禁必须在关闭前捕获原 RPC、通道和已声明关闭的执行器,验证它们终止 +并从 factory 移除,同时独立记录未释放的 listener 线程池。不得手动清理这些池后 +把结果当作 SDK 自身释放成功,也不得以测试 JVM 退出代替原传输资源释放断言。 +关闭后不要再调用配置客户端的 `getServerStatus()`:其 +[health 路径](https://github.com/alibaba/nacos/blob/2.5.1/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java#L1304-L1311) +会经 `ensureRpcClient("0")` 创建并启动新的 RPC 客户端,可能重新建立连接, +具有观察副作用。 +本项需独立修复 SDK 生命周期,并以同 JVM 多轮创建、监听和销毁验证无累积; +一次隔离进程的协议兼容通过不能关闭本项。 ## CLOSED diff --git a/services/judge/pom.xml b/services/judge/pom.xml index f69064c0ff..e096703cda 100644 --- a/services/judge/pom.xml +++ b/services/judge/pom.xml @@ -15,7 +15,7 @@ - 1.0.7 + 1.0.8 backend-judge diff --git a/services/notification/pom.xml b/services/notification/pom.xml index bf9bb67356..dcd008dc29 100644 --- a/services/notification/pom.xml +++ b/services/notification/pom.xml @@ -14,7 +14,7 @@ - 1.0.7 + 1.0.8 backend-notification ${service.version.notification} diff --git a/services/platform/nacos-api-patched/.gitattributes b/services/platform/nacos-api-patched/.gitattributes new file mode 100644 index 0000000000..3305a1640b --- /dev/null +++ b/services/platform/nacos-api-patched/.gitattributes @@ -0,0 +1,8 @@ +* text eol=lf +*.java text eol=lf +*.proto text eol=lf +*.json text eol=lf +*.properties text eol=lf +*.xml text eol=lf +src/main/resources/META-INF/LICENSE text eol=lf +src/main/resources/META-INF/NOTICE text eol=lf diff --git a/services/platform/nacos-api-patched/.gitignore b/services/platform/nacos-api-patched/.gitignore new file mode 100644 index 0000000000..0bcc41062a --- /dev/null +++ b/services/platform/nacos-api-patched/.gitignore @@ -0,0 +1,6 @@ +# This build directory contains reviewed build-tool source, not build output. +!build/ +build/* +!build/PatchNacosApi.java +!build/inputs.sha256.properties +!build/protoc.sha256.properties diff --git a/services/platform/nacos-api-patched/build/PatchNacosApi.java b/services/platform/nacos-api-patched/build/PatchNacosApi.java new file mode 100644 index 0000000000..537f0af13c --- /dev/null +++ b/services/platform/nacos-api-patched/build/PatchNacosApi.java @@ -0,0 +1,230 @@ +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HexFormat; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.zip.ZipFile; + +/** Build-only JDK 17 tool; never packaged or placed on the application classpath. */ +public final class PatchNacosApi { + private static final String PACKAGE = "com/alibaba/nacos/api/grpc/auto/"; + private static final String UPSTREAM_METADATA = "META-INF/maven/com.alibaba.nacos/nacos-api/"; + private static final Set SOURCES = Set.of( + "Metadata.java", "MetadataOrBuilder.java", "Payload.java", + "PayloadOrBuilder.java", "NacosGrpcService.java"); + private static final Set REPLACED = Set.of( + "Metadata.class", "Metadata$1.class", "Metadata$Builder.class", + "Metadata$HeadersDefaultEntryHolder.class", "MetadataOrBuilder.class", + "Payload.class", "Payload$1.class", "Payload$Builder.class", + "PayloadOrBuilder.class", "NacosGrpcService.class"); + private static final String UPSTREAM_SHA256 = + "19b0eabdc1f637eeeec72cba9d6f85fe2bcae62bb9be7e7fb8a25ce183ce8738"; + private static final String POM_SHA256 = + "6b814f79526b1043bc3f81c6c65ab93003205fd0d2c76bf514927967b91a972b"; + + public static void main(String[] args) throws Exception { + Path base = Path.of(args[1]).toAbsolutePath().normalize(); + Path build = Path.of(args[2]).toAbsolutePath().normalize(); + if (!build.startsWith(base) || build.equals(base)) { + throw new IOException("Build directory must be inside this module: " + build); + } + verifyInputs(base); + switch (args[0]) { + case "prepare" -> prepare(base, build); + case "verify" -> verify(base, build, Path.of(args[3])); + case "regenerate" -> regenerate(base, build, Path.of(args[3])); + default -> throw new IllegalArgumentException("Unknown operation: " + args[0]); + } + } + + private static void verifyInputs(Path base) throws Exception { + Properties hashes = properties(base.resolve("build/inputs.sha256.properties")); + for (String file : hashes.stringPropertyNames()) { + checkHash(base.resolve(file), hashes.getProperty(file)); + } + Set actual = new TreeSet<>(); + try (var paths = Files.walk(base.resolve("src/main/java"))) { + paths.filter(Files::isRegularFile).forEach(path -> actual.add( + base.resolve("src/main/java").relativize(path).toString().replace('\\', '/'))); + } + Set expected = new TreeSet<>(); + SOURCES.forEach(name -> expected.add(PACKAGE + name)); + require(actual.equals(expected), "Only the five audited generated Java sources are allowed"); + } + + private static Map upstream(Path build) throws Exception { + Path jar = build.resolve("upstream/nacos-api-2.5.1.jar"); + checkHash(jar, UPSTREAM_SHA256); + checkHash(build.resolve("upstream/nacos-api-2.5.1.pom"), POM_SHA256); + return entries(jar); + } + + private static void prepare(Path base, Path build) throws Exception { + Map original = upstream(build); + Path output = build.resolve("classes"); + // A non-clean build must not keep stale classes from a previous patch revision. + remove(output); + Set replaced = new TreeSet<>(); + for (var entry : original.entrySet()) { + String name = entry.getKey(); + if (isReplaced(name)) { + replaced.add(name.substring(PACKAGE.length())); + continue; + } + if (name.equals("META-INF/MANIFEST.MF")) { + name = "META-INF/nacos-upstream/MANIFEST.MF"; + } else if (name.startsWith(UPSTREAM_METADATA)) { + name = "META-INF/nacos-upstream/" + name.substring(UPSTREAM_METADATA.length()); + } + write(output, name, entry.getValue()); + } + require(replaced.equals(REPLACED), "Upstream generated class set differs from audited baseline"); + write(output, "META-INF/nacos-upstream/published-pom.xml", + Files.readAllBytes(build.resolve("upstream/nacos-api-2.5.1.pom"))); + write(output, "META-INF/nacos-patch/inputs.sha256.properties", + Files.readAllBytes(base.resolve("build/inputs.sha256.properties"))); + write(output, "META-INF/nacos-patch/protoc.sha256.properties", + Files.readAllBytes(base.resolve("build/protoc.sha256.properties"))); + // Include the exact schema and imported descriptor licenses with provenance. + try (var paths = Files.walk(base.resolve("src/main/proto"))) { + for (Path path : paths.filter(Files::isRegularFile).toList()) { + write(output, "META-INF/nacos-patch/proto/" + + base.resolve("src/main/proto").relativize(path).toString().replace('\\', '/'), + Files.readAllBytes(path)); + } + } + System.out.println("Verified upstream SHA-256; preserving all classes except 10 regenerated classes"); + } + + private static void verify(Path base, Path build, Path patchedJar) throws Exception { + Map original = upstream(build); + Map patched = entries(patchedJar); + Set oldClasses = new TreeSet<>(); + Set newClasses = new TreeSet<>(); + original.keySet().stream().filter(name -> name.endsWith(".class")).forEach(oldClasses::add); + patched.keySet().stream().filter(name -> name.endsWith(".class")).forEach(newClasses::add); + require(oldClasses.equals(newClasses), "Patched artifact must have exactly the upstream class names"); + for (var entry : original.entrySet()) { + String name = entry.getKey(); + if (isReplaced(name)) { + require(!Arrays.equals(entry.getValue(), patched.get(name)), "Unpatched generated class: " + name); + continue; + } + if (name.equals("META-INF/MANIFEST.MF")) { + name = "META-INF/nacos-upstream/MANIFEST.MF"; + } else if (name.startsWith(UPSTREAM_METADATA)) { + name = "META-INF/nacos-upstream/" + name.substring(UPSTREAM_METADATA.length()); + } + require(Arrays.equals(entry.getValue(), patched.get(name)), "Changed or missing upstream entry: " + name); + } + require(patched.keySet().stream().noneMatch(name -> name.startsWith(UPSTREAM_METADATA)), + "Artifact must not masquerade as the upstream Maven artifact"); + require(patched.containsKey("META-INF/maven/com.ulticode/nacos-api-patched/pom.properties"), + "Missing project-owned Maven identity"); + require(patched.containsKey("META-INF/nacos-patch/bom.json"), "Missing upstream SBOM pedigree"); + try (var paths = Files.walk(base.resolve("src/main/resources"))) { + for (Path path : paths.filter(Files::isRegularFile).toList()) { + String name = base.resolve("src/main/resources").relativize(path).toString().replace('\\', '/'); + require(Arrays.equals(Files.readAllBytes(path), patched.get(name)), + "Changed or missing audited artifact resource: " + name); + } + } + System.out.println("Verified packaged jar: exactly 10 replaced classes; all other upstream content preserved"); + } + + private static void regenerate(Path base, Path build, Path compiler) throws Exception { + Properties tools = properties(base.resolve("build/protoc.sha256.properties")); + String hash = sha256(Files.readAllBytes(compiler)); + require(tools.containsValue(hash), "Unrecognized protoc binary; expected pinned official protoc 3.25.6"); + require(compiler.toFile().setExecutable(true) || Files.isExecutable(compiler), + "Cannot execute verified protoc binary"); + Path generated = build.resolve("regenerated"); + remove(generated); + Files.createDirectories(generated); + Process process = new ProcessBuilder(compiler.toAbsolutePath().toString(), + "--proto_path=" + base.resolve("src/main/proto"), + "--java_out=" + generated, "nacos_grpc_service.proto") + .directory(base.toFile()).inheritIO().start(); + require(process.waitFor() == 0, "protoc failed"); + Map actual = new LinkedHashMap<>(); + try (var paths = Files.walk(generated)) { + for (Path path : paths.filter(Files::isRegularFile).toList()) { + actual.put(generated.relativize(path).toString().replace('\\', '/'), Files.readAllBytes(path)); + } + } + require(actual.size() == SOURCES.size(), "protoc produced an unexpected number of sources"); + for (String name : SOURCES) { + require(Arrays.equals(Files.readAllBytes(base.resolve("src/main/java/" + PACKAGE + name)), + actual.get(PACKAGE + name)), "Generated source differs: " + name); + } + System.out.println("Verified byte-identical regeneration with official protoc 3.25.6"); + } + + private static boolean isReplaced(String name) { + return name.startsWith(PACKAGE) && REPLACED.contains(name.substring(PACKAGE.length())); + } + + private static Map entries(Path jar) throws IOException { + Map result = new LinkedHashMap<>(); + try (ZipFile zip = new ZipFile(jar.toFile())) { + var entries = zip.entries(); + while (entries.hasMoreElements()) { + var entry = entries.nextElement(); + if (!entry.isDirectory()) { + try (InputStream stream = zip.getInputStream(entry)) { + require(result.put(entry.getName(), stream.readAllBytes()) == null, + "Duplicate jar entry: " + entry.getName()); + } + } + } + } + return result; + } + + private static void write(Path root, String name, byte[] bytes) throws IOException { + Path target = root.resolve(name).normalize(); + require(target.startsWith(root), "Invalid upstream entry: " + name); + Files.createDirectories(target.getParent()); + Files.write(target, bytes); + } + + private static void remove(Path directory) throws IOException { + if (Files.exists(directory)) { + try (var paths = Files.walk(directory)) { + for (Path path : paths.sorted(Comparator.reverseOrder()).toList()) { + Files.delete(path); + } + } + } + } + + private static Properties properties(Path path) throws IOException { + Properties result = new Properties(); + try (InputStream stream = Files.newInputStream(path)) { + result.load(stream); + } + return result; + } + + private static void checkHash(Path file, String expected) throws Exception { + require(sha256(Files.readAllBytes(file)).equals(expected), "SHA-256 mismatch: " + file); + } + + private static String sha256(byte[] bytes) throws Exception { + return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes)); + } + + private static void require(boolean condition, String message) throws IOException { + if (!condition) { + throw new IOException(message); + } + } +} diff --git a/services/platform/nacos-api-patched/build/inputs.sha256.properties b/services/platform/nacos-api-patched/build/inputs.sha256.properties new file mode 100644 index 0000000000..85dca848d6 --- /dev/null +++ b/services/platform/nacos-api-patched/build/inputs.sha256.properties @@ -0,0 +1,14 @@ +build/protoc.sha256.properties=4b59c49dac2485c963256c69f2de005f5dc71eadc9673816644fe6bc2df47131 +src/main/java/com/alibaba/nacos/api/grpc/auto/Metadata.java=b1d35338889b6ab3cdccf28517d1b94e9bb1a4762c3fea031b36e36557af7e5b +src/main/java/com/alibaba/nacos/api/grpc/auto/MetadataOrBuilder.java=2aa446c0fd872de299dc547c57418c73128a5c9862c98f40352894957b70d6b0 +src/main/java/com/alibaba/nacos/api/grpc/auto/NacosGrpcService.java=fa4a1425a50808fd58754e47bd858a8981a4fe5e8cabbb15a136910016609c21 +src/main/java/com/alibaba/nacos/api/grpc/auto/Payload.java=d1e67ebc4a311af450f4f3f137fac0a8c6645a5755abcfb9534e24f778057267 +src/main/java/com/alibaba/nacos/api/grpc/auto/PayloadOrBuilder.java=90fcf36a15b8f1d394013ef95de282bcff512cfcd6b5430db5f70b1112ece426 +src/main/proto/google/protobuf/any.proto=8e56f61e3078e9232d39ce1b1bab28613783af8191f93a910c3617933f87a179 +src/main/proto/google/protobuf/timestamp.proto=fa6c44dca2c90a2aaf3a1e4776cef2e9177f70fba365f236269f0f41ac166c96 +src/main/proto/nacos_grpc_service.proto=be9c1e5767ddf2450ec3824bf57038d921e53b8010bc51b2635f83df1c4e7e9e +src/main/resources/META-INF/LICENSE=c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4 +src/main/resources/META-INF/NOTICE=5bf6cfef7f75d90dc4a9b50c10f0240912f3646be584c5ac32b9dd36d06efce4 +src/main/resources/META-INF/nacos-patch/UPSTREAM-NOTICE=2a466e8e6f71b655b1b267a4221f2272b89cb4927aa8805af9e8ff82c3f5fac2 +src/main/resources/META-INF/nacos-patch/bom.json=2099f3a5553ff67c7640c7e153b10a78985a89ef0688fc074c02537d688bfdca +src/main/resources/META-INF/nacos-patch/provenance.json=33956981ec8ceb10829790d7ff31c1c0ff7172bd3588ef6b13d7ad5f23b6a784 diff --git a/services/platform/nacos-api-patched/build/protoc.sha256.properties b/services/platform/nacos-api-patched/build/protoc.sha256.properties new file mode 100644 index 0000000000..11bff69ee4 --- /dev/null +++ b/services/platform/nacos-api-patched/build/protoc.sha256.properties @@ -0,0 +1,6 @@ +linux-x86_64=a13450e63255d9e0d311feb6de70de0356b50f7791dc01cb9ce3732b74a41c69 +linux-aarch_64=aa9553045a8195a09745f57a6e1f617d7347cf3b16324afe5e1d64647fddcced +osx-x86_64=d55ac3681a454e4827af768f311a97c7583b09fef483e25c8e487a2a9c263bb7 +osx-aarch_64=d4a724a36fc80b3c74815d394ae46ff26a8f82e0f5f71f13d31ac8cf3a501087 +windows-x86_64=06f45952579be50e73e3f576c90413bfe94ff3602bff4cd290f4a1d1e04c364d +windows-x86_32=2fb31da786c66aeea2352b6461431279d7c357525569c85166bc4437b5e66d7e diff --git a/services/platform/nacos-api-patched/pom.xml b/services/platform/nacos-api-patched/pom.xml new file mode 100644 index 0000000000..ba2f8649af --- /dev/null +++ b/services/platform/nacos-api-patched/pom.xml @@ -0,0 +1,255 @@ + + + 4.0.0 + + com.ulticode + ulticode-backend + ${revision} + ../../pom.xml + + nacos-api-patched + 2.5.1-ulticode-1 + UltiCode patched Nacos API + Nacos API 2.5.1 with five protobuf sources regenerated using protoc 3.25.6. + This independently versioned derivative retains all other upstream class bytes. + Upstream hashes, Apache licensing and CycloneDX pedigree are included in the jar. + https://github.com/DavidHLP/UltiCode + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + 2025-03-11T13:53:00Z + linux-x86_64 + + + + + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + io.grpc + grpc-netty-shaded + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + io.grpc + grpc-util + + + com.google.api.grpc + proto-google-common-protos + 2.17.0 + + + com.google.protobuf + protobuf-java + + + javax.annotation + javax.annotation-api + 1.3.2 + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.8.1 + + + fetch-pinned-upstream-inputs + generate-sources + copy + + + + com.alibaba.nacos + nacos-api + 2.5.1 + jar + + + com.alibaba.nacos + nacos-api + 2.5.1 + pom + + + ${project.build.directory}/upstream + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.2.0 + + + verify-and-prepare-patched-api + generate-resources + run + + + + + + + + + + + + + + + verify-packaged-api + package + run + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + none + 8 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + UltiCode patched Nacos API + ${project.version} + 2.5.1 + 3.25.6 + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.1 + + + attach-upstream-pedigree + package + attach-artifact + + + + ${project.basedir}/src/main/resources/META-INF/nacos-patch/bom.json + json + cyclonedx + + + + + + + + + + + + + verify-nacos-generation + + + + org.apache.maven.plugins + maven-dependency-plugin + + + fetch-pinned-protoc + generate-sources + copy + + + + com.google.protobuf + protoc + 3.25.6 + ${nacos.protoc.classifier} + exe + protoc.exe + + + ${project.build.directory}/toolchain + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-generated-sources + process-sources + run + + + + + + + + + + + + + + + + + + diff --git a/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/Metadata.java b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/Metadata.java new file mode 100644 index 0000000000..0c38bf1dad --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/Metadata.java @@ -0,0 +1,957 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: nacos_grpc_service.proto + +// Protobuf Java Version: 3.25.6 +package com.alibaba.nacos.api.grpc.auto; + +/** + * Protobuf type {@code Metadata} + */ +public final class Metadata extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:Metadata) + MetadataOrBuilder { +private static final long serialVersionUID = 0L; + // Use Metadata.newBuilder() to construct. + private Metadata(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Metadata() { + type_ = ""; + clientIp_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Metadata(); + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Metadata_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + @java.lang.Override + protected com.google.protobuf.MapFieldReflectionAccessor internalGetMapFieldReflection( + int number) { + switch (number) { + case 7: + return internalGetHeaders(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Metadata_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.nacos.api.grpc.auto.Metadata.class, com.alibaba.nacos.api.grpc.auto.Metadata.Builder.class); + } + + public static final int TYPE_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object type_ = ""; + /** + * string type = 3; + * @return The type. + */ + @java.lang.Override + public java.lang.String getType() { + java.lang.Object ref = type_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + type_ = s; + return s; + } + } + /** + * string type = 3; + * @return The bytes for type. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getTypeBytes() { + java.lang.Object ref = type_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + type_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int CLIENTIP_FIELD_NUMBER = 8; + @SuppressWarnings("serial") + private volatile java.lang.Object clientIp_ = ""; + /** + * string clientIp = 8; + * @return The clientIp. + */ + @java.lang.Override + public java.lang.String getClientIp() { + java.lang.Object ref = clientIp_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + clientIp_ = s; + return s; + } + } + /** + * string clientIp = 8; + * @return The bytes for clientIp. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getClientIpBytes() { + java.lang.Object ref = clientIp_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientIp_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int HEADERS_FIELD_NUMBER = 7; + private static final class HeadersDefaultEntryHolder { + static final com.google.protobuf.MapEntry< + java.lang.String, java.lang.String> defaultEntry = + com.google.protobuf.MapEntry + .newDefaultInstance( + com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Metadata_HeadersEntry_descriptor, + com.google.protobuf.WireFormat.FieldType.STRING, + "", + com.google.protobuf.WireFormat.FieldType.STRING, + ""); + } + @SuppressWarnings("serial") + private com.google.protobuf.MapField< + java.lang.String, java.lang.String> headers_; + private com.google.protobuf.MapField + internalGetHeaders() { + if (headers_ == null) { + return com.google.protobuf.MapField.emptyMapField( + HeadersDefaultEntryHolder.defaultEntry); + } + return headers_; + } + public int getHeadersCount() { + return internalGetHeaders().getMap().size(); + } + /** + * map<string, string> headers = 7; + */ + @java.lang.Override + public boolean containsHeaders( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + return internalGetHeaders().getMap().containsKey(key); + } + /** + * Use {@link #getHeadersMap()} instead. + */ + @java.lang.Override + @java.lang.Deprecated + public java.util.Map getHeaders() { + return getHeadersMap(); + } + /** + * map<string, string> headers = 7; + */ + @java.lang.Override + public java.util.Map getHeadersMap() { + return internalGetHeaders().getMap(); + } + /** + * map<string, string> headers = 7; + */ + @java.lang.Override + public /* nullable */ +java.lang.String getHeadersOrDefault( + java.lang.String key, + /* nullable */ +java.lang.String defaultValue) { + if (key == null) { throw new NullPointerException("map key"); } + java.util.Map map = + internalGetHeaders().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<string, string> headers = 7; + */ + @java.lang.Override + public java.lang.String getHeadersOrThrow( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + java.util.Map map = + internalGetHeaders().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(type_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 3, type_); + } + com.google.protobuf.GeneratedMessageV3 + .serializeStringMapTo( + output, + internalGetHeaders(), + HeadersDefaultEntryHolder.defaultEntry, + 7); + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(clientIp_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 8, clientIp_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(type_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, type_); + } + for (java.util.Map.Entry entry + : internalGetHeaders().getMap().entrySet()) { + com.google.protobuf.MapEntry + headers__ = HeadersDefaultEntryHolder.defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build(); + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(7, headers__); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(clientIp_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, clientIp_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.nacos.api.grpc.auto.Metadata)) { + return super.equals(obj); + } + com.alibaba.nacos.api.grpc.auto.Metadata other = (com.alibaba.nacos.api.grpc.auto.Metadata) obj; + + if (!getType() + .equals(other.getType())) return false; + if (!getClientIp() + .equals(other.getClientIp())) return false; + if (!internalGetHeaders().equals( + other.internalGetHeaders())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + TYPE_FIELD_NUMBER; + hash = (53 * hash) + getType().hashCode(); + hash = (37 * hash) + CLIENTIP_FIELD_NUMBER; + hash = (53 * hash) + getClientIp().hashCode(); + if (!internalGetHeaders().getMap().isEmpty()) { + hash = (37 * hash) + HEADERS_FIELD_NUMBER; + hash = (53 * hash) + internalGetHeaders().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static com.alibaba.nacos.api.grpc.auto.Metadata parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + + public static com.alibaba.nacos.api.grpc.auto.Metadata parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.nacos.api.grpc.auto.Metadata parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.nacos.api.grpc.auto.Metadata prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code Metadata} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:Metadata) + com.alibaba.nacos.api.grpc.auto.MetadataOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Metadata_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapFieldReflectionAccessor internalGetMapFieldReflection( + int number) { + switch (number) { + case 7: + return internalGetHeaders(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapFieldReflectionAccessor internalGetMutableMapFieldReflection( + int number) { + switch (number) { + case 7: + return internalGetMutableHeaders(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Metadata_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.nacos.api.grpc.auto.Metadata.class, com.alibaba.nacos.api.grpc.auto.Metadata.Builder.class); + } + + // Construct using com.alibaba.nacos.api.grpc.auto.Metadata.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + type_ = ""; + clientIp_ = ""; + internalGetMutableHeaders().clear(); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Metadata_descriptor; + } + + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Metadata getDefaultInstanceForType() { + return com.alibaba.nacos.api.grpc.auto.Metadata.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Metadata build() { + com.alibaba.nacos.api.grpc.auto.Metadata result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Metadata buildPartial() { + com.alibaba.nacos.api.grpc.auto.Metadata result = new com.alibaba.nacos.api.grpc.auto.Metadata(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(com.alibaba.nacos.api.grpc.auto.Metadata result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.type_ = type_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.clientIp_ = clientIp_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.headers_ = internalGetHeaders(); + result.headers_.makeImmutable(); + } + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.nacos.api.grpc.auto.Metadata) { + return mergeFrom((com.alibaba.nacos.api.grpc.auto.Metadata)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.nacos.api.grpc.auto.Metadata other) { + if (other == com.alibaba.nacos.api.grpc.auto.Metadata.getDefaultInstance()) return this; + if (!other.getType().isEmpty()) { + type_ = other.type_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (!other.getClientIp().isEmpty()) { + clientIp_ = other.clientIp_; + bitField0_ |= 0x00000002; + onChanged(); + } + internalGetMutableHeaders().mergeFrom( + other.internalGetHeaders()); + bitField0_ |= 0x00000004; + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 26: { + type_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 26 + case 58: { + com.google.protobuf.MapEntry + headers__ = input.readMessage( + HeadersDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry); + internalGetMutableHeaders().getMutableMap().put( + headers__.getKey(), headers__.getValue()); + bitField0_ |= 0x00000004; + break; + } // case 58 + case 66: { + clientIp_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 66 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object type_ = ""; + /** + * string type = 3; + * @return The type. + */ + public java.lang.String getType() { + java.lang.Object ref = type_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + type_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string type = 3; + * @return The bytes for type. + */ + public com.google.protobuf.ByteString + getTypeBytes() { + java.lang.Object ref = type_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + type_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string type = 3; + * @param value The type to set. + * @return This builder for chaining. + */ + public Builder setType( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + type_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * string type = 3; + * @return This builder for chaining. + */ + public Builder clearType() { + type_ = getDefaultInstance().getType(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * string type = 3; + * @param value The bytes for type to set. + * @return This builder for chaining. + */ + public Builder setTypeBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + type_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private java.lang.Object clientIp_ = ""; + /** + * string clientIp = 8; + * @return The clientIp. + */ + public java.lang.String getClientIp() { + java.lang.Object ref = clientIp_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + clientIp_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string clientIp = 8; + * @return The bytes for clientIp. + */ + public com.google.protobuf.ByteString + getClientIpBytes() { + java.lang.Object ref = clientIp_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientIp_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string clientIp = 8; + * @param value The clientIp to set. + * @return This builder for chaining. + */ + public Builder setClientIp( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + clientIp_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * string clientIp = 8; + * @return This builder for chaining. + */ + public Builder clearClientIp() { + clientIp_ = getDefaultInstance().getClientIp(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * string clientIp = 8; + * @param value The bytes for clientIp to set. + * @return This builder for chaining. + */ + public Builder setClientIpBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + clientIp_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private com.google.protobuf.MapField< + java.lang.String, java.lang.String> headers_; + private com.google.protobuf.MapField + internalGetHeaders() { + if (headers_ == null) { + return com.google.protobuf.MapField.emptyMapField( + HeadersDefaultEntryHolder.defaultEntry); + } + return headers_; + } + private com.google.protobuf.MapField + internalGetMutableHeaders() { + if (headers_ == null) { + headers_ = com.google.protobuf.MapField.newMapField( + HeadersDefaultEntryHolder.defaultEntry); + } + if (!headers_.isMutable()) { + headers_ = headers_.copy(); + } + bitField0_ |= 0x00000004; + onChanged(); + return headers_; + } + public int getHeadersCount() { + return internalGetHeaders().getMap().size(); + } + /** + * map<string, string> headers = 7; + */ + @java.lang.Override + public boolean containsHeaders( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + return internalGetHeaders().getMap().containsKey(key); + } + /** + * Use {@link #getHeadersMap()} instead. + */ + @java.lang.Override + @java.lang.Deprecated + public java.util.Map getHeaders() { + return getHeadersMap(); + } + /** + * map<string, string> headers = 7; + */ + @java.lang.Override + public java.util.Map getHeadersMap() { + return internalGetHeaders().getMap(); + } + /** + * map<string, string> headers = 7; + */ + @java.lang.Override + public /* nullable */ +java.lang.String getHeadersOrDefault( + java.lang.String key, + /* nullable */ +java.lang.String defaultValue) { + if (key == null) { throw new NullPointerException("map key"); } + java.util.Map map = + internalGetHeaders().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<string, string> headers = 7; + */ + @java.lang.Override + public java.lang.String getHeadersOrThrow( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + java.util.Map map = + internalGetHeaders().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + public Builder clearHeaders() { + bitField0_ = (bitField0_ & ~0x00000004); + internalGetMutableHeaders().getMutableMap() + .clear(); + return this; + } + /** + * map<string, string> headers = 7; + */ + public Builder removeHeaders( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + internalGetMutableHeaders().getMutableMap() + .remove(key); + return this; + } + /** + * Use alternate mutation accessors instead. + */ + @java.lang.Deprecated + public java.util.Map + getMutableHeaders() { + bitField0_ |= 0x00000004; + return internalGetMutableHeaders().getMutableMap(); + } + /** + * map<string, string> headers = 7; + */ + public Builder putHeaders( + java.lang.String key, + java.lang.String value) { + if (key == null) { throw new NullPointerException("map key"); } + if (value == null) { throw new NullPointerException("map value"); } + internalGetMutableHeaders().getMutableMap() + .put(key, value); + bitField0_ |= 0x00000004; + return this; + } + /** + * map<string, string> headers = 7; + */ + public Builder putAllHeaders( + java.util.Map values) { + internalGetMutableHeaders().getMutableMap() + .putAll(values); + bitField0_ |= 0x00000004; + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:Metadata) + } + + // @@protoc_insertion_point(class_scope:Metadata) + private static final com.alibaba.nacos.api.grpc.auto.Metadata DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.nacos.api.grpc.auto.Metadata(); + } + + public static com.alibaba.nacos.api.grpc.auto.Metadata getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Metadata parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Metadata getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/MetadataOrBuilder.java b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/MetadataOrBuilder.java new file mode 100644 index 0000000000..9c11b4197b --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/MetadataOrBuilder.java @@ -0,0 +1,68 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: nacos_grpc_service.proto + +// Protobuf Java Version: 3.25.6 +package com.alibaba.nacos.api.grpc.auto; + +public interface MetadataOrBuilder extends + // @@protoc_insertion_point(interface_extends:Metadata) + com.google.protobuf.MessageOrBuilder { + + /** + * string type = 3; + * @return The type. + */ + java.lang.String getType(); + /** + * string type = 3; + * @return The bytes for type. + */ + com.google.protobuf.ByteString + getTypeBytes(); + + /** + * string clientIp = 8; + * @return The clientIp. + */ + java.lang.String getClientIp(); + /** + * string clientIp = 8; + * @return The bytes for clientIp. + */ + com.google.protobuf.ByteString + getClientIpBytes(); + + /** + * map<string, string> headers = 7; + */ + int getHeadersCount(); + /** + * map<string, string> headers = 7; + */ + boolean containsHeaders( + java.lang.String key); + /** + * Use {@link #getHeadersMap()} instead. + */ + @java.lang.Deprecated + java.util.Map + getHeaders(); + /** + * map<string, string> headers = 7; + */ + java.util.Map + getHeadersMap(); + /** + * map<string, string> headers = 7; + */ + /* nullable */ +java.lang.String getHeadersOrDefault( + java.lang.String key, + /* nullable */ +java.lang.String defaultValue); + /** + * map<string, string> headers = 7; + */ + java.lang.String getHeadersOrThrow( + java.lang.String key); +} diff --git a/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/NacosGrpcService.java b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/NacosGrpcService.java new file mode 100644 index 0000000000..ba785410b6 --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/NacosGrpcService.java @@ -0,0 +1,84 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: nacos_grpc_service.proto + +// Protobuf Java Version: 3.25.6 +package com.alibaba.nacos.api.grpc.auto; + +public final class NacosGrpcService { + private NacosGrpcService() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + static final com.google.protobuf.Descriptors.Descriptor + internal_static_Metadata_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_Metadata_fieldAccessorTable; + static final com.google.protobuf.Descriptors.Descriptor + internal_static_Metadata_HeadersEntry_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_Metadata_HeadersEntry_fieldAccessorTable; + static final com.google.protobuf.Descriptors.Descriptor + internal_static_Payload_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_Payload_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\030nacos_grpc_service.proto\032\031google/proto" + + "buf/any.proto\032\037google/protobuf/timestamp" + + ".proto\"\203\001\n\010Metadata\022\014\n\004type\030\003 \001(\t\022\020\n\010cli" + + "entIp\030\010 \001(\t\022\'\n\007headers\030\007 \003(\0132\026.Metadata." + + "HeadersEntry\032.\n\014HeadersEntry\022\013\n\003key\030\001 \001(" + + "\t\022\r\n\005value\030\002 \001(\t:\0028\001\"J\n\007Payload\022\033\n\010metad" + + "ata\030\002 \001(\0132\t.Metadata\022\"\n\004body\030\003 \001(\0132\024.goo" + + "gle.protobuf.Any2*\n\007Request\022\037\n\007request\022\010" + + ".Payload\032\010.Payload\"\0002>\n\017BiRequestStream\022" + + "+\n\017requestBiStream\022\010.Payload\032\010.Payload\"\000" + + "(\0010\001B#\n\037com.alibaba.nacos.api.grpc.autoP" + + "\001b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + com.google.protobuf.AnyProto.getDescriptor(), + com.google.protobuf.TimestampProto.getDescriptor(), + }); + internal_static_Metadata_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_Metadata_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_Metadata_descriptor, + new java.lang.String[] { "Type", "ClientIp", "Headers", }); + internal_static_Metadata_HeadersEntry_descriptor = + internal_static_Metadata_descriptor.getNestedTypes().get(0); + internal_static_Metadata_HeadersEntry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_Metadata_HeadersEntry_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_Payload_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_Payload_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_Payload_descriptor, + new java.lang.String[] { "Metadata", "Body", }); + com.google.protobuf.AnyProto.getDescriptor(); + com.google.protobuf.TimestampProto.getDescriptor(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/Payload.java b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/Payload.java new file mode 100644 index 0000000000..03b100daa6 --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/Payload.java @@ -0,0 +1,784 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: nacos_grpc_service.proto + +// Protobuf Java Version: 3.25.6 +package com.alibaba.nacos.api.grpc.auto; + +/** + * Protobuf type {@code Payload} + */ +public final class Payload extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:Payload) + PayloadOrBuilder { +private static final long serialVersionUID = 0L; + // Use Payload.newBuilder() to construct. + private Payload(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Payload() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Payload(); + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Payload_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Payload_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.nacos.api.grpc.auto.Payload.class, com.alibaba.nacos.api.grpc.auto.Payload.Builder.class); + } + + private int bitField0_; + public static final int METADATA_FIELD_NUMBER = 2; + private com.alibaba.nacos.api.grpc.auto.Metadata metadata_; + /** + * .Metadata metadata = 2; + * @return Whether the metadata field is set. + */ + @java.lang.Override + public boolean hasMetadata() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * .Metadata metadata = 2; + * @return The metadata. + */ + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Metadata getMetadata() { + return metadata_ == null ? com.alibaba.nacos.api.grpc.auto.Metadata.getDefaultInstance() : metadata_; + } + /** + * .Metadata metadata = 2; + */ + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.MetadataOrBuilder getMetadataOrBuilder() { + return metadata_ == null ? com.alibaba.nacos.api.grpc.auto.Metadata.getDefaultInstance() : metadata_; + } + + public static final int BODY_FIELD_NUMBER = 3; + private com.google.protobuf.Any body_; + /** + * .google.protobuf.Any body = 3; + * @return Whether the body field is set. + */ + @java.lang.Override + public boolean hasBody() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * .google.protobuf.Any body = 3; + * @return The body. + */ + @java.lang.Override + public com.google.protobuf.Any getBody() { + return body_ == null ? com.google.protobuf.Any.getDefaultInstance() : body_; + } + /** + * .google.protobuf.Any body = 3; + */ + @java.lang.Override + public com.google.protobuf.AnyOrBuilder getBodyOrBuilder() { + return body_ == null ? com.google.protobuf.Any.getDefaultInstance() : body_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) != 0)) { + output.writeMessage(2, getMetadata()); + } + if (((bitField0_ & 0x00000002) != 0)) { + output.writeMessage(3, getBody()); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, getMetadata()); + } + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, getBody()); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.nacos.api.grpc.auto.Payload)) { + return super.equals(obj); + } + com.alibaba.nacos.api.grpc.auto.Payload other = (com.alibaba.nacos.api.grpc.auto.Payload) obj; + + if (hasMetadata() != other.hasMetadata()) return false; + if (hasMetadata()) { + if (!getMetadata() + .equals(other.getMetadata())) return false; + } + if (hasBody() != other.hasBody()) return false; + if (hasBody()) { + if (!getBody() + .equals(other.getBody())) return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasMetadata()) { + hash = (37 * hash) + METADATA_FIELD_NUMBER; + hash = (53 * hash) + getMetadata().hashCode(); + } + if (hasBody()) { + hash = (37 * hash) + BODY_FIELD_NUMBER; + hash = (53 * hash) + getBody().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static com.alibaba.nacos.api.grpc.auto.Payload parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + + public static com.alibaba.nacos.api.grpc.auto.Payload parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.nacos.api.grpc.auto.Payload parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.nacos.api.grpc.auto.Payload prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code Payload} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:Payload) + com.alibaba.nacos.api.grpc.auto.PayloadOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Payload_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Payload_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.nacos.api.grpc.auto.Payload.class, com.alibaba.nacos.api.grpc.auto.Payload.Builder.class); + } + + // Construct using com.alibaba.nacos.api.grpc.auto.Payload.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getMetadataFieldBuilder(); + getBodyFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + metadata_ = null; + if (metadataBuilder_ != null) { + metadataBuilder_.dispose(); + metadataBuilder_ = null; + } + body_ = null; + if (bodyBuilder_ != null) { + bodyBuilder_.dispose(); + bodyBuilder_ = null; + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.nacos.api.grpc.auto.NacosGrpcService.internal_static_Payload_descriptor; + } + + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Payload getDefaultInstanceForType() { + return com.alibaba.nacos.api.grpc.auto.Payload.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Payload build() { + com.alibaba.nacos.api.grpc.auto.Payload result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Payload buildPartial() { + com.alibaba.nacos.api.grpc.auto.Payload result = new com.alibaba.nacos.api.grpc.auto.Payload(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(com.alibaba.nacos.api.grpc.auto.Payload result) { + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.metadata_ = metadataBuilder_ == null + ? metadata_ + : metadataBuilder_.build(); + to_bitField0_ |= 0x00000001; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.body_ = bodyBuilder_ == null + ? body_ + : bodyBuilder_.build(); + to_bitField0_ |= 0x00000002; + } + result.bitField0_ |= to_bitField0_; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.nacos.api.grpc.auto.Payload) { + return mergeFrom((com.alibaba.nacos.api.grpc.auto.Payload)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.nacos.api.grpc.auto.Payload other) { + if (other == com.alibaba.nacos.api.grpc.auto.Payload.getDefaultInstance()) return this; + if (other.hasMetadata()) { + mergeMetadata(other.getMetadata()); + } + if (other.hasBody()) { + mergeBody(other.getBody()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 18: { + input.readMessage( + getMetadataFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00000001; + break; + } // case 18 + case 26: { + input.readMessage( + getBodyFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00000002; + break; + } // case 26 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private com.alibaba.nacos.api.grpc.auto.Metadata metadata_; + private com.google.protobuf.SingleFieldBuilderV3< + com.alibaba.nacos.api.grpc.auto.Metadata, com.alibaba.nacos.api.grpc.auto.Metadata.Builder, com.alibaba.nacos.api.grpc.auto.MetadataOrBuilder> metadataBuilder_; + /** + * .Metadata metadata = 2; + * @return Whether the metadata field is set. + */ + public boolean hasMetadata() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * .Metadata metadata = 2; + * @return The metadata. + */ + public com.alibaba.nacos.api.grpc.auto.Metadata getMetadata() { + if (metadataBuilder_ == null) { + return metadata_ == null ? com.alibaba.nacos.api.grpc.auto.Metadata.getDefaultInstance() : metadata_; + } else { + return metadataBuilder_.getMessage(); + } + } + /** + * .Metadata metadata = 2; + */ + public Builder setMetadata(com.alibaba.nacos.api.grpc.auto.Metadata value) { + if (metadataBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + metadata_ = value; + } else { + metadataBuilder_.setMessage(value); + } + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * .Metadata metadata = 2; + */ + public Builder setMetadata( + com.alibaba.nacos.api.grpc.auto.Metadata.Builder builderForValue) { + if (metadataBuilder_ == null) { + metadata_ = builderForValue.build(); + } else { + metadataBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * .Metadata metadata = 2; + */ + public Builder mergeMetadata(com.alibaba.nacos.api.grpc.auto.Metadata value) { + if (metadataBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0) && + metadata_ != null && + metadata_ != com.alibaba.nacos.api.grpc.auto.Metadata.getDefaultInstance()) { + getMetadataBuilder().mergeFrom(value); + } else { + metadata_ = value; + } + } else { + metadataBuilder_.mergeFrom(value); + } + if (metadata_ != null) { + bitField0_ |= 0x00000001; + onChanged(); + } + return this; + } + /** + * .Metadata metadata = 2; + */ + public Builder clearMetadata() { + bitField0_ = (bitField0_ & ~0x00000001); + metadata_ = null; + if (metadataBuilder_ != null) { + metadataBuilder_.dispose(); + metadataBuilder_ = null; + } + onChanged(); + return this; + } + /** + * .Metadata metadata = 2; + */ + public com.alibaba.nacos.api.grpc.auto.Metadata.Builder getMetadataBuilder() { + bitField0_ |= 0x00000001; + onChanged(); + return getMetadataFieldBuilder().getBuilder(); + } + /** + * .Metadata metadata = 2; + */ + public com.alibaba.nacos.api.grpc.auto.MetadataOrBuilder getMetadataOrBuilder() { + if (metadataBuilder_ != null) { + return metadataBuilder_.getMessageOrBuilder(); + } else { + return metadata_ == null ? + com.alibaba.nacos.api.grpc.auto.Metadata.getDefaultInstance() : metadata_; + } + } + /** + * .Metadata metadata = 2; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.alibaba.nacos.api.grpc.auto.Metadata, com.alibaba.nacos.api.grpc.auto.Metadata.Builder, com.alibaba.nacos.api.grpc.auto.MetadataOrBuilder> + getMetadataFieldBuilder() { + if (metadataBuilder_ == null) { + metadataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.alibaba.nacos.api.grpc.auto.Metadata, com.alibaba.nacos.api.grpc.auto.Metadata.Builder, com.alibaba.nacos.api.grpc.auto.MetadataOrBuilder>( + getMetadata(), + getParentForChildren(), + isClean()); + metadata_ = null; + } + return metadataBuilder_; + } + + private com.google.protobuf.Any body_; + private com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder> bodyBuilder_; + /** + * .google.protobuf.Any body = 3; + * @return Whether the body field is set. + */ + public boolean hasBody() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * .google.protobuf.Any body = 3; + * @return The body. + */ + public com.google.protobuf.Any getBody() { + if (bodyBuilder_ == null) { + return body_ == null ? com.google.protobuf.Any.getDefaultInstance() : body_; + } else { + return bodyBuilder_.getMessage(); + } + } + /** + * .google.protobuf.Any body = 3; + */ + public Builder setBody(com.google.protobuf.Any value) { + if (bodyBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + body_ = value; + } else { + bodyBuilder_.setMessage(value); + } + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * .google.protobuf.Any body = 3; + */ + public Builder setBody( + com.google.protobuf.Any.Builder builderForValue) { + if (bodyBuilder_ == null) { + body_ = builderForValue.build(); + } else { + bodyBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * .google.protobuf.Any body = 3; + */ + public Builder mergeBody(com.google.protobuf.Any value) { + if (bodyBuilder_ == null) { + if (((bitField0_ & 0x00000002) != 0) && + body_ != null && + body_ != com.google.protobuf.Any.getDefaultInstance()) { + getBodyBuilder().mergeFrom(value); + } else { + body_ = value; + } + } else { + bodyBuilder_.mergeFrom(value); + } + if (body_ != null) { + bitField0_ |= 0x00000002; + onChanged(); + } + return this; + } + /** + * .google.protobuf.Any body = 3; + */ + public Builder clearBody() { + bitField0_ = (bitField0_ & ~0x00000002); + body_ = null; + if (bodyBuilder_ != null) { + bodyBuilder_.dispose(); + bodyBuilder_ = null; + } + onChanged(); + return this; + } + /** + * .google.protobuf.Any body = 3; + */ + public com.google.protobuf.Any.Builder getBodyBuilder() { + bitField0_ |= 0x00000002; + onChanged(); + return getBodyFieldBuilder().getBuilder(); + } + /** + * .google.protobuf.Any body = 3; + */ + public com.google.protobuf.AnyOrBuilder getBodyOrBuilder() { + if (bodyBuilder_ != null) { + return bodyBuilder_.getMessageOrBuilder(); + } else { + return body_ == null ? + com.google.protobuf.Any.getDefaultInstance() : body_; + } + } + /** + * .google.protobuf.Any body = 3; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder> + getBodyFieldBuilder() { + if (bodyBuilder_ == null) { + bodyBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder>( + getBody(), + getParentForChildren(), + isClean()); + body_ = null; + } + return bodyBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:Payload) + } + + // @@protoc_insertion_point(class_scope:Payload) + private static final com.alibaba.nacos.api.grpc.auto.Payload DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.nacos.api.grpc.auto.Payload(); + } + + public static com.alibaba.nacos.api.grpc.auto.Payload getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Payload parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.nacos.api.grpc.auto.Payload getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/PayloadOrBuilder.java b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/PayloadOrBuilder.java new file mode 100644 index 0000000000..9776b0c1d2 --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/java/com/alibaba/nacos/api/grpc/auto/PayloadOrBuilder.java @@ -0,0 +1,40 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: nacos_grpc_service.proto + +// Protobuf Java Version: 3.25.6 +package com.alibaba.nacos.api.grpc.auto; + +public interface PayloadOrBuilder extends + // @@protoc_insertion_point(interface_extends:Payload) + com.google.protobuf.MessageOrBuilder { + + /** + * .Metadata metadata = 2; + * @return Whether the metadata field is set. + */ + boolean hasMetadata(); + /** + * .Metadata metadata = 2; + * @return The metadata. + */ + com.alibaba.nacos.api.grpc.auto.Metadata getMetadata(); + /** + * .Metadata metadata = 2; + */ + com.alibaba.nacos.api.grpc.auto.MetadataOrBuilder getMetadataOrBuilder(); + + /** + * .google.protobuf.Any body = 3; + * @return Whether the body field is set. + */ + boolean hasBody(); + /** + * .google.protobuf.Any body = 3; + * @return The body. + */ + com.google.protobuf.Any getBody(); + /** + * .google.protobuf.Any body = 3; + */ + com.google.protobuf.AnyOrBuilder getBodyOrBuilder(); +} diff --git a/services/platform/nacos-api-patched/src/main/proto/google/protobuf/any.proto b/services/platform/nacos-api-patched/src/main/proto/google/protobuf/any.proto new file mode 100644 index 0000000000..e95b5b49cf --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/proto/google/protobuf/any.proto @@ -0,0 +1,106 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// In its binary encoding, an `Any` is an ordinary message; but in other wire +// forms like JSON, it has a special encoding. The format of the type URL is +// described on the `type_url` field. +// +// Protobuf APIs provide utilities to interact with `Any` values: +// +// - A 'pack' operation accepts a message and constructs a generic `Any` wrapper +// around it. +// - An 'unpack' operation reads the content of an `Any` message, either into an +// existing message or a new one. Unpack operations must check the type of the +// value they unpack against the declared `type_url`. +// - An 'is' operation decides whether an `Any` contains a message of the given +// type, i.e. whether it can 'unpack' that type. +// +// The JSON format representation of an `Any` follows one of these cases: +// +// - For types without special-cased JSON encodings, the JSON format +// representation of the `Any` is the same as that of the message, with an +// additional `@type` field which contains the type URL. +// - For types with special-cased JSON encodings (typically called 'well-known' +// types, listed in https://protobuf.dev/programming-guides/json/#any), the +// JSON format representation has a key `@type` which contains the type URL +// and a key `value` which contains the JSON-serialized value. +// +// The text format representation of an `Any` is like a message with one field +// whose name is the type URL in brackets. For example, an `Any` containing a +// `foo.Bar` message may be written `[type.googleapis.com/foo.Bar] { a: 2 }`. +message Any { + // Identifies the type of the serialized Protobuf message with a URI reference + // consisting of a prefix ending in a slash and the fully-qualified type name. + // + // Example: type.googleapis.com/google.protobuf.StringValue + // + // This string must contain at least one `/` character, and the content after + // the last `/` must be the fully-qualified name of the type in canonical + // form, without a leading dot. Do not write a scheme on these URI references + // so that clients do not attempt to contact them. + // + // The prefix is arbitrary and Protobuf implementations are expected to + // simply strip off everything up to and including the last `/` to identify + // the type. `type.googleapis.com/` is a common default prefix that some + // legacy implementations require. This prefix does not indicate the origin of + // the type, and URIs containing it are not expected to respond to any + // requests. + // + // All type URL strings must be legal URI references with the additional + // restriction (for the text format) that the content of the reference + // must consist only of alphanumeric characters, percent-encoded escapes, and + // characters in the following set (not including the outer backticks): + // `/-.~_!$&()*+,;=`. Despite our allowing percent encodings, implementations + // should not unescape them to prevent confusion with existing parsers. For + // example, `type.googleapis.com%2FFoo` should be rejected. + // + // In the original design of `Any`, the possibility of launching a type + // resolution service at these type URLs was considered but Protobuf never + // implemented one and considers contacting these URLs to be problematic and + // a potential security issue. Do not attempt to contact type URLs. + string type_url = 1; + + // Holds a Protobuf serialization of the type described by type_url. + bytes value = 2; +} diff --git a/services/platform/nacos-api-patched/src/main/proto/google/protobuf/timestamp.proto b/services/platform/nacos-api-patched/src/main/proto/google/protobuf/timestamp.proto new file mode 100644 index 0000000000..6bc1efc6d6 --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/proto/google/protobuf/timestamp.proto @@ -0,0 +1,145 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/timestamppb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// Example 5: Compute Timestamp from Java `Instant.now()`. +// +// Instant now = Instant.now(); +// +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); +// +// Example 6: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A ProtoJSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a ProtoJSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() +// ) to obtain a formatter capable of generating timestamps in this format. +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must + // be between -62135596800 and 253402300799 inclusive (which corresponds to + // 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z). + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. This field is + // the nanosecond portion of the duration, not an alternative to seconds. + // Negative second values with fractions must still have non-negative nanos + // values that count forward in time. Must be between 0 and 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/services/platform/nacos-api-patched/src/main/proto/nacos_grpc_service.proto b/services/platform/nacos-api-patched/src/main/proto/nacos_grpc_service.proto new file mode 100644 index 0000000000..adf2a30a1d --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/proto/nacos_grpc_service.proto @@ -0,0 +1,47 @@ + +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ + +syntax = "proto3"; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +option java_multiple_files = true; +option java_package = "com.alibaba.nacos.api.grpc.auto"; + +message Metadata { + string type = 3; + string clientIp = 8; + map headers = 7; +} + +message Payload { + Metadata metadata = 2; + google.protobuf.Any body = 3; +} + +service Request { + // Sends a commonRequest + rpc request (Payload) returns (Payload) { + } +} + +service BiRequestStream { + // Sends a biStreamRequest + rpc requestBiStream (stream Payload) returns (stream Payload) { + } +} diff --git a/services/platform/nacos-api-patched/src/main/resources/META-INF/LICENSE b/services/platform/nacos-api-patched/src/main/resources/META-INF/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/resources/META-INF/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/services/platform/nacos-api-patched/src/main/resources/META-INF/NOTICE b/services/platform/nacos-api-patched/src/main/resources/META-INF/NOTICE new file mode 100644 index 0000000000..7103488e06 --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/resources/META-INF/NOTICE @@ -0,0 +1,16 @@ +UltiCode patched Nacos API + +Derived from Alibaba Nacos API 2.5.1, Apache License 2.0. +Copyright 1999-2020 Alibaba Group Holding Ltd. + +UltiCode modification: regenerate Metadata, MetadataOrBuilder, Payload, +PayloadOrBuilder and NacosGrpcService from the unchanged upstream schema +with official protoc 3.25.6. Original gRPC stubs and all other upstream +class bytes are retained. This is not an official Alibaba Nacos release. + +See META-INF/nacos-patch/UPSTREAM-NOTICE for the original distribution +notice, and META-INF/nacos-patch/proto/google/protobuf for imported +descriptor copyright and BSD license notices. The original artifact and +POM hashes, schema provenance and CycloneDX pedigree are included under +META-INF/nacos-patch/. Original Maven metadata is retained only under +META-INF/nacos-upstream/, not as this artifact's Maven identity. diff --git a/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/UPSTREAM-NOTICE b/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/UPSTREAM-NOTICE new file mode 100644 index 0000000000..3681718ce7 --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/UPSTREAM-NOTICE @@ -0,0 +1,108 @@ +Nacos +Copyright 2018-2020 + +This product includes software developed at +The Alibaba MiddleWare Group. + +------ +This product has a bundle Spring Boot: + The Spring Boot Project + ================= + +Please visit the Spring Boot web site for more information: + + * https://spring.io/projects/spring-boot + +Copyright 2014 The Spring Boot Project + +The Spring Boot Project 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. + +Also, please refer to each LICENSE..txt file, which is located in +the 'license' directory of the distribution file, for the license terms of the +components that this product depends on. + +------ +This product has a bundle Spring Framework: + Spring Framework + ================= + +Please visit the git for more information: + + * https://github.com/spring-projects/spring-framework.git + +Copyright 2002-2020 the original author or authors. + +The Spring Framework 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. + +Also, please refer to each LICENSE..txt file, which is located in +the 'license' directory of the distribution file, for the license terms of the +components that this product depends on. + +------ +com.alibaba.nacos.common.utils.InetAddressValidator.java in this product is +copied from com.dynatrace.openkit.core.util.InetAddressValidator.java of openkit-java project. +https://github.com/Dynatrace/openkit-java + openkit-java + ====================== + + Copyright 2018-2021 Dynatrace LLC + + Licensed 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. + +------ +This product has a bundle Protocol Buffers�� + Protocol Buffers + ======================= +Protocol Buffers for Go with Gadgets + +Copyright (c) 2013, The GoGo Authors. All rights reserved. +http://github.com/gogo/protobuf + +------ +This product has a bundle Istio�� + Istio + ======================= + + Copyright 2018 Istio Authors + + Licensed 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. diff --git a/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/bom.json b/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/bom.json new file mode 100644 index 0000000000..c7d18e0060 --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/bom.json @@ -0,0 +1,132 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "version": 1, + "metadata": { + "component": { + "type": "library", + "bom-ref": "pkg:maven/com.ulticode/nacos-api-patched@2.5.1-ulticode-1", + "group": "com.ulticode", + "name": "nacos-api-patched", + "version": "2.5.1-ulticode-1", + "purl": "pkg:maven/com.ulticode/nacos-api-patched@2.5.1-ulticode-1", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "pedigree": { + "ancestors": [ + { + "type": "library", + "group": "com.alibaba.nacos", + "name": "nacos-api", + "version": "2.5.1", + "purl": "pkg:maven/com.alibaba.nacos/nacos-api@2.5.1", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "19b0eabdc1f637eeeec72cba9d6f85fe2bcae62bb9be7e7fb8a25ce183ce8738" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://repo.maven.apache.org/maven2/com/alibaba/nacos/nacos-api/2.5.1/nacos-api-2.5.1.jar" + }, + { + "type": "vcs", + "url": "https://github.com/alibaba/nacos/tree/2.5.1" + } + ] + } + ], + "notes": "Regenerate Metadata, MetadataOrBuilder, Payload, PayloadOrBuilder and NacosGrpcService only. Preserve all other upstream class and resource bytes. Relocate upstream Maven metadata; assign distinct project-owned identity." + }, + "properties": [ + { + "name": "ulticode:protoc-version", + "value": "3.25.6" + }, + { + "name": "ulticode:schema-sha256", + "value": "be9c1e5767ddf2450ec3824bf57038d921e53b8010bc51b2635f83df1c4e7e9e" + }, + { + "name": "ulticode:upstream-pom-sha256", + "value": "6b814f79526b1043bc3f81c6c65ab93003205fd0d2c76bf514927967b91a972b" + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/DavidHLP/UltiCode" + }, + { + "type": "advisories", + "url": "https://github.com/protocolbuffers/protobuf/security/advisories/GHSA-h4h5-3hr4-j3g2" + } + ] + } + }, + "components": [ + { + "type": "library", + "group": "com.alibaba.nacos", + "name": "nacos-api", + "version": "2.5.1", + "purl": "pkg:maven/com.alibaba.nacos/nacos-api@2.5.1", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "19b0eabdc1f637eeeec72cba9d6f85fe2bcae62bb9be7e7fb8a25ce183ce8738" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://repo.maven.apache.org/maven2/com/alibaba/nacos/nacos-api/2.5.1/nacos-api-2.5.1.jar" + }, + { + "type": "vcs", + "url": "https://github.com/alibaba/nacos/tree/2.5.1" + } + ], + "bom-ref": "pkg:maven/com.alibaba.nacos/nacos-api@2.5.1", + "properties": [ + { + "name": "ulticode:relationship", + "value": "Upstream code incorporated into this derivative, not a second runtime jar; retained for ordinary SBOM vulnerability attribution." + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:maven/com.ulticode/nacos-api-patched@2.5.1-ulticode-1", + "dependsOn": [ + "pkg:maven/com.alibaba.nacos/nacos-api@2.5.1" + ] + }, + { + "ref": "pkg:maven/com.alibaba.nacos/nacos-api@2.5.1", + "dependsOn": [] + } + ] +} diff --git a/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/provenance.json b/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/provenance.json new file mode 100644 index 0000000000..a9c045d86d --- /dev/null +++ b/services/platform/nacos-api-patched/src/main/resources/META-INF/nacos-patch/provenance.json @@ -0,0 +1,28 @@ +{ + "artifact": "pkg:maven/com.ulticode/nacos-api-patched@2.5.1-ulticode-1", + "upstream": { + "coordinates": "com.alibaba.nacos:nacos-api:2.5.1", + "url": "https://repo.maven.apache.org/maven2/com/alibaba/nacos/nacos-api/2.5.1/nacos-api-2.5.1.jar", + "sha256": "19b0eabdc1f637eeeec72cba9d6f85fe2bcae62bb9be7e7fb8a25ce183ce8738", + "pomSha256": "6b814f79526b1043bc3f81c6c65ab93003205fd0d2c76bf514927967b91a972b" + }, + "schema": { + "url": "https://github.com/alibaba/nacos/blob/2.5.1/api/src/main/proto/nacos_grpc_service.proto", + "gitBlob": "adf2a30a1d35363bf53eae3873975e2f0d6c79d2", + "sha256": "be9c1e5767ddf2450ec3824bf57038d921e53b8010bc51b2635f83df1c4e7e9e" + }, + "generator": { + "coordinates": "com.google.protobuf:protoc:3.25.6", + "url": "https://repo.maven.apache.org/maven2/com/google/protobuf/protoc/3.25.6/", + "hashManifest": "META-INF/nacos-patch/protoc.sha256.properties", + "arguments": [ + "--proto_path=src/main/proto", + "--java_out=", + "nacos_grpc_service.proto" + ] + }, + "modification": "Regenerate Metadata, MetadataOrBuilder, Payload, PayloadOrBuilder and NacosGrpcService only. Preserve all other upstream class and resource bytes. Relocate upstream Maven metadata; assign distinct project-owned identity.", + "advisory": "https://github.com/protocolbuffers/protobuf/security/advisories/GHSA-h4h5-3hr4-j3g2", + "verification": "Normal Maven package verifies pinned inputs, exact class inventory and byte-identical unmodified entries. verify-nacos-generation profile additionally verifies official protoc hash and regenerates all five sources byte-for-byte. No runtime compatibility switches are used.", + "scope": "Client API derivative only; does not certify the Nacos server, complete registry integration, or unrelated transitive generated protobuf classes." +} diff --git a/services/platform/rpc-resilience/pom.xml b/services/platform/rpc-resilience/pom.xml index 06a9afffc4..df79d181c3 100644 --- a/services/platform/rpc-resilience/pom.xml +++ b/services/platform/rpc-resilience/pom.xml @@ -27,9 +27,8 @@ ${nacos-client.version} - com.alibaba.nacos - nacos-api - ${nacos-client.version} + com.ulticode + nacos-api-patched org.apache.dubbo @@ -64,6 +63,9 @@ maven-surefire-plugin -Djdk.attach.allowAttachSelf=true + + true + diff --git a/services/platform/rpc-resilience/src/test/java/com/ulticode/rpc/resilience/NacosRegistryIT.java b/services/platform/rpc-resilience/src/test/java/com/ulticode/rpc/resilience/NacosRegistryIT.java new file mode 100644 index 0000000000..c7cda16c29 --- /dev/null +++ b/services/platform/rpc-resilience/src/test/java/com/ulticode/rpc/resilience/NacosRegistryIT.java @@ -0,0 +1,530 @@ +package com.ulticode.rpc.resilience; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.alibaba.nacos.api.NacosFactory; +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.config.listener.Listener; +import com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest; +import com.alibaba.nacos.api.config.remote.request.ConfigPublishRequest; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.client.config.NacosConfigService; +import com.alibaba.nacos.client.config.impl.ClientWorker; +import com.alibaba.nacos.client.config.impl.ConfigTransportClient; +import com.alibaba.nacos.common.remote.client.RpcClient; +import com.alibaba.nacos.common.remote.client.RpcClientFactory; +import com.alibaba.nacos.common.remote.client.grpc.GrpcClient; +import com.alibaba.nacos.common.remote.client.grpc.GrpcConnection; +import io.grpc.ManagedChannel; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.time.Duration; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.function.Predicate; +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; + +/** Run only through scripts/test/nacos-client-integration.sh, never against an existing registry. */ +class NacosRegistryIT { + + private static final String SERVICE = "backend-auth"; + private static final String GROUP = "DEFAULT_GROUP"; + private static final String DATA_ID = "com.ulticode.auth.ProtobufFixture"; + private static final String CONFIG_GROUP = "mapping"; + private static final String FIXTURE_DATA_ID = "protobuf-fixture-metadata"; + private static final String FIXTURE_CONFIG_GROUP = "FIXTURE_CONFIG"; + private static final String WORKLOAD_CONTENT = "workload-metadata-v1"; + private static final Duration WAIT = Duration.ofSeconds(60); + private static String phase = "startup"; + private static String callbackReadback = "not-run"; + + public static void main(String[] args) throws Exception { + NacosRuntimeDependencyTest.requiresSafeGencodeWithoutAnUnsafeOverride(); + assertThat(NacosFactory.class.getProtectionDomain().getCodeSource().getLocation().toString()) + .endsWith("nacos-api-patched-2.5.1-ulticode-1.jar"); + try { + new NacosRegistryIT().authenticatedRegistrySurvivesServerRestart(); + } catch (Throwable failure) { + Files.writeString(Path.of(required("NACOS_FIXTURE_CONTROL"), "failure"), failureDiagnostic(failure)); + throw failure; + } + System.out.println("PASS: isolated client compatibility and declared-resource closure; " + + "original workload batch-listen (403) and upstream listener-pool retention remain unresolved"); + } + + @Test + void authenticatedRegistrySurvivesServerRestart() throws Exception { + NacosRuntimeDependencyTest.requiresSafeGencodeWithoutAnUnsafeOverride(); + NacosRuntimeDependencyTest.assertSinglePatchedApi(); + Path control = Path.of(required("NACOS_FIXTURE_CONTROL")); + assertThat(Files.isDirectory(control)).isTrue(); + assertThat(required("NACOS_FIXTURE_SERVER")).matches("127\\.0\\.0\\.1:[0-9]{2,5}"); + BlockingQueue> namingEvents = new LinkedBlockingQueue<>(); + BlockingQueue configEvents = new LinkedBlockingQueue<>(); + EventListener namingListener = event -> namingEvents.add(((NamingEvent) event).getInstances()); + Listener configListener = new Listener() { + public Executor getExecutor() { return null; } + public void receiveConfigInfo(String value) { configEvents.add(value); } + }; + phase = "initial-transport"; + try (Clients clients = new Clients()) { + NamingService producer = clients.naming(properties(true, false)); + NamingService consumer = clients.naming(properties(true, false)); + ConfigService workloadConfig = clients.config(properties(true, false)); + ConfigService config = clients.config(configProperties(true, false)); + // RpcClient.start() may return while asynchronous connection setup + // continues. HTTP container health does not prove client transport + // readiness; never reinterpret a disconnected call as an auth result. + awaitConnected(producer::getServerStatus, consumer::getServerStatus, + workloadConfig::getServerStatus, config::getServerStatus); + phase = "authenticated-naming"; + consumer.subscribe(SERVICE, GROUP, namingListener); + Instance initial = instance(20880, "before-restart"); + producer.registerInstance(SERVICE, GROUP, initial); + await("authenticated discovery", () -> hasInstance(consumer.getAllInstances(SERVICE, GROUP, false), 20880)); + awaitEvent(namingEvents, instances -> hasInstance(instances, 20880), "initial naming callback"); + assertThat(consumer.getAllInstances(SERVICE, GROUP, false)).anySatisfy(instance -> + assertThat(instance.getMetadata()).containsEntry("protobuf-fixture", "before-restart")); + + // The unchanged production AUTH grants allow these direct calls, + // but server 2.3.2 authorizes batch listen as namespace:*:config/*. + // Require the actual numeric denial, not an absent callback. + phase = "workload-direct-config"; + assertThat(workloadConfig.publishConfig(DATA_ID, CONFIG_GROUP, WORKLOAD_CONTENT)).isTrue(); + await("workload config publication", + () -> WORKLOAD_CONTENT.equals(workloadConfig.getConfig(DATA_ID, CONFIG_GROUP, 5000))); + phase = "workload-batch-denial"; + Response batch = requestOnConfigTransport(workloadConfig, + workloadBatchRequest(required("NACOS_FIXTURE_NAMESPACE"))); + Files.writeString(control.resolve("evidence"), batchResponseDiagnostic(batch)); + requireNoRightResponse(batch); + + // Positive protocol coverage uses a separate synthetic-only namespace + // and principal. Its namespace-wide read is NOT a production ACL fix. + phase = "isolated-config"; + assertThat(config.publishConfig(FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP, "metadata-v1")).isTrue(); + await("isolated config publication", () -> "metadata-v1".equals( + config.getConfig(FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP, 5000))); + assertThat(config.getConfigAndSignListener(FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP, 5000, configListener)) + .isEqualTo("metadata-v1"); + assertThat(config.publishConfig(FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP, "metadata-v2")).isTrue(); + awaitConfigEvent(config, configEvents, "metadata-v2", "initial isolated config callback"); + + phase = "isolated-config-scope"; + Properties outsideFixture = configProperties(true, false); + outsideFixture.setProperty(PropertyKeyConst.NAMESPACE, required("NACOS_FIXTURE_NAMESPACE")); + assertConfigReadRejected(outsideFixture, DATA_ID, CONFIG_GROUP); + Properties outsideWorkload = properties(true, false); + outsideWorkload.setProperty(PropertyKeyConst.NAMESPACE, required("NACOS_FIXTURE_CONFIG_NAMESPACE")); + assertConfigReadRejected(outsideWorkload, FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP); + // A false publish result alone could be a transport failure. Inspect + // the same standard request's numeric response for exact-write ACL. + requireNoRightResponse(requestOnConfigTransport(config, new ConfigPublishRequest( + FIXTURE_DATA_ID + "-other", FIXTURE_CONFIG_GROUP, + required("NACOS_FIXTURE_CONFIG_NAMESPACE"), "must-be-rejected"))); + + phase = "missing-auth-rejection"; + assertRejected(false, false); + phase = "wrong-auth-rejection"; + assertRejected(true, true); + phase = "observe-disconnect"; + Files.createFile(control.resolve("ready-for-outage")); + await("server stopped marker", () -> Files.exists(control.resolve("server-stopped"))); + await("naming/config clients observe disconnect", () -> !"UP".equals(producer.getServerStatus()) + && !"UP".equals(consumer.getServerStatus()) && !"UP".equals(workloadConfig.getServerStatus()) + && !"UP".equals(config.getServerStatus())); + Files.createFile(control.resolve("observed-disconnect")); + await("server restarted marker", () -> Files.exists(control.resolve("server-restarted"))); + phase = "automatic-recovery"; + await("original clients reconnect", () -> "UP".equals(producer.getServerStatus()) + && "UP".equals(consumer.getServerStatus()) && "UP".equals(workloadConfig.getServerStatus()) + && "UP".equals(config.getServerStatus())); + // No manual register or subscribe calls on recovery: the original + // producer must redo its ephemeral registration after the server lost it. + await("automatic re-registration", () -> hasInstance(consumer.getAllInstances(SERVICE, GROUP, false), 20880)); + assertThat(workloadConfig.getConfig(DATA_ID, CONFIG_GROUP, 5000)).isEqualTo(WORKLOAD_CONTENT); + assertThat(config.getConfig(FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP, 5000)).isEqualTo("metadata-v2"); + phase = "subscription-recovery"; + namingEvents.clear(); + configEvents.clear(); + Instance recovered = instance(20881, "after-restart"); + producer.registerInstance(SERVICE, GROUP, recovered); + awaitEvent(namingEvents, instances -> hasInstance(instances, 20881), "recovered naming subscription"); + assertThat(config.publishConfig(FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP, "metadata-v3")).isTrue(); + awaitConfigEvent(config, configEvents, "metadata-v3", "recovered isolated config subscription"); + + phase = "clean-shutdown"; + producer.deregisterInstance(SERVICE, GROUP, initial); + producer.deregisterInstance(SERVICE, GROUP, recovered); + await("deregistration", () -> consumer.getAllInstances(SERVICE, GROUP, false).isEmpty()); + consumer.unsubscribe(SERVICE, GROUP, namingListener); + config.removeListener(FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP, configListener); + assertThat(config.removeConfig(FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP)).isTrue(); + assertThat(workloadConfig.removeConfig(DATA_ID, CONFIG_GROUP)).isTrue(); + ConfigShutdown workloadShutdown = ConfigShutdown.capture(workloadConfig); + ConfigShutdown isolatedShutdown = ConfigShutdown.capture(config); + clients.close(); + assertThat(producer.getServerStatus()).isEqualTo("DOWN"); + assertThat(consumer.getServerStatus()).isEqualTo("DOWN"); + // Nacos 2.5.1 config getServerStatus() calls ensureRpcClient("0"): + // after shutdown removes its factory entry, that observer creates + // a replacement connection. Inspect captured resources instead. + Files.writeString(control.resolve("evidence"), retainedListenerDiagnostic( + workloadShutdown.retainedListenerPools() + isolatedShutdown.retainedListenerPools()), + StandardOpenOption.APPEND); + workloadShutdown.assertClosed(WAIT); + isolatedShutdown.assertClosed(WAIT); + Files.writeString(control.resolve("evidence"), "config-declared-resources-closed=true\n", + StandardOpenOption.APPEND); + } + } + + private static void assertRejected(boolean authenticated, boolean wrongPassword) { + // An arbitrary error (timeout, missing service, serialization failure) + // is NOT evidence of authentication enforcement. Require Nacos NO_RIGHT. + assertThatThrownBy(() -> { + try (Clients clients = new Clients()) { + NamingService naming = clients.naming(properties(authenticated, wrongPassword)); + awaitConnected(naming::getServerStatus); + naming.registerInstance(SERVICE, GROUP, instance(20882, "must-be-rejected")); + } + }).isInstanceOfSatisfying(NacosException.class, + failure -> assertThat(failure.getErrCode()).isEqualTo(NacosException.NO_RIGHT)); + assertConfigReadRejected(properties(authenticated, wrongPassword), DATA_ID, CONFIG_GROUP); + assertConfigReadRejected(configProperties(authenticated, wrongPassword), FIXTURE_DATA_ID, FIXTURE_CONFIG_GROUP); + } + + private static void assertConfigReadRejected(Properties properties, String dataId, String group) { + assertThatThrownBy(() -> { + try (Clients clients = new Clients()) { + ConfigService config = clients.config(properties); + awaitConnected(config::getServerStatus); + config.getConfig(dataId, group, 5000); + } + }).isInstanceOfSatisfying(NacosException.class, + failure -> assertThat(failure.getErrCode()).isEqualTo(NacosException.NO_RIGHT)); + } + + static ConfigBatchListenRequest workloadBatchRequest(String namespace) throws Exception { + assertThat(namespace).isEqualTo("protobuf-fixture"); + ConfigBatchListenRequest request = new ConfigBatchListenRequest(); + request.setListen(true); + String md5 = HexFormat.of().formatHex(MessageDigest.getInstance("MD5") + .digest(WORKLOAD_CONTENT.getBytes(StandardCharsets.UTF_8))); + request.addConfigListenContext(CONFIG_GROUP, DATA_ID, namespace, md5); + return request; + } + + static void requireNoRightResponse(Response response) { + assertThat(response).isNotNull(); + assertThat(response.isSuccess()).isFalse(); + assertThat(response.getErrorCode()).isEqualTo(NacosException.NO_RIGHT); + } + + static String batchResponseDiagnostic(Response response) { + assertThat(response).isNotNull(); + return "workload-batch-result-code=" + response.getResultCode() + "\n" + + "workload-batch-error-code=" + response.getErrorCode() + "\n"; + } + + private static ClientWorker configWorker(ConfigService config) throws ReflectiveOperationException { + return (ClientWorker) field(NacosConfigService.class, "worker", config); + } + + private static Object field(Class owner, String name, Object target) throws ReflectiveOperationException { + Field field = owner.getDeclaredField(name); + field.setAccessible(true); + return field.get(target); + } + + static String retainedListenerDiagnostic(long retained) { + assertThat(retained).isNotNegative(); + return "config-retained-listener-pools=" + retained + "\n"; + } + + record RpcShutdown(RpcClient client, ManagedChannel channel, ExecutorService events, ExecutorService grpc) { + static RpcShutdown capture(RpcClient client) throws ReflectiveOperationException { + GrpcConnection connection = (GrpcConnection) field(RpcClient.class, "currentConnection", client); + assertThat(connection).isNotNull(); + return new RpcShutdown(client, connection.getChannel(), + (ExecutorService) field(RpcClient.class, "clientEventExecutor", client), + (ExecutorService) field(GrpcClient.class, "grpcExecutor", client)); + } + + void assertShutdown() { + assertThat(client.isShutdown()).as("original RPC must be shut down").isTrue(); + assertThat(client.isRunning()).isFalse(); + assertThat(channel.isShutdown()).as("original channel must be shut down").isTrue(); + assertThat(events.isShutdown()).as("RPC event executor must be shut down").isTrue(); + assertThat(grpc.isShutdown()).as("gRPC executor must be shut down").isTrue(); + } + + boolean terminated() { + return channel.isTerminated() && events.isTerminated() && grpc.isTerminated(); + } + } + + record ConfigShutdown(String prefix, List rpcs, ExecutorService scheduler, + List listenerPools) { + static ConfigShutdown capture(ConfigService config) throws ReflectiveOperationException { + ClientWorker worker = configWorker(config); + String prefix = field(ClientWorker.class, "uuid", worker) + "_config-"; + List rpcs = new java.util.ArrayList<>(); + for (var entry : RpcClientFactory.getAllClientEntries()) { + if (entry.getKey().startsWith(prefix)) { + rpcs.add(RpcShutdown.capture(entry.getValue())); + } + } + assertThat(rpcs).as("capture original transports, never create replacements").isNotEmpty(); + Object agent = worker.getAgent(); + ExecutorService scheduler = (ExecutorService) field(ConfigTransportClient.class, "executor", agent); + Map listeners = (Map) field(agent.getClass(), "multiTaskExecutor", agent); + return new ConfigShutdown(prefix, List.copyOf(rpcs), scheduler, + listeners.values().stream().map(ThreadPoolExecutor.class::cast).toList()); + } + + long retainedListenerPools() { + // Upstream 2.5.1 does not shut these per-client daemon pools down. + // Report retention; never terminate them here or claim leak-free cleanup. + return listenerPools.stream().filter(pool -> !pool.isShutdown()).count(); + } + + void assertClosed(Duration timeout) throws Exception { + assertThat(scheduler.isShutdown()).as("config login/listen scheduler must be shut down").isTrue(); + for (RpcShutdown rpc : rpcs) { + rpc.assertShutdown(); + } + assertFactoryAbsent(); + // Bounded condition wait for actual termination, not a sleep or a + // post-close SDK health call. Keep the fixture's existing 60s bound. + await("captured config resources terminate", () -> scheduler.isTerminated() + && rpcs.stream().allMatch(RpcShutdown::terminated) + && listenerPools.stream().allMatch(pool -> pool.getActiveCount() == 0 && pool.getQueue().isEmpty()), + timeout); + assertFactoryAbsent(); + } + + private void assertFactoryAbsent() { + assertThat(RpcClientFactory.getAllClientEntries().stream() + .noneMatch(entry -> entry.getKey().startsWith(prefix))) + .as("no original or replacement config transport may remain registered").isTrue(); + } + } + + private static Response requestOnConfigTransport(ConfigService config, Request request) throws Exception { + // Pinned 2.5.1 test-only access: ConfigService hides unsuccessful async + // batch responses. Reuse its existing task-0 RPC and security headers; + // never open a raw unauthenticated channel or alter SDK internals. + Object agent = configWorker(config).getAgent(); + Method ensureClient = agent.getClass().getDeclaredMethod("ensureRpcClient", String.class); + RpcClient client = (RpcClient) invoke(ensureClient, agent, "0"); + Method requestProxy = agent.getClass().getDeclaredMethod("requestProxy", RpcClient.class, Request.class, long.class); + return (Response) invoke(requestProxy, agent, client, request, 5000L); + } + + private static Object invoke(Method method, Object target, Object... arguments) throws Exception { + method.setAccessible(true); + try { + return method.invoke(target, arguments); + } catch (InvocationTargetException failure) { + if (failure.getCause() instanceof Exception exception) { throw exception; } + if (failure.getCause() instanceof Error error) { throw error; } + throw new IllegalStateException("Unexpected reflected failure type"); + } + } + + private static void awaitConfigEvent(ConfigService config, BlockingQueue events, + String expected, String label) throws Exception { + verifyCallback(() -> awaitEvent(events, expected::equals, label), + () -> expected.equals(configWorker(config).getAgent().queryConfig(FIXTURE_DATA_ID, + FIXTURE_CONFIG_GROUP, required("NACOS_FIXTURE_CONFIG_NAMESPACE"), 5000, false).getContent())); + } + + static void verifyCallback(CheckedAction callback, CheckedCondition readback) throws Exception { + callbackReadback = "not-run"; + try { + callback.run(); + } catch (AssertionError failure) { + // Diagnostic only, after failure. A successful direct query can + // never replace the required SDK callback or change the outcome. + try { + callbackReadback = readback.matches() ? "matches" : "differs"; + } catch (Exception | AssertionError probeFailure) { + callbackReadback = "request-failed"; + } + throw failure; + } + } + + static String failureDiagnostic(Throwable failure) { + // Never export SDK exception messages or request values. Those can + // contain credentials; numeric codes and these fixed labels are safe. + StringBuilder diagnostic = new StringBuilder("phase=" + phase + "\n"); + diagnostic.append("callback-server-readback=").append(callbackReadback).append('\n'); + for (Throwable cause = failure; cause != null; cause = cause.getCause()) { + diagnostic.append("exception=").append(cause.getClass().getName()).append('\n'); + if (cause instanceof NacosException nacos) { + diagnostic.append("nacos-error-code=").append(nacos.getErrCode()).append('\n'); + String classification = switch (nacos.getErrCode()) { + case NacosException.CLIENT_DISCONNECT -> "client-disconnected"; + case NacosException.NO_RIGHT -> "authorization-rejected"; + case NacosException.INVALID_SERVER_STATUS -> "server-not-ready"; + case NacosException.NOT_FOUND, NacosException.RESOURCE_NOT_FOUND -> "resource-not-found"; + default -> "nacos-request-failed"; + }; + diagnostic.append("classification=").append(classification).append('\n'); + } + for (StackTraceElement frame : cause.getStackTrace()) { + if (frame.getClassName().equals(NacosRegistryIT.class.getName())) { + diagnostic.append("line=").append(frame.getLineNumber()).append('\n'); + } + } + } + return diagnostic.toString(); + } + + @SafeVarargs + static void awaitConnected(Supplier... statuses) throws Exception { + await("client transport readiness", () -> { + for (Supplier status : statuses) { + if (!"UP".equals(status.get())) { + return false; + } + } + return true; + }); + } + + private static Properties properties(boolean authenticated, boolean wrongPassword) { + Properties properties = new Properties(); + properties.setProperty(PropertyKeyConst.SERVER_ADDR, required("NACOS_FIXTURE_SERVER")); + properties.setProperty(PropertyKeyConst.NAMESPACE, required("NACOS_FIXTURE_NAMESPACE")); + properties.setProperty(PropertyKeyConst.NAMING_LOAD_CACHE_AT_START, "false"); + properties.setProperty(PropertyKeyConst.LOG_ALL_PROPERTIES, "false"); + if (authenticated) { + properties.setProperty(PropertyKeyConst.USERNAME, required("AUTH_NACOS_USERNAME")); + properties.setProperty(PropertyKeyConst.PASSWORD, + required(wrongPassword ? "NACOS_FIXTURE_WRONG_PASSWORD" : "AUTH_NACOS_PASSWORD")); + } + return properties; + } + + private static Properties configProperties(boolean authenticated, boolean wrongPassword) { + Properties properties = properties(false, false); + properties.setProperty(PropertyKeyConst.NAMESPACE, required("NACOS_FIXTURE_CONFIG_NAMESPACE")); + if (authenticated) { + properties.setProperty(PropertyKeyConst.USERNAME, required("NACOS_FIXTURE_CONFIG_USERNAME")); + properties.setProperty(PropertyKeyConst.PASSWORD, + required(wrongPassword ? "NACOS_FIXTURE_WRONG_PASSWORD" : "NACOS_FIXTURE_CONFIG_PASSWORD")); + } + return properties; + } + + private static String required(String name) { + String value = System.getenv(name); + assertThat(value).as("fixture variable %s must be set by the disposable integration script", name).isNotEmpty(); + return value; + } + + private static Instance instance(int port, String phase) { + Instance instance = new Instance(); + instance.setIp("127.0.0.1"); + instance.setPort(port); + instance.setEphemeral(true); + instance.setMetadata(Map.of("protobuf-fixture", phase)); + return instance; + } + + private static boolean hasInstance(List instances, int port) { + return instances.stream().anyMatch(instance -> instance.getPort() == port && instance.isHealthy()); + } + + private static void awaitEvent(BlockingQueue events, Predicate matches, String label) throws Exception { + long deadline = System.nanoTime() + WAIT.toNanos(); + while (System.nanoTime() < deadline) { + T event = events.poll(Math.max(1, deadline - System.nanoTime()), TimeUnit.NANOSECONDS); + if (event != null && matches.test(event)) { + return; + } + } + throw new AssertionError("Timed out waiting for " + label); + } + + private static void await(String label, CheckedCondition condition) throws Exception { + await(label, condition, WAIT); + } + + static void await(String label, CheckedCondition condition, Duration timeout) throws Exception { + long deadline = System.nanoTime() + timeout.toNanos(); + while (System.nanoTime() < deadline) { + if (condition.matches()) { + return; + } + Thread.sleep(200); + } + throw new AssertionError("Timed out waiting for " + label); + } + + @FunctionalInterface + interface CheckedCondition { + boolean matches() throws Exception; + } + + @FunctionalInterface + interface CheckedAction { + void run() throws Exception; + } + + private static final class Clients implements AutoCloseable { + private final Deque resources = new ArrayDeque<>(); + + NamingService naming(Properties properties) throws NacosException { + NamingService service = NacosFactory.createNamingService(properties); + resources.push(service::shutDown); + return service; + } + + ConfigService config(Properties properties) throws NacosException { + ConfigService service = NacosFactory.createConfigService(properties); + resources.push(service::shutDown); + return service; + } + + public void close() throws Exception { + Exception failure = null; + while (!resources.isEmpty()) { + try { + resources.pop().close(); + } catch (Exception error) { + if (failure == null) { failure = error; } else { failure.addSuppressed(error); } + } + } + if (failure != null) { throw failure; } + } + } +} diff --git a/services/platform/rpc-resilience/src/test/java/com/ulticode/rpc/resilience/NacosRuntimeDependencyTest.java b/services/platform/rpc-resilience/src/test/java/com/ulticode/rpc/resilience/NacosRuntimeDependencyTest.java index 155e280d28..7ff10a9142 100644 --- a/services/platform/rpc-resilience/src/test/java/com/ulticode/rpc/resilience/NacosRuntimeDependencyTest.java +++ b/services/platform/rpc-resilience/src/test/java/com/ulticode/rpc/resilience/NacosRuntimeDependencyTest.java @@ -1,71 +1,441 @@ package com.ulticode.rpc.resilience; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.alibaba.nacos.api.NacosFactory; +import com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.grpc.auto.BiRequestStreamGrpc; import com.alibaba.nacos.api.grpc.auto.Metadata; import com.alibaba.nacos.api.grpc.auto.Payload; import com.alibaba.nacos.api.grpc.auto.RequestGrpc; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.client.config.NacosConfigService; +import com.alibaba.nacos.client.config.impl.ClientWorker; import com.alibaba.nacos.client.naming.NacosNamingService; +import com.alibaba.nacos.common.remote.client.RpcClient; +import com.alibaba.nacos.common.remote.client.RpcClientFactory; +import com.alibaba.nacos.common.remote.ConnectionType; import com.google.protobuf.Any; import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.UnknownFieldSet; +import com.sun.management.ThreadMXBean; import io.grpc.ManagedChannel; +import io.grpc.MethodDescriptor; import io.grpc.Server; +import io.grpc.ServerServiceDefinition; +import io.grpc.StatusRuntimeException; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; +import io.grpc.protobuf.ProtoUtils; +import io.grpc.stub.ServerCalls; import io.grpc.stub.StreamObserver; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.lang.management.ManagementFactory; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class NacosRuntimeDependencyTest { + private static volatile Payload allocationSink; + + @BeforeAll + static void requiresSafeGencodeWithoutAnUnsafeOverride() { + assertThat(System.getProperty("com.google.protobuf.error_on_unsafe_pre22_gencode")) + .as("the forked test JVM must reject legacy vulnerable generated code").isEqualTo("true"); + assertThat(Boolean.getBoolean("com.google.protobuf.use_unsafe_pre22_gencode")).isFalse(); + } + @Test void excludesTheSdkWithUnmanagedShadedDependencies() throws Exception { assertThat(NacosNamingService.class.getProtectionDomain().getCodeSource().getLocation().toString()) .endsWith("-pure.jar"); - assertThat(getClass().getClassLoader().getResources( - "com/alibaba/nacos/api/NacosFactory.class").asIterator()) - .toIterable().hasSize(1); + // The API gencode patch must not replace SDK lifecycle implementation. + for (Class clientClass : List.of(NacosConfigService.class, ClientWorker.class)) { + assertThat(clientClass.getProtectionDomain().getCodeSource().getLocation()) + .isEqualTo(NacosNamingService.class.getProtectionDomain().getCodeSource().getLocation()); + assertThat(clientClass.getProtectionDomain().getCodeSource().getLocation().toString()) + .endsWith("nacos-client-2.5.1-pure.jar"); + } + assertSinglePatchedApi(); assertThat(getClass().getClassLoader().getResource( - "com/alibaba/nacos/shaded/com/google/common/collect/ImmutableList.class")) - .isNull(); + "com/alibaba/nacos/shaded/com/google/common/collect/ImmutableList.class")).isNull(); + } + + static void assertSinglePatchedApi() throws Exception { + ClassLoader loader = NacosRuntimeDependencyTest.class.getClassLoader(); + for (Class apiClass : List.of(NacosFactory.class, Payload.class, Metadata.class, + RequestGrpc.class, BiRequestStreamGrpc.class)) { + assertThat(loader.getResources(apiClass.getName().replace('.', '/') + ".class").asIterator()) + .toIterable().hasSize(1); + assertThat(apiClass.getProtectionDomain().getCodeSource().getLocation()) + .isEqualTo(NacosFactory.class.getProtectionDomain().getCodeSource().getLocation()); + } + assertThat(loader.getResource("META-INF/maven/com.alibaba.nacos/nacos-api/pom.properties")).isNull(); + String source = NacosFactory.class.getProtectionDomain().getCodeSource().getLocation().toExternalForm(); + assertThat(source.endsWith("nacos-api-patched-2.5.1-ulticode-1.jar") + || source.endsWith("/nacos-api-patched/target/classes/")) + .as("API classes must originate from the owned artifact: %s", source).isTrue(); + String provenance = "META-INF/nacos-patch/provenance.json"; + assertThat(loader.getResources(provenance).asIterator()).toIterable().hasSize(1); + String resourceRoot = source.endsWith(".jar") ? "jar:" + source + "!/" : source; + assertThat(loader.getResource(provenance).toExternalForm()).isEqualTo(resourceRoot + provenance); + try (InputStream input = loader.getResourceAsStream(provenance)) { + assertThat(new String(input.readAllBytes(), StandardCharsets.UTF_8)) + .contains("\"artifact\": \"pkg:maven/com.ulticode/nacos-api-patched@2.5.1-ulticode-1\""); + } + // Maven test uses reactor target/classes; Maven adds its own metadata + // only when packaging. The standalone registry gate requires the JAR. + if (source.endsWith(".jar")) { + String marker = "META-INF/maven/com.ulticode/nacos-api-patched/pom.properties"; + assertThat(loader.getResources(marker).asIterator()).toIterable().hasSize(1); + Properties identity = new Properties(); + try (InputStream input = loader.getResourceAsStream(marker)) { + identity.load(input); + } + assertThat(identity.getProperty("groupId")).isEqualTo("com.ulticode"); + assertThat(identity.getProperty("artifactId")).isEqualTo("nacos-api-patched"); + assertThat(identity.getProperty("version")).isEqualTo("2.5.1-ulticode-1"); + } + } + + @Test + void fixtureWaitsForEveryTransportBeforeSendingBusinessRequests() throws Exception { + AtomicInteger consumerPolls = new AtomicInteger(); + AtomicInteger configPolls = new AtomicInteger(); + NacosRegistryIT.awaitConnected(() -> "UP", + () -> consumerPolls.incrementAndGet() == 1 ? "DOWN" : "UP", + () -> { configPolls.incrementAndGet(); return "UP"; }); + assertThat(consumerPolls.get()).isEqualTo(2); + assertThat(configPolls.get()).isEqualTo(1); + } + + @Test + void fixtureReadinessFailsClosedWithoutSwallowingRequestErrors() { + assertThatThrownBy(() -> NacosRegistryIT.await("disconnected fixture", () -> false, Duration.ZERO)) + .isInstanceOf(AssertionError.class).hasMessageContaining("disconnected fixture"); + NacosException forbidden = new NacosException(NacosException.NO_RIGHT, "must not be retried"); + AtomicInteger attempts = new AtomicInteger(); + assertThatThrownBy(() -> NacosRegistryIT.await("forbidden", () -> { + attempts.incrementAndGet(); + throw forbidden; + }, Duration.ofSeconds(60))).isSameAs(forbidden); + assertThat(attempts.get()).isEqualTo(1); + } + + @Test + void fixtureDiagnosticsDistinguishTransportAndAuthWithoutExposingMessages() { + for (Map.Entry error : Map.of( + NacosException.CLIENT_DISCONNECT, "client-disconnected", + NacosException.NO_RIGHT, "authorization-rejected", + NacosException.INVALID_SERVER_STATUS, "server-not-ready", + NacosException.NOT_FOUND, "resource-not-found", + NacosException.SERVER_ERROR, "nacos-request-failed").entrySet()) { + String privateValue = java.util.UUID.randomUUID().toString(); + String diagnostic = NacosRegistryIT.failureDiagnostic(new IllegalStateException(privateValue, + new NacosException(error.getKey(), privateValue))); + assertThat(diagnostic).contains("nacos-error-code=" + error.getKey() + "\n", + "classification=" + error.getValue() + "\n").doesNotContain(privateValue); + } + } + + @Test + void fixtureBatchNegativeControlRequiresAnActual403Response() throws Exception { + var request = NacosRegistryIT.workloadBatchRequest("protobuf-fixture"); + assertThat(request.isListen()).isTrue(); + assertThat(request.getConfigListenContexts()).singleElement().satisfies(context -> { + assertThat(context.getTenant()).isEqualTo("protobuf-fixture"); + assertThat(context.getGroup()).isEqualTo("mapping"); + assertThat(context.getDataId()).isEqualTo("com.ulticode.auth.ProtobufFixture"); + assertThat(context.getMd5()).matches("[a-f0-9]{32}"); + }); + assertThatThrownBy(() -> NacosRegistryIT.workloadBatchRequest("another-namespace")) + .isInstanceOf(AssertionError.class); + String privateValue = java.util.UUID.randomUUID().toString(); + ConfigChangeBatchListenResponse denied = new ConfigChangeBatchListenResponse(); + denied.setErrorInfo(NacosException.NO_RIGHT, privateValue); + NacosRegistryIT.requireNoRightResponse(denied); + assertThat(NacosRegistryIT.batchResponseDiagnostic(denied)) + .isEqualTo("workload-batch-result-code=500\nworkload-batch-error-code=403\n") + .doesNotContain(privateValue); + for (int error : List.of(NacosException.CLIENT_DISCONNECT, NacosException.SERVER_ERROR)) { + ConfigChangeBatchListenResponse otherFailure = new ConfigChangeBatchListenResponse(); + otherFailure.setErrorInfo(error, privateValue); + assertThatThrownBy(() -> NacosRegistryIT.requireNoRightResponse(otherFailure)) + .isInstanceOf(AssertionError.class); + } + assertThatThrownBy(() -> NacosRegistryIT.requireNoRightResponse(new ConfigChangeBatchListenResponse())) + .isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> NacosRegistryIT.requireNoRightResponse(null)).isInstanceOf(AssertionError.class); + } + + @Test + void fixtureProbeMatchesThePinnedClientPrivateTransportContract() throws Exception { + assertThat(NacosConfigService.class.getDeclaredField("worker").getType()).isEqualTo(ClientWorker.class); + Class transport = Class.forName("com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient", + false, getClass().getClassLoader()); + assertThat(transport.getDeclaredMethod("ensureRpcClient", String.class).getReturnType()).isEqualTo(RpcClient.class); + assertThat(transport.getDeclaredMethod("requestProxy", RpcClient.class, Request.class, long.class).getReturnType()) + .isEqualTo(Response.class); + } + + @Test + void fixtureReadbackNeverReplacesAnActualCallback() throws Exception { + AssertionError timeout = new AssertionError("callback absent"); + AtomicInteger probes = new AtomicInteger(); + NacosRegistryIT.verifyCallback(() -> {}, () -> { probes.incrementAndGet(); return true; }); + assertThat(probes.get()).isZero(); + for (boolean matches : List.of(true, false)) { + assertThatThrownBy(() -> NacosRegistryIT.verifyCallback(() -> { throw timeout; }, + () -> { probes.incrementAndGet(); return matches; })).isSameAs(timeout); + assertThat(NacosRegistryIT.failureDiagnostic(timeout)) + .contains("callback-server-readback=" + (matches ? "matches" : "differs") + "\n"); + } + assertThat(probes.get()).isEqualTo(2); + assertThatThrownBy(() -> NacosRegistryIT.verifyCallback(() -> { throw timeout; }, + () -> { throw new NacosException(NacosException.CLIENT_DISCONNECT, "private-request-message"); })) + .isSameAs(timeout); + assertThat(NacosRegistryIT.failureDiagnostic(timeout)) + .contains("callback-server-readback=request-failed\n").doesNotContain("private-request-message"); + } + + @Test + void fixtureLifecycleChecksOriginalResourcesAndRejectsFactoryResurrection() throws Exception { + String prefix = "fixture-lifecycle-" + java.util.UUID.randomUUID() + "-"; + RpcClient rpc = RpcClientFactory.createClient(prefix + "0", ConnectionType.GRPC, Map.of()); + // Construct, never start/connect: this is an infrastructure-free state test. + RpcClientFactory.getAllClientEntries().removeIf(entry -> entry.getKey().equals(prefix + "0")); + ManagedChannel channel = NettyChannelBuilder.forAddress("127.0.0.1", 9).usePlaintext().build(); + ExecutorService events = Executors.newSingleThreadExecutor(); + ExecutorService grpc = Executors.newSingleThreadExecutor(); + ExecutorService scheduler = Executors.newSingleThreadExecutor(); + ExecutorService blocked = Executors.newSingleThreadExecutor(); + CountDownLatch release = new CountDownLatch(1); + ThreadPoolExecutor listener = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>()); + var original = new NacosRegistryIT.RpcShutdown(rpc, channel, events, grpc); + var snapshot = new NacosRegistryIT.ConfigShutdown(prefix, List.of(original), scheduler, List.of(listener)); + try { + assertThatThrownBy(() -> snapshot.assertClosed(Duration.ZERO)) + .isInstanceOf(AssertionError.class).hasMessageContaining("scheduler"); + scheduler.shutdown(); + assertThatThrownBy(() -> snapshot.assertClosed(Duration.ZERO)) + .isInstanceOf(AssertionError.class).hasMessageContaining("original RPC"); + rpc.shutdown(); + assertThatThrownBy(() -> snapshot.assertClosed(Duration.ZERO)) + .isInstanceOf(AssertionError.class).hasMessageContaining("original channel"); + channel.shutdownNow(); + assertThatThrownBy(() -> snapshot.assertClosed(Duration.ZERO)) + .isInstanceOf(AssertionError.class).hasMessageContaining("event executor"); + events.shutdown(); + assertThatThrownBy(() -> snapshot.assertClosed(Duration.ZERO)) + .isInstanceOf(AssertionError.class).hasMessageContaining("gRPC executor"); + grpc.shutdown(); + snapshot.assertClosed(Duration.ofSeconds(5)); + assertThat(snapshot.retainedListenerPools()).isEqualTo(1); + assertThat(NacosRegistryIT.retainedListenerDiagnostic(snapshot.retainedListenerPools())) + .isEqualTo("config-retained-listener-pools=1\n"); + assertThat(listener.isShutdown()).isFalse(); // observation must not hide upstream retention + assertThat(RpcClientFactory.getClient(prefix + "0")).isNull(); + + RpcClientFactory.createClient(prefix + "replacement", ConnectionType.GRPC, Map.of()); + assertThatThrownBy(() -> snapshot.assertClosed(Duration.ZERO)) + .isInstanceOf(AssertionError.class).hasMessageContaining("remain registered"); + RpcClientFactory.destroyClient(prefix + "replacement"); + + CountDownLatch started = new CountDownLatch(1); + blocked.submit(() -> { + started.countDown(); + try { release.await(); } catch (InterruptedException failure) { Thread.currentThread().interrupt(); } + }); + assertThat(started.await(5, TimeUnit.SECONDS)).isTrue(); + blocked.shutdown(); + var notTerminated = new NacosRegistryIT.ConfigShutdown(prefix, List.of(original), blocked, List.of()); + assertThatThrownBy(() -> notTerminated.assertClosed(Duration.ZERO)) + .isInstanceOf(AssertionError.class).hasMessageContaining("resources terminate"); + } finally { + release.countDown(); + blocked.shutdownNow(); + // These are test-created stand-ins, never the SDK's retained pools. + listener.shutdownNow(); + scheduler.shutdownNow(); + events.shutdownNow(); + grpc.shutdownNow(); + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + rpc.shutdown(); + RpcClientFactory.destroyClient(prefix + "replacement"); + } } @Test void roundTripsNacosMessagesWithTheManagedProtobufRuntime() throws Exception { Payload payload = payload(); assertThat(Payload.parseFrom(payload.toByteArray())).isEqualTo(payload); + assertThat(Payload.parseFrom(new ByteArrayInputStream(payload.toByteArray()))).isEqualTo(payload); + assertThat(payload.getMetadata().getField(Metadata.getDescriptor().findFieldByNumber(7))) + .isInstanceOf(List.class); + assertThat(payload.getUnknownFields().getField(99).getVarintList()).containsExactly(7L); + for (byte[] malformed : List.of(new byte[] {(byte) 0x80}, new byte[] {0x12, 0x05, 0x00})) { + assertThatThrownBy(() -> Payload.parseFrom(malformed)) + .isInstanceOf(InvalidProtocolBufferException.class); + } } @Test - void exchangesNacosMessagesOverTheManagedGrpcTransport() throws Exception { + void repeatedEmbeddedMetadataMergesWithinLinearAllocationBounds() throws Exception { + ThreadMXBean allocation = (ThreadMXBean) ManagementFactory.getThreadMXBean(); + assertThat(allocation.isThreadAllocatedMemorySupported()).isTrue(); + allocation.setThreadAllocatedMemoryEnabled(true); + for (boolean unknownFields : List.of(false, true)) { + byte[] smaller = repeatedMetadata(1024, unknownFields); + byte[] larger = repeatedMetadata(2048, unknownFields); + assertThat(larger.length).isLessThanOrEqualTo(32 * 1024); + for (int warmup = 0; warmup < 10; warmup++) { + allocationSink = Payload.parseFrom(smaller); + allocationSink = Payload.parseFrom(larger); + } + long smallBytes = allocatedBytes(allocation, smaller); + long largeBytes = allocatedBytes(allocation, larger); + // Allocation, not elapsed time: pre-22 gencode repeatedly copies the + // entire accumulated submessage. These generous bounds reject that + // quadratic growth without depending on host speed or scheduling. + assertThat(largeBytes).isLessThan(4L * 1024 * 1024); + assertThat(largeBytes).isLessThan(3 * smallBytes + 64 * 1024); + assertMerged(allocationSink, unknownFields); + } + } + + private static long allocatedBytes(ThreadMXBean allocation, byte[] wire) throws Exception { + long minimum = Long.MAX_VALUE; + for (int sample = 0; sample < 3; sample++) { + long before = allocation.getThreadAllocatedBytes(Thread.currentThread().getId()); + allocationSink = Payload.parseFrom(wire); + minimum = Math.min(minimum, + allocation.getThreadAllocatedBytes(Thread.currentThread().getId()) - before); + } + return minimum; + } + + @Test + void parsesBoundedRawRegistryResponsesOverUnaryAndBidirectionalGrpc() throws Exception { + AtomicReference responseWire = new AtomicReference<>(payload().toByteArray()); Server server = NettyServerBuilder.forAddress(new InetSocketAddress("127.0.0.1", 0)) - .addService(new RequestGrpc.RequestImplBase() { - @Override - public void request(Payload request, StreamObserver response) { - response.onNext(request); - response.onCompleted(); - } - }).build().start(); - ManagedChannel channel = null; + .addService(ServerServiceDefinition.builder(RequestGrpc.SERVICE_NAME) + .addMethod(rawResponseMethod(RequestGrpc.getRequestMethod()), + ServerCalls.asyncUnaryCall((Payload request, StreamObserver response) -> { + response.onNext(responseWire.get()); + response.onCompleted(); + })).build()) + .addService(ServerServiceDefinition.builder(BiRequestStreamGrpc.SERVICE_NAME) + .addMethod(rawResponseMethod(BiRequestStreamGrpc.getRequestBiStreamMethod()), + ServerCalls.asyncBidiStreamingCall((StreamObserver response) -> + new StreamObserver() { + public void onNext(Payload request) { response.onNext(responseWire.get()); } + public void onError(Throwable error) { response.onError(error); } + public void onCompleted() { response.onCompleted(); } + })).build()) + .build().start(); + ManagedChannel channel = NettyChannelBuilder.forAddress("127.0.0.1", server.getPort()) + .usePlaintext().build(); try { - channel = NettyChannelBuilder.forAddress("127.0.0.1", server.getPort()) - .usePlaintext().build(); assertThat(RequestGrpc.newBlockingStub(channel).withDeadlineAfter(5, TimeUnit.SECONDS) .request(payload())).isEqualTo(payload()); + for (boolean unknownFields : List.of(false, true)) { + responseWire.set(repeatedMetadata(2048, unknownFields)); + assertMerged(RequestGrpc.newBlockingStub(channel).withDeadlineAfter(5, TimeUnit.SECONDS) + .request(payload()), unknownFields); + assertMerged(bidirectionalReply(channel).get(6, TimeUnit.SECONDS), unknownFields); + } + responseWire.set(new byte[] {0x12, 0x05, 0x00}); + assertThatThrownBy(() -> RequestGrpc.newBlockingStub(channel).withDeadlineAfter(5, TimeUnit.SECONDS) + .request(payload())).isInstanceOfSatisfying(StatusRuntimeException.class, + failure -> assertThat(failure).hasRootCauseInstanceOf(InvalidProtocolBufferException.class)); + assertThatThrownBy(() -> bidirectionalReply(channel).get(6, TimeUnit.SECONDS)) + .hasRootCauseInstanceOf(InvalidProtocolBufferException.class); } finally { - if (channel != null) { - channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + assertThat(channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS)).isTrue(); + assertThat(server.shutdownNow().awaitTermination(5, TimeUnit.SECONDS)).isTrue(); + } + } + + private static CompletableFuture bidirectionalReply(ManagedChannel channel) { + CompletableFuture result = new CompletableFuture<>(); + StreamObserver request = BiRequestStreamGrpc.newStub(channel).withDeadlineAfter(5, TimeUnit.SECONDS) + .requestBiStream(new StreamObserver() { + public void onNext(Payload value) { result.complete(value); } + public void onError(Throwable error) { result.completeExceptionally(error); } + public void onCompleted() {} + }); + request.onNext(payload()); + request.onCompleted(); + return result; + } + + private static MethodDescriptor rawResponseMethod(MethodDescriptor original) { + return MethodDescriptor.newBuilder().setFullMethodName(original.getFullMethodName()) + .setType(original.getType()).setRequestMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance())) + .setResponseMarshaller(new MethodDescriptor.Marshaller<>() { + public InputStream stream(byte[] wire) { return new ByteArrayInputStream(wire); } + public byte[] parse(InputStream input) { throw new UnsupportedOperationException("server writes only"); } + }).build(); + } + + private static byte[] repeatedMetadata(int count, boolean unknownFields) throws Exception { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + CodedOutputStream wire = CodedOutputStream.newInstance(bytes); + for (int index = 0; index < count; index++) { + Metadata.Builder metadata = Metadata.newBuilder(); + if (unknownFields) { + metadata.setUnknownFields(UnknownFieldSet.newBuilder().addField(99, + UnknownFieldSet.Field.newBuilder().addVarint(index).build()).build()); + } else { + metadata.putHeaders("k" + index, "v"); } - server.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + wire.writeMessage(2, metadata.build()); + } + wire.flush(); + assertThat(bytes.size()).isLessThanOrEqualTo(32 * 1024); + return bytes.toByteArray(); + } + + private static void assertMerged(Payload response, boolean unknownFields) { + if (unknownFields) { + assertThat(response.getMetadata().getUnknownFields().getField(99).getVarintList()) + .hasSize(2048).startsWith(0L).endsWith(2047L); + } else { + assertThat(response.getMetadata().getHeadersMap()).hasSize(2048) + .containsEntry("k0", "v").containsEntry("k2047", "v"); } } private static Payload payload() { return Payload.newBuilder() - .setMetadata(Metadata.newBuilder().setType("ServerCheckRequest") - .putHeaders("requestId", "dependency-regression")) - .setBody(Any.newBuilder().setValue(ByteString.copyFromUtf8("{}"))) + .setMetadata(Metadata.newBuilder().setType("ServerCheckRequest").setClientIp("127.0.0.1") + .putHeaders("requestId", "dependency-regression").putHeaders("unicode", "测试")) + .setBody(Any.newBuilder().setTypeUrl("test.invalid/Fixture").setValue(ByteString.copyFromUtf8("{}"))) + .setUnknownFields(UnknownFieldSet.newBuilder().addField(99, + UnknownFieldSet.Field.newBuilder().addVarint(7).build()).build()) .build(); } } diff --git a/services/pom.xml b/services/pom.xml index 1ac5c5cda6..a2bd072ba2 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -22,6 +22,7 @@ platform/common platform/storage + platform/nacos-api-patched platform/rpc-resilience platform/web-security platform/integration-inbox @@ -52,13 +53,13 @@ - 1.0.7 - 1.0.7 - 1.0.7 - 1.0.7 - 1.0.7 - 1.0.7 - 1.0.7 + 1.0.8 + 1.0.8 + 1.0.8 + 1.0.8 + 1.0.8 + 1.0.8 + 1.0.8 17 2.21.7 @@ -66,6 +67,7 @@ 11.0.25 33.7.2-jre 2.5.1 + 2.5.1-ulticode-1 UTF-8 3.5.17 0.13.0 @@ -227,6 +229,36 @@ okhttp ${okhttp.version} + + + com.ulticode + nacos-api-patched + ${nacos-api-patched.version} + + + com.alibaba.nacos + nacos-client + ${nacos-client.version} + pure + + + com.alibaba.nacos + nacos-api + + + + + com.alibaba.nacos + nacos-common + ${nacos-client.version} + + + com.alibaba.nacos + nacos-api + + + org.apache.dubbo diff --git a/services/search/pom.xml b/services/search/pom.xml index 8c87cc5fb9..b8c403342c 100644 --- a/services/search/pom.xml +++ b/services/search/pom.xml @@ -14,7 +14,7 @@ - 1.0.7 + 1.0.8 backend-search diff --git a/services/submission/pom.xml b/services/submission/pom.xml index 4d310e10fa..89e519256b 100644 --- a/services/submission/pom.xml +++ b/services/submission/pom.xml @@ -15,7 +15,7 @@ - 1.0.7 + 1.0.8 backend-submission