Skip to content

Commit 2a302ae

Browse files
authored
Add independent DPU upgrade support via gNOI streaming operations (#532)
* Add gRPC interceptor framework and DPU proxy for gNMI Implement a gRPC interceptor framework to support request routing based on metadata headers. Add DPU proxy interceptor that detects requests with x-sonic-ss-target-type=dpu metadata and resolves DPU information from Redis STATE_DB. Changes: - Add pkg/interceptors: Core interceptor framework with chaining support - Add pkg/interceptors/dpuproxy: DPU proxy implementation with: - Metadata extraction for x-sonic-ss-target-type and x-sonic-ss-target-index - Redis client interface and adapter for pure Go compatibility - DPU resolver to query CHASSIS_MIDPLANE_TABLE from STATE_DB - Comprehensive unit and integration tests using miniredis Current implementation (Phase 1.5): - Intercepts unary and streaming gRPC calls - Extracts DPU routing metadata from request headers - Resolves DPU IP address and reachability status from Redis - Logs resolved information and passes through to local handler Future work (Phase 2): - Implement actual gRPC forwarding to DPU servers - Add connection pooling for DPU clients - Handle bidirectional streaming proxy Testing: - All pure Go packages pass make -f pure.mk ci - Integration tests with miniredis for Redis operations - Verified on hardware with DPU metadata headers * Implement DPU gRPC client framework with Time method forwarding Add connection pooling with keepalive for long-lived connections to DPU gNOI servers. Implement forwarding for System.Time method to enable testing of the DPU proxy infrastructure. * Implement gNOI File.Put RPC with comprehensive unit tests - Enable File.Put RPC in gnmi_server to handle file uploads - Implement HandlePut function in pkg/gnoi/file with: - Streaming file upload support - MD5 hash verification - Path security validation (only /tmp and /var/tmp allowed) - Atomic file write (temp file + rename) - Container path translation support - Add comprehensive unit tests covering: - Successful uploads and multi-chunk transfers - Hash validation and mismatch detection - Path security enforcement - Error handling for invalid inputs - Large file uploads (1MB test) - Edge cases (EOF, invalid messages) - Achieve 86.4% test coverage with 83.3% for HandlePut * Refactor DPU TransferToRemote logic to pure package - Move HandleTransferToRemoteForDPU from gnoi_file.go to pkg/gnoi/file - Update gnoi_file.go to use pure package function - Add proper parameter validation and documentation - Maintain same functionality with cleaner separation - Business logic now in testable pure package * Fix hardcoded DPU gNMI port - read from CONFIG_DB - Add CONFIG_DB lookup for DPU gNMI port configuration - Update DPUResolver to query both StateDB and ConfigDB - Add GNMIPort field to DPUInfo structure - Update getConnection to use dynamic port instead of hardcoded 50052 - Initialize both Redis clients in telemetry.go - Default to 50052 if CONFIG_DB doesn't specify gnmi_port - Improve error messages to show actual connection targets Fixes connection failures when DPUs use non-standard gNMI ports. * Add robust DPU gNMI port discovery with fallback - Try multiple common gNMI ports (8080, 50052) if first fails - Add GNMIPortsToTry field to DPUInfo for port list - Update getConnection to attempt multiple ports with quick health check - Cache successful port for better logging - Prioritize CONFIG_DB configured port, then try common alternatives - Improve error messages to show all attempted ports - Prevents connection failures due to port misconfigurations This makes the DPU proxy resilient to: - CONFIG_DB vs actual service port mismatches - Different DPU gNMI service configurations - Port configuration changes * Refactor System.SetPackage to use sonic-installer instead of dbus - Move SetPackage business logic from gnmi_server to pure pkg/gnoi/system package - Replace dbus.InstallImage() with sonic-installer install -y command execution - Replace dbus.DownloadImage() with requirement for local images only - Use pkg/exec for sonic-installer command execution via nsenter - Add comprehensive test coverage for pure packages (71.1% coverage) - Remove RemoteDownload support - images must be local for security - Update pure.mk to include new testable packages: pkg/exec, pkg/gnoi/os, pkg/gnoi/system - Simplify gnmi_server/gnoi_system.go SetPackage from 87 lines to 22 lines - Also refactor OS.Verify to use sonic-installer list instead of dbus This eliminates dbus dependency for image management operations and makes the business logic testable without SONiC environment dependencies. Tested end-to-end: System.SetPackage successfully installs and activates SONiC images using sonic-installer commands. * Fix code formatting with gofmt - Remove trailing empty lines and fix indentation - Clean up whitespace in struct field alignment - Remove extra blank lines between constants No functional changes, only formatting cleanup. * Fix dpuproxy package tests to use dual Redis clients Update test functions to match NewDPUResolver constructor signature that requires separate state and config Redis clients instead of single client. This fixes the broken CI for pure packages. * Add DPU reboot capability to gNOI System.Reboot RPC - Add HandleDPUReboot function to pkg/gnoi/system for DPU-specific reboot logic - Modify System.Reboot to detect DPU metadata and route to HandleDPUReboot when present - Register /gnoi.system.System/Reboot in DPU proxy with HandleOnNPU mode - Use reboot -d DPU{index} command via pkg/exec for host-level DPU control - Handle async reboot behavior - don't treat non-zero exit codes as errors - Add comprehensive tests for DPU reboot validation and function signatures - Preserve existing system reboot functionality for non-DPU requests This enables remote DPU rebooting via gRPC metadata headers: x-sonic-ss-target-type: dpu x-sonic-ss-target-index: 3 Tested with DPU3 on NPU 10.3.146.230 - successfully reboots DPU. * Add DPU streaming support for gNOI System.SetPackage RPC - Add SetPackage to forwardable methods registry for DPU proxy - Implement forwardSetPackageStream function for streaming RPC forwarding - Increase timeouts for sonic-installer commands (10min install, 2min set-default) - Enable complete DPU package installation workflow through proxy * Implement streaming proxy for DPU File.TransferToRemote operations This change replaces the inefficient download-store-upload pattern with a direct streaming proxy that eliminates NPU disk usage and minimizes memory consumption for large file transfers to DPU devices. Key improvements: - Zero NPU disk usage (was: full file size) - Constant memory usage ~128KB (was: full file in RAM) - Concurrent MD5 hash calculation during streaming - Direct HTTP-to-gRPC streaming with io.TeeReader - Comprehensive test coverage (85.5% download, 91.7% hash) Components added: - internal/download: DownloadHTTPStreaming() for size-limited HTTP streams - internal/hash: StreamingMD5Calculator for concurrent hash calculation - pkg/gnoi/file: HandleTransferToRemoteForDPUStreaming() main proxy logic - gnmi_server: Updated routing to use streaming implementation The streaming proxy maintains the same security model and error handling while dramatically improving resource efficiency for large firmware and package transfers to DPU devices. * format fix * Fix duplicate OSServer function declarations causing build failure Remove duplicate Activate and Verify functions from gnoi.go that were incorrectly added during merge resolution. These functions already exist in gnoi_os.go as part of the gNOI OS refactor from master branch. The duplication caused compilation errors: - OSServer.Activate redeclared - OSServer.Verify redeclared Also cleaned up unused imports that were only needed by the removed functions. * Move DPU interceptor setup logic to pure package with proper cleanup Extract DPU proxy creation logic from telemetry.go into pkg/interceptors/setup.go to improve testability and fix resource leaks identified by Copilot review. Changes: - Add pkg/interceptors/setup.go with NewServerChain() factory function - Simplify telemetry.go from 13 lines of setup to 4 lines - Fix Redis client resource leak with proper defer cleanup - Maintain pure package architecture for better testing This addresses Copilot feedback about Redis clients never being closed, preventing connection leaks during server operation. * Fix critical closure variable capture bug in interceptor chain Address closure capture issue identified by GitHub Copilot where loop variables were captured by reference instead of value, causing all interceptors to use the last iteration's values. Changes: - Fix UnaryInterceptor: Use immediate function invocation to capture variables by value - Fix StreamInterceptor: Apply same pattern for streaming RPCs - Remove unused "strings" import from gnoi.go This bug would have caused complete failure of the DPU interceptor chain, where all interceptors would reference the same (last) interceptor instance, breaking the entire DPU upgrade workflow. The fix uses the Go idiom of immediate function invocation to capture loop variables by value rather than reference, ensuring each interceptor closure captures the correct interceptor instance. * Fix temp file cleanup in gNOI File.Put operations Improve temp file cleanup logic to only remove files on error paths, preventing removal of successfully renamed files. Add error logging for cleanup failures to aid troubleshooting. Changes: - Replace unconditional os.Remove with conditional cleanup - Check file existence before attempting removal - Log cleanup errors without failing the operation - Add missing log import for error reporting This resolves a potential temp file leak when rename operations fail after successful file writes. * Improve error handling and resource management across core components * Enhanced temp file cleanup error handling in gNOI file operations * Simplified exit code handling in command execution for more consistent behavior * Replaced connection state checks with actual RPC health checks in DPU proxy * Added proper interceptor chain lifecycle management in telemetry server * Improved logging for DPU reboot operations to reflect expected behavior * Added detailed comments for file size limit detection logic These changes focus on defensive programming practices and prevent resource leaks in long-running services. * Remove obsolete tests after pure package refactoring * Remove TestSetPackage tests in gnmi_server that tested deprecated dbus implementation * Remove Put_Fails_with_Unimplemented_Error test since gNOI File.Put is now implemented * Keep pure package tests in pkg/gnoi/system for new sonic-installer implementation These test removals address pipeline failures caused by tests expecting obsolete behavior after the DPU interceptor and pure package refactoring. * Enhance test coverage for pure packages - Add comprehensive tests for pkg/gnoi/file/file.go (39.3% -> 70.1% coverage) - Add extensive DPU function validation and HTTP streaming tests - Add missing tests for pkg/interceptors/setup.go (0% -> 97.1% coverage) - Enhance pkg/gnoi/os/os_test.go with context cancellation tests - Add validation tests for pkg/gnoi/system/system_test.go - Improve error path coverage across all pure packages - Add security validation and edge case tests Coverage improvements: - pkg/interceptors/setup.go: 0% -> 97.1% (+97.1%) - pkg/gnoi/file/file.go: 39.3% -> 70.1% (+30.8%) - pkg/gnoi/os/os.go: 41.7% -> 66.7% (+25.0%) - Comprehensive validation and error handling coverage Note: DPU gRPC functions remain at ~55-57% due to untestable networking code that requires full integration test setup. * fix format * just to rerun pipeline * Refactor DPU routing logic to pure Go package and improve test coverage Move DPU metadata parsing from gnmi_server/gnoi_file.go to pkg/gnoi/file/file.go to enable easier testing without CGO dependencies. This improves architecture by separating authentication concerns from business logic. Changes: - Move DPU routing logic into HandlePut function in pure Go package - Simplify gnmi_server Put implementation to just auth + delegation - Add comprehensive tests for DPU routing in pure Go package - Achieve 71.4% coverage for pkg/gnoi/file package - Add missing Put success path test for gnmi_server coverage Benefits: - Better testability with pure Go package (no CGO required) - Cleaner separation of concerns (auth vs business logic) - Reduced complexity in gnmi_server layer - Improved test coverage for DPU functionality * Achieve 87.1% test coverage for pkg/gnoi/file package - Add comprehensive monkey patching tests for DPU functions using gomonkey - Update pure.mk to include -gcflags="all=-N -l" for monkey patching compatibility - Add coverage boost tests for edge cases and error paths - Fix infinite recursion issues in container path testing Coverage improvements: - HandleTransferToRemoteForDPU: 57.1% → 83.7% - HandleTransferToRemoteForDPUStreaming: 55.0% → 88.3% - Overall package coverage: 71.4% → 87.1% (exceeds 80% requirement) The monkey patching tests mock the entire call chain (HandleTransferToRemote, grpc.Dial, gNOI file client methods) to test DPU routing logic without requiring actual network connections or external dependencies. Pipeline fix: The key issue was that Azure pipeline ran tests without -gcflags="all=-N -l" flags needed for monkey patching. Without these flags, Go compiler optimizations prevent gomonkey from working, causing DPU function tests to fail and resulting in lower coverage (71.4% vs 87.1%). * Achieve 100% test coverage for pkg/gnoi/os package - Add comprehensive monkey patching tests for HandleVerify function - Mock exec.RunHostCommand to test success and error paths - Test various sonic-installer output formats and parsing edge cases Coverage improvements: - HandleVerify: 42.9% → 100% - parseCurrentVersion: 100% (maintained) - Overall package coverage: 66.7% → 100% The monkey patching tests cover all execution paths: - Successful sonic-installer execution with version parsing - Command execution failures (permission denied, etc.) - Version parsing failures (malformed output) - Different version string formats (standard, master builds, with spaces) This exceeds the 80% coverage requirement for the pipeline. * Achieve 100% test coverage for pkg/gnoi/system package - Add comprehensive monkey patching tests for all system functions - Mock exec.RunHostCommand to test success and error paths - Test HandleDPUReboot function with various DPU indices - Test package installation and activation workflows Coverage improvements: - HandleSetPackage: 75.9% → 100% - installPackage: 66.7% → 100% - activatePackage: 66.7% → 100% - HandleDPUReboot: 0% → 100% - Overall package coverage: 60.7% → 100% The monkey patching tests cover all execution paths: - Successful package installation with and without activation - Command execution failures (permission denied, command not found) - DPU reboot operations for different DPU indices - Error handling in both installation and activation phases This far exceeds the 80% coverage requirement for the pipeline. * Extract business logic from server wrappers to pure handlers for testability Moved DPU metadata extraction and routing logic from server wrapper files into testable pure handler functions to improve test coverage and resolve diff coverage issues preventing merge approval. Changes: - Consolidated DPU routing logic into existing handlers (HandleTransferToRemote, HandleReboot, HandleSetPackageStream) - Simplified server wrappers to delegate all business logic to pure handlers - Added comprehensive tests using gomonkey for newly testable pure functions - Coverage improvements: file 87.8%, system 95.9% (target: 80% minimum) Fixes diff coverage below threshold in: - gnmi_server/gnoi_file.go (lines 117,120,125-126) - gnmi_server/gnoi_system.go (lines 206,210,337,342) * Format and cleanup test files * Fix compilation errors for incremental build Remove unused imports and fix variable declarations: - Remove unused dpuproxy import from gnoi_system.go - Remove unused metadata import from gnoi_file.go - Fix variable naming conflict in sendRebootReqOnNotifCh usage Enables successful dpkg-buildpackage build with extracted business logic. * Replace NPU terminology with generic local device terminology Update symbols and comments to use more accurate terminology: - HandleOnNPU → HandleLocally (clearer intent for local processing) - handleTransferToRemoteNPU → handleTransferToRemoteLocal - TestHandleReboot_NPU_Fallback → TestHandleReboot_Local_Fallback - Update comments: 'NPU fallback/handling' → 'local fallback/handling' Improves architectural clarity by distinguishing between: - DPU operations (forwarded to remote DPU) - Local operations (processed on host device) No functional changes - purely terminology cleanup for better code readability.
1 parent 6052f33 commit 2a302ae

39 files changed

+9071
-491
lines changed

gnmi_server/gnoi_file.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,27 +97,30 @@ func (srv *FileServer) Get(req *gnoi_file_pb.GetRequest, stream gnoi_file_pb.Fil
9797
return status.Errorf(codes.Unimplemented, "Method file.Get is unimplemented.")
9898
}
9999

100-
// TransferToRemote RPC is unimplemented.
100+
// TransferToRemote downloads a file from a remote URL.
101+
// If DPU headers are present (HandleOnNPU mode), it downloads to NPU then uploads to the specified DPU.
101102
func (srv *FileServer) TransferToRemote(ctx context.Context, req *gnoi_file_pb.TransferToRemoteRequest) (*gnoi_file_pb.TransferToRemoteResponse, error) {
102103
log.Infof("GNOI File TransferToRemote RPC called with request: %+v", req)
103104
_, err := authenticate(srv.config, ctx, "gnoi", false)
104105
if err != nil {
105106
log.Errorf("authentication failed in TransferToRemote RPC: %v", err)
106107
return nil, err
107108
}
109+
110+
// Delegate all logic to the pure handler function
108111
return gnoifile.HandleTransferToRemote(ctx, req)
109112
}
110113

111-
// Put RPC is unimplemented.
114+
// Put implements the gNOI File.Put RPC.
115+
// It authenticates the request and delegates to the pure Go handler.
112116
func (srv *FileServer) Put(stream gnoi_file_pb.File_PutServer) error {
113117
log.Infof("GNOI File Put RPC called")
114118
_, err := authenticate(srv.config, stream.Context(), "gnoi", false)
115119
if err != nil {
116120
log.Errorf("authentication failed in Put RPC: %v", err)
117121
return err
118122
}
119-
log.Warning("file.Put RPC is unimplemented")
120-
return status.Errorf(codes.Unimplemented, "Method file.Put is unimplemented.")
123+
return gnoifile.HandlePut(stream)
121124
}
122125

123126
// Remove implements the corresponding RPC.

gnmi_server/gnoi_file_test.go

Lines changed: 144 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,28 @@ import (
1010
"github.com/agiledragon/gomonkey/v2"
1111
gnoi_common "github.com/openconfig/gnoi/common"
1212
gnoi_file_pb "github.com/openconfig/gnoi/file"
13+
gnoifile "github.com/sonic-net/sonic-gnmi/pkg/gnoi/file"
1314
ssc "github.com/sonic-net/sonic-gnmi/sonic_service_client"
1415

1516
"github.com/stretchr/testify/assert"
1617
"google.golang.org/grpc"
1718
"google.golang.org/grpc/codes"
1819
"google.golang.org/grpc/credentials/insecure"
20+
"google.golang.org/grpc/metadata"
1921
"google.golang.org/grpc/status"
2022
)
2123

2224
// === Test Setup Helpers ===
23-
func createFileServer(t *testing.T, port int) *grpc.Server {
24-
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
25+
func createFileServer(t *testing.T, port int) (*grpc.Server, string) {
26+
var listener net.Listener
27+
var err error
28+
29+
if port == 0 {
30+
// Use dynamic port
31+
listener, err = net.Listen("tcp", ":0")
32+
} else {
33+
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", port))
34+
}
2535
if err != nil {
2636
t.Fatalf("Failed to listen: %v", err)
2737
}
@@ -40,19 +50,19 @@ func createFileServer(t *testing.T, port int) *grpc.Server {
4050
}
4151
}()
4252

43-
return s
53+
return s, listener.Addr().String()
4454
}
4555

4656
// === Actual Tests ===
4757
func TestGnoiFileServer(t *testing.T) {
48-
s := createFileServer(t, 8081)
58+
s, addr := createFileServer(t, 0) // Use dynamic port
4959
defer s.Stop()
5060

5161
//tlsConfig := &tls.Config{InsecureSkipVerify: true}
5262
//opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))}
5363
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
5464

55-
conn, err := grpc.Dial("127.0.0.1:8081", opts...)
65+
conn, err := grpc.Dial(addr, opts...)
5666
if err != nil {
5767
t.Fatalf("Failed to dial server: %v", err)
5868
}
@@ -188,21 +198,6 @@ func TestGnoiFileServer(t *testing.T) {
188198
assert.Contains(t, err.Error(), "invalid syntax")
189199
})
190200

191-
t.Run("Put Fails with Unimplemented Error", func(t *testing.T) {
192-
patch := gomonkey.ApplyFuncReturn(authenticate, nil, nil)
193-
defer patch.Reset()
194-
195-
putStream, err := client.Put(context.Background())
196-
if err != nil {
197-
t.Fatalf("Failed to create Put stream: %v", err)
198-
}
199-
200-
// Expect Unimplemented error on CloseAndRecv
201-
_, err = putStream.CloseAndRecv()
202-
if err == nil || status.Code(err) != codes.Unimplemented {
203-
t.Fatalf("Expected Unimplemented error, got: %v", err)
204-
}
205-
})
206201
t.Run("Put Fails with Auth Error", func(t *testing.T) {
207202
patch := gomonkey.ApplyFuncReturn(authenticate, nil, status.Error(codes.Unauthenticated, "unauthenticated"))
208203
defer patch.Reset()
@@ -236,6 +231,71 @@ func TestGnoiFileServer(t *testing.T) {
236231
}
237232
})
238233

234+
t.Run("TransferToRemote DPU Success", func(t *testing.T) {
235+
patches := gomonkey.NewPatches()
236+
defer patches.Reset()
237+
238+
// Mock authenticate to succeed
239+
patches.ApplyFuncReturn(authenticate, nil, nil)
240+
241+
// Mock HandleTransferToRemoteForDPUStreaming to succeed
242+
patches.ApplyFunc(gnoifile.HandleTransferToRemoteForDPUStreaming,
243+
func(ctx context.Context, req *gnoi_file_pb.TransferToRemoteRequest, dpuIndex string, dpuAddr string) (*gnoi_file_pb.TransferToRemoteResponse, error) {
244+
return &gnoi_file_pb.TransferToRemoteResponse{}, nil
245+
})
246+
247+
fs := &FileServer{
248+
Server: &Server{
249+
config: &Config{},
250+
},
251+
}
252+
253+
// Create context with DPU metadata (lines 117, 120, 125-126)
254+
md := metadata.New(map[string]string{
255+
"x-sonic-ss-target-type": "dpu",
256+
"x-sonic-ss-target-index": "0",
257+
})
258+
ctx := metadata.NewIncomingContext(context.Background(), md)
259+
260+
req := &gnoi_file_pb.TransferToRemoteRequest{
261+
LocalPath: "/tmp/test.txt",
262+
}
263+
264+
resp, err := fs.TransferToRemote(ctx, req)
265+
assert.NoError(t, err)
266+
assert.NotNil(t, resp)
267+
})
268+
269+
t.Run("TransferToRemote NPU Success", func(t *testing.T) {
270+
patches := gomonkey.NewPatches()
271+
defer patches.Reset()
272+
273+
// Mock authenticate to succeed
274+
patches.ApplyFuncReturn(authenticate, nil, nil)
275+
276+
// Mock HandleTransferToRemote to succeed
277+
patches.ApplyFunc(gnoifile.HandleTransferToRemote,
278+
func(ctx context.Context, req *gnoi_file_pb.TransferToRemoteRequest) (*gnoi_file_pb.TransferToRemoteResponse, error) {
279+
return &gnoi_file_pb.TransferToRemoteResponse{}, nil
280+
})
281+
282+
fs := &FileServer{
283+
Server: &Server{
284+
config: &Config{},
285+
},
286+
}
287+
288+
ctx := context.Background() // No DPU metadata - should call regular function
289+
290+
req := &gnoi_file_pb.TransferToRemoteRequest{
291+
LocalPath: "/tmp/test.txt",
292+
}
293+
294+
resp, err := fs.TransferToRemote(ctx, req)
295+
assert.NoError(t, err)
296+
assert.NotNil(t, resp)
297+
})
298+
239299
t.Run("TransferToRemote Fails with Auth Error", func(t *testing.T) {
240300
patch := gomonkey.ApplyFuncReturn(authenticate, nil, status.Error(codes.Unauthenticated, "unauthenticated"))
241301
defer patch.Reset()
@@ -399,4 +459,68 @@ func TestGnoiFileServer(t *testing.T) {
399459
}
400460
})
401461

462+
// Test Put function success path (line 143)
463+
t.Run("Put_Success", func(t *testing.T) {
464+
patches := gomonkey.NewPatches()
465+
defer patches.Reset()
466+
467+
patches.ApplyFuncReturn(authenticate, nil, nil)
468+
469+
// Mock the gnoifile.HandlePut function to return success
470+
patches.ApplyFunc(gnoifile.HandlePut,
471+
func(stream gnoi_file_pb.File_PutServer) error {
472+
return nil
473+
})
474+
475+
fs := &FileServer{
476+
Server: &Server{
477+
config: &Config{},
478+
},
479+
}
480+
481+
// Create a mock stream
482+
mockStream := &mockPutStream{
483+
ctx: context.Background(),
484+
}
485+
486+
err := fs.Put(mockStream)
487+
assert.NoError(t, err)
488+
})
489+
490+
}
491+
492+
// Mock stream for Put testing
493+
type mockPutStream struct {
494+
ctx context.Context
495+
}
496+
497+
func (m *mockPutStream) Context() context.Context {
498+
return m.ctx
499+
}
500+
501+
func (m *mockPutStream) SendMsg(msg interface{}) error {
502+
return nil
503+
}
504+
505+
func (m *mockPutStream) RecvMsg(msg interface{}) error {
506+
return nil
507+
}
508+
509+
func (m *mockPutStream) Recv() (*gnoi_file_pb.PutRequest, error) {
510+
return nil, nil
511+
}
512+
513+
func (m *mockPutStream) SendAndClose(resp *gnoi_file_pb.PutResponse) error {
514+
return nil
515+
}
516+
517+
func (m *mockPutStream) SendHeader(metadata.MD) error {
518+
return nil
519+
}
520+
521+
func (m *mockPutStream) SetTrailer(metadata.MD) {
522+
}
523+
524+
func (m *mockPutStream) SetHeader(metadata.MD) error {
525+
return nil
402526
}

