Switch to using the dep tool and update all the dependencies
This commit is contained in:
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type ExampleReadHandler struct {
|
||||
buf []byte
|
||||
name string // In this example, the service can handle one name only.
|
||||
}
|
||||
|
||||
func (mr *ExampleReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
|
||||
if mr.name == "" {
|
||||
mr.name = name
|
||||
log.Printf("read from name: %q", name)
|
||||
} else if mr.name != name {
|
||||
return nil, grpc.Errorf(codes.NotFound, "reader has name %q, name %q not allowed", mr.name, name)
|
||||
}
|
||||
return bytes.NewReader(mr.buf), nil
|
||||
}
|
||||
|
||||
// Close can be a no-op.
|
||||
func (mr *ExampleReadHandler) Close(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ExampleWriteHandler struct {
|
||||
buf bytes.Buffer // bytes.Buffer implements io.Writer
|
||||
name string // In this example, the service can handle one name only.
|
||||
}
|
||||
|
||||
// Handle writes to a given name.
|
||||
func (mw *ExampleWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
|
||||
if mw.name == "" {
|
||||
mw.name = name
|
||||
log.Printf("write to name: %q", name)
|
||||
} else if mw.name != name {
|
||||
return nil, grpc.Errorf(codes.NotFound, "reader has name %q, name=%q not allowed", mw.name, name)
|
||||
}
|
||||
// TODO: initOffset is ignored.
|
||||
return &mw.buf, nil
|
||||
}
|
||||
|
||||
// Close can be a no-op.
|
||||
func (mw *ExampleWriteHandler) Close(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExampleNewServer() {
|
||||
reader := &ExampleReadHandler{
|
||||
buf: []byte("Hello World!"),
|
||||
name: "foo",
|
||||
}
|
||||
writer := &ExampleWriteHandler{}
|
||||
gsrv := grpc.NewServer()
|
||||
bytestreamServer, err := NewServer(gsrv, reader, writer)
|
||||
if err != nil {
|
||||
log.Printf("NewServer: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Start accepting incoming connections.
|
||||
// See gRPC docs and newGRPCServer in google.golang.org/api/transport/bytestream/client_test.go.
|
||||
_ = bytestreamServer
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
// This file contains the server implementation of Bytestream declared at:
|
||||
// https://github.com/googleapis/googleapis/blob/master/google/bytestream/bytestream.proto
|
||||
//
|
||||
// Bytestream uses bidirectional streaming (http://grpc.io/docs/guides/concepts.html#bidirectional-streaming-rpc).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
pb "google.golang.org/genproto/googleapis/bytestream"
|
||||
)
|
||||
|
||||
// ReadHandler reads from the Bytestream.
|
||||
// Note: error returns must return an instance of grpc.rpcError unless otherwise handled in grpc-go/rpc_util.go.
|
||||
// http://google.golang.org/grpc provides Errorf(code, fmt, ...) to create instances of grpc.rpcError.
|
||||
// Note: Cancelling the context will abort the stream ("drop the connection"). Consider returning a non-nil error instead.
|
||||
type ReadHandler interface {
|
||||
// GetReader provides an io.ReaderAt, which will not be retained by the Server after the pb.ReadRequest.
|
||||
GetReader(ctx context.Context, name string) (io.ReaderAt, error)
|
||||
// Close does not have to do anything, but is here for if the io.ReaderAt wants to call Close().
|
||||
Close(ctx context.Context, name string) error
|
||||
}
|
||||
|
||||
// WriteHandler handles writes from the Bytestream. For example:
|
||||
// Note: error returns must return an instance of grpc.rpcError unless otherwise handled in grpc-go/rpc_util.go.
|
||||
// grpc-go/rpc_util.go provides the helper func Errorf(code, fmt, ...) to create instances of grpc.rpcError.
|
||||
// Note: Cancelling the context will abort the stream ("drop the connection"). Consider returning a non-nil error instead.
|
||||
type WriteHandler interface {
|
||||
// GetWriter provides an io.Writer that is ready to write at initOffset.
|
||||
// The io.Writer will not be retained by the Server after the pb.WriteRequest.
|
||||
GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error)
|
||||
// Close does not have to do anything, but is related to Server.AllowOverwrite. Or if the io.Writer simply wants a Close() call.
|
||||
// Close is called when the server receives a pb.WriteRequest with finish_write = true.
|
||||
// If Server.AllowOverwrite == true then Close() followed by GetWriter() for the same name indicates the name is being overwritten, even if the initOffset is different.
|
||||
Close(ctx context.Context, name string) error
|
||||
}
|
||||
|
||||
// Internal service that implements pb.ByteStreamServer. Because the methods Write() and Read() are exported for grpc to link against,
|
||||
// grpcService is deliberately not exported so go code cannot call grpcService.Write() or grpcService.Read().
|
||||
type grpcService struct {
|
||||
parent *Server
|
||||
}
|
||||
|
||||
// Server wraps the RPCs in pb. Use bytestream.NewServer() to create a Server.
|
||||
type Server struct {
|
||||
status map[string]*pb.QueryWriteStatusResponse
|
||||
readHandler ReadHandler
|
||||
writeHandler WriteHandler
|
||||
rpc *grpcService
|
||||
|
||||
// AllowOverwrite controls Server behavior when a WriteRequest with finish_write = true is followed by another WriteRequest.
|
||||
AllowOverwrite bool
|
||||
|
||||
// Bytestream allows a WriteRequest to omit the resource name, in which case it will be appended to the last WriteRequest.
|
||||
LastWrittenResource string
|
||||
}
|
||||
|
||||
// NewServer creates a new bytestream.Server using gRPC.
|
||||
// gsrv is the *grpc.Server this bytestream.Server will listen on.
|
||||
// readHandler handles any incoming pb.ReadRequest or nil which means all pb.ReadRequests will be rejected.
|
||||
// writeHandler handles any incoming pb.WriteRequest or nil which means all pb.WriteRequests will be rejected.
|
||||
// readHandler and writeHandler cannot both be nil.
|
||||
func NewServer(gsrv *grpc.Server, readHandler ReadHandler, writeHandler WriteHandler) (*Server, error) {
|
||||
if readHandler == nil && writeHandler == nil {
|
||||
return nil, fmt.Errorf("readHandler and writeHandler cannot both be nil")
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
status: make(map[string]*pb.QueryWriteStatusResponse),
|
||||
readHandler: readHandler,
|
||||
writeHandler: writeHandler,
|
||||
rpc: &grpcService{},
|
||||
}
|
||||
server.rpc.parent = server
|
||||
|
||||
// Register a server.
|
||||
pb.RegisterByteStreamServer(gsrv, server.rpc)
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// Write handles the pb.ByteStream_WriteServer and sends a pb.WriteResponse
|
||||
// Implements bytestream.proto "rpc Write(stream WriteRequest) returns (WriteResponse)".
|
||||
func (rpc *grpcService) Write(stream pb.ByteStream_WriteServer) error {
|
||||
for {
|
||||
writeReq, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
// io.EOF errors are a non-error for the Write() caller.
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return grpc.Errorf(codes.Unknown, "stream.Recv() failed: %v", err)
|
||||
}
|
||||
if rpc.parent.writeHandler == nil {
|
||||
return grpc.Errorf(codes.Unimplemented, "instance of NewServer(writeHandler = nil) rejects all writes")
|
||||
}
|
||||
|
||||
status, ok := rpc.parent.status[writeReq.ResourceName]
|
||||
if !ok {
|
||||
// writeReq.ResourceName is a new resource name.
|
||||
if writeReq.ResourceName == "" {
|
||||
return grpc.Errorf(codes.InvalidArgument, "WriteRequest: empty or missing resource_name")
|
||||
}
|
||||
status = &pb.QueryWriteStatusResponse{
|
||||
CommittedSize: writeReq.WriteOffset,
|
||||
}
|
||||
rpc.parent.status[writeReq.ResourceName] = status
|
||||
} else {
|
||||
// writeReq.ResourceName has already been seen by this server.
|
||||
if status.Complete {
|
||||
if !rpc.parent.AllowOverwrite {
|
||||
return grpc.Errorf(codes.InvalidArgument, "%q finish_write = true already, got %d byte WriteRequest and Server.AllowOverwrite = false",
|
||||
writeReq.ResourceName, len(writeReq.Data))
|
||||
}
|
||||
// Truncate the resource stream.
|
||||
status.Complete = false
|
||||
status.CommittedSize = writeReq.WriteOffset
|
||||
}
|
||||
}
|
||||
|
||||
if writeReq.WriteOffset != status.CommittedSize {
|
||||
return grpc.Errorf(codes.FailedPrecondition, "%q write_offset=%d differs from server internal committed_size=%d",
|
||||
writeReq.ResourceName, writeReq.WriteOffset, status.CommittedSize)
|
||||
}
|
||||
|
||||
// WriteRequest with empty data is ok.
|
||||
if len(writeReq.Data) != 0 {
|
||||
writer, err := rpc.parent.writeHandler.GetWriter(stream.Context(), writeReq.ResourceName, status.CommittedSize)
|
||||
if err != nil {
|
||||
return grpc.Errorf(codes.Internal, "GetWriter(%q): %v", writeReq.ResourceName, err)
|
||||
}
|
||||
wroteLen, err := writer.Write(writeReq.Data)
|
||||
if err != nil {
|
||||
return grpc.Errorf(codes.Internal, "Write(%q): %v", writeReq.ResourceName, err)
|
||||
}
|
||||
status.CommittedSize += int64(wroteLen)
|
||||
}
|
||||
|
||||
if writeReq.FinishWrite {
|
||||
r := &pb.WriteResponse{CommittedSize: status.CommittedSize}
|
||||
// Note: SendAndClose does NOT close the server stream.
|
||||
if err = stream.SendAndClose(r); err != nil {
|
||||
return grpc.Errorf(codes.Internal, "stream.SendAndClose(%q, WriteResponse{ %d }): %v", writeReq.ResourceName, status.CommittedSize, err)
|
||||
}
|
||||
status.Complete = true
|
||||
if status.CommittedSize == 0 {
|
||||
return grpc.Errorf(codes.FailedPrecondition, "writeHandler.Close(%q): 0 bytes written", writeReq.ResourceName)
|
||||
}
|
||||
if err = rpc.parent.writeHandler.Close(stream.Context(), writeReq.ResourceName); err != nil {
|
||||
return grpc.Errorf(codes.Internal, "writeHandler.Close(%q): %v", writeReq.ResourceName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// QueryWriteStatus implements bytestream.proto "rpc QueryWriteStatus(QueryWriteStatusRequest) returns (QueryWriteStatusResponse)".
|
||||
// QueryWriteStatus returns the CommittedSize known to the server.
|
||||
func (rpc *grpcService) QueryWriteStatus(ctx context.Context, request *pb.QueryWriteStatusRequest) (*pb.QueryWriteStatusResponse, error) {
|
||||
s, ok := rpc.parent.status[request.ResourceName]
|
||||
if !ok {
|
||||
return nil, grpc.Errorf(codes.NotFound, "resource_name not found: QueryWriteStatusRequest %v", request)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (rpc *grpcService) readFrom(request *pb.ReadRequest, reader io.ReaderAt, stream pb.ByteStream_ReadServer) error {
|
||||
limit := int(request.ReadLimit)
|
||||
if limit < 0 {
|
||||
return grpc.Errorf(codes.InvalidArgument, "Read(): read_limit=%d is invalid", limit)
|
||||
}
|
||||
offset := request.ReadOffset
|
||||
if offset < 0 {
|
||||
return grpc.Errorf(codes.InvalidArgument, "Read(): offset=%d is invalid", offset)
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
if limit > 0 {
|
||||
buf = make([]byte, limit)
|
||||
} else {
|
||||
buf = make([]byte, 1024*1024) // 1M buffer is reasonable.
|
||||
}
|
||||
bytesSent := 0
|
||||
for limit == 0 || bytesSent < limit {
|
||||
n, err := reader.ReadAt(buf, offset)
|
||||
if n > 0 {
|
||||
if err := stream.Send(&pb.ReadResponse{Data: buf[:n]}); err != nil {
|
||||
return grpc.Errorf(grpc.Code(err), "Send(resourceName=%q offset=%d): %v", request.ResourceName, offset, grpc.ErrorDesc(err))
|
||||
}
|
||||
} else if err == nil {
|
||||
return grpc.Errorf(codes.Internal, "nil error on empty read: io.ReaderAt contract violated")
|
||||
}
|
||||
offset += int64(n)
|
||||
bytesSent += n
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return grpc.Errorf(codes.Unknown, "ReadAt(resourceName=%q offset=%d): %v", request.ResourceName, offset, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read handles a pb.ReadRequest sending bytes to the pb.ByteStream_ReadServer
|
||||
// Implements bytestream.proto "rpc Read(ReadRequest) returns (stream ReadResponse)"
|
||||
func (rpc *grpcService) Read(request *pb.ReadRequest, stream pb.ByteStream_ReadServer) error {
|
||||
if rpc.parent.readHandler == nil {
|
||||
return grpc.Errorf(codes.Unimplemented, "instance of NewServer(readHandler = nil) rejects all reads")
|
||||
}
|
||||
if request == nil {
|
||||
return grpc.Errorf(codes.Internal, "Read(ReadRequest == nil)")
|
||||
}
|
||||
if request.ResourceName == "" {
|
||||
return grpc.Errorf(codes.InvalidArgument, "ReadRequest: empty or missing resource_name")
|
||||
}
|
||||
|
||||
reader, err := rpc.parent.readHandler.GetReader(stream.Context(), request.ResourceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = rpc.readFrom(request, reader, stream); err != nil {
|
||||
rpc.parent.readHandler.Close(stream.Context(), request.ResourceName)
|
||||
return err
|
||||
}
|
||||
if err = rpc.parent.readHandler.Close(stream.Context(), request.ResourceName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+850
@@ -0,0 +1,850 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
pb "google.golang.org/genproto/googleapis/bytestream"
|
||||
)
|
||||
|
||||
const (
|
||||
testName = "testName"
|
||||
testData = "0123456789"
|
||||
)
|
||||
|
||||
var (
|
||||
setupServerOnce sync.Once
|
||||
server *Server
|
||||
)
|
||||
|
||||
func TestNewServerWithInvalidInputs(t *testing.T) {
|
||||
_, err := NewServer(grpc.NewServer(), nil, nil)
|
||||
if err == nil {
|
||||
t.Fatal("NewServer(nil, nil) should not succeed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerWrite(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
writeHandler WriteHandler
|
||||
input []interface{}
|
||||
writeCount int
|
||||
allowEmptyCommits bool
|
||||
allowOverwrite bool
|
||||
wantErr bool
|
||||
wantResponse int
|
||||
}{
|
||||
{
|
||||
name: "empty resource name",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
wantResponse: 0,
|
||||
}, {
|
||||
name: "Recv returns io.EOF",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
io.EOF,
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: false,
|
||||
wantResponse: 0,
|
||||
}, {
|
||||
name: "Recv returns error, 0 WriteRequests",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
errors.New("Recv returns error, 0 WriteRequests"),
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
wantResponse: 0,
|
||||
}, {
|
||||
name: "simple test",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
io.EOF,
|
||||
},
|
||||
writeCount: 1,
|
||||
wantResponse: 1,
|
||||
}, {
|
||||
name: "Recv returns error, 1 WriteRequests",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: false,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
errors.New("Recv returns error, 1 WriteRequests"),
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
wantResponse: 0,
|
||||
}, {
|
||||
name: "attempt to overwrite the same name",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
io.EOF,
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
},
|
||||
writeCount: 2,
|
||||
wantErr: true,
|
||||
wantResponse: 1,
|
||||
}, {
|
||||
name: "overwrite with the same name + AllowOverwrite",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
io.EOF,
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
io.EOF,
|
||||
},
|
||||
writeCount: 2,
|
||||
allowOverwrite: true,
|
||||
wantResponse: 2,
|
||||
}, {
|
||||
name: "two WriteRequests - 1st is empty",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: false,
|
||||
Data: nil,
|
||||
},
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
io.EOF,
|
||||
},
|
||||
writeCount: 1,
|
||||
wantResponse: 1,
|
||||
allowEmptyCommits: true,
|
||||
}, {
|
||||
name: "two WriteRequests - 2nd is empty",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: false,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: int64(len(testData)),
|
||||
FinishWrite: true,
|
||||
Data: nil,
|
||||
},
|
||||
io.EOF,
|
||||
},
|
||||
writeCount: 1,
|
||||
wantResponse: 1,
|
||||
}, {
|
||||
name: "two WriteRequests - all empty",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: false,
|
||||
Data: nil,
|
||||
},
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: nil,
|
||||
},
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
wantResponse: 1,
|
||||
allowEmptyCommits: true,
|
||||
}, {
|
||||
name: "two WriteRequests - varying offset",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 100,
|
||||
FinishWrite: false,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 100 + int64(len(testData)),
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
io.EOF,
|
||||
},
|
||||
writeCount: 1,
|
||||
wantResponse: 1,
|
||||
}, {
|
||||
name: "two WriteRequests - disjoint offset",
|
||||
writeHandler: &TestWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 100,
|
||||
FinishWrite: false,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 200,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
wantResponse: 0,
|
||||
}, {
|
||||
name: "fails with UngettableWriteHandler",
|
||||
writeHandler: &UngettableWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
}, {
|
||||
name: "fails with UnwritableWriteHandler",
|
||||
writeHandler: &UnwritableWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
}, {
|
||||
name: "fails with UnclosableWriteHandler",
|
||||
writeHandler: &UnclosableWriteHandler{},
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
wantResponse: 1,
|
||||
}, {
|
||||
name: "fails with nil WriteHandler",
|
||||
writeHandler: nil,
|
||||
input: []interface{}{
|
||||
&pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
},
|
||||
},
|
||||
writeCount: 1,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for _, tc := range testCases {
|
||||
readHandler := &TestReadHandler{}
|
||||
if tc.writeHandler != nil {
|
||||
readHandler = nil
|
||||
}
|
||||
setupServer(readHandler, tc.writeHandler)
|
||||
server.AllowOverwrite = tc.allowOverwrite
|
||||
var requestCount, responseCount int
|
||||
var err error
|
||||
|
||||
for i := 0; i < tc.writeCount; i++ {
|
||||
err = server.rpc.Write(&fakeWriteServerImpl{
|
||||
ctx: ctx,
|
||||
receiver: func() (*pb.WriteRequest, error) {
|
||||
if requestCount >= len(tc.input) {
|
||||
t.Fatalf("%s: got %d call(s) to Recv, want %d from len(input)", tc.name, requestCount+1, len(tc.input))
|
||||
}
|
||||
v := tc.input[requestCount]
|
||||
requestCount++
|
||||
request, ok := v.(*pb.WriteRequest)
|
||||
if ok {
|
||||
return request, nil
|
||||
}
|
||||
err, ok := v.(error)
|
||||
if !ok {
|
||||
t.Fatalf("%s: unknown input: %v", tc.name, v)
|
||||
}
|
||||
return nil, err
|
||||
},
|
||||
sender: func(response *pb.WriteResponse) error {
|
||||
if !tc.allowEmptyCommits && response.CommittedSize == 0 {
|
||||
t.Fatalf("%s: invalid response: WriteResponse %v", tc.name, response)
|
||||
}
|
||||
responseCount++
|
||||
return nil
|
||||
},
|
||||
})
|
||||
gotErr := (err != nil)
|
||||
if i+1 < tc.writeCount {
|
||||
if gotErr {
|
||||
t.Errorf("%s: Write got err=%v, wantErr=%t, but on Write[%d/%d]. Error should not happen until last call to Write.", tc.name, err, tc.wantErr, i+1, tc.writeCount)
|
||||
break // The t.Errorf conditions below may erroneously fire, pay them no mind.
|
||||
}
|
||||
} else if gotErr != tc.wantErr {
|
||||
t.Errorf("%s: Write got err=%v, wantErr=%t", tc.name, err, tc.wantErr)
|
||||
break // The t.Errorf conditions below may erroneously fire, pay them no mind.
|
||||
}
|
||||
}
|
||||
if requestCount != len(tc.input) {
|
||||
t.Errorf("%s: got %d call(s) to Recv, want %d", tc.name, requestCount, len(tc.input))
|
||||
}
|
||||
if responseCount != tc.wantResponse {
|
||||
t.Errorf("%s: got %d call(s) to SendProto, want %d", tc.name, responseCount, tc.wantResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerWrite_SendAndCloseError(t *testing.T) {
|
||||
const (
|
||||
wantRequest = 2
|
||||
wantResponse = 1
|
||||
)
|
||||
|
||||
ctx := context.Background()
|
||||
setupServer(nil, &TestWriteHandler{})
|
||||
var requestCount, responseCount int
|
||||
|
||||
err := server.rpc.Write(&fakeWriteServerImpl{
|
||||
ctx: ctx,
|
||||
receiver: func() (*pb.WriteRequest, error) {
|
||||
if requestCount >= wantRequest {
|
||||
t.Fatalf("got %d call(s) to Recv, want %d", requestCount+1, wantRequest)
|
||||
}
|
||||
requestCount++
|
||||
return &pb.WriteRequest{
|
||||
ResourceName: testName,
|
||||
WriteOffset: 0,
|
||||
FinishWrite: true,
|
||||
Data: []byte(testData),
|
||||
}, nil
|
||||
},
|
||||
sender: func(response *pb.WriteResponse) error {
|
||||
responseCount++
|
||||
return errors.New("TestServerWrite SendProto error")
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Errorf("Write should have failed, but succeeded")
|
||||
}
|
||||
if requestCount != wantRequest {
|
||||
t.Errorf("got %d call(s) to Recv, want %d", requestCount, wantRequest)
|
||||
}
|
||||
if responseCount != wantResponse {
|
||||
t.Errorf("got %d call(s) to SendProto, want %d", responseCount, wantResponse)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryWriteStatus(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
existingName string
|
||||
requestName string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "existing name should work",
|
||||
existingName: testName,
|
||||
requestName: testName,
|
||||
}, {
|
||||
name: "missing name should break",
|
||||
existingName: testName,
|
||||
requestName: "invalidName",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for _, tc := range testCases {
|
||||
setupServer(nil, &TestWriteHandler{})
|
||||
server.status[tc.existingName] = &pb.QueryWriteStatusResponse{}
|
||||
|
||||
_, err := server.rpc.QueryWriteStatus(ctx, &pb.QueryWriteStatusRequest{
|
||||
ResourceName: tc.requestName,
|
||||
})
|
||||
|
||||
if gotErr := (err != nil); gotErr != tc.wantErr {
|
||||
t.Errorf("%s: QueryWriteStatus(%q) got err=%v, wantErr=%t", tc.name, tc.requestName, err, tc.wantErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerRead(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
readHandler ReadHandler
|
||||
input *pb.ReadRequest
|
||||
readCount int
|
||||
wantErr bool
|
||||
wantResponse []string
|
||||
}{
|
||||
{
|
||||
name: "empty resource name",
|
||||
readHandler: &TestReadHandler{},
|
||||
input: &pb.ReadRequest{
|
||||
ReadLimit: 1,
|
||||
},
|
||||
readCount: 1,
|
||||
wantErr: true,
|
||||
wantResponse: []string{},
|
||||
}, {
|
||||
name: "test ReadLimit=-1",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: -1,
|
||||
},
|
||||
readCount: 1,
|
||||
wantErr: true,
|
||||
}, {
|
||||
name: "test ReadLimit=1",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: 1,
|
||||
},
|
||||
readCount: 1,
|
||||
wantResponse: []string{"0"},
|
||||
}, {
|
||||
name: "test ReadLimit=2",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: 2,
|
||||
},
|
||||
readCount: 1,
|
||||
wantResponse: []string{"01"},
|
||||
}, {
|
||||
name: "test ReadOffset=1 ReadLimit=2",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 1,
|
||||
ReadLimit: 2,
|
||||
},
|
||||
readCount: 1,
|
||||
wantResponse: []string{"12"},
|
||||
}, {
|
||||
name: "test ReadOffset=2 ReadLimit=2",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 2,
|
||||
ReadLimit: 2,
|
||||
},
|
||||
readCount: 1,
|
||||
wantResponse: []string{"23"},
|
||||
}, {
|
||||
name: "read all testData at exactly the limit",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: int64(len(testData)),
|
||||
},
|
||||
readCount: 1,
|
||||
wantResponse: []string{"0123456789"},
|
||||
}, {
|
||||
name: "read all testData",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: int64(len(testData)) * 2,
|
||||
},
|
||||
readCount: 1,
|
||||
wantResponse: []string{"0123456789"},
|
||||
}, {
|
||||
name: "read all testData 2 times",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: int64(len(testData)) * 2,
|
||||
},
|
||||
readCount: 2,
|
||||
wantResponse: []string{"0123456789", "0123456789"},
|
||||
}, {
|
||||
name: "test ReadLimit=0",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: 0,
|
||||
},
|
||||
readCount: 1,
|
||||
wantResponse: []string{"0123456789"},
|
||||
}, {
|
||||
name: "test ReadLimit=1000",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: 1000,
|
||||
},
|
||||
readCount: 1,
|
||||
wantResponse: []string{"0123456789"},
|
||||
}, {
|
||||
name: "fails with UngettableReadHandler",
|
||||
readHandler: &UngettableReadHandler{},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: int64(len(testData)),
|
||||
},
|
||||
readCount: 1,
|
||||
wantErr: true,
|
||||
}, {
|
||||
name: "fails with UnreadableReadHandler",
|
||||
readHandler: &UnreadableReadHandler{},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: int64(len(testData)),
|
||||
},
|
||||
readCount: 1,
|
||||
wantErr: true,
|
||||
}, {
|
||||
name: "fails with UnclosableReadHandler",
|
||||
readHandler: &UnclosableReadHandler{buf: testData},
|
||||
input: &pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: int64(len(testData)) * 2,
|
||||
},
|
||||
readCount: 1,
|
||||
wantErr: true,
|
||||
wantResponse: []string{"0123456789"},
|
||||
}, {
|
||||
name: "fails with nil ReadRequest",
|
||||
readHandler: &TestReadHandler{buf: testData},
|
||||
readCount: 1,
|
||||
wantErr: true,
|
||||
}, {
|
||||
name: "fails with nil ReadHandler",
|
||||
readHandler: nil,
|
||||
readCount: 1,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for _, tc := range testCases {
|
||||
var writeHandler WriteHandler
|
||||
if tc.readHandler == nil {
|
||||
writeHandler = &TestWriteHandler{}
|
||||
}
|
||||
setupServer(tc.readHandler, writeHandler)
|
||||
var responseCount int
|
||||
var err error
|
||||
|
||||
for i := 0; i < tc.readCount; i++ {
|
||||
err = server.rpc.Read(tc.input, &fakeReadServerImpl{
|
||||
ctx: ctx,
|
||||
sender: func(response *pb.ReadResponse) error {
|
||||
if responseCount >= len(tc.wantResponse) {
|
||||
t.Fatalf("%s: got %d call(s) to Send(), want %d", tc.name, responseCount+1, len(tc.wantResponse))
|
||||
}
|
||||
if got, want := string(response.Data), tc.wantResponse[responseCount]; got != want {
|
||||
t.Fatalf("%s: response[%d] got %q, want %q", tc.name, responseCount, got, want)
|
||||
}
|
||||
responseCount++
|
||||
return nil
|
||||
},
|
||||
})
|
||||
gotErr := (err != nil)
|
||||
if i+1 < tc.readCount {
|
||||
if gotErr {
|
||||
t.Errorf("%s: Read got err=%v, wantErr=%t, but on Read[%d/%d]. Error should not happen until last call to Read", tc.name, err, tc.wantErr, i+1, tc.readCount)
|
||||
break
|
||||
}
|
||||
} else if gotErr != tc.wantErr {
|
||||
t.Errorf("%s: Read got err=%v, wantErr=%t", tc.name, err, tc.wantErr)
|
||||
break
|
||||
}
|
||||
}
|
||||
if responseCount != len(tc.wantResponse) {
|
||||
t.Errorf("%s: got %d call(s) to Send, want %d", tc.name, responseCount, len(tc.wantResponse))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerRead_SendError(t *testing.T) {
|
||||
setupServer(&TestReadHandler{buf: testData}, nil)
|
||||
|
||||
err := server.rpc.Read(&pb.ReadRequest{
|
||||
ResourceName: testName,
|
||||
ReadOffset: 0,
|
||||
ReadLimit: int64(len(testData)) * 2,
|
||||
}, &fakeReadServerImpl{
|
||||
ctx: context.Background(),
|
||||
sender: func(response *pb.ReadResponse) error {
|
||||
if string(response.Data) != testData {
|
||||
t.Fatalf("Send: got %v, want %q", response, testData)
|
||||
}
|
||||
return errors.New("TestServerRead Send() error")
|
||||
},
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Read() should have failed, but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeWriteServerImpl struct {
|
||||
pb.ByteStream_WriteServer
|
||||
ctx context.Context
|
||||
receiver func() (*pb.WriteRequest, error)
|
||||
sender func(*pb.WriteResponse) error
|
||||
}
|
||||
|
||||
func (fake *fakeWriteServerImpl) Context() context.Context {
|
||||
return fake.ctx
|
||||
}
|
||||
|
||||
func (fake *fakeWriteServerImpl) Recv() (*pb.WriteRequest, error) {
|
||||
return fake.receiver()
|
||||
}
|
||||
|
||||
func (fake *fakeWriteServerImpl) SendMsg(m interface{}) error {
|
||||
return fake.sender(m.(*pb.WriteResponse))
|
||||
}
|
||||
|
||||
func (fake *fakeWriteServerImpl) SendAndClose(m *pb.WriteResponse) error {
|
||||
fake.sender(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeReadServerImpl struct {
|
||||
pb.ByteStream_ReadServer
|
||||
ctx context.Context
|
||||
sender func(*pb.ReadResponse) error
|
||||
}
|
||||
|
||||
func (fake *fakeReadServerImpl) Context() context.Context {
|
||||
return fake.ctx
|
||||
}
|
||||
|
||||
func (fake *fakeReadServerImpl) Send(response *pb.ReadResponse) error {
|
||||
return fake.sender(response)
|
||||
}
|
||||
|
||||
type TestWriteHandler struct {
|
||||
buf bytes.Buffer // bytes.Buffer implements io.Writer
|
||||
name string // This service can handle one name only.
|
||||
}
|
||||
|
||||
func (w *TestWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
|
||||
if w.name == "" {
|
||||
w.name = name
|
||||
} else if w.name != name {
|
||||
return nil, fmt.Errorf("writer already has name=%q, now a new name=%q confuses me", w.name, name)
|
||||
}
|
||||
// initOffset is ignored.
|
||||
return &w.buf, nil
|
||||
}
|
||||
|
||||
func (w *TestWriteHandler) Close(ctx context.Context, name string) error {
|
||||
w.name = ""
|
||||
w.buf.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
type UngettableWriteHandler struct{}
|
||||
|
||||
func (w *UngettableWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
|
||||
return nil, errors.New("UngettableWriteHandler.GetWriter() always fails")
|
||||
}
|
||||
|
||||
func (w *UngettableWriteHandler) Close(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UnwritableWriter struct{}
|
||||
|
||||
func (w *UnwritableWriter) Write(p []byte) (int, error) {
|
||||
return 0, errors.New("UnwritableWriter.Write() always fails")
|
||||
}
|
||||
|
||||
type UnwritableWriteHandler struct{}
|
||||
|
||||
func (w *UnwritableWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
|
||||
return &UnwritableWriter{}, nil
|
||||
}
|
||||
|
||||
func (w *UnwritableWriteHandler) Close(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UnclosableWriter struct{}
|
||||
|
||||
func (w *UnclosableWriter) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type UnclosableWriteHandler struct{}
|
||||
|
||||
func (w *UnclosableWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
|
||||
return &UnclosableWriter{}, nil
|
||||
}
|
||||
|
||||
func (w *UnclosableWriteHandler) Close(ctx context.Context, name string) error {
|
||||
return errors.New("UnclosableWriteHandler.Close() always fails")
|
||||
}
|
||||
|
||||
type TestReadHandler struct {
|
||||
buf string
|
||||
name string // This service can handle one name only.
|
||||
}
|
||||
|
||||
// GetWriter() returns an io.ReaderAt to accept reads from the given name.
|
||||
func (r *TestReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
|
||||
if r.name == "" {
|
||||
r.name = name
|
||||
} else if r.name != name {
|
||||
return nil, fmt.Errorf("reader already has name=%q, now a new name=%q confuses me", r.name, name)
|
||||
}
|
||||
return bytes.NewReader([]byte(r.buf)), nil
|
||||
}
|
||||
|
||||
// Close does nothing.
|
||||
func (r *TestReadHandler) Close(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UngettableReadHandler struct{}
|
||||
|
||||
func (r *UngettableReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
|
||||
return nil, errors.New("UngettableReadHandler.GetReader() always fails")
|
||||
}
|
||||
|
||||
func (r *UngettableReadHandler) Close(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UnreadableReader struct{}
|
||||
|
||||
func (r *UnreadableReader) ReadAt(p []byte, offset int64) (int, error) {
|
||||
return 0, errors.New("UnreadableReader.ReadAt() always fails")
|
||||
}
|
||||
|
||||
type UnreadableReadHandler struct{}
|
||||
|
||||
func (r *UnreadableReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
|
||||
return &UnreadableReader{}, nil
|
||||
}
|
||||
|
||||
func (r *UnreadableReadHandler) Close(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UnclosableReadHandler struct {
|
||||
buf string
|
||||
}
|
||||
|
||||
func (r *UnclosableReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
|
||||
return bytes.NewReader([]byte(r.buf)), nil
|
||||
}
|
||||
|
||||
func (r *UnclosableReadHandler) Close(ctx context.Context, name string) error {
|
||||
return fmt.Errorf("UnclosableReader.Close(%s) always fails", name)
|
||||
}
|
||||
|
||||
func registerServer() {
|
||||
gsrv := grpc.NewServer()
|
||||
var err error
|
||||
server, err = NewServer(gsrv, &TestReadHandler{}, &TestWriteHandler{})
|
||||
if err != nil {
|
||||
log.Fatalf("NewServer() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupServer(readHandler ReadHandler, writeHandler WriteHandler) {
|
||||
setupServerOnce.Do(registerServer)
|
||||
server.status = make(map[string]*pb.QueryWriteStatusResponse)
|
||||
server.readHandler = readHandler
|
||||
server.writeHandler = writeHandler
|
||||
}
|
||||
Reference in New Issue
Block a user