how to use etcd in service discovery? - etcd

I am beginner for etcd
my purpose is the follow
1.service register itself after service start
2.client find service and invoke service
the follow code show test how to find service and invoke it
func ClientTestService() {
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{endpoint},
DialTimeout: time.Second * 5,
})
if err != nil {
log.Println("connect etcd err:", err.Error())
return
}
defer cli.Close()
r := &naming.GRPCResolver{Client: cli}
b := grpc.RoundRobin(r)
conn, err := grpc.Dial(service_name, grpc.WithBalancer(b), grpc.WithInsecure())
if err != nil {
log.Println("dial err:", err.Error())
return
}
defer conn.Close()
c := calc.NewCalcClient(conn)
req := calc.CalcRequest{IResult: 1, SResult: "req"}
resp, err := c.CalcResult(context.Background(), &req)
if err != nil {
log.Println("calc err:", err)
return
}
log.Println(resp.IResult, resp.SResult)
}
console output "calc err: rpc error: code = Unavailable desc = there is no address available" after execute resp, err := c.CalcResult(context.Background(), &req)
it means that resolver can't find the address of service from the service name
i guess there have two possible
1. call service need start etcd "proxy service" or "gateway" first
2. need to get service address from service name by manual
the follow code show register service
func RegisterService(w *sync.WaitGroup) {
w.Add(1)
defer func() {
w.Done()
}()
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{endpoint},
DialTimeout: time.Second * 5,
})
if err != nil {
log.Println("etcd err:", err)
return
}
cli.Delete(cli.Ctx(), service_name)
r := &naming.GRPCResolver{Client: cli}
for _, addr := range GetLocalAddrs() {
service_node := addr + ":" + strconv.Itoa(port)
err = r.Update(cli.Ctx(), service_name, gn.Update{Op: gn.Add, Addr: service_node})
log.Println("register node :", service_name, service_node, err)
}
}
My understanding is that function "ClientTestService" connect etcd server and resolve the service name to service address and invoke service by balance
but when i debug this code and then find it just invoke etcd by balance,
is there function in etcd for my need ?

Try add grpc.WithBlock() for Dial, like this:
grpc.Dial(service_name, grpc.WithBalancer(b), grpc.WithInsecure(), grpc.WithBlock() )

Related

Testing around GRPC stream Send function in Go

I have a Go GRPC server-side streaming function:
func (server *Server) GetClients(req *iam.GetClientsRequest, client iam.IAM_GetClientsServer) error {
ctx := client.(interface{ Context() context.Context }).Context()
userID, err := getUserIDStream(client)
if err != nil {
return err
}
clients, err := server.db.QueryByUserID(ctx, userID)
if err != nil {
return grpc.Errorf(codes.Internal, apiutils.ServerError)
}
for _, value := range clients {
converted, err := server.fromInternalClient(value)
if err != nil {
return err
}
if err := client.Send(converted); err != nil {
return err
}
}
return nil
}
and I'm testing it like this:
It("GetClients - Send fails - Error", func() {
handler := createHandler(db)
lis := bufconn.Listen(bufSize)
server := grpc.NewServer()
iam.RegisterIAMServer(server, NewServer(handler))
go func() {
if err := server.Serve(lis); err != nil {
log.Fatalf("Server exited with error: %v", err)
}
}()
defer lis.Close()
defer server.GracefulStop()
conn, err := grpc.DialContext(context.Background(), "bufnet",
grpc.WithContextDialer(createBufDialier(lis)), grpc.WithInsecure())
Expect(err).ShouldNot(HaveOccurred())
defer conn.Close()
client := iam.NewIAMClient(conn)
cclient, _ := client.GetClients(addAccessToken(context.Background()), new(iam.GetClientsRequest))
resp, err := cclient.Recv()
Expect(resp).Should(BeNil())
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(Equal(message))
})
My issue is that I'm not sure how to induce a failure on Send so I can test the response. Since I'm using an actual test server and client, I can't just mock out the object and I'd prefer not to go that route anyway. Is there a way I can do this?
Originally, I was trying to force Send to fail by setting bufSize to an artificially low value. However, this wasn't producing an error so I decided to try modifying the maxSendMessageSize on the server:
opts := []grpc.ServerOption{}
if sendFails {
opts = append(opts, grpc.MaxSendMsgSize(10))
}
lis := bufconn.Listen(bufSize)
server := grpc.NewServer(opts...)
And this worked in producing the error.

