From 52e35397d0cca60c9e69c4bb811935e6d3418744 Mon Sep 17 00:00:00 2001 From: Brenda Chan Date: Mon, 18 Jun 2018 17:34:48 -0400 Subject: [PATCH 1/2] Adds sample gRPC application * this sample depends on https://github.com/knative/serving/issues/1047 to be complete --- sample/grpc-ping/Dockerfile | 33 +++++ sample/grpc-ping/README.md | 51 ++++++++ sample/grpc-ping/client/client.go | 85 ++++++++++++ sample/grpc-ping/grpc-ping.go | 64 +++++++++ sample/grpc-ping/proto/ping.pb.go | 210 ++++++++++++++++++++++++++++++ sample/grpc-ping/proto/ping.proto | 17 +++ sample/grpc-ping/sample.yaml | 24 ++++ 7 files changed, 484 insertions(+) create mode 100644 sample/grpc-ping/Dockerfile create mode 100644 sample/grpc-ping/README.md create mode 100644 sample/grpc-ping/client/client.go create mode 100644 sample/grpc-ping/grpc-ping.go create mode 100644 sample/grpc-ping/proto/ping.pb.go create mode 100644 sample/grpc-ping/proto/ping.proto create mode 100644 sample/grpc-ping/sample.yaml diff --git a/sample/grpc-ping/Dockerfile b/sample/grpc-ping/Dockerfile new file mode 100644 index 000000000000..b7bbee595608 --- /dev/null +++ b/sample/grpc-ping/Dockerfile @@ -0,0 +1,33 @@ +# Copyright 2018 Google LLC +# +# 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 +# +# https://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. + +FROM golang AS builder +ARG SAMPLE +ARG BUILDTAG + +# Get the dependencies from GitHub +RUN go get google.golang.org/grpc + +WORKDIR /go/src/github.com/knative/serving +ADD . /go/src/github.com/knative/serving + +RUN CGO_ENABLED=0 go build -tags=${BUILDTAG} ./sample/${SAMPLE} + +FROM gcr.io/distroless/base +ARG SAMPLE + +EXPOSE 8080 +COPY --from=builder /go/src/github.com/knative/serving/${SAMPLE} /sample + +ENTRYPOINT ["/sample"] diff --git a/sample/grpc-ping/README.md b/sample/grpc-ping/README.md new file mode 100644 index 000000000000..1839c5ea533f --- /dev/null +++ b/sample/grpc-ping/README.md @@ -0,0 +1,51 @@ +# gRPC Ping + +A simple gRPC server written in Go that you can use for testing. + +This sample is dependent on [this issue](https://github.com/knative/serving/issues/1047) to be complete. + +## Prerequisites + +1. [Install Knative](https://github.com/knative/install/blob/master/README.md) +1. Install [docker](https://www.docker.com/) + +## Build and run the gRPC server + +Build and run the gRPC server. This command will build the server and use `kubectl` to apply the configuration. + +``` +REPO="gcr.io/" + +# Build and publish the container, run from the root directory. +docker build \ + --build-arg SAMPLE=grpc-ping \ + --build-arg BUILDTAG=grpcping \ + --tag "${REPO}/sample/grpc-ping" \ + --file=sample/grpc-ping/Dockerfile . +docker push "${REPO}/sample/grpc-ping" + +# Replace the image reference with our published image. +perl -pi -e "s@github.com/knative/serving/sample/grpc-ping@${REPO}/sample/grpc-ping@g" sample/grpc-ping/*.yaml + +# Deploy the Knative sample +kubectl apply -f sample/grpc-ping/sample.yaml + +``` + +## Use the client to stream messages to the gRPC server + +1. Fetch the created ingress hostname and IP. + +``` +# Put the Ingress Host name into an environment variable. +export SERVICE_HOST=`kubectl get route grpc-ping -o jsonpath="{.status.domain}"` + +# Put the Ingress IP into an environment variable. +export SERVICE_IP=`kubectl get ingress grpc-ping-ela-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +``` + +1. Use the client to send message streams to the gRPC server + +``` +go run -tags=grpcping sample/grpc-ping/client/client.go -server_addr="$SERVICE_IP:80" -server_host_override="$SERVICE_HOST" +``` diff --git a/sample/grpc-ping/client/client.go b/sample/grpc-ping/client/client.go new file mode 100644 index 000000000000..7a3a84a516e4 --- /dev/null +++ b/sample/grpc-ping/client/client.go @@ -0,0 +1,85 @@ +// +build grpcping + +package main + +import ( + "flag" + "fmt" + "io" + "log" + "time" + + pb "github.com/knative/serving/sample/grpc-ping/proto" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +var ( + serverAddr = flag.String("server_addr", "127.0.0.1:8080", "The server address in the format of host:port") + serverHostOverride = flag.String("server_host_override", "grpc.knative.dev", "") +) + +func main() { + flag.Parse() + + opts := []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithAuthority(*serverHostOverride), + } + + conn, err := grpc.Dial(*serverAddr, opts...) + if err != nil { + log.Fatalf("fail to dial: %v", err) + } + defer conn.Close() + client := pb.NewPingServiceClient(conn) + + ping(client, "hello") + pingStream(client, "hello") +} + +func pingStream(client pb.PingServiceClient, msg string) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + stream, err := client.PingStream(ctx) + if err != nil { + log.Fatalf("%v.(_) = _, %v", client, err) + } + + waitc := make(chan struct{}) + go func() { + for { + in, err := stream.Recv() + if err == io.EOF { + // read done. + close(waitc) + return + } + if err != nil { + log.Fatalf("Failed to receive a response : %v", err) + } + log.Printf("Got %s", in.GetMsg()) + } + }() + + i := 0 + for i < 20 { + if err := stream.Send(&pb.Request{Msg: fmt.Sprintf("%s-%d", msg, i)}); err != nil { + log.Fatalf("Failed to send a ping: %v", err) + } + i++ + } + stream.CloseSend() + <-waitc + +} + +func ping(client pb.PingServiceClient, msg string) { + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + rep, err := client.Ping(ctx, &pb.Request{Msg: msg}) + if err != nil { + log.Fatalf("%v.Ping failed %v: ", client, err) + } + log.Printf("Ping got %v\n", rep.GetMsg()) +} diff --git a/sample/grpc-ping/grpc-ping.go b/sample/grpc-ping/grpc-ping.go new file mode 100644 index 000000000000..b502b2b33549 --- /dev/null +++ b/sample/grpc-ping/grpc-ping.go @@ -0,0 +1,64 @@ +// +build grpcping + +package main + +import ( + "fmt" + "io" + "log" + "net" + "time" + + ping "github.com/knative/serving/sample/grpc-ping/proto" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +var port = 8080 + +type pingServer struct { +} + +func (p *pingServer) Ping(ctx context.Context, req *ping.Request) (*ping.Response, error) { + return &ping.Response{Msg: fmt.Sprintf("%s - pong", req.Msg)}, nil +} + +func (p *pingServer) PingStream(stream ping.PingService_PingStreamServer) error { + for { + req, err := stream.Recv() + + if err == io.EOF { + fmt.Println("Client disconnected") + return nil + } + + if err != nil { + fmt.Println("Failed to receive ping") + return err + } + + fmt.Printf("Replying to ping %s at %s\n", req.Msg, time.Now()) + + err = stream.Send(&ping.Response{ + Msg: fmt.Sprintf("pong %s", time.Now()), + }) + + if err != nil { + fmt.Printf("Failed to send pong %s\n", err) + return err + } + } +} + +func main() { + lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port)) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + + pingServer := &pingServer{} + + grpcServer := grpc.NewServer() + ping.RegisterPingServiceServer(grpcServer, pingServer) + grpcServer.Serve(lis) +} diff --git a/sample/grpc-ping/proto/ping.pb.go b/sample/grpc-ping/proto/ping.pb.go new file mode 100644 index 000000000000..508c4256a70d --- /dev/null +++ b/sample/grpc-ping/proto/ping.pb.go @@ -0,0 +1,210 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: ping.proto + +// +build grpcping + +package ping + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Request struct { + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} +func (*Request) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } + +func (m *Request) GetMsg() string { + if m != nil { + return m.Msg + } + return "" +} + +type Response struct { + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} +func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} } + +func (m *Response) GetMsg() string { + if m != nil { + return m.Msg + } + return "" +} + +func init() { + proto.RegisterType((*Request)(nil), "ping.Request") + proto.RegisterType((*Response)(nil), "ping.Response") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for PingService service + +type PingServiceClient interface { + Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + PingStream(ctx context.Context, opts ...grpc.CallOption) (PingService_PingStreamClient, error) +} + +type pingServiceClient struct { + cc *grpc.ClientConn +} + +func NewPingServiceClient(cc *grpc.ClientConn) PingServiceClient { + return &pingServiceClient{cc} +} + +func (c *pingServiceClient) Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := grpc.Invoke(ctx, "/ping.PingService/Ping", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *pingServiceClient) PingStream(ctx context.Context, opts ...grpc.CallOption) (PingService_PingStreamClient, error) { + stream, err := grpc.NewClientStream(ctx, &_PingService_serviceDesc.Streams[0], c.cc, "/ping.PingService/PingStream", opts...) + if err != nil { + return nil, err + } + x := &pingServicePingStreamClient{stream} + return x, nil +} + +type PingService_PingStreamClient interface { + Send(*Request) error + Recv() (*Response, error) + grpc.ClientStream +} + +type pingServicePingStreamClient struct { + grpc.ClientStream +} + +func (x *pingServicePingStreamClient) Send(m *Request) error { + return x.ClientStream.SendMsg(m) +} + +func (x *pingServicePingStreamClient) Recv() (*Response, error) { + m := new(Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for PingService service + +type PingServiceServer interface { + Ping(context.Context, *Request) (*Response, error) + PingStream(PingService_PingStreamServer) error +} + +func RegisterPingServiceServer(s *grpc.Server, srv PingServiceServer) { + s.RegisterService(&_PingService_serviceDesc, srv) +} + +func _PingService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PingServiceServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ping.PingService/Ping", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PingServiceServer).Ping(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _PingService_PingStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(PingServiceServer).PingStream(&pingServicePingStreamServer{stream}) +} + +type PingService_PingStreamServer interface { + Send(*Response) error + Recv() (*Request, error) + grpc.ServerStream +} + +type pingServicePingStreamServer struct { + grpc.ServerStream +} + +func (x *pingServicePingStreamServer) Send(m *Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *pingServicePingStreamServer) Recv() (*Request, error) { + m := new(Request) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _PingService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ping.PingService", + HandlerType: (*PingServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Ping", + Handler: _PingService_Ping_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "PingStream", + Handler: _PingService_PingStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "ping.proto", +} + +func init() { proto.RegisterFile("ping.proto", fileDescriptor1) } + +var fileDescriptor1 = []byte{ + // 139 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0xc8, 0xcc, 0x4b, + 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0xa4, 0xb9, 0xd8, 0x83, 0x52, + 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x04, 0xb8, 0x98, 0x73, 0x8b, 0xd3, 0x25, 0x18, 0x15, 0x18, + 0x35, 0x38, 0x83, 0x40, 0x4c, 0x25, 0x19, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, + 0x54, 0x4c, 0x59, 0xa3, 0x4c, 0x2e, 0xee, 0x80, 0xcc, 0xbc, 0xf4, 0xe0, 0xd4, 0xa2, 0xb2, 0xcc, + 0xe4, 0x54, 0x21, 0x75, 0x2e, 0x16, 0x10, 0x57, 0x88, 0x57, 0x0f, 0x6c, 0x09, 0xd4, 0x54, 0x29, + 0x3e, 0x18, 0x17, 0x62, 0x8e, 0x12, 0x83, 0x90, 0x21, 0x17, 0x17, 0x58, 0x5f, 0x49, 0x51, 0x6a, + 0x62, 0x2e, 0x41, 0xe5, 0x1a, 0x8c, 0x06, 0x8c, 0x49, 0x6c, 0x60, 0x27, 0x1b, 0x03, 0x02, 0x00, + 0x00, 0xff, 0xff, 0x85, 0x87, 0x57, 0xf8, 0xc0, 0x00, 0x00, 0x00, +} diff --git a/sample/grpc-ping/proto/ping.proto b/sample/grpc-ping/proto/ping.proto new file mode 100644 index 000000000000..88cd7f7fccdf --- /dev/null +++ b/sample/grpc-ping/proto/ping.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package ping; + +service PingService { + rpc Ping(Request) returns (Response) {} + rpc PingStream(stream Request) returns (stream Response) {} +} + +message Request { + string msg = 1; +} + +message Response { + string msg = 1; +} + diff --git a/sample/grpc-ping/sample.yaml b/sample/grpc-ping/sample.yaml new file mode 100644 index 000000000000..69e66c42f6a0 --- /dev/null +++ b/sample/grpc-ping/sample.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: serving.knative.dev/v1alpha1 +kind: Route +metadata: + name: grpc-ping + namespace: default +spec: + traffic: + - configurationName: grpc-ping + percent: 100 +--- +apiVersion: serving.knative.dev/v1alpha1 +kind: Configuration +metadata: + name: grpc-ping + namespace: default +spec: + revisionTemplate: + metadata: + labels: + elafros.dev/type: app + spec: + container: + image: github.com/knative/serving/sample/grpc-ping From 5680b447e91472813672c3d8881fb20f257d9088 Mon Sep 17 00:00:00 2001 From: Brenda Chan Date: Tue, 19 Jun 2018 12:07:08 -0400 Subject: [PATCH 2/2] * Adds '-insecure' flag to sample grpc client * Adds comment that the sample grpc server serves h2c by default --- sample/grpc-ping/client/client.go | 8 +++++--- sample/grpc-ping/grpc-ping.go | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/sample/grpc-ping/client/client.go b/sample/grpc-ping/client/client.go index 7a3a84a516e4..79b239c258eb 100644 --- a/sample/grpc-ping/client/client.go +++ b/sample/grpc-ping/client/client.go @@ -17,15 +17,17 @@ import ( var ( serverAddr = flag.String("server_addr", "127.0.0.1:8080", "The server address in the format of host:port") serverHostOverride = flag.String("server_host_override", "grpc.knative.dev", "") + insecure = flag.Bool("insecure", false, "Set to true to skip SSL validation") ) func main() { flag.Parse() - opts := []grpc.DialOption{ - grpc.WithInsecure(), - grpc.WithAuthority(*serverHostOverride), + opts := []grpc.DialOption{grpc.WithAuthority(*serverHostOverride)} + if *insecure { + opts = append(opts, grpc.WithInsecure()) } + grpc.With conn, err := grpc.Dial(*serverAddr, opts...) if err != nil { diff --git a/sample/grpc-ping/grpc-ping.go b/sample/grpc-ping/grpc-ping.go index b502b2b33549..fb78d4220dee 100644 --- a/sample/grpc-ping/grpc-ping.go +++ b/sample/grpc-ping/grpc-ping.go @@ -58,6 +58,8 @@ func main() { pingServer := &pingServer{} + // The grpcServer is currently configured to serve h2c traffic by default. + // To configure credentials or encyrption, see: https://grpc.io/docs/guides/auth.html#go grpcServer := grpc.NewServer() ping.RegisterPingServiceServer(grpcServer, pingServer) grpcServer.Serve(lis)