package clientv3

import (
    "crypto/tls"
    "errors"
    "fmt"
    "net"
    "net/url"
    "strings"
    "time"

    "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
    prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
)

var (
    ErrNoAvailableEndpoints = errors.New("etcdclient: no available endpoints")
)

// Client provides and manages an etcd v3 client session.
type Client struct {
    Cluster
    KV
    Lease
    Watcher
    Auth
    Maintenance

    conn         *grpc.ClientConn
    cfg          Config
    creds        *credentials.TransportCredentials
    balancer     *simpleBalancer
    retryWrapper retryRpcFunc

    ctx    context.Context
    cancel context.CancelFunc

    // Username is a username for authentication
    Username string
    // Password is a password for authentication
    Password string
}

// New creates a new etcdv3 client from a given configuration.
func New(cfg Config) (*Client, error) {
    if len(cfg.Endpoints) == 0 {
        return nil, ErrNoAvailableEndpoints
    }

    return newClient(&cfg)
}

// NewFromURL creates a new etcdv3 client from a URL.
func NewFromURL(url string) (*Client, error) {
    return New(Config{Endpoints: []string{url}})
}

// NewFromConfigFile creates a new etcdv3 client from a configuration file.
func NewFromConfigFile(path string) (*Client, error) {
    cfg, err := configFromFile(path)
    if err != nil {
        return nil, err
    }
    return New(*cfg)
}

// Close shuts down the client's etcd connections.
func (c *Client) Close() error {
    c.cancel()
    c.Watcher.Close()
    return toErr(c.ctx, c.conn.Close())
}

// Ctx is a context for "out of band" messages (e.g., for sending
// "clean up" message when another context is canceled). It is
// canceled on client Close().
func (c *Client) Ctx() context.Context { return c.ctx }

// Endpoints lists the registered endpoints for the client.
func (c *Client) Endpoints() []string { return c.cfg.Endpoints }

// SetEndpoints updates client's endpoints.
func (c *Client) SetEndpoints(eps ...string) {
    c.cfg.Endpoints = eps
    c.balancer.updateAddrs(eps)
}

// Sync synchronizes client's endpoints with the known endpoints from the etcd membership.
func (c *Client) Sync(ctx context.Context) error {
    mresp, err := c.MemberList(ctx)
    if err != nil {
        return err
    }
    var eps []string
    for _, m := range mresp.Members {
        eps = append(eps, m.ClientURLs...)
    }
    c.SetEndpoints(eps...)
    return nil
}

func (c *Client) autoSync() {
    if c.cfg.AutoSyncInterval == time.Duration(0) {
        return
    }

    for {
        select {
        case <-c.ctx.Done():
            return
        case <-time.After(c.cfg.AutoSyncInterval):
            ctx, _ := context.WithTimeout(c.ctx, 5*time.Second)
            if err := c.Sync(ctx); err != nil && err != c.ctx.Err() {
                logger.Println("Auto sync endpoints failed:", err)
            }
        }
    }
}

type authTokenCredential struct {
    token string
}

func (cred authTokenCredential) RequireTransportSecurity() bool {
    return false
}

func (cred authTokenCredential) GetRequestMetadata(ctx context.Context, s ...string) (map[string]string, error) {
    return map[string]string{
        "token": cred.token,
    }, nil
}

func parseEndpoint(endpoint string) (proto string, host string, scheme string) {
    proto = "tcp"
    host = endpoint
    url, uerr := url.Parse(endpoint)
    if uerr != nil || !strings.Contains(endpoint, "://") {
        return
    }
    scheme = url.Scheme

    // strip scheme:// prefix since grpc dials by host
    host = url.Host
    switch url.Scheme {
    case "http", "https":
    case "unix":
        proto = "unix"
    default:
        proto, host = "", ""
    }
    return
}

func (c *Client) processCreds(scheme string) (creds *credentials.TransportCredentials) {
    creds = c.creds
    switch scheme {
    case "unix":
    case "http":
        creds = nil
    case "https":
        if creds != nil {
            break
        }
        tlsconfig := &tls.Config{}
        emptyCreds := credentials.NewTLS(tlsconfig)
        creds = &emptyCreds
    default:
        creds = nil
    }
    return
}

// dialSetupOpts gives the dial opts prior to any authentication
func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts []grpc.DialOption) {
    if c.cfg.DialTimeout > 0 {
        opts = []grpc.DialOption{grpc.WithTimeout(c.cfg.DialTimeout)}
    }
    opts = append(opts, dopts...)

    f := func(host string, t time.Duration) (net.Conn, error) {
        proto, host, _ := parseEndpoint(c.balancer.getEndpoint(host))
        if proto == "" {
            return nil, fmt.Errorf("unknown scheme for %q", host)
        }
        select {
        case <-c.ctx.Done():
            return nil, c.ctx.Err()
        default:
        }
        return net.DialTimeout(proto, host, t)
    }
    opts = append(opts, grpc.WithDialer(f))

    creds := c.creds
    if _, _, scheme := parseEndpoint(endpoint); len(scheme) != 0 {
        creds = c.processCreds(scheme)
    }
    if creds != nil {
        opts = append(opts, grpc.WithTransportCredentials(*creds))
    } else {
        opts = append(opts, grpc.WithInsecure())
    }

    return opts
}