UDP server issues with multi ports connections

I have a UDP server that expose two ports, 8080 for normal data and 8082 for controll (keep alive signal). When I start the server one new instance of Server is created. It have a list of clients: a client is represented by a struct that contains two types of UDPConn, one for control and one for data. What is happening is when I run my Client.go I connect to server in both ports but, the server creates two clients instead of only one. Downward are part of my Server.go and Client.go. What can I do to create only one user for both connections?
Server.go
func main() {
server := NewLobby()
addressCntrl, err := net.ResolveUDPAddr(CONN_TYPE, CONN_CNTRL_PORT)
if err != nil {
log.Fatalf("Error resolving controller address: %s", err)
}
listenerCntrl, err := net.ListenUDP(CONN_TYPE, addressCntrl)
defer listenerCntrl.Close()
log.Printf("Listening controll on port %s", CONN_CNTRL_PORT)
addressData, err := net.ResolveUDPAddr(CONN_TYPE, CONN_DATA_PORT)
if err != nil {
log.Fatalf("Error Resolving data address: %s", err)
}
listenerData, err := net.ListenUDP(CONN_TYPE, addressData)
defer listenerData.Close()
log.Printf("Listening data on port %s", CONN_DATA_PORT)
for {
buffer = make([]byte, 1024)
listenerData.ReadFromUDP(buffer)
c := NewClient(*listenerData, *listenerCntrl)
server.Join(c)
}
}
NewServer (server.go)
func NewServer() *Server {
server := &Server{
clients: make([]*Client, 0),
chatRooms: make(map[string]*ChatRoom),
incoming: make(chan *Message),
join: make(chan *Client),
leave: make(chan *Client),
delete: make(chan *ChatRoom),
}
server.Listen()
return server
}
NewClient (server.go)
func NewClient(connData net.UDPConn, connCntrl net.UDPConn) *Client {
writerData := bufio.NewWriter(&connData)
readerData := bufio.NewReader(&connData)
writerCntrl := bufio.NewWriter(&connCntrl)
readerCntrl := bufio.NewReader(&connCntrl)
client := &Client{
name: CLIENT_NAME,
chatRoom: nil,
incoming: make(chan *Message),
outgoing: make(chan string),
connData: connData,
connCntrl: connCntrl,
readerData: readerData,
writerData: writerData,
readerCntrl: readerCntrl,
writerCntrl: writerCntrl,
}
log.Printf("New client connected")
client.Listen()
return client
}
Client.go
func main() {
server := NewServer()
addressCntrl, err := net.ResolveUDPAddr(CONN_TYPE, CONN_CNTRL_PORT)
if err != nil {
log.Fatalf("Error resolving controller address: %s", err)
}
listenerCntrl, err := net.ListenUDP(CONN_TYPE, addressCntrl)
defer listenerCntrl.Close()
log.Printf("Listening controll on port %s", CONN_CNTRL_PORT)
addressData, err := net.ResolveUDPAddr(CONN_TYPE, CONN_DATA_PORT)
if err != nil {
log.Fatalf("Error Resolving data address: %s", err)
}
listenerData, err := net.ListenUDP(CONN_TYPE, addressData)
defer listenerData.Close()
log.Printf("Listening data on port %s", CONN_DATA_PORT)
for {
buffer = make([]byte, 1024)
listenerData.ReadFromUDP(buffer)
c := NewClient(*listenerData, *listenerCntrl)
server.Join(c)
}
}
One example of output is bellow
Server output
2021/05/06 20:03:52 Listening controll on port :8082
2021/05/06 20:03:52 Listening data on port :8080
2021/05/06 20:03:56 New client connected
2021/05/06 20:03:58 New client connected
Client output
Welcome to the server! Type "/help" to get a list of commands.
Welcome to the server! Type "/help" to get a list of commands.
If needed my functions Read() and Write() of client is bellow.
Read (client.go)
func Read(connData net.UDPConn, connCntrl net.UDPConn) {
//Controller
go func() {
readerCntrl := bufio.NewReader(&connCntrl)
for {
str, _ := readerCntrl.ReadString('\n')
if strings.Compare(str, "timeout") == 0 {
os.Exit(1)
}
}
}()
//Data
reader := bufio.NewReader(&connData)
for {
str, err := reader.ReadString('\n')
if err != nil {
fmt.Printf(MSG_DISCONNECT)
wg.Done()
return
}
fmt.Print(str)
}
}
Write (client.go)
func Write(connData net.UDPConn, connCntrl net.UDPConn) {
//Controller
go func() {
writerCntrl := bufio.NewWriter(&connCntrl)
for range time.Tick(10 * time.Second) {
writerCntrl.WriteString("ka")
}
}()
//Data
reader := bufio.NewReader(os.Stdin)
writerData := bufio.NewWriter(&connData)
for {
input, err := reader.ReadString('\n')
if err != nil {
log.Fatalf("Error reading input: %s", err)
}
_, err = writerData.WriteString(input)
if err != nil {
log.Fatalf("Error writing input to server: %s", err)
}
err = writerData.Flush()
if err != nil {
log.Fatalf("Error flushing the input: %s", err)
wg.Done()
}
}
}

