From de2817f2d3b34e86f6df7d4c45c067137d357916 Mon Sep 17 00:00:00 2001 From: Brian Neville <29639579+brianneville@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:36:14 +0100 Subject: [PATCH 1/4] gnoi/os: add functionality to download from remote add a field enabling the client to instruct the target to perform a remote download of the OS package, rather than requiring the client to stream the content to the server. --- os/BUILD.bazel | 2 ++ os/os.pb.go | 81 ++++++++++++++++++++++++++++++------------------ os/os.proto | 18 +++++++++++ os/os_grpc.pb.go | 20 ++++++------ 4 files changed, 82 insertions(+), 39 deletions(-) diff --git a/os/BUILD.bazel b/os/BUILD.bazel index a29b1e03..ed4a600a 100644 --- a/os/BUILD.bazel +++ b/os/BUILD.bazel @@ -30,6 +30,7 @@ proto_library( deps = [ "//bootconfig:bootconfig_proto", "//types:types_proto", + "//common:common_proto", ], ) @@ -56,6 +57,7 @@ go_proto_library( deps = [ "//bootconfig", "//types", + "//common", ], ) diff --git a/os/os.pb.go b/os/os.pb.go index 7ac79305..efadf74a 100644 --- a/os/os.pb.go +++ b/os/os.pb.go @@ -1,13 +1,14 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v6.33.1 +// protoc-gen-go v1.36.11 +// protoc v7.35.1 // source: github.com/openconfig/gnoi/os/os.proto package os import ( bootconfig "github.com/openconfig/gnoi/bootconfig" + common "github.com/openconfig/gnoi/common" _ "github.com/openconfig/gnoi/types" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -201,6 +202,7 @@ type InstallRequest struct { // *InstallRequest_TransferRequest // *InstallRequest_TransferContent // *InstallRequest_TransferEnd + // *InstallRequest_RemoteDownload Request isInstallRequest_Request `protobuf_oneof:"request"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -270,6 +272,15 @@ func (x *InstallRequest) GetTransferEnd() *TransferEnd { return nil } +func (x *InstallRequest) GetRemoteDownload() *common.RemoteDownload { + if x != nil { + if x, ok := x.Request.(*InstallRequest_RemoteDownload); ok { + return x.RemoteDownload + } + } + return nil +} + type isInstallRequest_Request interface { isInstallRequest_Request() } @@ -286,12 +297,18 @@ type InstallRequest_TransferEnd struct { TransferEnd *TransferEnd `protobuf:"bytes,3,opt,name=transfer_end,json=transferEnd,proto3,oneof"` } +type InstallRequest_RemoteDownload struct { + RemoteDownload *common.RemoteDownload `protobuf:"bytes,4,opt,name=remote_download,json=remoteDownload,proto3,oneof"` +} + func (*InstallRequest_TransferRequest) isInstallRequest_Request() {} func (*InstallRequest_TransferContent) isInstallRequest_Request() {} func (*InstallRequest_TransferEnd) isInstallRequest_Request() {} +func (*InstallRequest_RemoteDownload) isInstallRequest_Request() {} + type TransferRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` @@ -1278,11 +1295,12 @@ var File_github_com_openconfig_gnoi_os_os_proto protoreflect.FileDescriptor const file_github_com_openconfig_gnoi_os_os_proto_rawDesc = "" + "\n" + - "&github.com/openconfig/gnoi/os/os.proto\x12\agnoi.os\x1a6github.com/openconfig/gnoi/bootconfig/bootconfig.proto\x1a,github.com/openconfig/gnoi/types/types.proto\"\xca\x01\n" + + "&github.com/openconfig/gnoi/os/os.proto\x12\agnoi.os\x1a6github.com/openconfig/gnoi/bootconfig/bootconfig.proto\x1a,github.com/openconfig/gnoi/types/types.proto\x1a.github.com/openconfig/gnoi/common/common.proto\"\x92\x02\n" + "\x0eInstallRequest\x12E\n" + "\x10transfer_request\x18\x01 \x01(\v2\x18.gnoi.os.TransferRequestH\x00R\x0ftransferRequest\x12+\n" + "\x10transfer_content\x18\x02 \x01(\fH\x00R\x0ftransferContent\x129\n" + - "\ftransfer_end\x18\x03 \x01(\v2\x14.gnoi.os.TransferEndH\x00R\vtransferEndB\t\n" + + "\ftransfer_end\x18\x03 \x01(\v2\x14.gnoi.os.TransferEndH\x00R\vtransferEnd\x12F\n" + + "\x0fremote_download\x18\x04 \x01(\v2\x1b.gnoi.common.RemoteDownloadH\x00R\x0eremoteDownloadB\t\n" + "\arequest\"}\n" + "\x0fTransferRequest\x12\x18\n" + "\aversion\x18\x01 \x01(\tR\aversion\x12-\n" + @@ -1403,36 +1421,38 @@ var file_github_com_openconfig_gnoi_os_os_proto_goTypes = []any{ (*VerifyStandby)(nil), // 18: gnoi.os.VerifyStandby (*StandbyState)(nil), // 19: gnoi.os.StandbyState (*StandbyResponse)(nil), // 20: gnoi.os.StandbyResponse - (*bootconfig.SetBootConfigRequest)(nil), // 21: gnoi.bootconfig.SetBootConfigRequest + (*common.RemoteDownload)(nil), // 21: gnoi.common.RemoteDownload + (*bootconfig.SetBootConfigRequest)(nil), // 22: gnoi.bootconfig.SetBootConfigRequest } var file_github_com_openconfig_gnoi_os_os_proto_depIdxs = []int32{ 4, // 0: gnoi.os.InstallRequest.transfer_request:type_name -> gnoi.os.TransferRequest 5, // 1: gnoi.os.InstallRequest.transfer_end:type_name -> gnoi.os.TransferEnd - 7, // 2: gnoi.os.InstallResponse.transfer_ready:type_name -> gnoi.os.TransferReady - 8, // 3: gnoi.os.InstallResponse.transfer_progress:type_name -> gnoi.os.TransferProgress - 9, // 4: gnoi.os.InstallResponse.sync_progress:type_name -> gnoi.os.SyncProgress - 10, // 5: gnoi.os.InstallResponse.validated:type_name -> gnoi.os.Validated - 11, // 6: gnoi.os.InstallResponse.install_error:type_name -> gnoi.os.InstallError - 0, // 7: gnoi.os.InstallError.type:type_name -> gnoi.os.InstallError.Type - 21, // 8: gnoi.os.ActivateRequest.boot_config:type_name -> gnoi.bootconfig.SetBootConfigRequest - 14, // 9: gnoi.os.ActivateResponse.activate_ok:type_name -> gnoi.os.ActivateOK - 15, // 10: gnoi.os.ActivateResponse.activate_error:type_name -> gnoi.os.ActivateError - 1, // 11: gnoi.os.ActivateError.type:type_name -> gnoi.os.ActivateError.Type - 18, // 12: gnoi.os.VerifyResponse.verify_standby:type_name -> gnoi.os.VerifyStandby - 19, // 13: gnoi.os.VerifyStandby.standby_state:type_name -> gnoi.os.StandbyState - 20, // 14: gnoi.os.VerifyStandby.verify_response:type_name -> gnoi.os.StandbyResponse - 2, // 15: gnoi.os.StandbyState.state:type_name -> gnoi.os.StandbyState.State - 3, // 16: gnoi.os.OS.Install:input_type -> gnoi.os.InstallRequest - 12, // 17: gnoi.os.OS.Activate:input_type -> gnoi.os.ActivateRequest - 16, // 18: gnoi.os.OS.Verify:input_type -> gnoi.os.VerifyRequest - 6, // 19: gnoi.os.OS.Install:output_type -> gnoi.os.InstallResponse - 13, // 20: gnoi.os.OS.Activate:output_type -> gnoi.os.ActivateResponse - 17, // 21: gnoi.os.OS.Verify:output_type -> gnoi.os.VerifyResponse - 19, // [19:22] is the sub-list for method output_type - 16, // [16:19] is the sub-list for method input_type - 16, // [16:16] is the sub-list for extension type_name - 16, // [16:16] is the sub-list for extension extendee - 0, // [0:16] is the sub-list for field type_name + 21, // 2: gnoi.os.InstallRequest.remote_download:type_name -> gnoi.common.RemoteDownload + 7, // 3: gnoi.os.InstallResponse.transfer_ready:type_name -> gnoi.os.TransferReady + 8, // 4: gnoi.os.InstallResponse.transfer_progress:type_name -> gnoi.os.TransferProgress + 9, // 5: gnoi.os.InstallResponse.sync_progress:type_name -> gnoi.os.SyncProgress + 10, // 6: gnoi.os.InstallResponse.validated:type_name -> gnoi.os.Validated + 11, // 7: gnoi.os.InstallResponse.install_error:type_name -> gnoi.os.InstallError + 0, // 8: gnoi.os.InstallError.type:type_name -> gnoi.os.InstallError.Type + 22, // 9: gnoi.os.ActivateRequest.boot_config:type_name -> gnoi.bootconfig.SetBootConfigRequest + 14, // 10: gnoi.os.ActivateResponse.activate_ok:type_name -> gnoi.os.ActivateOK + 15, // 11: gnoi.os.ActivateResponse.activate_error:type_name -> gnoi.os.ActivateError + 1, // 12: gnoi.os.ActivateError.type:type_name -> gnoi.os.ActivateError.Type + 18, // 13: gnoi.os.VerifyResponse.verify_standby:type_name -> gnoi.os.VerifyStandby + 19, // 14: gnoi.os.VerifyStandby.standby_state:type_name -> gnoi.os.StandbyState + 20, // 15: gnoi.os.VerifyStandby.verify_response:type_name -> gnoi.os.StandbyResponse + 2, // 16: gnoi.os.StandbyState.state:type_name -> gnoi.os.StandbyState.State + 3, // 17: gnoi.os.OS.Install:input_type -> gnoi.os.InstallRequest + 12, // 18: gnoi.os.OS.Activate:input_type -> gnoi.os.ActivateRequest + 16, // 19: gnoi.os.OS.Verify:input_type -> gnoi.os.VerifyRequest + 6, // 20: gnoi.os.OS.Install:output_type -> gnoi.os.InstallResponse + 13, // 21: gnoi.os.OS.Activate:output_type -> gnoi.os.ActivateResponse + 17, // 22: gnoi.os.OS.Verify:output_type -> gnoi.os.VerifyResponse + 20, // [20:23] is the sub-list for method output_type + 17, // [17:20] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_github_com_openconfig_gnoi_os_os_proto_init() } @@ -1444,6 +1464,7 @@ func file_github_com_openconfig_gnoi_os_os_proto_init() { (*InstallRequest_TransferRequest)(nil), (*InstallRequest_TransferContent)(nil), (*InstallRequest_TransferEnd)(nil), + (*InstallRequest_RemoteDownload)(nil), } file_github_com_openconfig_gnoi_os_os_proto_msgTypes[3].OneofWrappers = []any{ (*InstallResponse_TransferReady)(nil), diff --git a/os/os.proto b/os/os.proto index b92fcc59..41840866 100644 --- a/os/os.proto +++ b/os/os.proto @@ -19,6 +19,7 @@ package gnoi.os; import "github.com/openconfig/gnoi/bootconfig/bootconfig.proto"; import "github.com/openconfig/gnoi/types/types.proto"; +import "github.com/openconfig/gnoi/common/common.proto"; option go_package = "github.com/openconfig/gnoi/os"; @@ -136,6 +137,20 @@ service OS { // TransferEnd --> // <-- [Validated|InstallError] // + // Rather than transferring a file, clients may initiate a remote download + // of the OS package if the requested OS package is not present (on either + // supervisor). + // After the package is downloaded, the target will respond with Validated + // (or InstallError if the download failed). + // This scenario applies to both single and dual supervisor targets. + // + // Client :--------------|--------------> Target + // TransferRequest --> + // <-- [TransferReady|InstallError] + // remote_download --> + // ... + // <-- [Validated|InstallError] + // rpc Install(stream InstallRequest) returns (stream InstallResponse); // Activate sets the requested OS version as the version which is used at the @@ -161,6 +176,9 @@ message InstallRequest { TransferRequest transfer_request = 1; bytes transfer_content = 2; TransferEnd transfer_end = 3; + + // Details for the device to download the image from a remote location + common.RemoteDownload remote_download = 4; } } diff --git a/os/os_grpc.pb.go b/os/os_grpc.pb.go index ae944731..85fcd898 100644 --- a/os/os_grpc.pb.go +++ b/os/os_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v6.33.1 +// - protoc-gen-go-grpc v1.6.2 +// - protoc v7.35.1 // source: github.com/openconfig/gnoi/os/os.proto package os @@ -75,15 +75,16 @@ func (c *oSClient) Verify(ctx context.Context, in *VerifyRequest, opts ...grpc.C } // OSServer is the server API for OS service. -// All implementations should embed UnimplementedOSServer +// All implementations must embed UnimplementedOSServer // for forward compatibility. type OSServer interface { Install(grpc.BidiStreamingServer[InstallRequest, InstallResponse]) error Activate(context.Context, *ActivateRequest) (*ActivateResponse, error) Verify(context.Context, *VerifyRequest) (*VerifyResponse, error) + mustEmbedUnimplementedOSServer() } -// UnimplementedOSServer should be embedded to have +// UnimplementedOSServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil @@ -91,15 +92,16 @@ type OSServer interface { type UnimplementedOSServer struct{} func (UnimplementedOSServer) Install(grpc.BidiStreamingServer[InstallRequest, InstallResponse]) error { - return status.Errorf(codes.Unimplemented, "method Install not implemented") + return status.Error(codes.Unimplemented, "method Install not implemented") } func (UnimplementedOSServer) Activate(context.Context, *ActivateRequest) (*ActivateResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Activate not implemented") + return nil, status.Error(codes.Unimplemented, "method Activate not implemented") } func (UnimplementedOSServer) Verify(context.Context, *VerifyRequest) (*VerifyResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Verify not implemented") + return nil, status.Error(codes.Unimplemented, "method Verify not implemented") } -func (UnimplementedOSServer) testEmbeddedByValue() {} +func (UnimplementedOSServer) mustEmbedUnimplementedOSServer() {} +func (UnimplementedOSServer) testEmbeddedByValue() {} // UnsafeOSServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to OSServer will @@ -109,7 +111,7 @@ type UnsafeOSServer interface { } func RegisterOSServer(s grpc.ServiceRegistrar, srv OSServer) { - // If the following call pancis, it indicates UnimplementedOSServer was + // If the following call panics, it indicates UnimplementedOSServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. From 3ee417d395faf3af32a8bd0c482c7b0d5248b6b6 Mon Sep 17 00:00:00 2001 From: Brian Neville <29639579+brianneville@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:37:37 +0100 Subject: [PATCH 2/4] sort imports --- os/os.proto | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/os/os.proto b/os/os.proto index 41840866..2ceb948a 100644 --- a/os/os.proto +++ b/os/os.proto @@ -18,9 +18,8 @@ syntax = "proto3"; package gnoi.os; import "github.com/openconfig/gnoi/bootconfig/bootconfig.proto"; -import "github.com/openconfig/gnoi/types/types.proto"; import "github.com/openconfig/gnoi/common/common.proto"; - +import "github.com/openconfig/gnoi/types/types.proto"; option go_package = "github.com/openconfig/gnoi/os"; From 11f6f6187df899d8eeae482c60372a3895614ac0 Mon Sep 17 00:00:00 2001 From: Brian Neville <29639579+brianneville@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:55:32 +0100 Subject: [PATCH 3/4] remove os_grpc.pb.go changes --- os/os_grpc.pb.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/os/os_grpc.pb.go b/os/os_grpc.pb.go index 85fcd898..ae944731 100644 --- a/os/os_grpc.pb.go +++ b/os/os_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.6.2 -// - protoc v7.35.1 +// - protoc-gen-go-grpc v1.5.1 +// - protoc v6.33.1 // source: github.com/openconfig/gnoi/os/os.proto package os @@ -75,16 +75,15 @@ func (c *oSClient) Verify(ctx context.Context, in *VerifyRequest, opts ...grpc.C } // OSServer is the server API for OS service. -// All implementations must embed UnimplementedOSServer +// All implementations should embed UnimplementedOSServer // for forward compatibility. type OSServer interface { Install(grpc.BidiStreamingServer[InstallRequest, InstallResponse]) error Activate(context.Context, *ActivateRequest) (*ActivateResponse, error) Verify(context.Context, *VerifyRequest) (*VerifyResponse, error) - mustEmbedUnimplementedOSServer() } -// UnimplementedOSServer must be embedded to have +// UnimplementedOSServer should be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil @@ -92,16 +91,15 @@ type OSServer interface { type UnimplementedOSServer struct{} func (UnimplementedOSServer) Install(grpc.BidiStreamingServer[InstallRequest, InstallResponse]) error { - return status.Error(codes.Unimplemented, "method Install not implemented") + return status.Errorf(codes.Unimplemented, "method Install not implemented") } func (UnimplementedOSServer) Activate(context.Context, *ActivateRequest) (*ActivateResponse, error) { - return nil, status.Error(codes.Unimplemented, "method Activate not implemented") + return nil, status.Errorf(codes.Unimplemented, "method Activate not implemented") } func (UnimplementedOSServer) Verify(context.Context, *VerifyRequest) (*VerifyResponse, error) { - return nil, status.Error(codes.Unimplemented, "method Verify not implemented") + return nil, status.Errorf(codes.Unimplemented, "method Verify not implemented") } -func (UnimplementedOSServer) mustEmbedUnimplementedOSServer() {} -func (UnimplementedOSServer) testEmbeddedByValue() {} +func (UnimplementedOSServer) testEmbeddedByValue() {} // UnsafeOSServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to OSServer will @@ -111,7 +109,7 @@ type UnsafeOSServer interface { } func RegisterOSServer(s grpc.ServiceRegistrar, srv OSServer) { - // If the following call panics, it indicates UnimplementedOSServer was + // If the following call pancis, it indicates UnimplementedOSServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. From 20c4cc2b01e91a968043e2fcf2f19b3e3be4ec1a Mon Sep 17 00:00:00 2001 From: Brian Neville <29639579+brianneville@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:31:22 +0100 Subject: [PATCH 4/4] fix dependancy ordering for gazelle --- os/BUILD.bazel | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/os/BUILD.bazel b/os/BUILD.bazel index ed4a600a..18f27596 100644 --- a/os/BUILD.bazel +++ b/os/BUILD.bazel @@ -29,8 +29,8 @@ proto_library( import_prefix = "github.com/openconfig/gnoi", deps = [ "//bootconfig:bootconfig_proto", - "//types:types_proto", "//common:common_proto", + "//types:types_proto", ], ) @@ -56,8 +56,8 @@ go_proto_library( proto = ":os_proto", deps = [ "//bootconfig", - "//types", "//common", + "//types", ], )