debugd: implement upload of multiple binaries

This commit is contained in:
Malte Poll 2023-01-20 10:11:41 +01:00 committed by Malte Poll
parent e6ac8e2a91
commit 6f56ed69f8
21 changed files with 2040 additions and 661 deletions

View File

@ -14,7 +14,6 @@ import (
"os"
"sync"
"github.com/edgelesssys/constellation/v2/debugd/internal/bootstrapper"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/deploy"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/info"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/logcollector"
@ -22,6 +21,8 @@ import (
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/metadata/cloudprovider"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/metadata/fallback"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/server"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer/streamer"
awscloud "github.com/edgelesssys/constellation/v2/internal/cloud/aws"
azurecloud "github.com/edgelesssys/constellation/v2/internal/cloud/azure"
platform "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -45,7 +46,8 @@ func main() {
log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity))
fs := afero.NewOsFs()
streamer := bootstrapper.NewFileStreamer(fs)
streamer := streamer.New(fs)
filetransferer := filetransfer.New(log.Named("filetransfer"), streamer, filetransfer.DontShowProgress)
serviceManager := deploy.NewServiceManager(log.Named("serviceManager"))
ctx, cancel := context.WithCancel(context.Background())
@ -92,13 +94,10 @@ func main() {
logcollector.NewStartTrigger(ctx, wg, platform.FromString(csp), fetcher, log.Named("logcollector")),
)
download := deploy.New(log.Named("download"), &net.Dialer{}, serviceManager, streamer, infoMap)
download := deploy.New(log.Named("download"), &net.Dialer{}, serviceManager, filetransferer, infoMap)
sched := metadata.NewScheduler(log.Named("scheduler"), fetcher, download)
serv := server.New(log.Named("server"), serviceManager, streamer, infoMap)
if err := deploy.DefaultServiceUnit(ctx, serviceManager); err != nil {
log.With(zap.Error(err)).Fatalf("Failed to create default service unit")
}
serv := server.New(log.Named("server"), serviceManager, filetransferer, infoMap)
writeDebugBanner(log)

View File