Golang grpc: how to determine when the server has started listening?

So I have the following:
type Node struct {
Table map[string]string
thing.UnimplementedGreeterServer
address string
}
func (n *Node) Start() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
thing.RegisterGreeterServer(s, n)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
In my main function I'll spin up mulitple nodes like so:
func main() {
n :=Node{Table: map[string]string{}}
go n.Start()
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock())
}
The problem is, because I'm spinning up the node concurrently, there's a chance the dial up connection might not work because the node might not have been setup yet.
Ideally, I'd like a done channel that tells me when the grpc server has actually started listening. How do I accomplish this?
This is essntially the same problem as How to add hook on golang grpc server start? which doesn't have an answer
s.Serve(listener) blocks, so you can't achieve your purpose by having a done chan, instead you have to implement the healthcheck and readiness for your service, and check those before performing any request by the client.
The server should implement the following proto:
syntax = "proto3";
package grpc.health.v1;
message HealthCheckRequest {
string service = 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
SERVICE_UNKNOWN = 3; // Used only by the Watch method.
}
ServingStatus status = 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
For example, the envoy proxy grpc_health_check works with the above proto.
Read GRPC Health Checking Protocol for more information.
The server can be Dialed as soon as net.Listen returns a nil error. Dial will block until the server calls Accept (which will happen somewhere in s.Serve in this case).
Either move creation of the listener into the caller and pass it as an argument:
func (n *Node) Start(lis net.Listener) {
s := grpc.NewServer()
thing.RegisterGreeterServer(s, n)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
n := Node{Table: map[string]string{}}
go n.Start(lis)
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock())
}
Or signal that the listener is up after Listen returns:
func (n *Node) Start(up chan struct{}) {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
if up != nil {
close(up)
}
s := grpc.NewServer()
thing.RegisterGreeterServer(s, n)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
func main() {
n := Node{Table: map[string]string{}}
up := make(chan struct{})
go n.Start(up)
<-up
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock())
}
For all those who are still looking for an answer to this, here is another simple way to do it. Start the server in a child routine. Here is a code snippet:
// Start the server in a child routine
go func() {
if err := s.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}()
fmt.Println("Server succesfully started on port :50051")
In my case I am using MongoDB as well, so when you run it, you get:
grpc-go-mongodb-cobra>go run server/main.go
Starting server on port :50051...
Connecting to MongoDB...
Connected to MongoDB
Server succesfully started on port :50051
I have also written a Blog post on this, with working code in GitHub. Here is the link: https://softwaredevelopercentral.blogspot.com/2021/03/golang-grpc-microservice.html

How to handle multiple endpoints via grpc-gateway?

