Trillian 的使用

源码:https://github.com/google/trillian

编译及安装

go get github.com/google/trillian
cd $GOPATH/src/github.com/google/trillian
go get -t -u -v ./...

可执行程序

主要由三个进程 trillian_log_server, trillian_log_signer 和 trillian_map_server

  1. trillian_log_server
./trillian_log_server ${ETCD_OPTS} ${pkcs11_opts} ${logserver_opts} --rpc_endpoint="localhost:${port}" --http_endpoint="localhost:${http}" &
  1. trillian_log_signer
./trillian_log_signer ${ETCD_OPTS} ${pkcs11_opts} ${logsigner_opts} --sequencer_interval="1s" --batch_size=500 --http_endpoint="localhost:${http}" --num_sequencers 2 &

3 trillian_map_server
注意,上述步骤启动的两个服务,分别提供 rpc 服务和 http 服务,rpc 服务(admin_server 和 log_server)使用的端口默认是 8090,http 服务使用的端口默认是 8091启动服务时,会连接默认的 MySQL 数据库 test:zaphod@tcp(127.0.0.1:3306)/test ,所以,本机测试的时候,需要事先确保有相应的连接权限。

先启动一个本地的 etcd 服务器(执行 etcd 或者 etcd -data-dir=/var/edata
etcd 服务器的主要目的是注册当前的 grpc 二进制服务器,每个服务器对应一个 etcd 存储中的 key。

启动 trillian_log_server 之后,我们编译 cmd/createtree/main.go 目录,生成一个客户端进程 createtree,这个客户端进程通过调用 trillian_log_server 提供的 grpc 服务来创建一个 tree,并返回一个 treeId。

./createtree --admin_server="localhost:8090" \ 
             --pem_key_path=../../testdata/log-rpc-server.privkey.pem \
             --pem_key_password=towel \
             --signature_algorithm=ECDSA

创建 Tree 的过程

cmd/createtree/main.go 中会构建一个 trillian.CreateTreeRequest,
构建好 request 对象之后,调用 trillian.NewTrillianAdminClient(conn).CreateTree(ctx, req) 来创建一个 Tree,并返回一个 TreeID。

代码组织方式

这里调用的服务是 TrillianAdminServer 提供的 CreateTree 服务(在 server/admin/admin_server.go 中),但是这个 TrillianAdminServer 却不是通过一个独立的服务进程(main.go)来提供的,而是被封装在 server/main.go 中的 Main 结构体中,然后通过 server/trillian_log_server/main.go 来使用 Main 注册 grpc 服务而提供的。

因为 grpc 根据 trillian_admin_api.proto 生成的 trillian_admin_api.pb.go 代码中 是通过一个 TrillianAdminServer interface 类型来提供服务的

type TrillianAdminServer interface {
    // Lists all trees the requester has access to.
    ListTrees(context.Context, *ListTreesRequest) (*ListTreesResponse, error)
    // Retrieves a tree by ID.
    GetTree(context.Context, *GetTreeRequest) (*Tree, error)
    // Creates a new tree.
    // System-generated fields are not required and will be ignored if present,
    // e.g.: tree_id, create_time and update_time.
    // Returns the created tree, with all system-generated fields assigned.
    CreateTree(context.Context, *CreateTreeRequest) (*Tree, error)
    // Updates a tree.
    // See Tree for details. Readonly fields cannot be updated.
    UpdateTree(context.Context, *UpdateTreeRequest) (*Tree, error)
    // Soft-deletes a tree.
    // A soft-deleted tree may be undeleted for a certain period, after which
    // it'll be permanently deleted.
    // TODO(codingllama): Provide an undelete RPC.
    DeleteTree(context.Context, *DeleteTreeRequest) (*google_protobuf5.Empty, error)
}

server/admin/admin_server.go 里定义了一个 Server 的 struct 来实现上述接口

// Server is an implementation of trillian.TrillianAdminServer.
type Server struct {
    registry extension.Registry
}

// New returns a trillian.TrillianAdminServer implementation.
func New(registry extension.Registry) *Server {
    return &Server{registry}
}

可见,Server 结构体里面通过 registry 来实现了扩展。

Trillian 提供的是 trillian_log_servertrillian_map_server 两个 grpc 服务端进程,具体的服务是通过 server/log_rpc_server.goserver/map_rpc_server.go 来实现的。

比如 QueueLeaf(),GetInclusionProof() 等。

Trillian 还用了一个 server/main.go 来封装把上述两个 server 封装在一个 Main 的 struct 中,如下:

// Main encapsulates the data and logic to start a Trillian server (Log or Map).
type Main struct {
    // Endpoints for RPC and HTTP/REST servers.
    // HTTP/REST is optional, if empty it'll not be bound.
    RPCEndpoint, HTTPEndpoint string
    DB                        *sql.DB
    Registry                  extension.Registry
    Server                    *grpc.Server
    // RegisterHandlerFn is called to register REST-proxy handlers.
    RegisterHandlerFn func(context.Context, *runtime.ServeMux, string, []grpc.DialOption) error
    // RegisterServerFn is called to register RPC servers.
    RegisterServerFn func(*grpc.Server, extension.Registry) error
}

// Run starts the configured server. Blocks until the server exits.
func (m *Main) Run(ctx context.Context) error {
    //...
}

Main struct 中的 RPCEndpoint 和 HTTPEndpoint,服务端启动时,默认会使用 localhost:8090 作为 RPC 端口,localhost:8091 最为 http 端口。

var (
    rpcEndpoint        = flag.String("rpc_endpoint", "localhost:8090", "Endpoint for RPC requests (host:port)")
    httpEndpoint       = flag.String("http_endpoint", "localhost:8091", "Endpoint for HTTP metrics and REST requests on (host:port, empty means disabled)")
    //...
)

这个 Main struct 实现了一个 Run,然后在 server/trillian_log_server/main.goserver/trillian_map_server/main.go 中就可以都通过调用 Main.Run() 来启动 grpc 服务。

这个地方的设计非常巧妙,两个进程都通过调用一个统一的封装好的结构体 Main 的 Run 函数来启动 grpc 服务。 然后。在这个统一的 Run 函数里面,我们又可以封装其他许多公共的服务,比如 http 服务,TrillianAdminServer 等。

这里本质上利用了一个 grpc server 可以注册多个实现,比如:

server := grpc.NewServer()
pb.RegisterSayHelloServer(server, NewHelloServer())
pb.RegisterSayGoodByeServer(server, NewGoodByeServer())
``

可见,上述代码为一个 grpc 服务器注册了多个实现。

#### Trillian 集成测试 integration test

执行下面两个脚本来进行 trillian_log_server 和 trillian_map_server 的集成测试:
integration/log_integration_test.sh
integration/map_integration_test.sh

集成测试时,需要准备环境,比如 integration/functions.sh 中的 log_prep_test() {} 函数,如下

```go
log_prep_test() {
  go build ${GOFLAGS} github.com/google/trillian/server/trillian_log_server/
  go build ${GOFLAGS} github.com/google/trillian/server/trillian_log_signer/

  "${TRILLIAN_PATH}/scripts/resetdb.sh"

  if [[ -x "${ETCD_DIR}/etcd" ]]; then
    ${ETCD_DIR}/etcd &
  else
    ...
  fi
  ./trillian_log_server ${ETCD_OPTS} ${pkcs11_opts} ${logserver_opts} --rpc_endpoint="localhost:${port}" --http_endpoint="localhost:${http}" &
  ./trillian_log_signer ${ETCD_OPTS} ${pkcs11_opts} ${logsigner_opts} --sequencer_interval="1s" --batch_size=500 --http_endpoint="localhost:${http}" --num_sequencers 2 &
}

会启动一定数量的 trillian_log_server 和 trillian_log_signer,默认是各一个。
同时,如果本地有 etcd 实例,那么会启动 etcd server。然后在启动 trillian_log_server 的时候通过 logserver_opts 的方式传入。

ETCD_OPTS="--etcd_servers=${etcd_server}"
logserver_opts="--etcd_http_service=trillian-logserver-http --etcd_service=trillian-logserver"
logsigner_opts="--etcd_http_service=trillian-logsigner-http"
pkcs11_opts="--pkcs11_module_path ${PKCS11_MODULE:-/usr/lib/softhsm/libsofthsm.so}"

# 启动 trillian_log_server
./trillian_log_server ${ETCD_OPTS} ${pkcs11_opts} ${logserver_opts} --rpc_endpoint="localhost:${rpc_port}" --http_endpoint="localhost:${http_port}" &

# 启动 trillian_log_signer
./trillian_log_signer ${ETCD_OPTS} ${pkcs11_opts} ${logsigner_opts} --sequencer_interval="1s" --batch_size=500 --http_endpoint="localhost:${http}" --num_sequencers 2 &

etcd 实际上是一个 k/v 数据库,因此,如果上述指定了 ETCD_OPTS,成功启动两个 trillian_log_server 和 trillian_log_signer 之后,它们会往 etcd 中写入值,这时候我们就应该能读取到写入的值:

if [[ ! -z "${ETCD_OPTS}" ]]; then
    RPC_SERVERS="trillian-logserver"
    echo "Registered log servers @${RPC_SERVERS}/"
    ETCDCTL_API=3 etcdctl get ${RPC_SERVERS}/ --prefix
    echo "Registered HTTP endpoints"
    ETCDCTL_API=3 etcdctl get trillian-logserver-http/ --prefix
    ETCDCTL_API=3 etcdctl get trillian-logsigner-http/ --prefix
  fi

log_integration_test.sh 整个过程是这样的

KEY_ARGS="--pem_key_path=testdata/log-rpc-server.privkey.pem --pem_key_password=towel --signature_algorithm=ECDSA"

# 1. 启动 trillian_log_server
log_prep_test 1 1

# 2. 编译并执行客户端,调用 grpc 服务来创建一颗 Tree 并拿到返回的 TreeID
go build ${GOFLAGS} github.com/google/trillian/cmd/createtree/
TEST_TREE_ID=$(./createtree \
  --admin_server="${RPC_SERVER_1}" \
  ${KEY_ARGS})

# 3. 根据拿到的 TreeId 来运行 log_integration_test.go 中的 test cases
go test -run ".*LiveLog.*" --timeout=5m ./ --treeid ${TEST_TREE_ID} --log_rpc_server="${RPC_SERVER_1}"

# 4. 停止 trillian_log_server
log_stop_test
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 简介 此项目是一个模拟公民身份信息链的区块链项目 github 地址: https://github.com/ak...
    CrazyWolf_46a9阅读 8,653评论 0 0
  • Codis 3.2 部署配置汇总 概念总结 集群配置前需要了解架构,集群分片主要分三种: 客户端分片:这个需要自己...
    三杯水Plus阅读 5,841评论 0 11
  • ETCD RBAC功能简介 ETCD的介绍网上已经烂大街了,我就不赘述了。因为本文主要是使用ETCD的RBAC功能...
    30的小周阅读 1,194评论 4 4
  • 原文出处:gRPC gRPC分享 概述 gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远...
    小波同学阅读 7,131评论 0 18
  • 1)简介 gRPC负载平衡的主要实现机制是外部负载平衡,即通过外部负载平衡器来向客户端提供更新后的服务器列表。 g...
    Jay_Guo阅读 13,060评论 6 22