@ -10,18 +10,19 @@ import (
"context"
"fmt"
"io"
"log"
"net"
"strconv"
"strings"
"github.com/edgelesssys/constellation/v2/debugd/internal/bootstrapper"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/logcollector"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer/streamer"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"google.golang.org/grpc"
@ -41,38 +42,52 @@ func newDeployCmd() *cobra.Command {
}
deployCmd.Flags().StringSlice("ips", nil, "override the ips that the bootstrapper will be uploaded to (defaults to ips from constellation config)")
deployCmd.Flags().String("bootstrapper", "./bootstrapper", "override the path to the bootstrapper binary uploaded to instances")
deployCmd.Flags().String("upgrade-agent", "./upgrade-agent", "override the path to the upgrade-agent binary uploaded to instances")
deployCmd.Flags().StringToString("info", nil, "additional info to be passed to the debugd, in the form --info key1=value1,key2=value2")
deployCmd.Flags().Int("verbosity", 0, logger.CmdLineVerbosityDescription)
return deployCmd
}
func runDeploy(cmd *cobra.Command, args []string) error {
verbosity, err := cmd.Flags().GetInt("verbosity")
if err != nil {
return err
}
log := logger.New(logger.PlainLog, logger.VerbosityFromInt(verbosity))
configName, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("parsing config path argument: %w", err)
}
fileHandler := file.NewHandler(afero.NewOsFs())
fs := afero.NewOsFs()
fileHandler := file.NewHandler(fs)
streamer := streamer.New(fs)
transfer := filetransfer.New(log, streamer, filetransfer.ShowProgress)
constellationConfig, err := config.FromFile(fileHandler, configName)
if err != nil {
return err
}
return deploy(cmd, fileHandler, constellationConfig, bootstrapper.NewFileStreamer(afero.NewOsFs()))
return deploy(cmd, fileHandler, constellationConfig, transfer, log)
}
func deploy(cmd *cobra.Command, fileHandler file.Handler, constellationConfig *config.Config, reader fileToStreamReader) error {
func deploy(cmd *cobra.Command, fileHandler file.Handler, constellationConfig *config.Config, transfer fileTransferer, log *logger.Logger) error {
bootstrapperPath, err := cmd.Flags().GetString("bootstrapper")
if err != nil {
return err
}
upgradeAgentPath, err := cmd.Flags().GetString("upgrade-agent")
if err != nil {
return err
}
if constellationConfig.IsReleaseImage() {
log.Println("WARNING: Constellation image does not look like a debug image. Are you using a debug image?")
log.Infof("WARNING: Constellation image does not look like a debug image. Are you using a debug image?")
}
if !constellationConfig.IsDebugCluster() {
log.Println("WARNING: The Constellation config has debugCluster set to false.")
log.Println("cdbg will likely not work unless you manually adjust the firewall / load balancing rules.")
log.Println("If you create the cluster with a debug image, you should also set debugCluster to true.")
log.Infof("WARNING: The Constellation config has debugCluster set to false.")
log.Infof("cdbg will likely not work unless you manually adjust the firewall / load balancing rules.")
log.Infof("If you create the cluster with a debug image, you should also set debugCluster to true.")
}
ips, err := cmd.Flags().GetStringSlice("ips")
@ -95,13 +110,28 @@ func deploy(cmd *cobra.Command, fileHandler file.Handler, constellationConfig *c
return err
}
for _, ip := range ips {
files := []filetransfer.FileStat{
{
SourcePath: bootstrapperPath,
TargetPath: debugd.BootstrapperDeployFilename,
Mode: debugd.BinaryAccessMode,
OverrideServiceUnit: "constellation-bootstrapper",
},
{
SourcePath: upgradeAgentPath,
TargetPath: debugd.UpgradeAgentDeployFilename,
Mode: debugd.BinaryAccessMode,
OverrideServiceUnit: "constellation-upgrade-agent",
},
}
for _, ip := range ips {
input := deployOnEndpointInput{
debugdEndpoint: ip,
infos: info,
bootstrapperPath: bootstrapperPath,
reader: reader,
files: files,
transfer: transfer,
log: log,
}
if err := deployOnEndpoint(cmd.Context(), input); err != nil {
return err
@ -113,14 +143,15 @@ func deploy(cmd *cobra.Command, fileHandler file.Handler, constellationConfig *c
type deployOnEndpointInput struct {
debugdEndpoint string
bootstrapperPath string
files []filetransfer.FileStat
infos map[string]string
reader fileToStreamReader
transfer fileTransferer
log *logger.Logger
}
// deployOnEndpoint deploys a custom built bootstrapper binary to a debugd endpoint.
func deployOnEndpoint(ctx context.Context, in deployOnEndpointInput) error {
log.Printf("Deploying on %v\n", in.debugdEndpoint)
in.log.Infof("Deploying on %v", in.debugdEndpoint)
client, closer, err := newDebugdClient(ctx, in.debugdEndpoint)
if err != nil {
@ -128,11 +159,11 @@ func deployOnEndpoint(ctx context.Context, in deployOnEndpointInput) error {
}
defer closer.Close()
if err := setInfo(ctx, client, in.infos); err != nil {
if err := setInfo(ctx, in.log, client, in.infos); err != nil {
return fmt.Errorf("sending info: %w", err)
}
if err := uploadBootstrapper(ctx, client, in); err != nil {
if err := uploadFiles(ctx, client, in); err != nil {
return fmt.Errorf("uploading bootstrapper: %w", err)
}
@ -152,8 +183,8 @@ func newDebugdClient(ctx context.Context, ip string) (pb.DebugdClient, io.Closer
return pb.NewDebugdClient(conn), conn, nil
}
func setInfo(ctx context.Context, client pb.DebugdClient, infos map[string]string) error {
log.Printf("Setting info with length %d", len(infos))
func setInfo(ctx context.Context, log *logger.Logger, client pb.DebugdClient, infos map[string]string) error {
log.Infof("Setting info with length %d", len(infos))
var infosPb []*pb.Info
for key, value := range infos {
@ -162,36 +193,52 @@ func setInfo(ctx context.Context, client pb.DebugdClient, infos map[string]strin
req := &pb.SetInfoRequest{Info: infosPb}
if _, err := client.SetInfo(ctx, req, grpc.WaitForReady(true)); err != nil {
status, err := client.SetInfo(ctx, req, grpc.WaitForReady(true))
if err != nil {
return fmt.Errorf("setting info: %w", err)
}
log.Println("Info set")
switch status.Status {
case pb.SetInfoStatus_SET_INFO_SUCCESS:
log.Infof("Info set")
case pb.SetInfoStatus_SET_INFO_ALREADY_SET:
log.Infof("Info already set")
default:
log.Warnf("Unknown status %v", status.Status)
}
return nil
}
func uploadBootstrapper(ctx context.Context, client pb.DebugdClient, in deployOnEndpointInput) error {
log.Println("Uploading bootstrapper")
func uploadFiles(ctx context.Context, client pb.DebugdClient, in deployOnEndpointInput) error {
in.log.Infof("Uploading files")
stream, err := client.UploadBootstrapper(ctx, grpc.WaitForReady(true))
stream, err := client.UploadFiles(ctx, grpc.WaitForReady(true))
if err != nil {
return fmt.Errorf("starting bootstrapper upload to instance %v: %w", in.debugdEndpoint, err)
}
streamErr := in.reader.ReadStream(in.bootstrapperPath, stream, debugd.Chunksize, true)
in.transfer.SetFiles(in.files)
if err := in.transfer.SendFiles(stream); err != nil {
return fmt.Errorf("sending files to %v: %w", in.debugdEndpoint, err)
}
uploadResponse, closeErr := stream.CloseAndRecv()
if closeErr != nil {
return fmt.Errorf("closing upload stream after uploading bootstrapper to %v: %w", in.debugdEndpoint, closeErr)
return fmt.Errorf("closing upload stream after uploading files to %v: %w", in.debugdEndpoint, closeErr)
}
if uploadResponse.Status == pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_FILE_EXISTS {
log.Println("Bootstrapper was already uploaded")
return nil
}
if uploadResponse.Status != pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_SUCCESS || streamErr != nil {
return fmt.Errorf("uploading bootstrapper to instance %v failed: %v / %w", in.debugdEndpoint, uploadResponse, streamErr)
switch uploadResponse.Status {
case pb.UploadFilesStatus_UPLOAD_FILES_SUCCESS:
in.log.Infof("Upload successful")
case pb.UploadFilesStatus_UPLOAD_FILES_ALREADY_FINISHED:
in.log.Infof("Files already uploaded")
case pb.UploadFilesStatus_UPLOAD_FILES_UPLOAD_FAILED:
return fmt.Errorf("uploading files to %v failed: %v", in.debugdEndpoint, uploadResponse)
case pb.UploadFilesStatus_UPLOAD_FILES_ALREADY_STARTED:
return fmt.Errorf("upload already started on %v", in.debugdEndpoint)
default:
return fmt.Errorf("unknown upload status %v", uploadResponse.Status)
}
log.Println("Uploaded bootstrapper")
return nil
}
@ -211,8 +258,9 @@ func checkInfoMap(info map[string]string) error {
return nil
}
type fileToStreamReader interface {
ReadStream(filename string, stream bootstrapper.WriteChunkStream, chunksize uint, showProgress bool) error
type fileTransferer interface {
SendFiles(stream filetransfer.SendFilesStream) error
SetFiles(files []filetransfer.FileStat)
}
type clusterIDsFile struct {

View File

@ -14,7 +14,9 @@ const (
GRPCTimeout = 5 * time.Minute
DiscoverDebugdInterval = 30 * time.Second
DownloadRetryBackoff = 1 * time.Minute
BinaryAccessMode = 0o755 // -rwxr-xr-x
BootstrapperDeployFilename = "/run/state/bin/bootstrapper"
UpgradeAgentDeployFilename = "/run/state/bin/upgrade-agent"
Chunksize = 1024
BootstrapperSystemdUnitName = "bootstrapper.service"
BootstrapperSystemdUnitContents = `[Unit]

View File

@ -8,13 +8,13 @@ package deploy
import (
"context"
"errors"
"fmt"
"io"
"net"
"strconv"
"github.com/edgelesssys/constellation/v2/debugd/internal/bootstrapper"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"
@ -27,19 +27,19 @@ import (
type Download struct {
log *logger.Logger
dialer NetDialer
writer streamToFileWriter
transfer fileTransferer
serviceManager serviceManager
info infoSetter
}
// New creates a new Download.
func New(log *logger.Logger, dialer NetDialer, serviceManager serviceManager,
writer streamToFileWriter, info infoSetter,
transfer fileTransferer, info infoSetter,
) *Download {
return &Download{
log: log,
dialer: dialer,
writer: writer,
transfer: transfer,
info: info,
serviceManager: serviceManager,
}
@ -47,6 +47,10 @@ func New(log *logger.Logger, dialer NetDialer, serviceManager serviceManager,
// DownloadInfo will try to download the info from another instance.
func (d *Download) DownloadInfo(ctx context.Context, ip string) error {
if d.info.Received() {
return nil
}
log := d.log.With(zap.String("ip", ip))
serverAddr := net.JoinHostPort(ip, strconv.Itoa(constants.DebugdPort))
@ -66,7 +70,7 @@ func (d *Download) DownloadInfo(ctx context.Context, ip string) error {
return d.info.SetProto(resp.Info)
}
// DownloadDeployment will open a new grpc connection to another instance, attempting to download a bootstrapper from that instance.
// DownloadDeployment will open a new grpc connection to another instance, attempting to download files from that instance.
func (d *Download) DownloadDeployment(ctx context.Context, ip string) error {
log := d.log.With(zap.String("ip", ip))
serverAddr := net.JoinHostPort(ip, strconv.Itoa(constants.DebugdPort))
@ -77,23 +81,38 @@ func (d *Download) DownloadDeployment(ctx context.Context, ip string) error {
}
defer closer.Close()
log.Infof("Trying to download bootstrapper")
stream, err := client.DownloadBootstrapper(ctx, &pb.DownloadBootstrapperRequest{})
log.Infof("Trying to download files")
stream, err := client.DownloadFiles(ctx, &pb.DownloadFilesRequest{})
if err != nil {
return fmt.Errorf("starting bootstrapper download from other instance: %w", err)
return fmt.Errorf("starting file download from other instance: %w", err)
}
if err := d.writer.WriteStream(debugd.BootstrapperDeployFilename, stream, true); err != nil {
return fmt.Errorf("streaming bootstrapper from other instance: %w", err)
}
log.Infof("Successfully downloaded bootstrapper")
// after the upload succeeds, try to restart the bootstrapper
restartAction := ServiceManagerRequest{
Unit: debugd.BootstrapperSystemdUnitName,
Action: Restart,
err = d.transfer.RecvFiles(stream)
switch {
case err == nil:
d.log.Infof("Downloading files succeeded")
case errors.Is(err, filetransfer.ErrReceiveRunning):
d.log.Warnf("Download already in progress")
return err
case errors.Is(err, filetransfer.ErrReceiveFinished):
d.log.Warnf("Download already finished")
return nil
default:
d.log.With(zap.Error(err)).Errorf("Downloading files failed")
return err
}
files := d.transfer.GetFiles()
for _, file := range files {
if file.OverrideServiceUnit == "" {
continue
}
if err := d.serviceManager.OverrideServiceUnitExecStart(
ctx, file.OverrideServiceUnit, file.TargetPath,
); err != nil {
// continue on error to allow other units to be overridden
d.log.With(zap.Error(err)).Errorf("Failed to override service unit %s", file.OverrideServiceUnit)
}
if err := d.serviceManager.SystemdAction(ctx, restartAction); err != nil {
return fmt.Errorf("restarting bootstrapper: %w", err)
}
return nil
@ -123,14 +142,17 @@ func (d *Download) grpcWithDialer() grpc.DialOption {
type infoSetter interface {
SetProto(infos []*pb.Info) error
Received() bool
}
type serviceManager interface {
SystemdAction(ctx context.Context, request ServiceManagerRequest) error
OverrideServiceUnitExecStart(ctx context.Context, unitName string, execStart string) error
}
type streamToFileWriter interface {
WriteStream(filename string, stream bootstrapper.ReadChunkStream, showProgress bool) error
type fileTransferer interface {
RecvFiles(stream filetransfer.RecvFilesStream) error
GetFiles() []filetransfer.FileStat
}
// NetDialer can open a net.Conn.

View File

@ -9,14 +9,11 @@ package deploy
import (
"context"
"errors"
"fmt"
"io"
"net"
"strconv"
"testing"
"github.com/edgelesssys/constellation/v2/debugd/internal/bootstrapper"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/grpc/testdialer"
@ -33,41 +30,72 @@ func TestMain(m *testing.M) {
)
}
func TestDownloadBootstrapper(t *testing.T) {
filename := "/run/state/bin/bootstrapper"
someErr := errors.New("failed")
func TestDownloadDeployment(t *testing.T) {
testCases := map[string]struct {
server fakeDownloadServer
serviceManager stubServiceManager
wantChunks [][]byte
wantDownloadErr bool
wantFile bool
wantSystemdAction bool
wantDeployed bool
files []filetransfer.FileStat
recvFilesErr error
overrideServiceUnitErr error
wantErr bool
wantOverrideCalls []struct{ UnitName, ExecStart string }
}{
"download works": {
server: fakeDownloadServer{
chunks: [][]byte{[]byte("test")},
files: []filetransfer.FileStat{
{
SourcePath: "source/testfileA",
TargetPath: "target/testfileA",
Mode: 0o644,
OverrideServiceUnit: "unitA",
},
wantChunks: [][]byte{[]byte("test")},
wantDownloadErr: false,
wantFile: true,
wantSystemdAction: true,
wantDeployed: true,
{
SourcePath: "source/testfileB",
TargetPath: "target/testfileB",
Mode: 0o644,
},
"download rpc call error is detected": {
server: fakeDownloadServer{downladErr: someErr},
wantDownloadErr: true,
},
"service restart error is detected": {
server: fakeDownloadServer{chunks: [][]byte{[]byte("test")}},
serviceManager: stubServiceManager{systemdActionErr: someErr},
wantChunks: [][]byte{[]byte("test")},
wantDownloadErr: true,
wantFile: true,
wantDeployed: true,
wantSystemdAction: false,
wantOverrideCalls: []struct{ UnitName, ExecStart string }{
{"unitA", "target/testfileA"},
},
},
"recv files error is detected": {
recvFilesErr: errors.New("some error"),
wantErr: true,
},
"recv already running": {
recvFilesErr: filetransfer.ErrReceiveRunning,
wantErr: true,
},
"recv already finished": {
files: []filetransfer.FileStat{
{
SourcePath: "source/testfileA",
TargetPath: "target/testfileA",
Mode: 0o644,
OverrideServiceUnit: "unitA",
},
},
recvFilesErr: filetransfer.ErrReceiveFinished,
wantErr: false,
},
"service unit fail does not stop further tries": {
files: []filetransfer.FileStat{
{
SourcePath: "source/testfileA",
TargetPath: "target/testfileA",
Mode: 0o644,
OverrideServiceUnit: "unitA",
},
{
SourcePath: "source/testfileB",
TargetPath: "target/testfileB",
Mode: 0o644,
OverrideServiceUnit: "unitB",
},
},
overrideServiceUnitErr: errors.New("some error"),
wantOverrideCalls: []struct{ UnitName, ExecStart string }{
{"unitA", "target/testfileA"},
{"unitB", "target/testfileB"},
},
},
}
@ -76,11 +104,13 @@ func TestDownloadBootstrapper(t *testing.T) {
assert := assert.New(t)
ip := "192.0.2.0"
writer := &fakeStreamToFileWriter{}
transfer := &stubTransfer{recvFilesErr: tc.recvFilesErr, files: tc.files}
serviceMgr := &stubServiceManager{overrideServiceUnitExecStartErr: tc.overrideServiceUnitErr}
dialer := testdialer.NewBufconnDialer()
server := &stubDownloadServer{}
grpcServ := grpc.NewServer()
pb.RegisterDebugdServer(grpcServ, &tc.server)
pb.RegisterDebugdServer(grpcServ, server)
lis := dialer.GetListener(net.JoinHostPort(ip, strconv.Itoa(constants.DebugdPort)))
go grpcServ.Serve(lis)
defer grpcServ.GracefulStop()
@ -88,30 +118,19 @@ func TestDownloadBootstrapper(t *testing.T) {
download := &Download{
log: logger.NewTest(t),
dialer: dialer,
writer: writer,
serviceManager: &tc.serviceManager,
transfer: transfer,
serviceManager: serviceMgr,
}
err := download.DownloadDeployment(context.Background(), ip)
if tc.wantDownloadErr {
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
if tc.wantFile {
assert.Equal(tc.wantChunks, writer.chunks)
assert.Equal(filename, writer.filename)
}
if tc.wantSystemdAction {
assert.ElementsMatch(
[]ServiceManagerRequest{
{Unit: debugd.BootstrapperSystemdUnitName, Action: Restart},
},
tc.serviceManager.requests,
)
}
assert.Equal(tc.wantOverrideCalls, serviceMgr.overrideCalls)
})
}
}
@ -189,6 +208,9 @@ func TestDownloadInfo(t *testing.T) {
type stubServiceManager struct {
requests []ServiceManagerRequest
systemdActionErr error
overrideCalls []struct{ UnitName, ExecStart string }
overrideServiceUnitExecStartErr error
}
func (s *stubServiceManager) SystemdAction(ctx context.Context, request ServiceManagerRequest) error {
@ -196,39 +218,34 @@ func (s *stubServiceManager) SystemdAction(ctx context.Context, request ServiceM
return s.systemdActionErr
}
type fakeStreamToFileWriter struct {
chunks [][]byte
filename string
func (s *stubServiceManager) OverrideServiceUnitExecStart(ctx context.Context, unitName string, execStart string) error {
s.overrideCalls = append(s.overrideCalls, struct {
UnitName, ExecStart string
}{UnitName: unitName, ExecStart: execStart})
return s.overrideServiceUnitExecStartErr
}
func (f *fakeStreamToFileWriter) WriteStream(filename string, stream bootstrapper.ReadChunkStream, showProgress bool) error {
f.filename = filename
for {
chunk, err := stream.Recv()
if err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return fmt.Errorf("reading stream: %w", err)
}
f.chunks = append(f.chunks, chunk.Content)
}
type stubTransfer struct {
recvFilesErr error
files []filetransfer.FileStat
}
// fakeDownloadServer implements DebugdServer; only fakes DownloadBootstrapper, panics on every other rpc.
type fakeDownloadServer struct {
chunks [][]byte
func (t *stubTransfer) RecvFiles(_ filetransfer.RecvFilesStream) error {
return t.recvFilesErr
}
func (t *stubTransfer) GetFiles() []filetransfer.FileStat {
return t.files
}
// stubDownloadServer implements DebugdServer; only stubs DownloadFiles, panics on every other rpc.
type stubDownloadServer struct {
downladErr error
pb.UnimplementedDebugdServer
}
func (s *fakeDownloadServer) DownloadBootstrapper(request *pb.DownloadBootstrapperRequest, stream pb.Debugd_DownloadBootstrapperServer) error {
for _, chunk := range s.chunks {
if err := stream.Send(&pb.Chunk{Content: chunk}); err != nil {
return fmt.Errorf("sending chunk: %w", err)
}
}
func (s *stubDownloadServer) DownloadFiles(request *pb.DownloadFilesRequest, stream pb.Debugd_DownloadFilesServer) error {
return s.downladErr
}
@ -244,6 +261,7 @@ func (s *stubDebugdServer) GetInfo(ctx context.Context, request *pb.GetInfoReque
type stubInfoSetter struct {
info []*pb.Info
received bool
setProtoErr error
}
@ -251,3 +269,7 @@ func (s *stubInfoSetter) SetProto(infos []*pb.Info) error {
s.info = infos
return s.setProtoErr
}
func (s *stubInfoSetter) Received() bool {
return s.received
}

View File

@ -9,9 +9,12 @@ package deploy
import (
"context"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/afero"
"go.uber.org/zap"
@ -21,6 +24,10 @@ const (
systemdUnitFolder = "/run/systemd/system"
)
// systemdUnitNameRegexp is a regular expression that matches valid systemd unit names.
// This is only the unit name, without the .service suffix.
var systemdUnitNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9@._\-\\]+$`)
// SystemdAction encodes the available actions.
//
//go:generate stringer -type=SystemdAction
@ -73,7 +80,7 @@ func NewServiceManager(log *logger.Logger) *ServiceManager {
type dbusClient interface {
// NewSystemConnectionContext establishes a connection to the system bus and authenticates.
// Callers should call Close() when done with the connection.
NewSystemdConnectionContext(ctx context.Context) (dbusConn, error)
NewSystemConnectionContext(ctx context.Context) (dbusConn, error)
}
type dbusConn interface {
@ -89,15 +96,18 @@ type dbusConn interface {
// ReloadContext instructs systemd to scan for and reload unit files. This is
// an equivalent to systemctl daemon-reload.
ReloadContext(ctx context.Context) error
// Close closes the connection.
Close()
}
// SystemdAction will perform a systemd action on a service unit (start, stop, restart, reload).
func (s *ServiceManager) SystemdAction(ctx context.Context, request ServiceManagerRequest) error {
log := s.log.With(zap.String("unit", request.Unit), zap.String("action", request.Action.String()))
conn, err := s.dbus.NewSystemdConnectionContext(ctx)
conn, err := s.dbus.NewSystemConnectionContext(ctx)
if err != nil {
return fmt.Errorf("establishing systemd connection: %w", err)
}
defer conn.Close()
resultChan := make(chan string, 1)
switch request.Action {
@ -149,28 +159,41 @@ func (s *ServiceManager) WriteSystemdUnitFile(ctx context.Context, unit SystemdU
}
log.Infof("Wrote systemd unit file and performed daemon-reload")
return nil
}
// DefaultServiceUnit will write the default "bootstrapper.service" unit file.
func DefaultServiceUnit(ctx context.Context, serviceManager *ServiceManager) error {
if err := serviceManager.WriteSystemdUnitFile(ctx, SystemdUnit{
Name: debugd.BootstrapperSystemdUnitName,
Contents: debugd.BootstrapperSystemdUnitContents,
}); err != nil {
return fmt.Errorf("writing systemd unit file %q: %w", debugd.BootstrapperSystemdUnitName, err)
}
// try to start the default service if the binary exists but ignore failure.
// this is meant to start the bootstrapper after a reboot
// if a bootstrapper binary was uploaded before.
if ok, err := afero.Exists(serviceManager.fs, debugd.BootstrapperDeployFilename); ok && err == nil {
_ = serviceManager.SystemdAction(ctx, ServiceManagerRequest{
Unit: debugd.BootstrapperSystemdUnitName,
Action: Start,
})
// OverrideServiceUnitExecStart will override the ExecStart of a systemd unit.
func (s *ServiceManager) OverrideServiceUnitExecStart(ctx context.Context, unitName, execStart string) error {
log := s.log.With(zap.String("unitFile", fmt.Sprintf("%s/%s", systemdUnitFolder, unitName)))
log.Infof("Overriding systemd unit file execStart")
if !systemdUnitNameRegexp.MatchString(unitName) {
return fmt.Errorf("unit name %q is invalid", unitName)
}
// validate execStart (no newlines)
if strings.Contains(execStart, "\n") || strings.Contains(execStart, "\r") {
return fmt.Errorf("execStart must not contain newlines")
}
overrideUnitContents := fmt.Sprintf("[Service]\nExecStart=\nExecStart=%s\n", execStart)
s.systemdUnitFilewriteLock.Lock()
defer s.systemdUnitFilewriteLock.Unlock()
path := filepath.Join(systemdUnitFolder, unitName+".service.d", "override.conf")
if err := s.fs.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return fmt.Errorf("creating systemd unit file override directory %q: %w", filepath.Dir(path), err)
}
if err := afero.WriteFile(s.fs, path, []byte(overrideUnitContents), 0o644); err != nil {
return fmt.Errorf("writing systemd unit override file %q: %w", unitName, err)
}
if err := s.SystemdAction(ctx, ServiceManagerRequest{Unit: unitName, Action: Reload}); err != nil {
// do not return early here
// the "daemon-reload" command may return an unrelated error
// and there is no way to know if the override was successful
log.Warnf("Failed to perform systemd daemon-reload: %v", err)
}
if err := s.SystemdAction(ctx, ServiceManagerRequest{Unit: unitName + ".service", Action: Restart}); err != nil {
log.Warnf("Failed to perform unit restart: %v", err)
return fmt.Errorf("performing systemd unit restart: %w", err)
}
log.Infof("Overrode systemd unit file execStart, performed daemon-reload and restarted unit %v", unitName)
return nil
}

View File

@ -158,7 +158,7 @@ func TestWriteSystemdUnitFile(t *testing.T) {
"systemd reload fails": {
dbus: stubDbus{
conn: &fakeDbusConn{
actionErr: errors.New("reload error"),
reloadErr: errors.New("reload error"),
},
},
unit: SystemdUnit{
@ -200,12 +200,127 @@ func TestWriteSystemdUnitFile(t *testing.T) {
}
}
func TestOverrideServiceUnitExecStart(t *testing.T) {
testCases := map[string]struct {
dbus stubDbus
unitName, execStart string
readonly bool
wantErr bool
wantFileContents string
wantActionCalls []dbusConnActionInput
wantReloads int
}{
"override works": {
dbus: stubDbus{
conn: &fakeDbusConn{
result: "done",
},
},
unitName: "test",
execStart: "/run/state/bin/test",
wantFileContents: "[Service]\nExecStart=\nExecStart=/run/state/bin/test\n",
wantActionCalls: []dbusConnActionInput{
{name: "test.service", mode: "replace"},
},
wantReloads: 1,
},
"unit name invalid": {
dbus: stubDbus{
conn: &fakeDbusConn{
result: "done",
},
},
unitName: "invalid name",
execStart: "/run/state/bin/test",
wantErr: true,
},
"exec start invalid": {
dbus: stubDbus{
conn: &fakeDbusConn{
result: "done",
},
},
unitName: "test",
execStart: "/run/state/bin/\r\ntest",
wantErr: true,
},
"write fails": {
dbus: stubDbus{
conn: &fakeDbusConn{
result: "done",
},
},
unitName: "test",
execStart: "/run/state/bin/test",
readonly: true,
wantErr: true,
},
"reload fails but restart is still attempted": {
dbus: stubDbus{
conn: &fakeDbusConn{
result: "done",
reloadErr: errors.New("reload error"),
},
},
unitName: "test",
execStart: "/run/state/bin/test",
wantFileContents: "[Service]\nExecStart=\nExecStart=/run/state/bin/test\n",
wantActionCalls: []dbusConnActionInput{
{name: "test.service", mode: "replace"},
},
wantReloads: 1,
},
"restart fails": {
dbus: stubDbus{
conn: &fakeDbusConn{
result: "done",
actionErr: errors.New("action error"),
},
},
unitName: "test",
execStart: "/run/state/bin/test",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
fs := afero.NewMemMapFs()
assert.NoError(fs.MkdirAll(systemdUnitFolder, 0o755))
if tc.readonly {
fs = afero.NewReadOnlyFs(fs)
}
manager := ServiceManager{
log: logger.NewTest(t),
dbus: &tc.dbus,
fs: fs,
systemdUnitFilewriteLock: sync.Mutex{},
}
err := manager.OverrideServiceUnitExecStart(context.Background(), tc.unitName, tc.execStart)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
fileContents, err := afero.ReadFile(fs, "/run/systemd/system/test.service.d/override.conf")
assert.NoError(err)
assert.Equal(tc.wantFileContents, string(fileContents))
assert.Equal(tc.wantActionCalls, tc.dbus.conn.(*fakeDbusConn).inputs)
assert.Equal(tc.wantReloads, tc.dbus.conn.(*fakeDbusConn).reloadCalls)
})
}
}
type stubDbus struct {
conn dbusConn
connErr error
}
func (s *stubDbus) NewSystemdConnectionContext(ctx context.Context) (dbusConn, error) {
func (s *stubDbus) NewSystemConnectionContext(ctx context.Context) (dbusConn, error) {
return s.conn, s.connErr
}
@ -217,9 +332,11 @@ type dbusConnActionInput struct {
type fakeDbusConn struct {
inputs []dbusConnActionInput
result string
reloadCalls int
jobID int
actionErr error
reloadErr error
}
func (c *fakeDbusConn) StartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) {
@ -244,5 +361,9 @@ func (c *fakeDbusConn) RestartUnitContext(ctx context.Context, name string, mode
}
func (c *fakeDbusConn) ReloadContext(ctx context.Context) error {
return c.actionErr
c.reloadCalls++
return c.reloadErr
}
func (c *fakeDbusConn) Close() {}

View File

@ -15,8 +15,8 @@ import (
// wraps go-systemd dbus.
type dbusWrapper struct{}
func (d *dbusWrapper) NewSystemdConnectionContext(ctx context.Context) (dbusConn, error) {
conn, err := dbus.NewSystemdConnectionContext(ctx)
func (d *dbusWrapper) NewSystemConnectionContext(ctx context.Context) (dbusConn, error) {
conn, err := dbus.NewSystemConnectionContext(ctx)
if err != nil {
return nil, err
}
@ -44,3 +44,7 @@ func (c *dbusConnWrapper) RestartUnitContext(ctx context.Context, name string, m
func (c *dbusConnWrapper) ReloadContext(ctx context.Context) error {
return c.conn.ReloadContext(ctx)
}
func (c *dbusConnWrapper) Close() {
c.conn.Close()
}

View File

@ -29,6 +29,13 @@ func NewMap() *Map {
}
}
// Received returns true if the info map has been set.
func (i *Map) Received() bool {
i.mux.RLock()
defer i.mux.RUnlock()
return i.received
}
// Get returns the value of the info with the given key.
func (i *Map) Get(key string) (string, bool, error) {
i.mux.RLock()
@ -67,7 +74,7 @@ func (i *Map) SetProto(infos []*servicepb.Info) error {
defer i.mux.Unlock()
if i.received {
return errors.New("info already set")
return ErrInfoAlreadySet
}
infoMap := make(map[string]string)
@ -114,3 +121,6 @@ func (i *Map) GetProto() ([]*servicepb.Info, error) {
}
return infos, nil
}
// ErrInfoAlreadySet is returned if the info map has already been set.
var ErrInfoAlreadySet = errors.New("info already set")

View File

@ -284,20 +284,28 @@ func TestConcurrency(t *testing.T) {
_, _ = i.GetProto()
}
go get()
go get()
go get()
go get()
go getCopy()
go getCopy()
go getCopy()
go getCopy()
go setProto()
go setProto()
go setProto()
go setProto()
go getProto()
go getProto()
go getProto()
go getProto()
received := func() {
_ = i.Received()
}
go get()
go get()
go get()
go get()
go getCopy()
go getCopy()
go getCopy()
go getCopy()
go setProto()
go setProto()
go setProto()
go setProto()
go getProto()
go getProto()
go getProto()
go getProto()
go received()
go received()
go received()
go received()
}

View File

@ -9,20 +9,18 @@ package server
import (
"context"
"errors"
"fmt"
"io/fs"
"net"
"strconv"
"sync"
"time"
"github.com/edgelesssys/constellation/v2/debugd/internal/bootstrapper"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/deploy"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/info"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"
"go.uber.org/multierr"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
@ -31,18 +29,18 @@ import (
type debugdServer struct {
log *logger.Logger
serviceManager serviceManager
streamer streamer
transfer fileTransferer
info *info.Map
pb.UnimplementedDebugdServer
}
// New creates a new debugdServer according to the gRPC spec.
func New(log *logger.Logger, serviceManager serviceManager, streamer streamer, infos *info.Map) pb.DebugdServer {
func New(log *logger.Logger, serviceManager serviceManager, transfer fileTransferer, infos *info.Map) pb.DebugdServer {
return &debugdServer{
log: log,
serviceManager: serviceManager,
streamer: streamer,
transfer: transfer,
info: infos,
}
}
@ -55,13 +53,23 @@ func (s *debugdServer) SetInfo(ctx context.Context, req *pb.SetInfoRequest) (*pb
s.log.Infof("Info is empty")
}
if err := s.info.SetProto(req.Info); err != nil {
s.log.With(zap.Error(err)).Errorf("Setting info failed")
return &pb.SetInfoResponse{}, err
setProtoErr := s.info.SetProto(req.Info)
if errors.Is(setProtoErr, info.ErrInfoAlreadySet) {
s.log.Warnf("Setting info failed (already set)")
return &pb.SetInfoResponse{
Status: pb.SetInfoStatus_SET_INFO_ALREADY_SET,
}, nil
}
if setProtoErr != nil {
s.log.With(zap.Error(setProtoErr)).Errorf("Setting info failed")
return nil, setProtoErr
}
s.log.Infof("Info set")
return &pb.SetInfoResponse{}, nil
return &pb.SetInfoResponse{
Status: pb.SetInfoStatus_SET_INFO_SUCCESS,
}, nil
}
// GetInfo returns the info of the debugd instance.
@ -76,46 +84,66 @@ func (s *debugdServer) GetInfo(ctx context.Context, req *pb.GetInfoRequest) (*pb
return &pb.GetInfoResponse{Info: info}, nil
}
// UploadBootstrapper receives a bootstrapper binary in a stream of chunks and writes to a file.
func (s *debugdServer) UploadBootstrapper(stream pb.Debugd_UploadBootstrapperServer) error {
startAction := deploy.ServiceManagerRequest{
Unit: debugd.BootstrapperSystemdUnitName,
Action: deploy.Start,
}
var responseStatus pb.UploadBootstrapperStatus
defer func() {
if err := s.serviceManager.SystemdAction(stream.Context(), startAction); err != nil {
s.log.With(zap.Error(err)).Errorf("Starting uploaded bootstrapper failed")
if responseStatus == pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_SUCCESS {
responseStatus = pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_START_FAILED
}
}
stream.SendAndClose(&pb.UploadBootstrapperResponse{
Status: responseStatus,
// UploadFiles receives a stream of files (each consisting of a header and a stream of chunks) and writes them to the filesystem.
func (s *debugdServer) UploadFiles(stream pb.Debugd_UploadFilesServer) error {
s.log.Infof("Received UploadFiles request")
err := s.transfer.RecvFiles(stream)
switch {
case err == nil:
s.log.Infof("Uploading files succeeded")
case errors.Is(err, filetransfer.ErrReceiveRunning):
s.log.Warnf("Upload already in progress")
return stream.SendAndClose(&pb.UploadFilesResponse{
Status: pb.UploadFilesStatus_UPLOAD_FILES_ALREADY_STARTED,
})
case errors.Is(err, filetransfer.ErrReceiveFinished):
s.log.Warnf("Upload already finished")
return stream.SendAndClose(&pb.UploadFilesResponse{
Status: pb.UploadFilesStatus_UPLOAD_FILES_ALREADY_FINISHED,
})
default:
s.log.With(zap.Error(err)).Errorf("Uploading files failed")
return stream.SendAndClose(&pb.UploadFilesResponse{
Status: pb.UploadFilesStatus_UPLOAD_FILES_UPLOAD_FAILED,
})
}()
s.log.Infof("Starting bootstrapper upload")
if err := s.streamer.WriteStream(debugd.BootstrapperDeployFilename, stream, true); err != nil {
if errors.Is(err, fs.ErrExist) {
// bootstrapper was already uploaded
s.log.Warnf("Bootstrapper already uploaded")
responseStatus = pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_FILE_EXISTS
return nil
}
s.log.With(zap.Error(err)).Errorf("Uploading bootstrapper failed")
responseStatus = pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_UPLOAD_FAILED
return fmt.Errorf("uploading bootstrapper: %w", err)
}
s.log.Infof("Successfully uploaded bootstrapper")
responseStatus = pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_SUCCESS
return nil
files := s.transfer.GetFiles()
var overrideUnitErr error
for _, file := range files {
if file.OverrideServiceUnit == "" {
continue
}
// continue on error to allow other units to be overridden
// TODO: switch to native go multierror once 1.20 is released
// err = s.serviceManager.OverrideServiceUnitExecStart(stream.Context(), file.OverrideServiceUnit, file.TargetPath)
// if err != nil {
// overrideUnitErr = errors.Join(overrideUnitErr, err)
// }
err = s.serviceManager.OverrideServiceUnitExecStart(stream.Context(), file.OverrideServiceUnit, file.TargetPath)
if err != nil {
overrideUnitErr = multierr.Append(overrideUnitErr, err)
}
}
// DownloadBootstrapper streams the local bootstrapper binary to other instances.
func (s *debugdServer) DownloadBootstrapper(request *pb.DownloadBootstrapperRequest, stream pb.Debugd_DownloadBootstrapperServer) error {
s.log.Infof("Sending bootstrapper to other instance")
return s.streamer.ReadStream(debugd.BootstrapperDeployFilename, stream, debugd.Chunksize, true)
if overrideUnitErr != nil {
s.log.With(zap.Error(overrideUnitErr)).Errorf("Overriding service units failed")
return stream.SendAndClose(&pb.UploadFilesResponse{
Status: pb.UploadFilesStatus_UPLOAD_FILES_START_FAILED,
})
}
return stream.SendAndClose(&pb.UploadFilesResponse{
Status: pb.UploadFilesStatus_UPLOAD_FILES_SUCCESS,
})
}
// DownloadFiles streams the previously received files to other instances.
func (s *debugdServer) DownloadFiles(request *pb.DownloadFilesRequest, stream pb.Debugd_DownloadFilesServer) error {
s.log.Infof("Sending files to other instance")
if !s.transfer.CanSend() {
return errors.New("cannot send files at this time")
}
return s.transfer.SendFiles(stream)
}
// UploadSystemServiceUnits receives systemd service units, writes them to a service file and schedules a daemon-reload.
@ -157,9 +185,12 @@ func Start(log *logger.Logger, wg *sync.WaitGroup, serv pb.DebugdServer) {
type serviceManager interface {
SystemdAction(ctx context.Context, request deploy.ServiceManagerRequest) error
WriteSystemdUnitFile(ctx context.Context, unit deploy.SystemdUnit) error
OverrideServiceUnitExecStart(ctx context.Context, unitName string, execStart string) error
}
type streamer interface {
WriteStream(filename string, stream bootstrapper.ReadChunkStream, showProgress bool) error
ReadStream(filename string, stream bootstrapper.WriteChunkStream, chunksize uint, showProgress bool) error
type fileTransferer interface {
RecvFiles(stream filetransfer.RecvFilesStream) error
SendFiles(stream filetransfer.SendFilesStream) error
GetFiles() []filetransfer.FileStat
CanSend() bool
}

View File

@ -9,15 +9,14 @@ package server
import (
"context"
"errors"
"fmt"
"io"
"net"
"strconv"
"testing"
"github.com/edgelesssys/constellation/v2/debugd/internal/bootstrapper"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/deploy"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/info"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/grpc/testdialer"
@ -40,21 +39,23 @@ func TestSetInfo(t *testing.T) {
info *info.Map
infoReceived bool
setInfo []*pb.Info
wantErr bool
wantStatus pb.SetInfoStatus
}{
"set info works": {
setInfo: []*pb.Info{{Key: "foo", Value: "bar"}},
info: info.NewMap(),
wantStatus: pb.SetInfoStatus_SET_INFO_SUCCESS,
},
"set empty info works": {
setInfo: []*pb.Info{},
info: info.NewMap(),
wantStatus: pb.SetInfoStatus_SET_INFO_SUCCESS,
},
"set fails when info already set": {
info: info.NewMap(),
infoReceived: true,
setInfo: []*pb.Info{{Key: "foo", Value: "bar"}},
wantErr: true,
wantStatus: pb.SetInfoStatus_SET_INFO_ALREADY_SET,
},
}
@ -78,20 +79,17 @@ func TestSetInfo(t *testing.T) {
defer conn.Close()
client := pb.NewDebugdClient(conn)
_, err = client.SetInfo(context.Background(), &pb.SetInfoRequest{Info: tc.setInfo})
setInfoStatus, err := client.SetInfo(context.Background(), &pb.SetInfoRequest{Info: tc.setInfo})
grpcServ.GracefulStop()
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.wantStatus, setInfoStatus.Status)
for i := range tc.setInfo {
value, ok, err := tc.info.Get(tc.setInfo[i].Key)
assert.NoError(err)
assert.True(ok)
assert.Equal(tc.setInfo[i].Value, value)
}
}
})
}
}
@ -152,35 +150,36 @@ func TestGetInfo(t *testing.T) {
}
}
func TestUploadBootstrapper(t *testing.T) {
func TestUploadFiles(t *testing.T) {
endpoint := "192.0.2.1:" + strconv.Itoa(constants.DebugdPort)
testCases := map[string]struct {
serviceManager stubServiceManager
streamer fakeStreamer
uploadChunks [][]byte
wantErr bool
wantResponseStatus pb.UploadBootstrapperStatus
wantFile bool
wantChunks [][]byte
files []filetransfer.FileStat
recvFilesErr error
wantResponseStatus pb.UploadFilesStatus
wantOverrideCalls []struct{ UnitName, ExecStart string }
}{
"upload works": {
uploadChunks: [][]byte{[]byte("test")},
wantFile: true,
wantChunks: [][]byte{[]byte("test")},
wantResponseStatus: pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_SUCCESS,
files: []filetransfer.FileStat{
{SourcePath: "source/testA", TargetPath: "target/testA", Mode: 0o644, OverrideServiceUnit: "testA"},
{SourcePath: "source/testB", TargetPath: "target/testB", Mode: 0o644},
},
wantOverrideCalls: []struct{ UnitName, ExecStart string }{
{"testA", "target/testA"},
},
wantResponseStatus: pb.UploadFilesStatus_UPLOAD_FILES_SUCCESS,
},
"recv fails": {
streamer: fakeStreamer{writeStreamErr: errors.New("recv error")},
wantResponseStatus: pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_UPLOAD_FAILED,
wantErr: true,
recvFilesErr: errors.New("recv error"),
wantResponseStatus: pb.UploadFilesStatus_UPLOAD_FILES_UPLOAD_FAILED,
},
"starting bootstrapper fails": {
uploadChunks: [][]byte{[]byte("test")},
serviceManager: stubServiceManager{systemdActionErr: errors.New("starting bootstrapper error")},
wantFile: true,
wantChunks: [][]byte{[]byte("test")},
wantResponseStatus: pb.UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_START_FAILED,
"upload in progress": {
recvFilesErr: filetransfer.ErrReceiveRunning,
wantResponseStatus: pb.UploadFilesStatus_UPLOAD_FILES_ALREADY_STARTED,
},
"upload already finished": {
recvFilesErr: filetransfer.ErrReceiveFinished,
wantResponseStatus: pb.UploadFilesStatus_UPLOAD_FILES_ALREADY_FINISHED,
},
}
@ -189,60 +188,49 @@ func TestUploadBootstrapper(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
serviceMgr := &stubServiceManager{}
transfer := &stubTransfer{files: tc.files, recvFilesErr: tc.recvFilesErr}
serv := debugdServer{
log: logger.NewTest(t),
serviceManager: &tc.serviceManager,
streamer: &tc.streamer,
serviceManager: serviceMgr,
transfer: transfer,
}
grpcServ, conn, err := setupServerWithConn(endpoint, &serv)
require.NoError(err)
defer conn.Close()
client := pb.NewDebugdClient(conn)
stream, err := client.UploadBootstrapper(context.Background())
stream, err := client.UploadFiles(context.Background())
require.NoError(err)
require.NoError(fakeWrite(stream, tc.uploadChunks))
resp, err := stream.CloseAndRecv()
grpcServ.GracefulStop()
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantResponseStatus, resp.Status)
if tc.wantFile {
assert.Equal(tc.wantChunks, tc.streamer.writeStreamChunks)
assert.Equal("/run/state/bin/bootstrapper", tc.streamer.writeStreamFilename)
} else {
assert.Empty(tc.streamer.writeStreamChunks)
assert.Empty(tc.streamer.writeStreamFilename)
}
assert.Equal(tc.wantOverrideCalls, serviceMgr.overrideCalls)
})
}
}
func TestDownloadBootstrapper(t *testing.T) {
func TestDownloadFiles(t *testing.T) {
endpoint := "192.0.2.1:" + strconv.Itoa(constants.DebugdPort)
testCases := map[string]struct {
serviceManager stubServiceManager
request *pb.DownloadBootstrapperRequest
streamer fakeStreamer
wantErr bool
wantChunks [][]byte
request *pb.DownloadFilesRequest
canSend bool
wantRecvErr bool
wantSendFileCalls int
}{
"download works": {
request: &pb.DownloadBootstrapperRequest{},
streamer: fakeStreamer{readStreamChunks: [][]byte{[]byte("test")}},
wantErr: false,
wantChunks: [][]byte{[]byte("test")},
request: &pb.DownloadFilesRequest{},
canSend: true,
wantSendFileCalls: 1,
},
"download fails": {
request: &pb.DownloadBootstrapperRequest{},
streamer: fakeStreamer{readStreamErr: errors.New("read bootstrapper fails")},
wantErr: true,
"transfer is not ready for sending": {
request: &pb.DownloadFilesRequest{},
wantRecvErr: true,
},
}
@ -251,28 +239,29 @@ func TestDownloadBootstrapper(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
transfer := &stubTransfer{canSend: tc.canSend}
serv := debugdServer{
log: logger.NewTest(t),
serviceManager: &tc.serviceManager,
streamer: &tc.streamer,
transfer: transfer,
}
grpcServ, conn, err := setupServerWithConn(endpoint, &serv)
require.NoError(err)
defer conn.Close()
client := pb.NewDebugdClient(conn)
stream, err := client.DownloadBootstrapper(context.Background(), tc.request)
stream, err := client.DownloadFiles(context.Background(), tc.request)
require.NoError(err)
chunks, err := fakeRead(stream)
grpcServ.GracefulStop()
if tc.wantErr {
assert.Error(err)
return
_, recvErr := stream.Recv()
if tc.wantRecvErr {
require.Error(recvErr)
} else {
require.ErrorIs(recvErr, io.EOF)
}
require.NoError(stream.CloseSend())
grpcServ.GracefulStop()
require.NoError(err)
assert.Equal(tc.wantChunks, chunks)
assert.Equal("/run/state/bin/bootstrapper", tc.streamer.readStreamFilename)
assert.Equal(tc.wantSendFileCalls, transfer.sendFilesCount)
})
}
}
@ -334,7 +323,6 @@ func TestUploadSystemServiceUnits(t *testing.T) {
serv := debugdServer{
log: logger.NewTest(t),
serviceManager: &tc.serviceManager,
streamer: &fakeStreamer{},
}
grpcServ, conn, err := setupServerWithConn(endpoint, &serv)
require.NoError(err)
@ -359,8 +347,11 @@ func TestUploadSystemServiceUnits(t *testing.T) {
type stubServiceManager struct {
requests []deploy.ServiceManagerRequest
unitFiles []deploy.SystemdUnit
overrideCalls []struct{ UnitName, ExecStart string }
systemdActionErr error
writeSystemdUnitFileErr error
overrideServiceUnitExecStartErr error
}
func (s *stubServiceManager) SystemdAction(ctx context.Context, request deploy.ServiceManagerRequest) error {
@ -373,6 +364,13 @@ func (s *stubServiceManager) WriteSystemdUnitFile(ctx context.Context, unit depl
return s.writeSystemdUnitFileErr
}
func (s *stubServiceManager) OverrideServiceUnitExecStart(ctx context.Context, unitName string, execStart string) error {
s.overrideCalls = append(s.overrideCalls, struct {
UnitName, ExecStart string
}{UnitName: unitName, ExecStart: execStart})
return s.overrideServiceUnitExecStartErr
}
type netDialer interface {
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
@ -386,37 +384,31 @@ func dial(ctx context.Context, dialer netDialer, target string) (*grpc.ClientCon
)
}
type fakeStreamer struct {
writeStreamChunks [][]byte
writeStreamFilename string
writeStreamErr error
readStreamChunks [][]byte
readStreamFilename string
readStreamErr error
type stubTransfer struct {
recvFilesCount int
sendFilesCount int
files []filetransfer.FileStat
canSend bool
recvFilesErr error
sendFilesErr error
}
func (f *fakeStreamer) WriteStream(filename string, stream bootstrapper.ReadChunkStream, showProgress bool) error {
f.writeStreamFilename = filename
for {
chunk, err := stream.Recv()
if err != nil {
if errors.Is(err, io.EOF) {
return f.writeStreamErr
}
return fmt.Errorf("reading stream: %w", err)
}
f.writeStreamChunks = append(f.writeStreamChunks, chunk.Content)
}
func (t *stubTransfer) RecvFiles(_ filetransfer.RecvFilesStream) error {
t.recvFilesCount++
return t.recvFilesErr
}
func (f *fakeStreamer) ReadStream(filename string, stream bootstrapper.WriteChunkStream, chunksize uint, showProgress bool) error {
f.readStreamFilename = filename
for _, chunk := range f.readStreamChunks {
if err := stream.Send(&pb.Chunk{Content: chunk}); err != nil {
panic(err)
func (t *stubTransfer) SendFiles(_ filetransfer.SendFilesStream) error {
t.sendFilesCount++
return t.sendFilesErr
}
func (t *stubTransfer) GetFiles() []filetransfer.FileStat {
return t.files
}
return f.readStreamErr
func (t *stubTransfer) CanSend() bool {
return t.canSend
}
func setupServerWithConn(endpoint string, serv *debugdServer) (*grpc.Server, *grpc.ClientConn, error) {
@ -433,29 +425,3 @@ func setupServerWithConn(endpoint string, serv *debugdServer) (*grpc.Server, *gr
return grpcServ, conn, nil
}
func fakeWrite(stream bootstrapper.WriteChunkStream, chunks [][]byte) error {
for _, chunk := range chunks {
err := stream.Send(&pb.Chunk{
Content: chunk,
})
if err != nil {
return err
}
}
return nil
}
func fakeRead(stream bootstrapper.ReadChunkStream) ([][]byte, error) {
var chunks [][]byte
for {
chunk, err := stream.Recv()
if err != nil {
if err == io.EOF {
return chunks, nil
}
return nil, err
}
chunks = append(chunks, chunk.Content)
}
}

View File

@ -0,0 +1,47 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package filetransfer
import (
"errors"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
)
// recvChunkStream is a wrapper around a RecvFilesStream that only returns chunks.
type recvChunkStream struct {
stream RecvFilesStream
}
// Recv receives a FileTransferMessage and returns the chunk.
func (s *recvChunkStream) Recv() (*pb.Chunk, error) {
msg, err := s.stream.Recv()
if err != nil {
return nil, err
}
chunk := msg.GetChunk()
if chunk == nil {
return nil, errors.New("expected chunk")
}
return chunk, nil
}
// sendChunkStream is a wrapper around a SendFilesStream that wraps chunks for every message.
type sendChunkStream struct {
stream SendFilesStream
}
// Send wraps the given chunk in a FileTransferMessage and sends it.
func (s *sendChunkStream) Send(chunk *pb.Chunk) error {
chunkMessage := &pb.FileTransferMessage_Chunk{
Chunk: chunk,
}
message := &pb.FileTransferMessage{
Kind: chunkMessage,
}
return s.stream.Send(message)
}

View File

@ -0,0 +1,134 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package filetransfer
import (
"errors"
"testing"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRecv(t *testing.T) {
testCases := map[string]struct {
stream stubRecvFilesStream
wantChunk *pb.Chunk
wantErr bool
}{
"chunk is received": {
stream: stubRecvFilesStream{
msg: &pb.FileTransferMessage{
Kind: &pb.FileTransferMessage_Chunk{
Chunk: &pb.Chunk{
Content: []byte("test"),
},
},
},
},
wantChunk: &pb.Chunk{
Content: []byte("test"),
},
},
"wrong type": {
stream: stubRecvFilesStream{
msg: &pb.FileTransferMessage{
Kind: &pb.FileTransferMessage_Header{},
},
},
wantErr: true,
},
"empty msg": {
stream: stubRecvFilesStream{},
wantErr: true,
},
"recv fails": {
stream: stubRecvFilesStream{
recvErr: errors.New("someErr"),
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
stream := recvChunkStream{stream: &tc.stream}
chunk, err := stream.Recv()
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantChunk, chunk)
})
}
}
func TestSend(t *testing.T) {
testCases := map[string]struct {
stream stubSendFilesStream
chunk *pb.Chunk
wantMsgs []*pb.FileTransferMessage
wantErr bool
}{
"chunk is wrapped correctly": {
chunk: &pb.Chunk{
Content: []byte("test"),
},
wantMsgs: []*pb.FileTransferMessage{
{
Kind: &pb.FileTransferMessage_Chunk{
Chunk: &pb.Chunk{
Content: []byte("test"),
},
},
},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
stream := sendChunkStream{stream: &tc.stream}
err := stream.Send(tc.chunk)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.EqualValues(tc.wantMsgs, tc.stream.msgs)
})
}
}
type stubRecvFilesStream struct {
msg *pb.FileTransferMessage
recvErr error
}
func (s *stubRecvFilesStream) Recv() (*pb.FileTransferMessage, error) {
return s.msg, s.recvErr
}
type stubSendFilesStream struct {
msgs []*pb.FileTransferMessage
sendErr error
}
func (s *stubSendFilesStream) Send(msg *pb.FileTransferMessage) error {
s.msgs = append(s.msgs, msg)
return s.sendErr
}

View File

@ -0,0 +1,233 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package filetransfer
import (
"errors"
"io"
"io/fs"
"sync"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer/streamer"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/edgelesssys/constellation/v2/internal/logger"
"go.uber.org/zap"
)
// RecvFilesStream is a stream that receives FileTransferMessages.
type RecvFilesStream interface {
Recv() (*pb.FileTransferMessage, error)
}
// SendFilesStream is a stream that sends FileTransferMessages.
type SendFilesStream interface {
Send(*pb.FileTransferMessage) error
}
// FileTransferer manages sending and receiving of files.
type FileTransferer struct {
mux sync.RWMutex
log *logger.Logger
receiveStarted bool
receiveFinished bool
files []FileStat
streamer streamReadWriter
showProgress bool
}
// New creates a new FileTransferer.
func New(log *logger.Logger, streamer streamReadWriter, showProgress bool) *FileTransferer {
return &FileTransferer{
log: log,
streamer: streamer,
showProgress: showProgress,
}
}
// SendFiles sends files to the given stream.
func (s *FileTransferer) SendFiles(stream SendFilesStream) error {
s.mux.RLock()
defer s.mux.RUnlock()
if !s.receiveFinished {
return errors.New("cannot send files before receiving them")
}
for _, file := range s.files {
if err := s.handleFileSend(stream, file); err != nil {
return err
}
}
return nil
}
// RecvFiles receives files from the given stream.
func (s *FileTransferer) RecvFiles(stream RecvFilesStream) (err error) {
if err := s.startRecv(); err != nil {
return err
}
defer func() {
if err != nil {
s.abortRecv()
} else {
s.finishRecv()
}
}()
var done bool
for !done && err == nil {
done, err = s.handleFileRecv(stream)
}
return err
}
// GetFiles returns the a copy of the list of files that have been received.
func (s *FileTransferer) GetFiles() []FileStat {
s.mux.RLock()
defer s.mux.RUnlock()
res := make([]FileStat, len(s.files))
copy(res, s.files)
return res
}
// SetFiles sets the list of files that can be sent.
func (s *FileTransferer) SetFiles(files []FileStat) {
s.mux.Lock()
defer s.mux.Unlock()
res := make([]FileStat, len(files))
copy(res, files)
s.files = res
s.receiveFinished = true
}
// CanSend returns true if the file receive has finished.
// This is called to determine if a debugd instance can request files from this server.
func (s *FileTransferer) CanSend() bool {
s.mux.RLock()
defer s.mux.RUnlock()
ret := s.receiveFinished
return ret
}
func (s *FileTransferer) handleFileSend(stream SendFilesStream, file FileStat) error {
header := &pb.FileTransferMessage_Header{
Header: &pb.FileTransferHeader{
TargetPath: file.TargetPath,
Mode: uint32(file.Mode),
},
}
if file.OverrideServiceUnit != "" {
header.Header.OverrideServiceUnit = &file.OverrideServiceUnit
}
if err := stream.Send(&pb.FileTransferMessage{Kind: header}); err != nil {
return err
}
sendChunkStream := &sendChunkStream{stream: stream}
return s.streamer.ReadStream(file.SourcePath, sendChunkStream, debugd.Chunksize, s.showProgress)
}
// handleFileRecv handles the file receive of a single file.
// It returns true if the stream is finished (all of the file consumed) and false otherwise.
func (s *FileTransferer) handleFileRecv(stream RecvFilesStream) (bool, error) {
// first message must be a header message
msg, err := stream.Recv()
switch {
case err == nil:
// nop
case errors.Is(err, io.EOF):
return true, nil // stream is finished
default:
return false, err
}
header := msg.GetHeader()
if header == nil {
return false, errors.New("first message must be a header message")
}
s.log.Infof("Starting file receive of %q", header.TargetPath)
s.addFile(FileStat{
SourcePath: header.TargetPath,
TargetPath: header.TargetPath,
Mode: fs.FileMode(header.Mode),
OverrideServiceUnit: func() string {
if header.OverrideServiceUnit != nil {
return *header.OverrideServiceUnit
}
return ""
}(),
})
recvChunkStream := &recvChunkStream{stream: stream}
if err := s.streamer.WriteStream(header.TargetPath, recvChunkStream, s.showProgress); err != nil {
s.log.With(zap.Error(err)).Errorf("Receive of file %q failed", header.TargetPath)
return false, err
}
s.log.Infof("Finished file receive of %q", header.TargetPath)
return false, nil
}
// startRecv marks the file receive as started. It returns an error if receiving has already started.
func (s *FileTransferer) startRecv() error {
s.mux.Lock()
defer s.mux.Unlock()
switch {
case s.receiveFinished:
return ErrReceiveFinished
case s.receiveStarted:
return ErrReceiveRunning
}
s.receiveStarted = true
return nil
}
// abortRecv marks the file receive as failed.
// This allows for a retry of the file receive.
func (s *FileTransferer) abortRecv() {
s.mux.Lock()
defer s.mux.Unlock()
s.receiveStarted = false
s.files = nil
}
// finishRecv marks the file receive as completed.
// This allows other debugd instances to request files from this server.
func (s *FileTransferer) finishRecv() {
s.mux.Lock()
defer s.mux.Unlock()
s.receiveStarted = false
s.receiveFinished = true
}
// addFile adds a file to the list of received files.
func (s *FileTransferer) addFile(file FileStat) {
s.mux.Lock()
defer s.mux.Unlock()
s.files = append(s.files, file)
}
// FileStat contains the metadata of a file that can be up/downloaded.
type FileStat struct {
SourcePath string
TargetPath string
Mode fs.FileMode
OverrideServiceUnit string // optional name of the service unit to override
}
var (
// ErrReceiveRunning is returned if a file receive is already running.
ErrReceiveRunning = errors.New("receive already running")
// ErrReceiveFinished is returned if a file receive has already finished.
ErrReceiveFinished = errors.New("receive finished")
)
const (
// ShowProgress indicates that progress should be shown.
ShowProgress = true
// DontShowProgress indicates that progress should not be shown.
DontShowProgress = false
)
type streamReadWriter interface {
WriteStream(filename string, stream streamer.ReadChunkStream, showProgress bool) error
ReadStream(filename string, stream streamer.WriteChunkStream, chunksize uint, showProgress bool) error
}

View File

@ -0,0 +1,411 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package filetransfer
import (
"errors"
"io"
"testing"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer/streamer"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestSendFiles(t *testing.T) {
testCases := map[string]struct {
files *[]FileStat
sendErr error
readStreamErr error
wantHeaders []*pb.FileTransferMessage
wantErr bool
}{
"can send files": {
files: &[]FileStat{
{
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: "somesvcA",
},
{
TargetPath: "testfileB",
Mode: 0o644,
OverrideServiceUnit: "somesvcB",
},
},
wantHeaders: []*pb.FileTransferMessage{
{
Kind: &pb.FileTransferMessage_Header{
Header: &pb.FileTransferHeader{
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: func() *string { s := "somesvcA"; return &s }(),
},
},
},
{
Kind: &pb.FileTransferMessage_Header{
Header: &pb.FileTransferHeader{
TargetPath: "testfileB",
Mode: 0o644,
OverrideServiceUnit: func() *string { s := "somesvcB"; return &s }(),
},
},
},
},
},
"no files set": {
wantErr: true,
},
"send fails": {
files: &[]FileStat{
{
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: "somesvcA",
},
},
sendErr: errors.New("send failed"),
wantErr: true,
},
"read stream fails": {
files: &[]FileStat{
{
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: "somesvcA",
},
},
readStreamErr: errors.New("read stream failed"),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
streamer := &stubStreamReadWriter{readStreamErr: tc.readStreamErr}
stream := &stubSendFilesStream{sendErr: tc.sendErr}
transfer := New(logger.NewTest(t), streamer, false)
if tc.files != nil {
transfer.SetFiles(*tc.files)
}
err := transfer.SendFiles(stream)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantHeaders, stream.msgs)
})
}
}
func TestRecvFiles(t *testing.T) {
testCases := map[string]struct {
msgs []*pb.FileTransferMessage
recvAlreadyStarted bool
recvAlreadyFinished bool
recvErr error
writeStreamErr error
wantFiles []FileStat
wantErr bool
}{
"can recv files": {
msgs: []*pb.FileTransferMessage{
{
Kind: &pb.FileTransferMessage_Header{
Header: &pb.FileTransferHeader{
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: func() *string { s := "somesvcA"; return &s }(),
},
},
},
// Chunk messages left out since they would be consumed by the streamReadWriter
{
Kind: &pb.FileTransferMessage_Header{
Header: &pb.FileTransferHeader{
TargetPath: "testfileB",
Mode: 0o644,
},
},
},
// Chunk messages left out since they would be consumed by the streamReadWriter
},
wantFiles: []FileStat{
{
SourcePath: "testfileA",
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: "somesvcA",
},
{
SourcePath: "testfileB",
TargetPath: "testfileB",
Mode: 0o644,
},
},
},
"no messages": {},
"recv fails": {
recvErr: errors.New("recv failed"),
wantErr: true,
},
"first recv does not yield file header": {
msgs: []*pb.FileTransferMessage{
{
Kind: &pb.FileTransferMessage_Chunk{},
},
},
wantErr: true,
},
"write stream fails": {
msgs: []*pb.FileTransferMessage{
{
Kind: &pb.FileTransferMessage_Header{
Header: &pb.FileTransferHeader{
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: func() *string { s := "somesvcA"; return &s }(),
},
},
},
// Chunk messages left out since they would be consumed by the streamReadWriter
},
writeStreamErr: errors.New("write stream failed"),
wantErr: true,
},
"recv has already started": {
msgs: []*pb.FileTransferMessage{
{
Kind: &pb.FileTransferMessage_Header{
Header: &pb.FileTransferHeader{
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: func() *string { s := "somesvcA"; return &s }(),
},
},
},
// Chunk messages left out since they would be consumed by the streamReadWriter
},
recvAlreadyStarted: true,
wantErr: true,
},
"recv has already finished": {
msgs: []*pb.FileTransferMessage{
{
Kind: &pb.FileTransferMessage_Header{
Header: &pb.FileTransferHeader{
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: func() *string { s := "somesvcA"; return &s }(),
},
},
},
// Chunk messages left out since they would be consumed by the streamReadWriter
},
recvAlreadyFinished: true,
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
streamer := &stubStreamReadWriter{writeStreamErr: tc.writeStreamErr}
stream := &fakeRecvFilesStream{msgs: tc.msgs, recvErr: tc.recvErr}
transfer := New(logger.NewTest(t), streamer, false)
if tc.recvAlreadyStarted {
transfer.receiveStarted = true
}
if tc.recvAlreadyFinished {
transfer.receiveFinished = true
}
err := transfer.RecvFiles(stream)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantFiles, transfer.files)
})
}
}
func TestGetSetFiles(t *testing.T) {
testCases := map[string]struct {
setFiles *[]FileStat
wantFiles []FileStat
wantErr bool
}{
"no files": {
wantFiles: []FileStat{},
},
"files": {
setFiles: &[]FileStat{
{
SourcePath: "testfileA",
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: "somesvcA",
},
},
wantFiles: []FileStat{
{
SourcePath: "testfileA",
TargetPath: "testfileA",
Mode: 0o644,
OverrideServiceUnit: "somesvcA",
},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
streamer := &dummyStreamReadWriter{}
transfer := New(logger.NewTest(t), streamer, false)
if tc.setFiles != nil {
transfer.SetFiles(*tc.setFiles)
}
gotFiles := transfer.GetFiles()
assert.Equal(tc.wantFiles, gotFiles)
assert.Equal(tc.setFiles != nil, transfer.receiveFinished)
})
}
}
func TestCanSend(t *testing.T) {
assert := assert.New(t)
streamer := &stubStreamReadWriter{}
stream := &stubRecvFilesStream{recvErr: io.EOF}
transfer := New(logger.NewTest(t), streamer, false)
assert.False(transfer.CanSend())
// manual set
transfer.SetFiles(nil)
assert.True(transfer.CanSend())
// reset
transfer.receiveStarted = false
transfer.receiveFinished = false
transfer.files = nil
assert.False(transfer.CanSend())
// receive files (empty)
assert.NoError(transfer.RecvFiles(stream))
assert.True(transfer.CanSend())
}
func TestConcurrency(t *testing.T) {
ft := New(logger.NewTest(t), &stubStreamReadWriter{}, false)
sendFiles := func() {
_ = ft.SendFiles(&stubSendFilesStream{})
}
recvFiles := func() {
_ = ft.RecvFiles(&stubRecvFilesStream{})
}
getFiles := func() {
_ = ft.GetFiles()
}
setFiles := func() {
ft.SetFiles([]FileStat{{SourcePath: "file", TargetPath: "file", Mode: 0o644}})
}
canSend := func() {
_ = ft.CanSend()
}
go sendFiles()
go sendFiles()
go sendFiles()
go sendFiles()
go recvFiles()
go recvFiles()
go recvFiles()
go recvFiles()
go getFiles()
go getFiles()
go getFiles()
go getFiles()
go setFiles()
go setFiles()
go setFiles()
go setFiles()
go canSend()
go canSend()
go canSend()
go canSend()
}
type stubStreamReadWriter struct {
readStreamFilename string
readStreamErr error
writeStreamFilename string
writeStreamErr error
}
func (s *stubStreamReadWriter) ReadStream(filename string, _ streamer.WriteChunkStream, _ uint, _ bool) error {
s.readStreamFilename = filename
return s.readStreamErr
}
func (s *stubStreamReadWriter) WriteStream(filename string, _ streamer.ReadChunkStream, _ bool) error {
s.writeStreamFilename = filename
return s.writeStreamErr
}
type fakeRecvFilesStream struct {
msgs []*pb.FileTransferMessage
pos int
recvErr error
}
func (s *fakeRecvFilesStream) Recv() (*pb.FileTransferMessage, error) {
if s.recvErr != nil {
return nil, s.recvErr
}
if s.pos < len(s.msgs) {
s.pos++
return s.msgs[s.pos-1], nil
}
return nil, io.EOF
}
type dummyStreamReadWriter struct{}
func (s *dummyStreamReadWriter) ReadStream(_ string, _ streamer.WriteChunkStream, _ uint, _ bool) error {
panic("dummy")
}
func (s *dummyStreamReadWriter) WriteStream(_ string, _ streamer.ReadChunkStream, _ bool) error {
panic("dummy")
}

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package bootstrapper
package streamer
import (
"errors"
@ -34,8 +34,8 @@ type WriteChunkStream interface {
Send(chunk *pb.Chunk) error
}
// NewFileStreamer creates a new FileStreamer.
func NewFileStreamer(fs afero.Fs) *FileStreamer {
// New creates a new FileStreamer.
func New(fs afero.Fs) *FileStreamer {
return &FileStreamer{
fs: fs,
mux: sync.RWMutex{},
@ -44,52 +44,25 @@ func NewFileStreamer(fs afero.Fs) *FileStreamer {
// WriteStream opens a file to write to and streams chunks from a gRPC stream into the file.
func (f *FileStreamer) WriteStream(filename string, stream ReadChunkStream, showProgress bool) error {
// try to read from stream once before acquiring write lock
chunk, err := stream.Recv()
if err != nil {
return fmt.Errorf("reading stream: %w", err)
}
f.mux.Lock()
defer f.mux.Unlock()
file, err := f.fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o755)
file, err := f.fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
return fmt.Errorf("open %v for writing: %w", filename, err)
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return fmt.Errorf("performing stat on %v to get the file size: %w", filename, err)
}
var bar *progressbar.ProgressBar
if showProgress {
bar = progressbar.NewOptions64(
-1,
progressbar.OptionSetDescription("receiving bootstrapper"),
progressbar.OptionShowBytes(true),
progressbar.OptionClearOnFinish(),
)
bar = newProgressBar(stat.Size())
defer bar.Close()
}
for {
if err != nil {
if errors.Is(err, io.EOF) {
break
}
_ = file.Close()
_ = f.fs.Remove(filename)
return fmt.Errorf("reading stream: %w", err)
}
if _, err := file.Write(chunk.Content); err != nil {
_ = file.Close()
_ = f.fs.Remove(filename)
return fmt.Errorf("writing chunk to disk: %w", err)
}
if showProgress {
_ = bar.Add(len(chunk.Content))
}
chunk, err = stream.Recv()
}
return nil
return writeInner(file, stream, bar)
}
// ReadStream opens a file to read from and streams its contents chunkwise over gRPC.
@ -101,44 +74,73 @@ func (f *FileStreamer) ReadStream(filename string, stream WriteChunkStream, chun
if f.mux.TryRLock() {
defer f.mux.RUnlock()
} else {
return errors.New("file is opened for writing cannot be read at this time")
return errors.New("a file is opened for writing so cannot read at this time")
}
file, err := f.fs.OpenFile(filename, os.O_RDONLY, 0o755)
if err != nil {
return fmt.Errorf("open %v for reading: %w", filename, err)
}
defer file.Close()
var bar *progressbar.ProgressBar
if showProgress {
stat, err := file.Stat()
if err != nil {
return fmt.Errorf("performing stat on %v to get the file size: %w", filename, err)
}
bar = progressbar.NewOptions64(
stat.Size(),
progressbar.OptionSetDescription("uploading bootstrapper"),
progressbar.OptionShowBytes(true),
progressbar.OptionClearOnFinish(),
)
var bar *progressbar.ProgressBar
if showProgress {
bar = newProgressBar(stat.Size())
defer bar.Close()
}
buf := make([]byte, chunksize)
for {
n, err := file.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return fmt.Errorf("reading file chunk: %w", err)
return readInner(file, stream, chunksize, bar)
}
if err = stream.Send(&pb.Chunk{Content: buf[:n]}); err != nil {
// readInner reads from a an io.Reader and sends chunks over a gRPC stream.
func readInner(fp io.Reader, stream WriteChunkStream, chunksize uint, bar *progressbar.ProgressBar) error {
buf := make([]byte, chunksize)
for {
n, readErr := fp.Read(buf)
isLast := errors.Is(readErr, io.EOF)
if readErr != nil && !isLast {
return fmt.Errorf("reading file chunk: %w", readErr)
}
if err := stream.Send(&pb.Chunk{Content: buf[:n], Last: isLast}); err != nil {
return fmt.Errorf("sending chunk: %w", err)
}
if showProgress {
if bar != nil {
_ = bar.Add(n)
}
if isLast {
return nil
}
}
}
// writeInner writes chunks from a gRPC stream to an io.Writer.
func writeInner(fp io.Writer, stream ReadChunkStream, bar *progressbar.ProgressBar) error {
for {
chunk, recvErr := stream.Recv()
if recvErr != nil {
return fmt.Errorf("reading stream: %w", recvErr)
}
if _, err := fp.Write(chunk.Content); err != nil {
return fmt.Errorf("writing chunk to disk: %w", err)
}
if bar != nil {
_ = bar.Add(len(chunk.Content))
}
if chunk.Last {
return nil
}
}
}
// newProgressBar creates a new progress bar.
func newProgressBar(size int64) *progressbar.ProgressBar {
return progressbar.NewOptions64(
size,
progressbar.OptionSetDescription("transferring file"),
progressbar.OptionShowBytes(true),
progressbar.OptionClearOnFinish(),
)
}

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package bootstrapper
package streamer
import (
"errors"
@ -82,7 +82,7 @@ func TestWriteStream(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
writer := NewFileStreamer(tc.fs)
writer := New(tc.fs)
err := writer.WriteStream(filename, &tc.readChunkStream, tc.showProgress)
if tc.wantErr {
@ -99,6 +99,7 @@ func TestWriteStream(t *testing.T) {
func TestReadStream(t *testing.T) {
correctFilename := "testfile"
eof := []byte{}
testCases := map[string]struct {
writeChunkStream stubWriteChunkStream
@ -114,6 +115,7 @@ func TestReadStream(t *testing.T) {
chunksize: 4,
wantChunks: [][]byte{
[]byte("test"),
eof,
},
wantErr: false,
},
@ -124,6 +126,7 @@ func TestReadStream(t *testing.T) {
wantChunks: [][]byte{
[]byte("te"),
[]byte("st"),
eof,
},
wantErr: false,
},
@ -140,6 +143,7 @@ func TestReadStream(t *testing.T) {
showProgress: true,
wantChunks: [][]byte{
[]byte("test"),
eof,
},
wantErr: false,
},
@ -165,7 +169,7 @@ func TestReadStream(t *testing.T) {
fs := afero.NewMemMapFs()
assert.NoError(afero.WriteFile(fs, correctFilename, []byte("test"), 0o755))
reader := NewFileStreamer(fs)
reader := New(fs)
err := reader.ReadStream(tc.filename, &tc.writeChunkStream, tc.chunksize, tc.showProgress)
if tc.wantErr {
@ -189,8 +193,10 @@ func (s *fakeReadChunkStream) Recv() (*pb.Chunk, error) {
return nil, s.recvErr
}
isLastChunk := s.pos == len(s.chunks)-1
if s.pos < len(s.chunks) {
result := &pb.Chunk{Content: s.chunks[s.pos]}
result := &pb.Chunk{Content: s.chunks[s.pos], Last: isLastChunk}
s.pos++
return result, nil
}

View File

@ -20,58 +20,107 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type UploadBootstrapperStatus int32
type SetInfoStatus int32
const (
UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_SUCCESS UploadBootstrapperStatus = 0
UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_UPLOAD_FAILED UploadBootstrapperStatus = 1
UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_START_FAILED UploadBootstrapperStatus = 2
UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_FILE_EXISTS UploadBootstrapperStatus = 3
SetInfoStatus_SET_INFO_SUCCESS SetInfoStatus = 0
SetInfoStatus_SET_INFO_ALREADY_SET SetInfoStatus = 1
)
// Enum value maps for UploadBootstrapperStatus.
// Enum value maps for SetInfoStatus.
var (
UploadBootstrapperStatus_name = map[int32]string{
0: "UPLOAD_BOOTSTRAPPER_SUCCESS",
1: "UPLOAD_BOOTSTRAPPER_UPLOAD_FAILED",
2: "UPLOAD_BOOTSTRAPPER_START_FAILED",
3: "UPLOAD_BOOTSTRAPPER_FILE_EXISTS",
SetInfoStatus_name = map[int32]string{
0: "SET_INFO_SUCCESS",
1: "SET_INFO_ALREADY_SET",
}
UploadBootstrapperStatus_value = map[string]int32{
"UPLOAD_BOOTSTRAPPER_SUCCESS": 0,
"UPLOAD_BOOTSTRAPPER_UPLOAD_FAILED": 1,
"UPLOAD_BOOTSTRAPPER_START_FAILED": 2,
"UPLOAD_BOOTSTRAPPER_FILE_EXISTS": 3,
SetInfoStatus_value = map[string]int32{
"SET_INFO_SUCCESS": 0,
"SET_INFO_ALREADY_SET": 1,
}
)
func (x UploadBootstrapperStatus) Enum() *UploadBootstrapperStatus {
p := new(UploadBootstrapperStatus)
func (x SetInfoStatus) Enum() *SetInfoStatus {
p := new(SetInfoStatus)
*p = x
return p
}
func (x UploadBootstrapperStatus) String() string {
func (x SetInfoStatus) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (UploadBootstrapperStatus) Descriptor() protoreflect.EnumDescriptor {
func (SetInfoStatus) Descriptor() protoreflect.EnumDescriptor {
return file_debugd_proto_enumTypes[0].Descriptor()
}
func (UploadBootstrapperStatus) Type() protoreflect.EnumType {
func (SetInfoStatus) Type() protoreflect.EnumType {
return &file_debugd_proto_enumTypes[0]
}
func (x UploadBootstrapperStatus) Number() protoreflect.EnumNumber {
func (x SetInfoStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use UploadBootstrapperStatus.Descriptor instead.
func (UploadBootstrapperStatus) EnumDescriptor() ([]byte, []int) {
// Deprecated: Use SetInfoStatus.Descriptor instead.
func (SetInfoStatus) EnumDescriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{0}
}
type UploadFilesStatus int32
const (
UploadFilesStatus_UPLOAD_FILES_SUCCESS UploadFilesStatus = 0
UploadFilesStatus_UPLOAD_FILES_UPLOAD_FAILED UploadFilesStatus = 1
UploadFilesStatus_UPLOAD_FILES_ALREADY_STARTED UploadFilesStatus = 2
UploadFilesStatus_UPLOAD_FILES_ALREADY_FINISHED UploadFilesStatus = 3
UploadFilesStatus_UPLOAD_FILES_START_FAILED UploadFilesStatus = 4
)
// Enum value maps for UploadFilesStatus.
var (
UploadFilesStatus_name = map[int32]string{
0: "UPLOAD_FILES_SUCCESS",
1: "UPLOAD_FILES_UPLOAD_FAILED",
2: "UPLOAD_FILES_ALREADY_STARTED",
3: "UPLOAD_FILES_ALREADY_FINISHED",
4: "UPLOAD_FILES_START_FAILED",
}
UploadFilesStatus_value = map[string]int32{
"UPLOAD_FILES_SUCCESS": 0,
"UPLOAD_FILES_UPLOAD_FAILED": 1,
"UPLOAD_FILES_ALREADY_STARTED": 2,
"UPLOAD_FILES_ALREADY_FINISHED": 3,
"UPLOAD_FILES_START_FAILED": 4,
}
)
func (x UploadFilesStatus) Enum() *UploadFilesStatus {
p := new(UploadFilesStatus)
*p = x
return p
}
func (x UploadFilesStatus) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (UploadFilesStatus) Descriptor() protoreflect.EnumDescriptor {
return file_debugd_proto_enumTypes[1].Descriptor()
}
func (UploadFilesStatus) Type() protoreflect.EnumType {
return &file_debugd_proto_enumTypes[1]
}
func (x UploadFilesStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use UploadFilesStatus.Descriptor instead.
func (UploadFilesStatus) EnumDescriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{1}
}
type UploadSystemdServiceUnitsStatus int32
const (
@ -102,11 +151,11 @@ func (x UploadSystemdServiceUnitsStatus) String() string {
}
func (UploadSystemdServiceUnitsStatus) Descriptor() protoreflect.EnumDescriptor {
return file_debugd_proto_enumTypes[1].Descriptor()
return file_debugd_proto_enumTypes[2].Descriptor()
}
func (UploadSystemdServiceUnitsStatus) Type() protoreflect.EnumType {
return &file_debugd_proto_enumTypes[1]
return &file_debugd_proto_enumTypes[2]
}
func (x UploadSystemdServiceUnitsStatus) Number() protoreflect.EnumNumber {
@ -115,7 +164,7 @@ func (x UploadSystemdServiceUnitsStatus) Number() protoreflect.EnumNumber {
// Deprecated: Use UploadSystemdServiceUnitsStatus.Descriptor instead.
func (UploadSystemdServiceUnitsStatus) EnumDescriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{1}
return file_debugd_proto_rawDescGZIP(), []int{2}
}
type SetInfoRequest struct {
@ -169,6 +218,8 @@ type SetInfoResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Status SetInfoStatus `protobuf:"varint,1,opt,name=status,proto3,enum=debugd.SetInfoStatus" json:"status,omitempty"`
}
func (x *SetInfoResponse) Reset() {
@ -203,6 +254,13 @@ func (*SetInfoResponse) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{1}
}
func (x *SetInfoResponse) GetStatus() SetInfoStatus {
if x != nil {
return x.Status
}
return SetInfoStatus_SET_INFO_SUCCESS
}
type GetInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -343,14 +401,14 @@ func (x *Info) GetValue() string {
return ""
}
type DownloadBootstrapperRequest struct {
type DownloadFilesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *DownloadBootstrapperRequest) Reset() {
*x = DownloadBootstrapperRequest{}
func (x *DownloadFilesRequest) Reset() {
*x = DownloadFilesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_debugd_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -358,13 +416,13 @@ func (x *DownloadBootstrapperRequest) Reset() {
}
}
func (x *DownloadBootstrapperRequest) String() string {
func (x *DownloadFilesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DownloadBootstrapperRequest) ProtoMessage() {}
func (*DownloadFilesRequest) ProtoMessage() {}
func (x *DownloadBootstrapperRequest) ProtoReflect() protoreflect.Message {
func (x *DownloadFilesRequest) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@ -376,23 +434,168 @@ func (x *DownloadBootstrapperRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use DownloadBootstrapperRequest.ProtoReflect.Descriptor instead.
func (*DownloadBootstrapperRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use DownloadFilesRequest.ProtoReflect.Descriptor instead.
func (*DownloadFilesRequest) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{5}
}
type FileTransferMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Kind:
//
// *FileTransferMessage_Header
// *FileTransferMessage_Chunk
Kind isFileTransferMessage_Kind `protobuf_oneof:"kind"`
}
func (x *FileTransferMessage) Reset() {
*x = FileTransferMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_debugd_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FileTransferMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FileTransferMessage) ProtoMessage() {}
func (x *FileTransferMessage) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FileTransferMessage.ProtoReflect.Descriptor instead.
func (*FileTransferMessage) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{6}
}
func (m *FileTransferMessage) GetKind() isFileTransferMessage_Kind {
if m != nil {
return m.Kind
}
return nil
}
func (x *FileTransferMessage) GetHeader() *FileTransferHeader {
if x, ok := x.GetKind().(*FileTransferMessage_Header); ok {
return x.Header
}
return nil
}
func (x *FileTransferMessage) GetChunk() *Chunk {
if x, ok := x.GetKind().(*FileTransferMessage_Chunk); ok {
return x.Chunk
}
return nil
}
type isFileTransferMessage_Kind interface {
isFileTransferMessage_Kind()
}
type FileTransferMessage_Header struct {
Header *FileTransferHeader `protobuf:"bytes,1,opt,name=header,proto3,oneof"` // start of transfer
}
type FileTransferMessage_Chunk struct {
Chunk *Chunk `protobuf:"bytes,2,opt,name=chunk,proto3,oneof"` // file content as chunks
}
func (*FileTransferMessage_Header) isFileTransferMessage_Kind() {}
func (*FileTransferMessage_Chunk) isFileTransferMessage_Kind() {}
type FileTransferHeader struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
TargetPath string `protobuf:"bytes,1,opt,name=targetPath,proto3" json:"targetPath,omitempty"`
Mode uint32 `protobuf:"varint,3,opt,name=mode,proto3" json:"mode,omitempty"`
OverrideServiceUnit *string `protobuf:"bytes,4,opt,name=overrideServiceUnit,proto3,oneof" json:"overrideServiceUnit,omitempty"`
}
func (x *FileTransferHeader) Reset() {
*x = FileTransferHeader{}
if protoimpl.UnsafeEnabled {
mi := &file_debugd_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FileTransferHeader) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FileTransferHeader) ProtoMessage() {}
func (x *FileTransferHeader) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FileTransferHeader.ProtoReflect.Descriptor instead.
func (*FileTransferHeader) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{7}
}
func (x *FileTransferHeader) GetTargetPath() string {
if x != nil {
return x.TargetPath
}
return ""
}
func (x *FileTransferHeader) GetMode() uint32 {
if x != nil {
return x.Mode
}
return 0
}
func (x *FileTransferHeader) GetOverrideServiceUnit() string {
if x != nil && x.OverrideServiceUnit != nil {
return *x.OverrideServiceUnit
}
return ""
}
type Chunk struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Content []byte `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"`
Last bool `protobuf:"varint,2,opt,name=last,proto3" json:"last,omitempty"`
}
func (x *Chunk) Reset() {
*x = Chunk{}
if protoimpl.UnsafeEnabled {
mi := &file_debugd_proto_msgTypes[6]
mi := &file_debugd_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -405,7 +608,7 @@ func (x *Chunk) String() string {
func (*Chunk) ProtoMessage() {}
func (x *Chunk) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[6]
mi := &file_debugd_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -418,7 +621,7 @@ func (x *Chunk) ProtoReflect() protoreflect.Message {
// Deprecated: Use Chunk.ProtoReflect.Descriptor instead.
func (*Chunk) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{6}
return file_debugd_proto_rawDescGZIP(), []int{8}
}
func (x *Chunk) GetContent() []byte {
@ -428,31 +631,38 @@ func (x *Chunk) GetContent() []byte {
return nil
}
type UploadBootstrapperResponse struct {
func (x *Chunk) GetLast() bool {
if x != nil {
return x.Last
}
return false
}
type UploadFilesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Status UploadBootstrapperStatus `protobuf:"varint,1,opt,name=status,proto3,enum=debugd.UploadBootstrapperStatus" json:"status,omitempty"`
Status UploadFilesStatus `protobuf:"varint,1,opt,name=status,proto3,enum=debugd.UploadFilesStatus" json:"status,omitempty"`
}
func (x *UploadBootstrapperResponse) Reset() {
*x = UploadBootstrapperResponse{}
func (x *UploadFilesResponse) Reset() {
*x = UploadFilesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_debugd_proto_msgTypes[7]
mi := &file_debugd_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UploadBootstrapperResponse) String() string {
func (x *UploadFilesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UploadBootstrapperResponse) ProtoMessage() {}
func (*UploadFilesResponse) ProtoMessage() {}
func (x *UploadBootstrapperResponse) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[7]
func (x *UploadFilesResponse) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -463,16 +673,16 @@ func (x *UploadBootstrapperResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use UploadBootstrapperResponse.ProtoReflect.Descriptor instead.
func (*UploadBootstrapperResponse) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{7}
// Deprecated: Use UploadFilesResponse.ProtoReflect.Descriptor instead.
func (*UploadFilesResponse) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{9}
}
func (x *UploadBootstrapperResponse) GetStatus() UploadBootstrapperStatus {
func (x *UploadFilesResponse) GetStatus() UploadFilesStatus {
if x != nil {
return x.Status
}
return UploadBootstrapperStatus_UPLOAD_BOOTSTRAPPER_SUCCESS
return UploadFilesStatus_UPLOAD_FILES_SUCCESS
}
type ServiceUnit struct {
@ -487,7 +697,7 @@ type ServiceUnit struct {
func (x *ServiceUnit) Reset() {
*x = ServiceUnit{}
if protoimpl.UnsafeEnabled {
mi := &file_debugd_proto_msgTypes[8]
mi := &file_debugd_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -500,7 +710,7 @@ func (x *ServiceUnit) String() string {
func (*ServiceUnit) ProtoMessage() {}
func (x *ServiceUnit) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[8]
mi := &file_debugd_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -513,7 +723,7 @@ func (x *ServiceUnit) ProtoReflect() protoreflect.Message {
// Deprecated: Use ServiceUnit.ProtoReflect.Descriptor instead.
func (*ServiceUnit) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{8}
return file_debugd_proto_rawDescGZIP(), []int{10}
}
func (x *ServiceUnit) GetName() string {
@ -541,7 +751,7 @@ type UploadSystemdServiceUnitsRequest struct {
func (x *UploadSystemdServiceUnitsRequest) Reset() {
*x = UploadSystemdServiceUnitsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_debugd_proto_msgTypes[9]
mi := &file_debugd_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -554,7 +764,7 @@ func (x *UploadSystemdServiceUnitsRequest) String() string {
func (*UploadSystemdServiceUnitsRequest) ProtoMessage() {}
func (x *UploadSystemdServiceUnitsRequest) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[9]
mi := &file_debugd_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -567,7 +777,7 @@ func (x *UploadSystemdServiceUnitsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UploadSystemdServiceUnitsRequest.ProtoReflect.Descriptor instead.
func (*UploadSystemdServiceUnitsRequest) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{9}
return file_debugd_proto_rawDescGZIP(), []int{11}
}
func (x *UploadSystemdServiceUnitsRequest) GetUnits() []*ServiceUnit {
@ -588,7 +798,7 @@ type UploadSystemdServiceUnitsResponse struct {
func (x *UploadSystemdServiceUnitsResponse) Reset() {
*x = UploadSystemdServiceUnitsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_debugd_proto_msgTypes[10]
mi := &file_debugd_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -601,7 +811,7 @@ func (x *UploadSystemdServiceUnitsResponse) String() string {
func (*UploadSystemdServiceUnitsResponse) ProtoMessage() {}
func (x *UploadSystemdServiceUnitsResponse) ProtoReflect() protoreflect.Message {
mi := &file_debugd_proto_msgTypes[10]
mi := &file_debugd_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -614,7 +824,7 @@ func (x *UploadSystemdServiceUnitsResponse) ProtoReflect() protoreflect.Message
// Deprecated: Use UploadSystemdServiceUnitsResponse.ProtoReflect.Descriptor instead.
func (*UploadSystemdServiceUnitsResponse) Descriptor() ([]byte, []int) {
return file_debugd_proto_rawDescGZIP(), []int{10}
return file_debugd_proto_rawDescGZIP(), []int{12}
}
func (x *UploadSystemdServiceUnitsResponse) GetStatus() UploadSystemdServiceUnitsStatus {
@ -631,89 +841,113 @@ var file_debugd_proto_rawDesc = []byte{
0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x22, 0x32, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66,
0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x65,
0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x0a,
0x0e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
0x33, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x0c, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04,
0x69, 0x6e, 0x66, 0x6f, 0x22, 0x2e, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x22, 0x1d, 0x0a, 0x1b, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64,
0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x22, 0x21, 0x0a, 0x05, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x18, 0x0a, 0x07,
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63,
0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x56, 0x0a, 0x1a, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64,
0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55, 0x70,
0x6c, 0x6f, 0x61, 0x64, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3d,
0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x12, 0x0a,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x4d, 0x0a,
0x20, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x29, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x13, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x64, 0x0a, 0x21,
0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x27, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61,
0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55,
0x6e, 0x69, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x2a, 0xad, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x6f, 0x6f,
0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x42, 0x4f, 0x4f, 0x54, 0x53, 0x54,
0x52, 0x41, 0x50, 0x50, 0x45, 0x52, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00,
0x12, 0x25, 0x0a, 0x21, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x42, 0x4f, 0x4f, 0x54, 0x53,
0x54, 0x52, 0x41, 0x50, 0x50, 0x45, 0x52, 0x5f, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x46,
0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x4c, 0x4f, 0x41,
0x44, 0x5f, 0x42, 0x4f, 0x4f, 0x54, 0x53, 0x54, 0x52, 0x41, 0x50, 0x50, 0x45, 0x52, 0x5f, 0x53,
0x54, 0x41, 0x52, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x23, 0x0a,
0x1f, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x42, 0x4f, 0x4f, 0x54, 0x53, 0x54, 0x52, 0x41,
0x50, 0x50, 0x45, 0x52, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x45, 0x58, 0x49, 0x53, 0x54, 0x53,
0x10, 0x03, 0x2a, 0x75, 0x0a, 0x1f, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74,
0x65, 0x6d, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x24, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f,
0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f,
0x55, 0x4e, 0x49, 0x54, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12,
0x28, 0x0a, 0x24, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d,
0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x53, 0x5f,
0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x32, 0x94, 0x03, 0x0a, 0x06, 0x44, 0x65,
0x62, 0x75, 0x67, 0x64, 0x12, 0x3c, 0x0a, 0x07, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12,
0x16, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64,
0x2e, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x12, 0x3c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e,
0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x47,
0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x4b, 0x0a, 0x12, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74,
0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x12, 0x0d, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e,
0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x22, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55,
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65,
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x4e, 0x0a,
0x14, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72,
0x61, 0x70, 0x70, 0x65, 0x72, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x44,
0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70,
0x70, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x64, 0x65, 0x62,
0x75, 0x67, 0x64, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x71, 0x0a,
0x18, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x64, 0x65, 0x62, 0x75,
0x67, 0x64, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55, 0x70, 0x6c,
0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65,
0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x74,
0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x64, 0x65, 0x62, 0x75,
0x67, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x40, 0x0a, 0x0f, 0x53, 0x65,
0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e,
0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x10, 0x0a, 0x0e,
0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x33,
0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x20, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x0c, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69,
0x6e, 0x66, 0x6f, 0x22, 0x2e, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46,
0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x7a, 0x0a, 0x13, 0x46,
0x69, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x46, 0x69, 0x6c, 0x65,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00,
0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x25, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e,
0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64,
0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x48, 0x00, 0x52, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x42,
0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x97, 0x01, 0x0a, 0x12, 0x46, 0x69, 0x6c, 0x65,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1e,
0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12,
0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x6d, 0x6f,
0x64, 0x65, 0x12, 0x35, 0x0a, 0x13, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48,
0x00, 0x52, 0x13, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x88, 0x01, 0x01, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x6f, 0x76,
0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69,
0x74, 0x22, 0x35, 0x0a, 0x05, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f,
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e,
0x74, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01,
0x28, 0x08, 0x52, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x22, 0x48, 0x0a, 0x13, 0x55, 0x70, 0x6c, 0x6f,
0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x31, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x19, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46,
0x69, 0x6c, 0x65, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
0x73, 0x22, 0x4d, 0x0a, 0x20, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65,
0x6d, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73,
0x22, 0x64, 0x0a, 0x21, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55,
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, 0x3f, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66,
0x6f, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x45, 0x54, 0x5f, 0x49,
0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a,
0x14, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41, 0x4c, 0x52, 0x45, 0x41, 0x44,
0x59, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x01, 0x2a, 0xb1, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x6c, 0x6f,
0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a,
0x14, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x53, 0x5f, 0x53, 0x55,
0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x50, 0x4c, 0x4f, 0x41,
0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x53, 0x5f, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x46,
0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x55, 0x50, 0x4c, 0x4f, 0x41,
0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x53, 0x5f, 0x41, 0x4c, 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f,
0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x55, 0x50, 0x4c,
0x4f, 0x41, 0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x53, 0x5f, 0x41, 0x4c, 0x52, 0x45, 0x41, 0x44,
0x59, 0x5f, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x03, 0x12, 0x1d, 0x0a, 0x19,
0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x53, 0x5f, 0x53, 0x54, 0x41,
0x52, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x75, 0x0a, 0x1f, 0x55,
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28,
0x0a, 0x24, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x44,
0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x53, 0x5f, 0x53,
0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, 0x55, 0x50, 0x4c, 0x4f,
0x41, 0x44, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49,
0x43, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45,
0x10, 0x01, 0x32, 0x94, 0x03, 0x0a, 0x06, 0x44, 0x65, 0x62, 0x75, 0x67, 0x64, 0x12, 0x3c, 0x0a,
0x07, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67,
0x64, 0x2e, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x17, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x66,
0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x07, 0x47,
0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e,
0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17,
0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0b, 0x55, 0x70, 0x6c,
0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67,
0x64, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1b, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55,
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x4e, 0x0a, 0x0d, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f,
0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64,
0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x46,
0x69, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x71, 0x0a, 0x18, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64,
0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69,
0x74, 0x73, 0x12, 0x28, 0x2e, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55, 0x70, 0x6c, 0x6f,
0x61, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x64,
0x65, 0x62, 0x75, 0x67, 0x64, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x79, 0x73, 0x74,
0x65, 0x6d, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73,
0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -728,44 +962,50 @@ func file_debugd_proto_rawDescGZIP() []byte {
return file_debugd_proto_rawDescData
}
var file_debugd_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_debugd_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_debugd_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_debugd_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
var file_debugd_proto_goTypes = []interface{}{
(UploadBootstrapperStatus)(0), // 0: debugd.UploadBootstrapperStatus
(UploadSystemdServiceUnitsStatus)(0), // 1: debugd.UploadSystemdServiceUnitsStatus
(*SetInfoRequest)(nil), // 2: debugd.SetInfoRequest
(*SetInfoResponse)(nil), // 3: debugd.SetInfoResponse
(*GetInfoRequest)(nil), // 4: debugd.GetInfoRequest
(*GetInfoResponse)(nil), // 5: debugd.GetInfoResponse
(*Info)(nil), // 6: debugd.Info
(*DownloadBootstrapperRequest)(nil), // 7: debugd.DownloadBootstrapperRequest
(*Chunk)(nil), // 8: debugd.Chunk
(*UploadBootstrapperResponse)(nil), // 9: debugd.UploadBootstrapperResponse
(*ServiceUnit)(nil), // 10: debugd.ServiceUnit
(*UploadSystemdServiceUnitsRequest)(nil), // 11: debugd.UploadSystemdServiceUnitsRequest
(*UploadSystemdServiceUnitsResponse)(nil), // 12: debugd.UploadSystemdServiceUnitsResponse
(SetInfoStatus)(0), // 0: debugd.SetInfoStatus
(UploadFilesStatus)(0), // 1: debugd.UploadFilesStatus
(UploadSystemdServiceUnitsStatus)(0), // 2: debugd.UploadSystemdServiceUnitsStatus
(*SetInfoRequest)(nil), // 3: debugd.SetInfoRequest
(*SetInfoResponse)(nil), // 4: debugd.SetInfoResponse
(*GetInfoRequest)(nil), // 5: debugd.GetInfoRequest
(*GetInfoResponse)(nil), // 6: debugd.GetInfoResponse
(*Info)(nil), // 7: debugd.Info
(*DownloadFilesRequest)(nil), // 8: debugd.DownloadFilesRequest
(*FileTransferMessage)(nil), // 9: debugd.FileTransferMessage
(*FileTransferHeader)(nil), // 10: debugd.FileTransferHeader
(*Chunk)(nil), // 11: debugd.Chunk
(*UploadFilesResponse)(nil), // 12: debugd.UploadFilesResponse
(*ServiceUnit)(nil), // 13: debugd.ServiceUnit
(*UploadSystemdServiceUnitsRequest)(nil), // 14: debugd.UploadSystemdServiceUnitsRequest
(*UploadSystemdServiceUnitsResponse)(nil), // 15: debugd.UploadSystemdServiceUnitsResponse
}
var file_debugd_proto_depIdxs = []int32{
6, // 0: debugd.SetInfoRequest.info:type_name -> debugd.Info
6, // 1: debugd.GetInfoResponse.info:type_name -> debugd.Info
0, // 2: debugd.UploadBootstrapperResponse.status:type_name -> debugd.UploadBootstrapperStatus
10, // 3: debugd.UploadSystemdServiceUnitsRequest.units:type_name -> debugd.ServiceUnit
1, // 4: debugd.UploadSystemdServiceUnitsResponse.status:type_name -> debugd.UploadSystemdServiceUnitsStatus
2, // 5: debugd.Debugd.SetInfo:input_type -> debugd.SetInfoRequest
4, // 6: debugd.Debugd.GetInfo:input_type -> debugd.GetInfoRequest
8, // 7: debugd.Debugd.UploadBootstrapper:input_type -> debugd.Chunk
7, // 8: debugd.Debugd.DownloadBootstrapper:input_type -> debugd.DownloadBootstrapperRequest
11, // 9: debugd.Debugd.UploadSystemServiceUnits:input_type -> debugd.UploadSystemdServiceUnitsRequest
3, // 10: debugd.Debugd.SetInfo:output_type -> debugd.SetInfoResponse
5, // 11: debugd.Debugd.GetInfo:output_type -> debugd.GetInfoResponse
9, // 12: debugd.Debugd.UploadBootstrapper:output_type -> debugd.UploadBootstrapperResponse
8, // 13: debugd.Debugd.DownloadBootstrapper:output_type -> debugd.Chunk
12, // 14: debugd.Debugd.UploadSystemServiceUnits:output_type -> debugd.UploadSystemdServiceUnitsResponse
10, // [10:15] is the sub-list for method output_type
5, // [5:10] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
7, // 0: debugd.SetInfoRequest.info:type_name -> debugd.Info
0, // 1: debugd.SetInfoResponse.status:type_name -> debugd.SetInfoStatus
7, // 2: debugd.GetInfoResponse.info:type_name -> debugd.Info
10, // 3: debugd.FileTransferMessage.header:type_name -> debugd.FileTransferHeader
11, // 4: debugd.FileTransferMessage.chunk:type_name -> debugd.Chunk
1, // 5: debugd.UploadFilesResponse.status:type_name -> debugd.UploadFilesStatus
13, // 6: debugd.UploadSystemdServiceUnitsRequest.units:type_name -> debugd.ServiceUnit
2, // 7: debugd.UploadSystemdServiceUnitsResponse.status:type_name -> debugd.UploadSystemdServiceUnitsStatus
3, // 8: debugd.Debugd.SetInfo:input_type -> debugd.SetInfoRequest
5, // 9: debugd.Debugd.GetInfo:input_type -> debugd.GetInfoRequest
9, // 10: debugd.Debugd.UploadFiles:input_type -> debugd.FileTransferMessage
8, // 11: debugd.Debugd.DownloadFiles:input_type -> debugd.DownloadFilesRequest
14, // 12: debugd.Debugd.UploadSystemServiceUnits:input_type -> debugd.UploadSystemdServiceUnitsRequest
4, // 13: debugd.Debugd.SetInfo:output_type -> debugd.SetInfoResponse
6, // 14: debugd.Debugd.GetInfo:output_type -> debugd.GetInfoResponse
12, // 15: debugd.Debugd.UploadFiles:output_type -> debugd.UploadFilesResponse
9, // 16: debugd.Debugd.DownloadFiles:output_type -> debugd.FileTransferMessage
15, // 17: debugd.Debugd.UploadSystemServiceUnits:output_type -> debugd.UploadSystemdServiceUnitsResponse
13, // [13:18] is the sub-list for method output_type
8, // [8:13] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
}
func init() { file_debugd_proto_init() }
@ -835,7 +1075,7 @@ func file_debugd_proto_init() {
}
}
file_debugd_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DownloadBootstrapperRequest); i {
switch v := v.(*DownloadFilesRequest); i {
case 0:
return &v.state
case 1:
@ -847,7 +1087,7 @@ func file_debugd_proto_init() {
}
}
file_debugd_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Chunk); i {
switch v := v.(*FileTransferMessage); i {
case 0:
return &v.state
case 1:
@ -859,7 +1099,7 @@ func file_debugd_proto_init() {
}
}
file_debugd_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UploadBootstrapperResponse); i {
switch v := v.(*FileTransferHeader); i {
case 0:
return &v.state
case 1:
@ -871,7 +1111,7 @@ func file_debugd_proto_init() {
}
}
file_debugd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ServiceUnit); i {
switch v := v.(*Chunk); i {
case 0:
return &v.state
case 1:
@ -883,7 +1123,7 @@ func file_debugd_proto_init() {
}
}
file_debugd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UploadSystemdServiceUnitsRequest); i {
switch v := v.(*UploadFilesResponse); i {
case 0:
return &v.state
case 1:
@ -895,6 +1135,30 @@ func file_debugd_proto_init() {
}
}
file_debugd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ServiceUnit); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_debugd_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UploadSystemdServiceUnitsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_debugd_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UploadSystemdServiceUnitsResponse); i {
case 0:
return &v.state
@ -907,13 +1171,18 @@ func file_debugd_proto_init() {
}
}
}
file_debugd_proto_msgTypes[6].OneofWrappers = []interface{}{
(*FileTransferMessage_Header)(nil),
(*FileTransferMessage_Chunk)(nil),
}
file_debugd_proto_msgTypes[7].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_debugd_proto_rawDesc,
NumEnums: 2,
NumMessages: 11,
NumEnums: 3,
NumMessages: 13,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -7,8 +7,8 @@ package debugd;
service Debugd {
rpc SetInfo (SetInfoRequest) returns (SetInfoResponse) {}
rpc GetInfo (GetInfoRequest) returns (GetInfoResponse) {}
rpc UploadBootstrapper(stream Chunk) returns (UploadBootstrapperResponse) {}
rpc DownloadBootstrapper(DownloadBootstrapperRequest) returns (stream Chunk) {}
rpc UploadFiles(stream FileTransferMessage) returns (UploadFilesResponse) {}
rpc DownloadFiles(DownloadFilesRequest) returns (stream FileTransferMessage) {}
rpc UploadSystemServiceUnits(UploadSystemdServiceUnitsRequest) returns (UploadSystemdServiceUnitsResponse) {}
}
@ -16,7 +16,14 @@ message SetInfoRequest {
repeated Info info = 1;
}
message SetInfoResponse {}
message SetInfoResponse {
SetInfoStatus status = 1;
}
enum SetInfoStatus {
SET_INFO_SUCCESS = 0;
SET_INFO_ALREADY_SET = 1;
}
message GetInfoRequest {}
@ -29,22 +36,36 @@ message Info {
string value = 2;
}
message DownloadFilesRequest {}
message DownloadBootstrapperRequest {}
message FileTransferMessage {
oneof kind {
FileTransferHeader header = 1; // start of transfer
Chunk chunk = 2; // file content as chunks
}
}
message FileTransferHeader {
string targetPath = 1;
uint32 mode = 3;
optional string overrideServiceUnit = 4;
}
message Chunk {
bytes content = 1;
bool last = 2;
}
message UploadBootstrapperResponse {
UploadBootstrapperStatus status = 1;
message UploadFilesResponse {
UploadFilesStatus status = 1;
}
enum UploadBootstrapperStatus {
UPLOAD_BOOTSTRAPPER_SUCCESS = 0;
UPLOAD_BOOTSTRAPPER_UPLOAD_FAILED = 1;
UPLOAD_BOOTSTRAPPER_START_FAILED = 2;
UPLOAD_BOOTSTRAPPER_FILE_EXISTS = 3;
enum UploadFilesStatus {
UPLOAD_FILES_SUCCESS = 0;
UPLOAD_FILES_UPLOAD_FAILED = 1;
UPLOAD_FILES_ALREADY_STARTED = 2;
UPLOAD_FILES_ALREADY_FINISHED = 3;
UPLOAD_FILES_START_FAILED = 4;
}
message ServiceUnit {

View File

@ -24,8 +24,8 @@ const _ = grpc.SupportPackageIsVersion7
type DebugdClient interface {
SetInfo(ctx context.Context, in *SetInfoRequest, opts ...grpc.CallOption) (*SetInfoResponse, error)
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
UploadBootstrapper(ctx context.Context, opts ...grpc.CallOption) (Debugd_UploadBootstrapperClient, error)
DownloadBootstrapper(ctx context.Context, in *DownloadBootstrapperRequest, opts ...grpc.CallOption) (Debugd_DownloadBootstrapperClient, error)
UploadFiles(ctx context.Context, opts ...grpc.CallOption) (Debugd_UploadFilesClient, error)
DownloadFiles(ctx context.Context, in *DownloadFilesRequest, opts ...grpc.CallOption) (Debugd_DownloadFilesClient, error)
UploadSystemServiceUnits(ctx context.Context, in *UploadSystemdServiceUnitsRequest, opts ...grpc.CallOption) (*UploadSystemdServiceUnitsResponse, error)
}
@ -55,46 +55,46 @@ func (c *debugdClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...
return out, nil
}
func (c *debugdClient) UploadBootstrapper(ctx context.Context, opts ...grpc.CallOption) (Debugd_UploadBootstrapperClient, error) {
stream, err := c.cc.NewStream(ctx, &Debugd_ServiceDesc.Streams[0], "/debugd.Debugd/UploadBootstrapper", opts...)
func (c *debugdClient) UploadFiles(ctx context.Context, opts ...grpc.CallOption) (Debugd_UploadFilesClient, error) {
stream, err := c.cc.NewStream(ctx, &Debugd_ServiceDesc.Streams[0], "/debugd.Debugd/UploadFiles", opts...)
if err != nil {
return nil, err
}
x := &debugdUploadBootstrapperClient{stream}
x := &debugdUploadFilesClient{stream}
return x, nil
}
type Debugd_UploadBootstrapperClient interface {
Send(*Chunk) error
CloseAndRecv() (*UploadBootstrapperResponse, error)
type Debugd_UploadFilesClient interface {
Send(*FileTransferMessage) error
CloseAndRecv() (*UploadFilesResponse, error)
grpc.ClientStream
}
type debugdUploadBootstrapperClient struct {
type debugdUploadFilesClient struct {
grpc.ClientStream
}
func (x *debugdUploadBootstrapperClient) Send(m *Chunk) error {
func (x *debugdUploadFilesClient) Send(m *FileTransferMessage) error {
return x.ClientStream.SendMsg(m)
}
func (x *debugdUploadBootstrapperClient) CloseAndRecv() (*UploadBootstrapperResponse, error) {
func (x *debugdUploadFilesClient) CloseAndRecv() (*UploadFilesResponse, error) {
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
m := new(UploadBootstrapperResponse)
m := new(UploadFilesResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *debugdClient) DownloadBootstrapper(ctx context.Context, in *DownloadBootstrapperRequest, opts ...grpc.CallOption) (Debugd_DownloadBootstrapperClient, error) {
stream, err := c.cc.NewStream(ctx, &Debugd_ServiceDesc.Streams[1], "/debugd.Debugd/DownloadBootstrapper", opts...)
func (c *debugdClient) DownloadFiles(ctx context.Context, in *DownloadFilesRequest, opts ...grpc.CallOption) (Debugd_DownloadFilesClient, error) {
stream, err := c.cc.NewStream(ctx, &Debugd_ServiceDesc.Streams[1], "/debugd.Debugd/DownloadFiles", opts...)
if err != nil {
return nil, err
}
x := &debugdDownloadBootstrapperClient{stream}
x := &debugdDownloadFilesClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
@ -104,17 +104,17 @@ func (c *debugdClient) DownloadBootstrapper(ctx context.Context, in *DownloadBoo
return x, nil
}
type Debugd_DownloadBootstrapperClient interface {
Recv() (*Chunk, error)
type Debugd_DownloadFilesClient interface {
Recv() (*FileTransferMessage, error)
grpc.ClientStream
}
type debugdDownloadBootstrapperClient struct {
type debugdDownloadFilesClient struct {
grpc.ClientStream
}
func (x *debugdDownloadBootstrapperClient) Recv() (*Chunk, error) {
m := new(Chunk)
func (x *debugdDownloadFilesClient) Recv() (*FileTransferMessage, error) {
m := new(FileTransferMessage)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
@ -136,8 +136,8 @@ func (c *debugdClient) UploadSystemServiceUnits(ctx context.Context, in *UploadS
type DebugdServer interface {
SetInfo(context.Context, *SetInfoRequest) (*SetInfoResponse, error)
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
UploadBootstrapper(Debugd_UploadBootstrapperServer) error
DownloadBootstrapper(*DownloadBootstrapperRequest, Debugd_DownloadBootstrapperServer) error
UploadFiles(Debugd_UploadFilesServer) error
DownloadFiles(*DownloadFilesRequest, Debugd_DownloadFilesServer) error
UploadSystemServiceUnits(context.Context, *UploadSystemdServiceUnitsRequest) (*UploadSystemdServiceUnitsResponse, error)
mustEmbedUnimplementedDebugdServer()
}
@ -152,11 +152,11 @@ func (UnimplementedDebugdServer) SetInfo(context.Context, *SetInfoRequest) (*Set
func (UnimplementedDebugdServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
}
func (UnimplementedDebugdServer) UploadBootstrapper(Debugd_UploadBootstrapperServer) error {
return status.Errorf(codes.Unimplemented, "method UploadBootstrapper not implemented")
func (UnimplementedDebugdServer) UploadFiles(Debugd_UploadFilesServer) error {
return status.Errorf(codes.Unimplemented, "method UploadFiles not implemented")
}
func (UnimplementedDebugdServer) DownloadBootstrapper(*DownloadBootstrapperRequest, Debugd_DownloadBootstrapperServer) error {
return status.Errorf(codes.Unimplemented, "method DownloadBootstrapper not implemented")
func (UnimplementedDebugdServer) DownloadFiles(*DownloadFilesRequest, Debugd_DownloadFilesServer) error {
return status.Errorf(codes.Unimplemented, "method DownloadFiles not implemented")
}
func (UnimplementedDebugdServer) UploadSystemServiceUnits(context.Context, *UploadSystemdServiceUnitsRequest) (*UploadSystemdServiceUnitsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UploadSystemServiceUnits not implemented")
@ -210,50 +210,50 @@ func _Debugd_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(inte
return interceptor(ctx, in, info, handler)
}
func _Debugd_UploadBootstrapper_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(DebugdServer).UploadBootstrapper(&debugdUploadBootstrapperServer{stream})
func _Debugd_UploadFiles_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(DebugdServer).UploadFiles(&debugdUploadFilesServer{stream})
}
type Debugd_UploadBootstrapperServer interface {
SendAndClose(*UploadBootstrapperResponse) error
Recv() (*Chunk, error)
type Debugd_UploadFilesServer interface {
SendAndClose(*UploadFilesResponse) error
Recv() (*FileTransferMessage, error)
grpc.ServerStream
}
type debugdUploadBootstrapperServer struct {
type debugdUploadFilesServer struct {
grpc.ServerStream
}
func (x *debugdUploadBootstrapperServer) SendAndClose(m *UploadBootstrapperResponse) error {
func (x *debugdUploadFilesServer) SendAndClose(m *UploadFilesResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *debugdUploadBootstrapperServer) Recv() (*Chunk, error) {
m := new(Chunk)
func (x *debugdUploadFilesServer) Recv() (*FileTransferMessage, error) {
m := new(FileTransferMessage)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _Debugd_DownloadBootstrapper_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(DownloadBootstrapperRequest)
func _Debugd_DownloadFiles_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(DownloadFilesRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(DebugdServer).DownloadBootstrapper(m, &debugdDownloadBootstrapperServer{stream})
return srv.(DebugdServer).DownloadFiles(m, &debugdDownloadFilesServer{stream})
}
type Debugd_DownloadBootstrapperServer interface {
Send(*Chunk) error
type Debugd_DownloadFilesServer interface {
Send(*FileTransferMessage) error
grpc.ServerStream
}
type debugdDownloadBootstrapperServer struct {
type debugdDownloadFilesServer struct {
grpc.ServerStream
}
func (x *debugdDownloadBootstrapperServer) Send(m *Chunk) error {
func (x *debugdDownloadFilesServer) Send(m *FileTransferMessage) error {
return x.ServerStream.SendMsg(m)
}
@ -297,13 +297,13 @@ var Debugd_ServiceDesc = grpc.ServiceDesc{
},
Streams: []grpc.StreamDesc{
{
StreamName: "UploadBootstrapper",
Handler: _Debugd_UploadBootstrapper_Handler,
StreamName: "UploadFiles",
Handler: _Debugd_UploadFiles_Handler,
ClientStreams: true,
},
{
StreamName: "DownloadBootstrapper",
Handler: _Debugd_DownloadBootstrapper_Handler,
StreamName: "DownloadFiles",
Handler: _Debugd_DownloadFiles_Handler,
ServerStreams: true,
},
},