I'm sure all the services are working properly.
I have the code below:
This snippet is used for registering two endpoints.
func RegisterEndpoints(ctx context.Context, c *utils.AppConfig, r resolver.Builder) (http.Handler, error) {
var err error
mux := runtime.NewServeMux()
dialOpts := []grpc.DialOption{grpc.WithBalancerName("round_robin"), grpc.WithInsecure()}
err = protos.RegisterUserCenterHandlerFromEndpoint(ctx, mux, r.Scheme()+"://author/user-center", dialOpts)
if err != nil {
return nil, err
}
err = protos.RegisterSsoHandlerFromEndpoint(ctx, mux, r.Scheme()+"://author/sso", dialOpts)
if err != nil {
return nil, err
}
return mux, nil
}
And in my main.go,I build a resolver to resolve name to address, then register the two endpoints and listen on port 8080.
func run() error {
c := utils.GetAppConfig()
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
r := localresolver.NewResolver(fmt.Sprintf("%s:%d", c.Registry.Host, c.Registry.Port))
resolver.Register(r)
mux := http.NewServeMux()
// Register endpoints here
gw, err := routes.RegisterEndpoints(ctx, c, r)
if err != nil {
return err
}
mux.Handle("/", gw)
fmt.Println("Listening localhost:8080...")
return http.ListenAndServe(fmt.Sprintf("%s:%d", c.Gateway.Host, c.Gateway.Port), mux)
}
func main() {
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
But after I ran go run main.go, I found that only the last service I registered can be accessed, that is sso service (the err = protos.RegisterSsoHandlerFromEndpoint(ctx, mux, r.Scheme()+"://author/sso", dialOpts) line).
Can anyone show me an example of the correct way to register multiple endpoints via grpc-gateway? (make all the services registered with grpc-gateway can successfully be visited)
[2020-01-31] Need more help, now my code is like below:
Other code are same as before.
Additional, this is the result which name resolver shows:
There is no need to pass the ServeMux (gw) to mux var as handler, you can just ListenAndServe to the returned gw variable.
// Register endpoints here
gw, err := routes.RegisterEndpoints(ctx, c, r)
if err != nil {
return err
}
fmt.Println("Listening localhost:8080...")
return http.ListenAndServe(fmt.Sprintf("%s:%d", c.Gateway.Host, c.Gateway.Port), gw)
and in RegisterEndpoints function, the endpoint parameter should be your host:port, the api endpoint should be provided in the google api annotation in the proto file.
err = protos.RegisterUserCenterHandlerFromEndpoint(ctx, mux, fmt.Sprintf("%s:%d", c.Gateway.Host, c.Gateway.Port), dialOpts)
if err != nil {
return nil, err
}
err = protos.RegisterSsoHandlerFromEndpoint(ctx, mux, fmt.Sprintf("%s:%d", c.Gateway.Host, c.Gateway.Port), dialOpts)
if err != nil {
return nil, err
}
I appended grpc.WithBlock() to grpc.DialOption, then all services can be accessed via grpc-gateway now.
Like below:
dialOpts := []grpc.DialOption{grpc.WithBalancerName("round_robin"), grpc.WithInsecure(), grpc.WithBlock()}

Docker API call returns "server gave HTTP response to HTTPS client"

I have the following Code and a Call to the API returns an error as follows, I hve also pasted Docker Daemon command below. I have tried a few combinations from HTTP/ HTTPS / TCP with / without TLS.
Where could I be wrong here?
"panic: An error occurred trying to connect: Get https://172.28.8.212:2375/v1.24/containers/json?limit=0: http: server gave HTTP response to HTTPS client
"
func main() {
var headers map[string]string
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
cl := &http.Client{Timeout: time.Minute}
cli, err := client.NewClient("tcp://172.28.8.212:2375", "1.24", cl, headers)
if err != nil {
panic(err)
}
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
panic(err)
}
for _, container := range containers {
fmt.Printf("%s %s\n", container.ID[:10], container.Image)
}
}
My Docker Daemon is started as follows
[Unit]
Description = Docker Service Daemon
[Service]
ExecStart=/usr/bin/docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=consul://172.28.8.211:8500
func main() {
var headers map[string]string
tr := &http.Transport{} <---- Note the difference here.
tr.Dial = func(proto, addr string) (net.Conn, error) {
fmt.Println("Dial called")
conn, err := net.DialTimeout(proto, addr, time.Minute)
if err != nil {
fmt.Println("There was an err", err)
}
return conn, err
}
cl := &http.Client{Transport: tr}
cli, err := client.NewClient("http://x.y.z.w:2376", "1.24", cl, headers)
if err != nil {
panic(err)
}
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
panic(err)
}
for _, container := range containers {
fmt.Printf("%s %s\n", container.ID[:10], container.Image)
}
}

Resources