gnmi_server/gnoi_system.go

Lines changed: 21 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/golang/protobuf/proto"
1313
syspb "github.com/openconfig/gnoi/system"
1414
"github.com/sonic-net/sonic-gnmi/common_utils"
15+
"github.com/sonic-net/sonic-gnmi/pkg/gnoi/system"
1516
ssc "github.com/sonic-net/sonic-gnmi/sonic_service_client"
1617
"google.golang.org/grpc/codes"
1718
"google.golang.org/grpc/status"
@@ -198,7 +199,20 @@ func (srv *Server) Reboot(ctx context.Context, req *syspb.RebootRequest) (*syspb
198199
return nil, status.Errorf(codes.InvalidArgument, err.Error())
199200
}
200201

201-
// Initialize State DB.
202+
// Try the pure handler first (it handles DPU routing internally)
203+
resp, err := system.HandleReboot(ctx, req)
204+
if err != nil {
205+
// If it's not the "local fallback" error, return the actual error
206+
if status.Code(err) != codes.Unimplemented {
207+
return nil, err
208+
}
209+
// Otherwise fall through to local handling
210+
} else {
211+
// DPU case handled successfully by pure handler
212+
return resp, nil
213+
}
214+
215+
// Initialize State DB for local reboot.
202216
rclient, err := common_utils.GetRedisDBClient()
203217
if err != nil {
204218
return nil, status.Errorf(codes.Internal, err.Error())
@@ -211,15 +225,15 @@ func (srv *Server) Reboot(ctx context.Context, req *syspb.RebootRequest) (*syspb
211225
}
212226

213227
// System reboot.
214-
resp, err, _ := sendRebootReqOnNotifCh(ctx, req, rclient, rebootKey)
228+
respInterface, err, _ := sendRebootReqOnNotifCh(ctx, req, rclient, rebootKey)
215229
if err != nil {
216230
return nil, err
217231
}
218-
if resp == nil {
232+
if respInterface == nil {
219233
log.V(2).Info("Reboot request received empty response from Reboot Backend.")
220-
resp = &syspb.RebootResponse{}
234+
respInterface = &syspb.RebootResponse{}
221235
}
222-
return resp.(*syspb.RebootResponse), nil
236+
return respInterface.(*syspb.RebootResponse), nil
223237
}
224238

225239
// RebootStatus implements the corresponding RPC.
@@ -308,83 +322,8 @@ func (srv *Server) SetPackage(rs syspb.System_SetPackageServer) error {
308322
}
309323
log.V(1).Info("gNOI: SetPackage request received")
310324

311-
// Create D-Bus client
312-
dbus, err := ssc.NewDbusClient()
313-
if err != nil {
314-
log.Errorf("Failed to create D-Bus client: %v", err)
315-
return status.Errorf(codes.Internal, "failed to create D-Bus client: %v", err)
316-
}
317-
defer dbus.Close()
318-
319-
// Receive the package request
320-
req, err := rs.Recv()
321-
if err != nil {
322-
log.Errorf("Failed to receive package request: %v", err)
323-
return err
324-
}
325-
326-
// Validate request type
327-
pkg, ok := req.GetRequest().(*syspb.SetPackageRequest_Package)
328-
if !ok {
329-
errMsg := fmt.Sprintf("invalid request type: %T, expected SetPackageRequest_Package", req.GetRequest())
330-
log.Errorf(errMsg)
331-
return status.Errorf(codes.InvalidArgument, errMsg)
332-
}
333-
334-
// A filename and a version must be provided
335-
if pkg.Package.Filename == "" {
336-
log.Errorf("Filename is missing in package request")
337-
return status.Errorf(codes.InvalidArgument, "filename is missing in package request")
338-
}
339-
if pkg.Package.Version == "" {
340-
log.Errorf("Version is missing in package request")
341-
return status.Errorf(codes.InvalidArgument, "version is missing in package request")
342-
}
343-
// Log the package filename and version
344-
log.V(1).Infof("Package filename: %s, version: %s", pkg.Package.Filename, pkg.Package.Version)
345-
346-
// Download the package if RemoteDownload is provided
347-
if pkg.Package.RemoteDownload != nil {
348-
// Validate RemoteDownload
349-
log.V(1).Infof("RemoteDownload provided")
350-
// Check if the path is provided
351-
if pkg.Package.RemoteDownload.Path == "" {
352-
log.Errorf("RemoteDownload path is missing")
353-
return status.Errorf(codes.InvalidArgument, "remote download path is missing")
354-
}
355-
log.V(1).Infof("RemoteDownload path: %s", pkg.Package.RemoteDownload.Path)
356-
357-
// Download the package
358-
err = dbus.DownloadImage(pkg.Package.RemoteDownload.Path, pkg.Package.Filename)
359-
if err != nil {
360-
log.Errorf("Failed to download image: %v", err)
361-
return status.Errorf(codes.Internal, "failed to download image: %v", err)
362-
}
363-
log.V(1).Infof("Package %s downloaded successfully to %s", pkg.Package.Version, pkg.Package.Filename)
364-
}
365-
366-
// If activate is requested, install the package and set it to be the next boot image
367-
if pkg.Package.Activate {
368-
log.V(1).Infof("Activate is requested")
369-
// Install the package
370-
err = dbus.InstallImage(pkg.Package.Filename)
371-
if err != nil {
372-
log.Errorf("Failed to install image: %v", err)
373-
return status.Errorf(codes.Internal, "failed to install image: %v", err)
374-
}
375-
log.V(1).Infof("Package %s installed successfully", pkg.Package.Filename)
376-
// Currently, Installing the image will automatically set it as the next boot image
377-
log.V(1).Infof("Package %s set as next boot image", pkg.Package.Filename)
378-
}
379-
380-
// Send response to client
381-
if err := rs.SendAndClose(&syspb.SetPackageResponse{}); err != nil {
382-
log.Errorf("Failed to send response: %v", err)
383-
return err
384-
}
385-
386-
log.V(1).Infof("SetPackage completed successfully for %s", pkg.Package.Filename)
387-
return nil
325+
// Delegate all logic to the pure handler
326+
return system.HandleSetPackageStream(rs)
388327
}
389328

390329
// SwitchControlProcessor implements the corresponding RPC.

0 commit comments

Comments
 (0)