// Dial connects to a single endpoint using the client's config.
func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) {
    return c.dial(endpoint)
}

func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) {
    opts := c.dialSetupOpts(endpoint, dopts...)
    host := getHost(endpoint)
    if c.Username != "" && c.Password != "" {
        // use dial options without dopts to avoid reusing the client balancer
        auth, err := newAuthenticator(host, c.dialSetupOpts(endpoint))
        if err != nil {
            return nil, err
        }
        defer auth.close()

        resp, err := auth.authenticate(c.ctx, c.Username, c.Password)
        if err != nil {
            return nil, err
        }
        opts = append(opts, grpc.WithPerRPCCredentials(authTokenCredential{token: resp.Token}))
    }

    // add metrics options
    opts = append(opts, grpc.WithUnaryInterceptor(prometheus.UnaryClientInterceptor))
    opts = append(opts, grpc.WithStreamInterceptor(prometheus.StreamClientInterceptor))

    conn, err := grpc.Dial(host, opts...)
    if err != nil {
        return nil, err
    }
    return conn, nil
}

// WithRequireLeader requires client requests to only succeed
// when the cluster has a leader.
func WithRequireLeader(ctx context.Context) context.Context {
    md := metadata.Pairs(rpctypes.MetadataRequireLeaderKey, rpctypes.MetadataHasLeader)
    return metadata.NewContext(ctx, md)
}

func newClient(cfg *Config) (*Client, error) {
    if cfg == nil {
        cfg = &Config{}
    }
    var creds *credentials.TransportCredentials
    if cfg.TLS != nil {
        c := credentials.NewTLS(cfg.TLS)
        creds = &c
    }

    // use a temporary skeleton client to bootstrap first connection
    ctx, cancel := context.WithCancel(context.TODO())
    client := &Client{
        conn:   nil,
        cfg:    *cfg,
        creds:  creds,
        ctx:    ctx,
        cancel: cancel,
    }
    if cfg.Username != "" && cfg.Password != "" {
        client.Username = cfg.Username
        client.Password = cfg.Password
    }

    client.balancer = newSimpleBalancer(cfg.Endpoints)
    conn, err := client.dial(cfg.Endpoints[0], grpc.WithBalancer(client.balancer))
    if err != nil {
        return nil, err
    }
    client.conn = conn
    client.retryWrapper = client.newRetryWrapper()

    // wait for a connection
    if cfg.DialTimeout > 0 {
        hasConn := false
        waitc := time.After(cfg.DialTimeout)
        select {
        case <-client.balancer.readyc:
            hasConn = true
        case <-ctx.Done():
        case <-waitc:
        }
        if !hasConn {
            client.cancel()
            conn.Close()
            return nil, grpc.ErrClientConnTimeout
        }
    }

    client.Cluster = NewCluster(client)
    client.KV = NewKV(client)
    client.Lease = NewLease(client)
    client.Watcher = NewWatcher(client)
    client.Auth = NewAuth(client)
    client.Maintenance = NewMaintenance(client)

    go client.autoSync()
    return client, nil
}

// ActiveConnection returns the current in-use connection
func (c *Client) ActiveConnection() *grpc.ClientConn { return c.conn }

// isHaltErr returns true if the given error and context indicate no forward
// progress can be made, even after reconnecting.
func isHaltErr(ctx context.Context, err error) bool {
    if ctx != nil && ctx.Err() != nil {
        return true
    }
    if err == nil {
        return false
    }
    code := grpc.Code(err)
    // Unavailable codes mean the system will be right back.
    // (e.g., can't connect, lost leader)
    // Treat Internal codes as if something failed, leaving the
    // system in an inconsistent state, but retrying could make progress.
    // (e.g., failed in middle of send, corrupted frame)
    // TODO: are permanent Internal errors possible from grpc?
    return code != codes.Unavailable && code != codes.Internal
}

func toErr(ctx context.Context, err error) error {
    if err == nil {
        return nil
    }
    err = rpctypes.Error(err)
    if _, ok := err.(rpctypes.EtcdError); ok {
        return err
    }
    code := grpc.Code(err)
    switch code {
    case codes.DeadlineExceeded:
        fallthrough
    case codes.Canceled:
        if ctx.Err() != nil {
            err = ctx.Err()
        }
    case codes.Unavailable:
        err = ErrNoAvailableEndpoints
    case codes.FailedPrecondition:
        err = grpc.ErrClientConnClosing
    }
    return err
}

client.go的更多相关文章

  1. vmware里面的名词 vSphere、vCenter Server、ESXI、vSphere Client

    vmware里面的名词 vSphere.vCenter Server.ESXI.vSphere Client vSphere.vCenter Server.ESXI.vSphere Client VS ...

  2. Apache2.4:AH01630 client denied by server configuration

    问题说明:Apache服务总共有4个,是为了防止单点故障和负载均衡,负载均衡控制由局方的F5提供. 访问的内容在NAS存储上,现象是直接访问每个apache的服务内容都是没有问题,但是从负载地址过来的 ...

  3. [异常解决] windows用SSH和linux同步文件&linux开启SSH&ssh client 报 algorithm negotiation failed的解决方法之一

    1.安装.配置与启动 SSH分客户端openssh-client和openssh-server 如果你只是想登陆别的机器的SSH只需要安装openssh-client(ubuntu有默认安装,如果没有 ...

  4. xamarin IOS 报错处理: an error occurred on client Build420719 while

    xamarin IOS 开发时如果报错如下: an error occurred on client Build420719 while...... 出现如下问题时,可能是1.丢失文件2.没有包括在项 ...

  5. ASP.NET OAuth:access token的加密解密,client secret与refresh token的生成

    在 ASP.NET OWIN OAuth(Microsoft.Owin.Security.OAuth)中,access token 的默认加密方法是: 1) System.Security.Crypt ...

  6. 在ASP.NET中基于Owin OAuth使用Client Credentials Grant授权发放Token

    OAuth真是一个复杂的东东,即使你把OAuth规范倒背如流,在具体实现时也会无从下手.因此,Microsoft.Owin.Security.OAuth应运而生(它的实现代码在Katana项目中),帮 ...

  7. [OAuth]基于DotNetOpenAuth实现Client Credentials Grant

    Client Credentials Grant是指直接由Client向Authorization Server请求access token,无需用户(Resource Owner)的授权.比如我们提 ...

  8. OData Client Code Generator

    转发. [Tutorial & Sample] How to use OData Client Code Generator to generate client-side proxy cla ...

  9. offset、client、scroll开头的属性归纳总结

    HTML元素有几个offset.client.scroll开头的属性,总是让人摸不着头脑.在书中看到记下来,分享给需要的小伙伴.主要是以下几个属性: 第一组:offsetWidth,offsetHei ...

  10. JavaScript学习笔记5 之 计时器 & scroll、offset、client系列属性 & 图片无缝滚动

    一.计时器 setInterval ( 函数/名称 , 毫秒数 )表示每经过一定的毫秒后,执行一次相应的函数(重复) setTimeout ( 函数/名称 , 毫秒数 ) 表示经过一定的毫秒后,只执行 ...

随机推荐

  1. 公司内网搭建代理DNS使用内网域名代替ip地址

    企业场景 一般在企业内部,开发.测试以及预生产都会有一套供开发以及测试人员使用的网络环境.运维人员会为每套环境的相关项目配置单独的Tomcat,然后开放一个端口,以 IP+Port 的形式访问.然而随 ...

  2. Java内部抽象类的匿名类初始化

    说在前面的话,以前写过一次这个变态代码,后来重构,把那个new的语法简化了,最近又要整,差点都想不起来了,留个文档把 1.下面这个案例更变态,抽象类还有一个个泛型类:首先内部抽象类的定义: /* * ...

  3. 晒stlink以及stm8“开发板”

    先上图:( ̄▽ ̄)" 单层板+几根飞线,之前做jlink-ob做过孔整怕了,画pcb的时候尽量朝单层画的 外壳用sw建模,并3d打印 引出了swim for stm8单总线调试接口 以及sw ...

  4. CSS学习笔记六:写原生导航栏

    因为刚开始学习CSS时,只了解了一些基本样式,然后就跑去学习bootstrap.bootstrap是个不错的东西,挺好玩,起码让你写界面写的轻轻松松,几行引入代码,再来个复制粘贴就解决了,而且boot ...

  5. Python学习 Part3:数据结构

    Python学习 Part3:数据结构 1. 深入列表: 所有的列表对象方法 list.append(x): 在列表的末尾添加一个元素 list.extend(L): 在列表的末尾添加一个指定列表的所 ...

  6. os模块介绍

    一.os模块概述 Python os模块包含普遍的操作系统功能.如果你希望你的程序能够与平台无关的话,这个模块是尤为重要的.(一语中的) 二.常用方法 1.os.name 输出字符串指示正在使用的平台 ...

  7. [CVPR 2016] Weakly Supervised Deep Detection Networks论文笔记

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "Helvetica Neue"; color: #323333 } p. ...

  8. 读《图解HTTP》有感-(简单的HTTP协议)

    写在前面 该章节主要是针对HTTP1.1版本进行基础的讲解 正文 HTTP协议能做什么: http协议用于客户端和服务端之间的通信 HTTP协议通信方式: http协议是基于请求响应的方式来实现消息通 ...

  9. SpringMVC中Controller的方法返回值

    1. 返回ModelAndView对象 controller方法中定义ModelAndView对象并返回,对象中可添加model数据.指定view. 实例 @RequestMapping(" ...

  10. Hessian基础入门案例

    Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能. 相比WebService,Hessian更简单.快捷.采用的是二进制RPC协议,因为采用的是二进制协 ...