maintnerd: start of gRPC service for maintner
Also, update docs on how to re-generate.
Switches to using go4.org/grpc and go4.org/grpc-codegen/protoc-gen-go4grpc
for now.
Updates golang/go#19866
Updates golang/go#20222
Change-Id: Ifa8a123fca2a30f17270c3c558b7395a02064eae
Reviewed-on: https://go-review.googlesource.com/43560
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/maintner/git.go b/maintner/git.go
index 80d0d0e..980ca4e 100644
--- a/maintner/git.go
+++ b/maintner/git.go
@@ -463,6 +463,21 @@
}
+// GitCommit returns the provided git commit, or nil if it's unknown.
+func (c *Corpus) GitCommit(hash string) *GitCommit {
+ if len(hash) != 40 {
+ // TODO: support prefix lookups. build a trie. But
+ // for now just avoid panicking in gitHashFromHexStr.
+ return nil
+ }
+ var buf [20]byte
+ _, err := decodeHexStr(buf[:], hash)
+ if err != nil {
+ return nil
+ }
+ return c.gitCommit[GitHash(buf[:])]
+}
+
// v is like '[+-]hhmm'
// c.mu must be held for writing.
func (c *Corpus) gitLocation(v []byte) *time.Location {
@@ -483,3 +498,37 @@
c.zoneCache[s] = loc
return loc
}
+
+func decodeHexStr(dst []byte, src string) (int, error) {
+ if len(src)%2 == 1 {
+ return 0, hex.ErrLength
+ }
+
+ for i := 0; i < len(src)/2; i++ {
+ a, ok := fromHexChar(src[i*2])
+ if !ok {
+ return 0, hex.InvalidByteError(src[i*2])
+ }
+ b, ok := fromHexChar(src[i*2+1])
+ if !ok {
+ return 0, hex.InvalidByteError(src[i*2+1])
+ }
+ dst[i] = (a << 4) | b
+ }
+
+ return len(src) / 2, nil
+}
+
+// fromHexChar converts a hex character into its value and a success flag.
+func fromHexChar(c byte) (byte, bool) {
+ switch {
+ case '0' <= c && c <= '9':
+ return c - '0', true
+ case 'a' <= c && c <= 'f':
+ return c - 'a' + 10, true
+ case 'A' <= c && c <= 'F':
+ return c - 'A' + 10, true
+ }
+
+ return 0, false
+}
diff --git a/maintner/maintner.go b/maintner/maintner.go
index 69adaea..a3c473e 100644
--- a/maintner/maintner.go
+++ b/maintner/maintner.go
@@ -123,16 +123,6 @@
return nil
}
-// GitCommit returns the provided git commit, or nil if it's unknown.
-func (c *Corpus) GitCommit(hash string) *GitCommit {
- if len(hash) != 40 {
- // TODO: support prefix lookups. build a trie. But
- // for now just avoid panicking in gitHashFromHexStr.
- return nil
- }
- return c.gitCommit[c.gitHashFromHexStr(hash)]
-}
-
// mustProtoFromTime turns a time.Time into a *timestamp.Timestamp or panics if
// in is invalid.
func mustProtoFromTime(in time.Time) *timestamp.Timestamp {
diff --git a/maintner/maintnerd/api.go b/maintner/maintnerd/api.go
new file mode 100644
index 0000000..3970600
--- /dev/null
+++ b/maintner/maintnerd/api.go
@@ -0,0 +1,42 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "context"
+ "errors"
+
+ "golang.org/x/build/maintner"
+ "golang.org/x/build/maintner/maintnerd/apipb"
+)
+
+// apiService implements apipb.MaintnerServiceServer using the Corpus c.
+type apiService struct {
+ c *maintner.Corpus
+}
+
+func (s apiService) HasAncestor(ctx context.Context, req *apipb.HasAncestorRequest) (*apipb.HasAncestorResponse, error) {
+ if len(req.Commit) != 40 {
+ return nil, errors.New("invalid Commit")
+ }
+ if len(req.Ancestor) != 40 {
+ return nil, errors.New("invalid Ancestor")
+ }
+ s.c.RLock()
+ defer s.c.RUnlock()
+
+ commit := s.c.GitCommit(req.Commit)
+ res := new(apipb.HasAncestorResponse)
+ if commit == nil {
+ // TODO: wait for it? kick off a fetch of it and then answer?
+ // optional?
+ res.UnknownCommit = true
+ return res, nil
+ }
+ if a := s.c.GitCommit(req.Ancestor); a != nil {
+ res.HasAncestor = commit.HasAncestor(a)
+ }
+ return res, nil
+}
diff --git a/maintner/maintnerd/apipb/api.pb.go b/maintner/maintnerd/apipb/api.pb.go
new file mode 100644
index 0000000..cb77811
--- /dev/null
+++ b/maintner/maintnerd/apipb/api.pb.go
@@ -0,0 +1,184 @@
+// Code generated by protoc-gen-go4grpc; DO NOT EDIT
+// source: api.proto
+
+/*
+Package apipb is a generated protocol buffer package.
+
+It is generated from these files:
+ api.proto
+
+It has these top-level messages:
+ HasAncestorRequest
+ HasAncestorResponse
+*/
+package apipb
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import (
+ context "context"
+ grpc "go4.org/grpc"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type HasAncestorRequest struct {
+ Commit string `protobuf:"bytes,1,opt,name=commit" json:"commit,omitempty"`
+ Ancestor string `protobuf:"bytes,2,opt,name=ancestor" json:"ancestor,omitempty"`
+}
+
+func (m *HasAncestorRequest) Reset() { *m = HasAncestorRequest{} }
+func (m *HasAncestorRequest) String() string { return proto.CompactTextString(m) }
+func (*HasAncestorRequest) ProtoMessage() {}
+func (*HasAncestorRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (m *HasAncestorRequest) GetCommit() string {
+ if m != nil {
+ return m.Commit
+ }
+ return ""
+}
+
+func (m *HasAncestorRequest) GetAncestor() string {
+ if m != nil {
+ return m.Ancestor
+ }
+ return ""
+}
+
+type HasAncestorResponse struct {
+ // has_ancestor is whether ancestor appears in commit's history.
+ HasAncestor bool `protobuf:"varint,1,opt,name=has_ancestor,json=hasAncestor" json:"has_ancestor,omitempty"`
+ // unknown_commit is true if the provided commit was unknown.
+ UnknownCommit bool `protobuf:"varint,2,opt,name=unknown_commit,json=unknownCommit" json:"unknown_commit,omitempty"`
+}
+
+func (m *HasAncestorResponse) Reset() { *m = HasAncestorResponse{} }
+func (m *HasAncestorResponse) String() string { return proto.CompactTextString(m) }
+func (*HasAncestorResponse) ProtoMessage() {}
+func (*HasAncestorResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (m *HasAncestorResponse) GetHasAncestor() bool {
+ if m != nil {
+ return m.HasAncestor
+ }
+ return false
+}
+
+func (m *HasAncestorResponse) GetUnknownCommit() bool {
+ if m != nil {
+ return m.UnknownCommit
+ }
+ return false
+}
+
+func init() {
+ proto.RegisterType((*HasAncestorRequest)(nil), "apipb.HasAncestorRequest")
+ proto.RegisterType((*HasAncestorResponse)(nil), "apipb.HasAncestorResponse")
+}
+
+// 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 MaintnerService service
+
+type MaintnerServiceClient interface {
+ // HasAncestor reports whether one commit contains another commit
+ // in its git history.
+ HasAncestor(ctx context.Context, in *HasAncestorRequest, opts ...grpc.CallOption) (*HasAncestorResponse, error)
+}
+
+type maintnerServiceClient struct {
+ cc *grpc.ClientConn
+}
+
+func NewMaintnerServiceClient(cc *grpc.ClientConn) MaintnerServiceClient {
+ return &maintnerServiceClient{cc}
+}
+
+func (c *maintnerServiceClient) HasAncestor(ctx context.Context, in *HasAncestorRequest, opts ...grpc.CallOption) (*HasAncestorResponse, error) {
+ out := new(HasAncestorResponse)
+ err := grpc.Invoke(ctx, "/apipb.MaintnerService/HasAncestor", in, out, c.cc, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// Server API for MaintnerService service
+
+type MaintnerServiceServer interface {
+ // HasAncestor reports whether one commit contains another commit
+ // in its git history.
+ HasAncestor(context.Context, *HasAncestorRequest) (*HasAncestorResponse, error)
+}
+
+func RegisterMaintnerServiceServer(s *grpc.Server, srv MaintnerServiceServer) {
+ s.RegisterService(&_MaintnerService_serviceDesc, srv)
+}
+
+func _MaintnerService_HasAncestor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(HasAncestorRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(MaintnerServiceServer).HasAncestor(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/apipb.MaintnerService/HasAncestor",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(MaintnerServiceServer).HasAncestor(ctx, req.(*HasAncestorRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+var _MaintnerService_serviceDesc = grpc.ServiceDesc{
+ ServiceName: "apipb.MaintnerService",
+ HandlerType: (*MaintnerServiceServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "HasAncestor",
+ Handler: _MaintnerService_HasAncestor_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "api.proto",
+}
+
+func init() { proto.RegisterFile("api.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+ // 194 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0x2c, 0xc8, 0xd4,
+ 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4d, 0x2c, 0xc8, 0x2c, 0x48, 0x52, 0xf2, 0xe0, 0x12,
+ 0xf2, 0x48, 0x2c, 0x76, 0xcc, 0x4b, 0x4e, 0x2d, 0x2e, 0xc9, 0x2f, 0x0a, 0x4a, 0x2d, 0x2c, 0x4d,
+ 0x2d, 0x2e, 0x11, 0x12, 0xe3, 0x62, 0x4b, 0xce, 0xcf, 0xcd, 0xcd, 0x2c, 0x91, 0x60, 0x54, 0x60,
+ 0xd4, 0xe0, 0x0c, 0x82, 0xf2, 0x84, 0xa4, 0xb8, 0x38, 0x12, 0xa1, 0x4a, 0x25, 0x98, 0xc0, 0x32,
+ 0x70, 0xbe, 0x52, 0x3c, 0x97, 0x30, 0x8a, 0x49, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x8a,
+ 0x5c, 0x3c, 0x19, 0x89, 0xc5, 0xf1, 0x70, 0x6d, 0x20, 0x03, 0x39, 0x82, 0xb8, 0x33, 0x10, 0x4a,
+ 0x85, 0x54, 0xb9, 0xf8, 0x4a, 0xf3, 0xb2, 0xf3, 0xf2, 0xcb, 0xf3, 0xe2, 0xa1, 0xb6, 0x32, 0x81,
+ 0x15, 0xf1, 0x42, 0x45, 0x9d, 0xc1, 0x82, 0x46, 0xe1, 0x5c, 0xfc, 0xbe, 0x89, 0x99, 0x79, 0x25,
+ 0x79, 0xa9, 0x45, 0xc1, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0x2e, 0x5c, 0xdc, 0x48, 0x76,
+ 0x0a, 0x49, 0xea, 0x81, 0x3d, 0xa5, 0x87, 0xe9, 0x23, 0x29, 0x29, 0x6c, 0x52, 0x10, 0x27, 0x26,
+ 0xb1, 0x81, 0x43, 0xc4, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x74, 0x5e, 0xcf, 0x54, 0x1e, 0x01,
+ 0x00, 0x00,
+}
diff --git a/maintner/maintnerd/apipb/api.proto b/maintner/maintnerd/apipb/api.proto
new file mode 100644
index 0000000..4157e9f
--- /dev/null
+++ b/maintner/maintnerd/apipb/api.proto
@@ -0,0 +1,27 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// https://developers.google.com/protocol-buffers/docs/proto3
+syntax = "proto3";
+
+package apipb;
+
+message HasAncestorRequest {
+ string commit = 1; // full git commit hash (subject of query)
+ string ancestor = 2; // full git commit hash of sought ancestor
+}
+
+message HasAncestorResponse {
+ // has_ancestor is whether ancestor appears in commit's history.
+ bool has_ancestor = 1;
+
+ // unknown_commit is true if the provided commit was unknown.
+ bool unknown_commit = 2;
+}
+
+service MaintnerService {
+ // HasAncestor reports whether one commit contains another commit
+ // in its git history.
+ rpc HasAncestor(HasAncestorRequest) returns (HasAncestorResponse);
+}
diff --git a/maintner/maintnerd/apipb/apipb.go b/maintner/maintnerd/apipb/apipb.go
new file mode 100644
index 0000000..18067c6
--- /dev/null
+++ b/maintner/maintnerd/apipb/apipb.go
@@ -0,0 +1,12 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package apipb
+
+// Run "go generate" in this directory to update. You need to have:
+//
+// - a protoc binary (see https://github.com/golang/protobuf#installation)
+// - go get go4.org/grpc-codegen/protoc-gen-go4grpc
+
+//go:generate protoc --proto_path=$GOPATH/src:. --go4grpc_out=plugins=grpc:. api.proto
diff --git a/maintner/maintnerd/maintnerd.go b/maintner/maintnerd/maintnerd.go
index 41de199..500e22a 100644
--- a/maintner/maintnerd/maintnerd.go
+++ b/maintner/maintnerd/maintnerd.go
@@ -17,23 +17,28 @@
"log"
"net"
"net/http"
+ "net/http/httptest"
"os"
"path/filepath"
"runtime"
+ "strconv"
"strings"
"time"
"cloud.google.com/go/compute/metadata"
"cloud.google.com/go/storage"
+ "go4.org/grpc"
"golang.org/x/build/autocertcache"
"golang.org/x/build/gerrit"
"golang.org/x/build/maintner"
"golang.org/x/build/maintner/godata"
+ "golang.org/x/build/maintner/maintnerd/apipb"
"golang.org/x/crypto/acme/autocert"
)
var (
listen = flag.String("listen", "localhost:6343", "listen address")
+ devTLSPort = flag.Int("dev-tls-port", 0, "if non-zero, port number to run localhost self-signed TLS server")
autocertDomain = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt TLS cert on this domain")
autocertBucket = flag.String("autocert-bucket", "", "if non-empty, Google Cloud Storage bucket to store LetsEncrypt cache in")
syncQuit = flag.Bool("sync-and-quit", false, "sync once and quit; don't run a server")
@@ -196,7 +201,15 @@
corpus.StartPubSubHelperSubscribe(*pubsub)
}
+ grpcServer := grpc.NewServer()
+ apipb.RegisterMaintnerServiceServer(grpcServer, &apiService{c: corpus})
+ http.Handle("/apipb.MaintnerService/", grpcServer)
+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ if strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
+ grpcServer.ServeHTTP(w, r)
+ return
+ }
if r.URL.Path != "/" {
http.NotFound(w, r)
return
@@ -224,7 +237,10 @@
go func() { errc <- fmt.Errorf("http.Serve = %v", http.Serve(ln, nil)) }()
}
if *autocertDomain != "" {
- go func() { errc <- serveTLS() }()
+ go func() { errc <- serveAutocertTLS() }()
+ }
+ if *devTLSPort != 0 {
+ go func() { errc <- serveDevTLS(*devTLSPort) }()
}
log.Fatal(<-errc)
@@ -287,7 +303,28 @@
return token, nil
}
-func serveTLS() error {
+func serveDevTLS(port int) error {
+ ln, err := net.Listen("tcp", "localhost:"+strconv.Itoa(port))
+ if err != nil {
+ return err
+ }
+ defer ln.Close()
+ log.Printf("Serving self-signed TLS at https://%s", ln.Addr())
+ // Abuse httptest for its localhost TLS setup code:
+ ts := httptest.NewUnstartedServer(http.DefaultServeMux)
+ // Ditch the provided listener, replace with our own:
+ ts.Listener.Close()
+ ts.Listener = ln
+ ts.TLS = &tls.Config{
+ NextProtos: []string{"h2", "http/1.1"},
+ InsecureSkipVerify: true,
+ }
+ ts.StartTLS()
+
+ select {}
+}
+
+func serveAutocertTLS() error {
if *autocertBucket == "" {
return fmt.Errorf("using --autocert requires --autocert-bucket.")
}
@@ -295,6 +332,7 @@
if err != nil {
return err
}
+ defer ln.Close()
sc, err := storage.NewClient(context.Background())
if err != nil {
return fmt.Errorf("storage.NewClient: %v", err)
diff --git a/maintner/maintpb/maintner.pb.go b/maintner/maintpb/maintner.pb.go
index d343df8..69b3e62 100644
--- a/maintner/maintpb/maintner.pb.go
+++ b/maintner/maintpb/maintner.pb.go
@@ -1,6 +1,5 @@
-// Code generated by protoc-gen-go.
+// Code generated by protoc-gen-go4grpc; DO NOT EDIT
// source: maintner.proto
-// DO NOT EDIT!
/*
Package maintpb is a generated protocol buffer package.
diff --git a/maintner/maintpb/maintpb.go b/maintner/maintpb/maintpb.go
index af7f668..6951b85 100644
--- a/maintner/maintpb/maintpb.go
+++ b/maintner/maintpb/maintpb.go
@@ -4,11 +4,12 @@
package maintpb
-// Run "go generate" in this directory to update. You need to have:
-// - a protoc binary
-// - a protoc-gen-go binary
-// - go get github.com/golang/protobuf
+// Run "go generate" in this directory to update. You need to:
//
-// See https://github.com/golang/protobuf#installation for more info.
+// - have the protoc binary in your $PATH
+// - go get go4.org/grpc-codegen/protoc-gen-go4grpc
+//
+// See https://github.com/golang/protobuf#installation for how to install
+// the protoc binary.
-//go:generate protoc --proto_path=$GOPATH/src:. --go_out=plugins=grpc:. maintner.proto
+//go:generate protoc --proto_path=$GOPATH/src:. --go4grpc_out=plugins=grpc:. maintner.proto
diff --git a/maintner/maintq/maintq.go b/maintner/maintq/maintq.go
new file mode 100644
index 0000000..62f2fbe
--- /dev/null
+++ b/maintner/maintq/maintq.go
@@ -0,0 +1,53 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The maintq command queries a maintnerd gRPC server.
+// This tool is mostly for debugging.
+package main
+
+import (
+ "context"
+ "crypto/tls"
+ "flag"
+ "log"
+ "net/http"
+ "strings"
+
+ "go4.org/grpc"
+ "golang.org/x/build/maintner/maintnerd/apipb"
+ "golang.org/x/net/http2"
+)
+
+var (
+ server = flag.String("server", "maintnerd.golang.org", "maintnerd server")
+)
+
+func main() {
+ flag.Parse()
+
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{
+ NextProtos: []string{"h2"},
+ InsecureSkipVerify: strings.HasPrefix(*server, "localhost:"),
+ },
+ }
+ hc := &http.Client{Transport: tr}
+ http2.ConfigureTransport(tr)
+
+ cc, err := grpc.NewClient(hc, "https://"+*server)
+ if err != nil {
+ log.Fatal(err)
+ }
+ mc := apipb.NewMaintnerServiceClient(cc)
+ ctx := context.Background()
+
+ res, err := mc.HasAncestor(ctx, &apipb.HasAncestorRequest{
+ Commit: "f700f89b0be0eda0cda20427fbdae4ff1cb7e6a8",
+ Ancestor: "2dc27839df7d51b0544c0ac8b2a0b8f030b7a90c",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("Got: %+v", res)
+}