Skip to content

[gNOI] Add sonic.gnoi.oras.v1.Oras service (Pull RPC) + design doc (#…#713

Open
v-cshekar wants to merge 1 commit into
sonic-net:202511from
v-cshekar:cherry-pick-692-202511
Open

[gNOI] Add sonic.gnoi.oras.v1.Oras service (Pull RPC) + design doc (#…#713
v-cshekar wants to merge 1 commit into
sonic-net:202511from
v-cshekar:cherry-pick-692-202511

Conversation

@v-cshekar

Copy link
Copy Markdown
Contributor

…692)

  • doc: design for gNOI ORAS Pull service

Draft RFC for a new sonic.gnoi.oras.v1.Oras service that lets an orchestrator instruct a switch to pull an OCI/ORAS artifact from a registry into local staging, decoupled from install.

Tracks ADO Feature #37984064.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • doc: correct framing of SONiC TransferToRemote

SONiC's TransferToRemote actually performs a download (HTTP GET into local_path), not an upload as upstream openconfig defines. Update §1 to describe the real current state and enumerate the concrete limitations that block its use for ACR/ORAS.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • proto: add sonic.gnoi.oras.v1.Oras service definition

PoC subset of the ORAS Pull design (doc/oras-pull-design.md): a single streaming Pull RPC with anonymous + basic auth and an optional http_proxy field. List/Delete and richer features are deferred.

Generated oras.pb.go is checked in following the existing precedent (proto/sonic.pb.go, proto/gnoi/sonic_debug.pb.go). Makefile wires the new binding into PROTO_GO_BINDINGS so make can regenerate it.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • pkg/gnoi/oras: implement Pull RPC

Streaming server implementation of sonic.gnoi.oras.v1.Oras.Pull:

  • Resolves manifest by tag or digest against an OCI registry.
  • Requires single-layer artifacts (PoC scope per the design doc; SONiC OS images are single layer).
  • Stages the layer into a temp dir next to local_path and renames into place on success, so a failed pull never leaves a partial file at local_path.
  • Emits PullStarted once the manifest is resolved, PullProgress at most once per second, and a final PullResult with elapsed time and per-layer digest.
  • Reuses the file-server path allowlist (/tmp, /var/tmp, /host).
  • Supports anonymous and basic auth (ACR admin user); workload identity and bearer modes are stubbed out for v1.
  • http_proxy field plumbed into the HTTP transport so testbeds where the registry is not reachable via the default route (e.g. sonic-vs vlabs behind a host tinyproxy) can still pull.
  • Best-effort registry-error to gRPC status mapping.

Adds oras.land/oras-go/v2 v2.6.0 and github.com/opencontainers/image-spec v1.1.1 to go.mod.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • gnmi_server: register sonic.gnoi.oras.v1.Oras

Add OrasServer wrapper and wire it into registerAllServices behind the existing EnableTranslibWrite || EnableNativeWrite gate, alongside the other gNOI services. Pull authenticates the caller and then delegates to pkg/gnoi/oras.HandlePull.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • gnmi_server: register Oras unconditionally

Drop the EnableTranslibWrite/EnableNativeWrite gate for sonic.gnoi.oras.v1.Oras. The other services inside that gate are there because gNMI write paths (translib / native YANG) should not be exposed unless the operator opted in to writes. Oras Pull does not touch any YANG datastore — it writes only into an allowlisted staging area inside the gnmi container — so the gate is not meaningful here. Keep the service available on every build, mirroring the unconditional registration of system / factory_reset.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • pkg/gnoi/oras: add unit tests, split HandlePull for testability

Introduce handlePullWithRepo as a seam so tests can drive the pull loop against an httptest-backed fake registry (PlainHTTP) without reaching the real network. HandlePull keeps the same public signature, validates the request, constructs the repository, then delegates.

New tests cover:

  • validatePullRequest (12 cases)
  • validateLocalPath (allowlist + traversal)
  • pullReference (tag vs digest precedence)
  • pickSingleLayer (0/1/2 layers, malformed JSON)
  • mapRegistryError (401/host/timeout/404/ENOSPC/default)
  • countingReader, copyAndRemove, jsonUnmarshalStrict
  • newRepository wires basic-auth credentials + leaves Credential nil for anonymous; rejects invalid registry refs
  • HandlePull happy path and auth-failure path against a fake registry

Package coverage now 81.8%.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • pkg/gnoi/oras: gofmt oras_test.go

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • pure.mk: register pkg/gnoi/oras as a pure package

pkg/gnoi/oras has no CGO or SONiC dependencies, so its tests can run in the pure-test stage. Registering it here makes the pipeline pick up the unit tests and include them in the diff-coverage gate.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • pkg/gnoi/oras: expand error-branch test coverage

Add tests for HandlePull wrapper (E2E + bad registry ref), multi-layer manifest rejection, MkdirTemp failure, blob fetch 500, Send error on PullStarted, and copyAndRemove dst-open error. Lifts statement coverage from 81.8% to 89.9% so the pipeline diff-coverage gate (>=80% lines) clears with comfortable margin.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • pkg/gnoi/oras: address review feedback
  • Serialize stream.Send through a safeStream mutex; close progressDone before final Send (and wait for the progress goroutine to exit) so the progress goroutine can never race with PullResult.
  • Restrict os.Rename copy-and-delete fallback to EXDEV only; surface permission / target-is-dir / etc. errors as-is instead of silently masking them.
  • Replace substring-based mapRegistryError with errors.As inspection of errcode.ErrorResponse, errdef.ErrNotFound, net.OpError/DNSError, net.Error.Timeout(), and syscall.ECONNREFUSED / ENOSPC. Adds PermissionDenied for 403.
  • validateLocalPath: walk path components for literal '..' segments instead of strings.Contains, which over-rejected names like 'a..b'.
  • Drop the dead '_ = oras.Copy' line and the oras-go top-level import.
  • Rename jsonUnmarshalStrict -> parseManifest; comment matches behavior.
  • proto: redact registry hostname example; move PoC-subset scope notes from the file-level comment (which protoc-gen-go places in front of the DO NOT EDIT marker) to the Oras service comment; regen pb.go.
  • Tests: switch hard-coded /tmp/oras-test-.bin paths to a per-test MkdirTemp helper under /tmp; rename proto_clone -> protoClone; add TestIsCrossDeviceError; rework TestMapRegistryError to use typed errors.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • pkg/gnoi/oras: drop http_proxy from PullRequest, lean on env vars

http_proxy on the wire mixes ops policy into the RPC contract. Go's http.DefaultTransport already honors the standard HTTP_PROXY / HTTPS_PROXY / NO_PROXY env vars via http.ProxyFromEnvironment, and lab testbeds can inject those on the gnmi process (e.g. via /usr/bin/gnmi-native.sh). Production switches with a default route to the registry need no configuration.

Also trims the design doc's PullRequest example: removes media_type_filter, source_address, source_vrf, skip_if_exists, expected_manifest_digest — all speculative v2+ knobs that don't belong in the first cut. Lists them in a 'deliberately deferred' section so the rationale is preserved.

Regenerates oras.pb.go to drop field #7.

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • pkg/hostfs: add shared host-path validate + container translate helpers

New /tmp/, /var/tmp/, /host/ allowlist + /mnt/host bind-mount translation lives in pkg/hostfs. pkg/gnoi/oras switches over so its writes land on the host filesystem (sonic-installer reads from the host /tmp, not the container's tmpfs).

internal/diskspace and pkg/gnoi/file still have their own private copies of this logic; migrating them is a follow-up to keep this change focused.

Also picks up an existing go.mod entry (opencontainers/go-digest is used directly by the oras tests; promote it from indirect).

Signed-off-by: Dawei Huang daweihuang@microsoft.com

  • doc/oras-pull-design: add mermaid sequence for end-to-end Pull flow

Documents how HandlePull drives oras-go through Resolve → Fetch → file.Store, where progress comes from (countingReader + 1s ticker), and why the staging dir is created next to the destination (same-fs rename). Captures hostfs.Translate as the container→host path seam.

Signed-off-by: Dawei Huang daweihuang@microsoft.com


Signed-off-by: Dawei Huang daweihuang@microsoft.com
(cherry picked from commit a42ed0d)

Conflicts:

gnmi_server/server.go

go.mod

go.sum

Why I did it

How I did it

How to verify it

Which release branch to backport (provide reason below if selected)

  • 201811
  • 201911
  • 202006
  • 202012
  • 202106
  • 202111

Description for the changelog

Link to config_db schema for YANG module changes

A picture of a cute animal (not mandatory but encouraged)

…onic-net#692)

* doc: design for gNOI ORAS Pull service

Draft RFC for a new sonic.gnoi.oras.v1.Oras service that lets an
orchestrator instruct a switch to pull an OCI/ORAS artifact from a
registry into local staging, decoupled from install.

Tracks ADO Feature #37984064.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* doc: correct framing of SONiC TransferToRemote

SONiC's TransferToRemote actually performs a download (HTTP GET into
local_path), not an upload as upstream openconfig defines. Update §1 to
describe the real current state and enumerate the concrete limitations
that block its use for ACR/ORAS.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* proto: add sonic.gnoi.oras.v1.Oras service definition

PoC subset of the ORAS Pull design (doc/oras-pull-design.md): a single
streaming Pull RPC with anonymous + basic auth and an optional
http_proxy field. List/Delete and richer features are deferred.

Generated oras.pb.go is checked in following the existing precedent
(proto/sonic.pb.go, proto/gnoi/sonic_debug.pb.go). Makefile wires the
new binding into PROTO_GO_BINDINGS so make can regenerate it.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* pkg/gnoi/oras: implement Pull RPC

Streaming server implementation of sonic.gnoi.oras.v1.Oras.Pull:

  * Resolves manifest by tag or digest against an OCI registry.
  * Requires single-layer artifacts (PoC scope per the design doc;
    SONiC OS images are single layer).
  * Stages the layer into a temp dir next to local_path and renames
    into place on success, so a failed pull never leaves a partial
    file at local_path.
  * Emits PullStarted once the manifest is resolved, PullProgress at
    most once per second, and a final PullResult with elapsed time
    and per-layer digest.
  * Reuses the file-server path allowlist (/tmp, /var/tmp, /host).
  * Supports anonymous and basic auth (ACR admin user); workload
    identity and bearer modes are stubbed out for v1.
  * http_proxy field plumbed into the HTTP transport so testbeds
    where the registry is not reachable via the default route (e.g.
    sonic-vs vlabs behind a host tinyproxy) can still pull.
  * Best-effort registry-error to gRPC status mapping.

Adds oras.land/oras-go/v2 v2.6.0 and github.com/opencontainers/image-spec
v1.1.1 to go.mod.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* gnmi_server: register sonic.gnoi.oras.v1.Oras

Add OrasServer wrapper and wire it into registerAllServices behind the
existing EnableTranslibWrite || EnableNativeWrite gate, alongside the
other gNOI services. Pull authenticates the caller and then delegates
to pkg/gnoi/oras.HandlePull.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* gnmi_server: register Oras unconditionally

Drop the EnableTranslibWrite/EnableNativeWrite gate for sonic.gnoi.oras.v1.Oras.
The other services inside that gate are there because gNMI write paths
(translib / native YANG) should not be exposed unless the operator opted in
to writes. Oras Pull does not touch any YANG datastore — it writes only into
an allowlisted staging area inside the gnmi container — so the gate is not
meaningful here. Keep the service available on every build, mirroring the
unconditional registration of system / factory_reset.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* pkg/gnoi/oras: add unit tests, split HandlePull for testability

Introduce handlePullWithRepo as a seam so tests can drive the pull loop
against an httptest-backed fake registry (PlainHTTP) without reaching
the real network. HandlePull keeps the same public signature, validates
the request, constructs the repository, then delegates.

New tests cover:
  - validatePullRequest (12 cases)
  - validateLocalPath (allowlist + traversal)
  - pullReference (tag vs digest precedence)
  - pickSingleLayer (0/1/2 layers, malformed JSON)
  - mapRegistryError (401/host/timeout/404/ENOSPC/default)
  - countingReader, copyAndRemove, jsonUnmarshalStrict
  - newRepository wires basic-auth credentials + leaves Credential nil
    for anonymous; rejects invalid registry refs
  - HandlePull happy path and auth-failure path against a fake registry

Package coverage now 81.8%.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* pkg/gnoi/oras: gofmt oras_test.go

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* pure.mk: register pkg/gnoi/oras as a pure package

pkg/gnoi/oras has no CGO or SONiC dependencies, so its tests can run
in the pure-test stage. Registering it here makes the pipeline pick up
the unit tests and include them in the diff-coverage gate.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* pkg/gnoi/oras: expand error-branch test coverage

Add tests for HandlePull wrapper (E2E + bad registry ref), multi-layer
manifest rejection, MkdirTemp failure, blob fetch 500, Send error on
PullStarted, and copyAndRemove dst-open error. Lifts statement coverage
from 81.8% to 89.9% so the pipeline diff-coverage gate (>=80% lines)
clears with comfortable margin.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* pkg/gnoi/oras: address review feedback

- Serialize stream.Send through a safeStream mutex; close progressDone
  before final Send (and wait for the progress goroutine to exit) so the
  progress goroutine can never race with PullResult.
- Restrict os.Rename copy-and-delete fallback to EXDEV only; surface
  permission / target-is-dir / etc. errors as-is instead of silently
  masking them.
- Replace substring-based mapRegistryError with errors.As inspection of
  errcode.ErrorResponse, errdef.ErrNotFound, net.OpError/DNSError,
  net.Error.Timeout(), and syscall.ECONNREFUSED / ENOSPC. Adds
  PermissionDenied for 403.
- validateLocalPath: walk path components for literal '..' segments
  instead of strings.Contains, which over-rejected names like 'a..b'.
- Drop the dead '_ = oras.Copy' line and the oras-go top-level import.
- Rename jsonUnmarshalStrict -> parseManifest; comment matches behavior.
- proto: redact registry hostname example; move PoC-subset scope notes
  from the file-level comment (which protoc-gen-go places in front of
  the DO NOT EDIT marker) to the Oras service comment; regen pb.go.
- Tests: switch hard-coded /tmp/oras-test-<pid>.bin paths to a per-test
  MkdirTemp helper under /tmp; rename proto_clone -> protoClone; add
  TestIsCrossDeviceError; rework TestMapRegistryError to use typed
  errors.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* pkg/gnoi/oras: drop http_proxy from PullRequest, lean on env vars

http_proxy on the wire mixes ops policy into the RPC contract. Go's
http.DefaultTransport already honors the standard HTTP_PROXY / HTTPS_PROXY
/ NO_PROXY env vars via http.ProxyFromEnvironment, and lab testbeds can
inject those on the gnmi process (e.g. via /usr/bin/gnmi-native.sh).
Production switches with a default route to the registry need no
configuration.

Also trims the design doc's PullRequest example: removes media_type_filter,
source_address, source_vrf, skip_if_exists, expected_manifest_digest — all
speculative v2+ knobs that don't belong in the first cut. Lists them in a
'deliberately deferred' section so the rationale is preserved.

Regenerates oras.pb.go to drop field sonic-net#7.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* pkg/hostfs: add shared host-path validate + container translate helpers

New /tmp/, /var/tmp/, /host/ allowlist + /mnt/host bind-mount translation
lives in pkg/hostfs. pkg/gnoi/oras switches over so its writes land on the
host filesystem (sonic-installer reads from the host /tmp, not the
container's tmpfs).

internal/diskspace and pkg/gnoi/file still have their own private copies
of this logic; migrating them is a follow-up to keep this change focused.

Also picks up an existing go.mod entry (opencontainers/go-digest is used
directly by the oras tests; promote it from indirect).

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

* doc/oras-pull-design: add mermaid sequence for end-to-end Pull flow

Documents how HandlePull drives oras-go through Resolve → Fetch →
file.Store, where progress comes from (countingReader + 1s ticker), and
why the staging dir is created next to the destination (same-fs rename).
Captures hostfs.Translate as the container→host path seam.

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>

---------

Signed-off-by: Dawei Huang <daweihuang@microsoft.com>
(cherry picked from commit a42ed0d)

# Conflicts:
#	gnmi_server/server.go
#	go.mod
#	go.sum
@mssonicbld

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@mssonicbld

Copy link
Copy Markdown
Contributor

Hi, there are workflow run(s) waiting for approval, you may be first-time contributor. I will notify maintainers to help approve once PR is approved. Thanks!

---Powered by SONiC BuildBot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants