Skip to content

Commit

Permalink
Client side code for GNOI.System.SetPackage (#358)
Browse files Browse the repository at this point in the history
#### Why I did it
Supports system.SetPackage, see sonic-net/SONiC#1906

This PR also improve ease of use for client by not using (in this `System.SetPackage` specifically) the generic `-jsonin` flag where we pass a json string, in favor of a more readable and verifiable flags like `-package_filename` and `-package_url` .

#### How I did it
Client side code for system.SetPackage. 

#### How to verify it

On physical switches:
```
admin@switch:~$ docker exec gnmi gnoi_client -target 127.0.0.1:50052 -logtostderr -insecure -module System -rpc SetPackage
System SetPackage
Error validating flags:  missing -package_filename

admin@switch:~$ docker exec gnmi gnoi_client -target 127.0.0.1:50052 -logtostderr -insecure -module System -rpc SetPackage -package_filename=filename
System SetPackage
Error validating flags:  missing -package_version

admin@switch:~$ docker exec gnmi gnoi_client -target 127.0.0.1:50052 -logtostderr -insecure -module System -rpc SetPackage -package_filename=filename -package_version=abc -package_activate=true -package_url=abc.com
System SetPackage
Error receiving response:  rpc error: code = Unimplemented desc =
```

Server already have placeholder code so the unimplemented error is expected.
  • Loading branch information
hdwhdw authored Feb 14, 2025
1 parent e06ff6c commit 558cda6
Show file tree
Hide file tree
Showing 7 changed files with 646 additions and 5 deletions.
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ $(GO_DEPS): go.mod $(PATCHES) swsscommon_wrap $(GNOI_YANG)
$(GO) mod download github.com/google/gnxi@v0.0.0-20181220173256-89f51f0ce1e2
cp -r $(GOPATH)/pkg/mod/github.com/google/gnxi@v0.0.0-20181220173256-89f51f0ce1e2/* vendor/github.com/google/gnxi/

# Apply patch from sonic-mgmt-common, ignore glog.patch because glog version changed
# Apply patch from sonic-mgmt-common, ignore glog.patch because glog version changed
sed -i 's/patch -d $${DEST_DIR}\/github.com\/golang\/glog/\#patch -d $${DEST_DIR}\/github.com\/golang\/glog/g' $(MGMT_COMMON_DIR)/patches/apply.sh
$(MGMT_COMMON_DIR)/patches/apply.sh vendor
sed -i 's/#patch -d $${DEST_DIR}\/github.com\/golang\/glog/patch -d $${DEST_DIR}\/github.com\/golang\/glog/g' $(MGMT_COMMON_DIR)/patches/apply.sh
Expand Down Expand Up @@ -106,7 +106,7 @@ endif
$(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/gnmi_dump
$(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/build/gnoi_yang/client/gnoi_openconfig_client
$(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/build/gnoi_yang/client/gnoi_sonic_client

endif

# download and apply patch for gnmi client, which will break advancetls
Expand Down Expand Up @@ -216,11 +216,12 @@ endif
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) test -race -coverprofile=coverage-data.txt -covermode=atomic -mod=vendor -v github.com/sonic-net/sonic-gnmi/sonic_data_client
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) test -race -coverprofile=coverage-dbus.txt -covermode=atomic -mod=vendor -v github.com/sonic-net/sonic-gnmi/sonic_service_client
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(TESTENV) $(GO) test -race -coverprofile=coverage-translutils.txt -covermode=atomic -mod=vendor -v github.com/sonic-net/sonic-gnmi/transl_utils
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(TESTENV) $(GO) test -race -coverprofile=coverage-gnoi-client-system.txt -covermode=atomic -mod=vendor -v github.com/sonic-net/sonic-gnmi/gnoi_client/system
$(GO) install github.com/axw/gocov/gocov@v1.1.0
$(GO) install github.com/AlekSi/gocov-xml@latest
$(GO) mod vendor
gocov convert coverage-*.txt | gocov-xml -source $(shell pwd) > coverage.xml
rm -rf coverage-*.txt
rm -rf coverage-*.txt

check_memleak: $(DBCONFG) $(ENVFILE)
sudo CGO_LDFLAGS="$(MEMCHECK_CGO_LDFLAGS)" CGO_CXXFLAGS="$(MEMCHECK_CGO_CXXFLAGS)" $(GO) test -coverprofile=coverage-telemetry.txt -covermode=atomic -mod=vendor $(MEMCHECK_FLAGS) -v github.com/sonic-net/sonic-gnmi/telemetry
Expand Down
2 changes: 2 additions & 0 deletions gnoi_client/gnoi_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ func main() {
system.RebootStatus(conn, ctx)
case "KillProcess":
system.KillProcess(conn, ctx)
case "SetPackage":
system.SetPackage(conn, ctx)
default:
panic("Invalid RPC Name")
}
Expand Down
120 changes: 120 additions & 0 deletions gnoi_client/system/set_package.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package system

import (
"context"
"flag"
"fmt"

"github.com/openconfig/gnoi/common"
"github.com/openconfig/gnoi/system"
"github.com/sonic-net/sonic-gnmi/gnoi_client/utils"
"google.golang.org/grpc"
)

var (
// Flags for System.SetPackage
filename = flag.String("package_filename", "", "Destination path and filename of the package")
version = flag.String("package_version", "", "Version of the package, i.e. vendor internal name")
url = flag.String("package_url", "", "URL to download the package from")
activate = flag.Bool("package_activate", true, "Whether to activate the package after setting it")
)

// newSystemClient is a package-level variable that returns a new system.SystemClient.
// We define it here so that unit tests can replace it with a mock constructor if needed.
var newSystemClient = func(conn *grpc.ClientConn) system.SystemClient {
return system.NewSystemClient(conn)
}

// SetPackage is the main entry point. It validates flags, creates the SystemClient,
// and then calls setPackageClient to perform the actual SetPackage gRPC flow.
func SetPackage(conn *grpc.ClientConn, ctx context.Context) {
fmt.Println("System SetPackage")

// Attach user credentials if needed.
ctx = utils.SetUserCreds(ctx)

// Validate the flags before proceeding.
err := validateFlags()
if err != nil {
fmt.Println("Error validating flags:", err)
return
}

// Create a new gNOI SystemClient using the function defined above.
sc := newSystemClient(conn)

// Call the helper that implements the SetPackage logic (sending requests, closing, etc.).
if err := setPackageClient(sc, ctx); err != nil {
fmt.Println("Error during SetPackage:", err)
}
}

// setPackageClient contains the core gRPC calls to send the package request and
// receive the response. We separate it out for easier testing and mocking.
func setPackageClient(sc system.SystemClient, ctx context.Context) error {
// Prepare the remote download info.
download := &common.RemoteDownload{
Path: *url,
}

// Build the package with flags.
pkg := &system.Package{
Filename: *filename,
Version: *version,
Activate: *activate,
RemoteDownload: download,
}

// The gNOI SetPackageRequest can contain different request types, but we only
// use the "Package" request type here.
req := &system.SetPackageRequest{
Request: &system.SetPackageRequest_Package{
Package: pkg,
},
}

// Open a streaming RPC.
stream, err := sc.SetPackage(ctx)
if err != nil {
return fmt.Errorf("error creating stream: %v", err)
}

// Send the package information.
if err := stream.Send(req); err != nil {
return fmt.Errorf("error sending package request: %v", err)
}

// Close the send direction of the stream. The device should download the
// package itself, so we are not sending direct data or checksums here.
if err := stream.CloseSend(); err != nil {
return fmt.Errorf("error closing send direction: %v", err)
}

// Receive the final response from the device.
resp, err := stream.CloseAndRecv()
if err != nil {
return fmt.Errorf("error receiving response: %v", err)
}

// For demonstration purposes, we simply print the response.
fmt.Println(resp)
return nil
}

// validateFlags ensures all required flags are set correctly before we proceed.
func validateFlags() error {
if *filename == "" {
return fmt.Errorf("missing -package_filename: Destination path and filename of the package is required for the SetPackage operation")
}
if *version == "" {
return fmt.Errorf("missing -package_version: Version of the package is required for the SetPackage operation")
}
if *url == "" {
return fmt.Errorf("missing -package_url: URL to download the package from is required for the SetPackage operation. Direct transfer is not supported yet")
}
if !*activate {
// TODO: Currently, installing the image will always set it to default.
return fmt.Errorf("-package_activate=false is not yet supported: The package will always be activated after setting it")
}
return nil
}
Loading

0 comments on commit 558cda6

Please sign in to comment.