Repository: matrixorigin/matrixone
Pull request #3927 (2022-07-12 13:05:50)

hakeeper: added hakeeper client
**What type of PR is this?**

- [ ] API-change
- [ ] BUG
- [ ] Improvement
- [ ] Documentation
- [x] Feature
- [ ] Test and CI
- [ ] Code Refactoring

**Which issue(s) this PR fixes:**

issue #

**What this PR does / why we need it:**

Initial commit of HAKeeper client, still has some rough edges. 

**Special notes for your reviewer:**

Not Available

**Additional documentation (e.g. design docs, usage docs, etc.):**

Not Available



Patch:
diff --git a/pkg/logservice/hachecker.go b/pkg/logservice/hakeeper_checker.go
similarity index 100%
rename from pkg/logservice/hachecker.go
rename to pkg/logservice/hakeeper_checker.go
diff --git a/pkg/logservice/hachecker_test.go b/pkg/logservice/hakeeper_checker_test.go
similarity index 100%
rename from pkg/logservice/hachecker_test.go
rename to pkg/logservice/hakeeper_checker_test.go
diff --git a/pkg/logservice/hakeeper_client.go b/pkg/logservice/hakeeper_client.go
new file mode 100644
index 000000000..02d1af185
--- /dev/null
+++ b/pkg/logservice/hakeeper_client.go
@@ -0,0 +1,222 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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
+//
+//      http://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.
+
+package logservice
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
+	"github.com/matrixorigin/matrixone/pkg/common/morpc"
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+var (
+	ErrNoHAKeeper = moerr.NewError(moerr.INVALID_STATE, "failed to locate HAKeeper")
+)
+
+// CNHAKeeperClient is the HAKeeper client used by a CN store.
+type CNHAKeeperClient interface {
+	// Close closes the hakeeper client.
+	Close() error
+	// GetClusterDetails queries the HAKeeper and return CN and DN nodes that are
+	// known to the HAKeeper.
+	GetClusterDetails(ctx context.Context) (pb.ClusterDetails, error)
+	// SendCNHeartbeat sends the specified heartbeat message to the HAKeeper.
+	SendCNHeartbeat(ctx context.Context, hb pb.CNStoreHeartbeat) error
+}
+
+// DNHAKeeperClient is the HAKeeper client used by a DN store.
+type DNHAKeeperClient interface {
+	// Close closes the hakeeper client.
+	Close() error
+	// SendDNHeartbeat sends the specified heartbeat message to the HAKeeper. The
+	// returned CommandBatch contains Schedule Commands to be executed by the local
+	// DN store.
+	SendDNHeartbeat(ctx context.Context, hb pb.DNStoreHeartbeat) (pb.CommandBatch, error)
+}
+
+// LogHAKeeperClient is the HAKeeper client used by a Log store.
+type LogHAKeeperClient interface {
+	// Close closes the hakeeper client.
+	Close() error
+	// SendLogHeartbeat sends the specified heartbeat message to the HAKeeper. The
+	// returned CommandBatch contains Schedule Commands to be executed by the local
+	// Log store.
+	SendLogHeartbeat(ctx context.Context, hb pb.LogStoreHeartbeat) (pb.CommandBatch, error)
+}
+
+// HAKeeperClientConfig is the config for HAKeeper clients.
+type HAKeeperClientConfig struct {
+	// DiscoveryAddress is the Log Service discovery address provided by k8s.
+	DiscoveryAddress string
+	// ServiceAddresses is a list of well known Log Services' service addresses.
+	ServiceAddresses []string
+}
+
+type hakeeperClient struct {
+	cfg      HAKeeperClientConfig
+	client   morpc.RPCClient
+	addr     string
+	pool     *sync.Pool
+	respPool *sync.Pool
+}
+
+func newHAKeeperClient(ctx context.Context,
+	cfg HAKeeperClientConfig) (*hakeeperClient, error) {
+	pool := &sync.Pool{}
+	pool.New = func() interface{} {
+		return &RPCRequest{pool: pool}
+	}
+	respPool := &sync.Pool{}
+	respPool.New = func() interface{} {
+		return &RPCResponse{pool: respPool}
+	}
+	c := &hakeeperClient{
+		cfg:      cfg,
+		pool:     pool,
+		respPool: respPool,
+	}
+	var e error
+	for _, addr := range cfg.ServiceAddresses {
+		cc, err := getRPCClient(ctx, addr, c.respPool)
+		if err != nil {
+			e = err
+			continue
+		}
+		c.addr = addr
+		c.client = cc
+		isHAKeeper, err := c.checkRemote(ctx)
+		if err == nil && isHAKeeper {
+			return c, nil
+		} else if err != nil {
+			e = err
+		}
+		if err := cc.Close(); err != nil {
+			plog.Errorf("failed to close the client %v", err)
+		}
+	}
+	if e == nil {
+		// didn't encounter any error
+		return nil, ErrNoHAKeeper
+	}
+	return nil, e
+}
+
+var _ CNHAKeeperClient = (*hakeeperClient)(nil)
+var _ DNHAKeeperClient = (*hakeeperClient)(nil)
+var _ LogHAKeeperClient = (*hakeeperClient)(nil)
+
+func NewCNHAKeeperClient(ctx context.Context,
+	cfg HAKeeperClientConfig) (CNHAKeeperClient, error) {
+	return newHAKeeperClient(ctx, cfg)
+}
+
+func NewDNHAKeeperClient(ctx context.Context,
+	cfg HAKeeperClientConfig) (DNHAKeeperClient, error) {
+	return newHAKeeperClient(ctx, cfg)
+}
+
+func NewLogHAKeeperClient(ctx context.Context,
+	cfg HAKeeperClientConfig) (LogHAKeeperClient, error) {
+	return newHAKeeperClient(ctx, cfg)
+}
+
+func (c *hakeeperClient) Close() error {
+	return c.client.Close()
+}
+
+func (c *hakeeperClient) GetClusterDetails(ctx context.Context) (pb.ClusterDetails, error) {
+	panic("not implemented")
+}
+
+func (c *hakeeperClient) SendCNHeartbeat(ctx context.Context, hb pb.CNStoreHeartbeat) error {
+	req := pb.Request{
+		Method:      pb.CN_HEARTBEAT,
+		CNHeartbeat: hb,
+	}
+	_, err := c.sendHeartbeat(ctx, req)
+	return err
+}
+
+func (c *hakeeperClient) SendDNHeartbeat(ctx context.Context,
+	hb pb.DNStoreHeartbeat) (pb.CommandBatch, error) {
+	req := pb.Request{
+		Method:      pb.DN_HEARTBEAT,
+		DNHeartbeat: hb,
+	}
+	return c.sendHeartbeat(ctx, req)
+}
+
+func (c *hakeeperClient) SendLogHeartbeat(ctx context.Context,
+	hb pb.LogStoreHeartbeat) (pb.CommandBatch, error) {
+	req := pb.Request{
+		Method:       pb.LOG_HEARTBEAT,
+		LogHeartbeat: hb,
+	}
+	return c.sendHeartbeat(ctx, req)
+}
+
+func (c *hakeeperClient) sendHeartbeat(ctx context.Context,
+	req pb.Request) (pb.CommandBatch, error) {
+	resp, err := c.request(ctx, req)
+	if err != nil {
+		return pb.CommandBatch{}, err
+	}
+	return resp.CommandBatch, nil
+}
+
+func (c *hakeeperClient) checkRemote(ctx context.Context) (bool, error) {
+	req := pb.Request{
+		Method: pb.CHECK_HAKEEPER,
+	}
+	resp, err := c.request(ctx, req)
+	if err != nil {
+		return false, err
+	}
+	return resp.IsHAKeeper, nil
+}
+
+func (c *hakeeperClient) request(ctx context.Context, req pb.Request) (pb.Response, error) {
+	timeout, err := getTimeoutFromContext(ctx)
+	if err != nil {
+		return pb.Response{}, err
+	}
+	req.Timeout = int64(timeout)
+	r := c.pool.Get().(*RPCRequest)
+	r.Request = req
+	future, err := c.client.Send(ctx,
+		c.addr, r, morpc.SendOptions{Timeout: time.Duration(timeout)})
+	if err != nil {
+		return pb.Response{}, err
+	}
+	defer future.Close()
+	msg, err := future.Get()
+	if err != nil {
+		return pb.Response{}, err
+	}
+	response, ok := msg.(*RPCResponse)
+	if !ok {
+		panic("unexpected response type")
+	}
+	resp := response.Response
+	defer response.Release()
+	err = toError(response.Response)
+	if err != nil {
+		return pb.Response{}, err
+	}
+	return resp, nil
+}
diff --git a/pkg/logservice/service.go b/pkg/logservice/service.go
index 55e3d1e7e..af44e8d45 100644
--- a/pkg/logservice/service.go
+++ b/pkg/logservice/service.go
@@ -23,6 +23,7 @@ import (
 	"github.com/lni/dragonboat/v4/logger"
 
 	"github.com/matrixorigin/matrixone/pkg/common/morpc"
+	"github.com/matrixorigin/matrixone/pkg/hakeeper"
 	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
 )
 
@@ -168,6 +169,8 @@ func (s *Service) handle(req pb.Request,
 		return s.handleCNHeartbeat(req), pb.LogRecordResponse{}
 	case pb.DN_HEARTBEAT:
 		return s.handleDNHeartbeat(req), pb.LogRecordResponse{}
+	case pb.CHECK_HAKEEPER:
+		return s.handleCheckHAKeeper(req), pb.LogRecordResponse{}
 	default:
 		panic("unknown log service method type")
 	}
@@ -317,3 +320,16 @@ func (s *Service) handleDNHeartbeat(req pb.Request) pb.Response {
 
 	return resp
 }
+
+func (s *Service) handleCheckHAKeeper(req pb.Request) pb.Response {
+	resp := getResponse(req)
+	hb := s.store.getHeartbeatMessage()
+	for _, replicaInfo := range hb.Replicas {
+		si := replicaInfo.LogShardInfo
+		if si.ShardID == hakeeper.DefaultHAKeeperShardID {
+			resp.IsHAKeeper = true
+			return resp
+		}
+	}
+	return resp
+}
diff --git a/pkg/logservice/store.go b/pkg/logservice/store.go
index 0c01229c6..486cce131 100644
--- a/pkg/logservice/store.go
+++ b/pkg/logservice/store.go
@@ -655,6 +655,7 @@ func (l *store) getHeartbeatMessage() pb.LogStoreHeartbeat {
 			},
 			ReplicaID: ci.ReplicaID,
 		}
+		// FIXME: why we need this?
 		if replicaInfo.Replicas == nil {
 			replicaInfo.Replicas = make(map[uint64]dragonboat.Target)
 		}
diff --git a/pkg/pb/logservice/logservice.pb.go b/pkg/pb/logservice/logservice.pb.go
index 85dd3be35..0e5ea2093 100644
--- a/pkg/pb/logservice/logservice.pb.go
+++ b/pkg/pb/logservice/logservice.pb.go
@@ -59,42 +59,45 @@ func (UpdateType) EnumDescriptor() ([]byte, []int) {
 type MethodType int32
 
 const (
-	TSO_UPDATE    MethodType = 0
-	APPEND        MethodType = 1
-	READ          MethodType = 2
-	TRUNCATE      MethodType = 3
-	GET_TRUNCATE  MethodType = 4
-	CONNECT       MethodType = 5
-	CONNECT_RO    MethodType = 6
-	LOG_HEARTBEAT MethodType = 7
-	CN_HEARTBEAT  MethodType = 8
-	DN_HEARTBEAT  MethodType = 9
+	TSO_UPDATE     MethodType = 0
+	APPEND         MethodType = 1
+	READ           MethodType = 2
+	TRUNCATE       MethodType = 3
+	GET_TRUNCATE   MethodType = 4
+	CONNECT        MethodType = 5
+	CONNECT_RO     MethodType = 6
+	LOG_HEARTBEAT  MethodType = 7
+	CN_HEARTBEAT   MethodType = 8
+	DN_HEARTBEAT   MethodType = 9
+	CHECK_HAKEEPER MethodType = 10
 )
 
 var MethodType_name = map[int32]string{
-	0: "TSO_UPDATE",
-	1: "APPEND",
-	2: "READ",
-	3: "TRUNCATE",
-	4: "GET_TRUNCATE",
-	5: "CONNECT",
-	6: "CONNECT_RO",
-	7: "LOG_HEARTBEAT",
-	8: "CN_HEARTBEAT",
-	9: "DN_HEARTBEAT",
+	0:  "TSO_UPDATE",
+	1:  "APPEND",
+	2:  "READ",
+	3:  "TRUNCATE",
+	4:  "GET_TRUNCATE",
+	5:  "CONNECT",
+	6:  "CONNECT_RO",
+	7:  "LOG_HEARTBEAT",
+	8:  "CN_HEARTBEAT",
+	9:  "DN_HEARTBEAT",
+	10: "CHECK_HAKEEPER",
 }
 
 var MethodType_value = map[string]int32{
-	"TSO_UPDATE":    0,
-	"APPEND":        1,
-	"READ":          2,
-	"TRUNCATE":      3,
-	"GET_TRUNCATE":  4,
-	"CONNECT":       5,
-	"CONNECT_RO":    6,
-	"LOG_HEARTBEAT": 7,
-	"CN_HEARTBEAT":  8,
-	"DN_HEARTBEAT":  9,
+	"TSO_UPDATE":     0,
+	"APPEND":         1,
+	"READ":           2,
+	"TRUNCATE":       3,
+	"GET_TRUNCATE":   4,
+	"CONNECT":        5,
+	"CONNECT_RO":     6,
+	"LOG_HEARTBEAT":  7,
+	"CN_HEARTBEAT":   8,
+	"DN_HEARTBEAT":   9,
+	"CHECK_HAKEEPER": 10,
 }
 
 func (x MethodType) String() string {
@@ -332,6 +335,148 @@ func (ServiceType) EnumDescriptor() ([]byte, []int) {
 	return fileDescriptor_fd1040c5381ab5a7, []int{7}
 }
 
+type CNNode struct {
+	UUID                 string   `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"`
+	Address              string   `protobuf:"bytes,2,opt,name=Address,proto3" json:"Address,omitempty"`
+	Tick                 uint64   `protobuf:"varint,3,opt,name=Tick,proto3" json:"Tick,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CNNode) Reset()         { *m = CNNode{} }
+func (m *CNNode) String() string { return proto.CompactTextString(m) }
+func (*CNNode) ProtoMessage()    {}
+func (*CNNode) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{0}
+}
+func (m *CNNode) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *CNNode) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_CNNode.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *CNNode) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CNNode.Merge(m, src)
+}
+func (m *CNNode) XXX_Size() int {
+	return m.Size()
+}
+func (m *CNNode) XXX_DiscardUnknown() {
+	xxx_messageInfo_CNNode.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CNNode proto.InternalMessageInfo
+
+func (m *CNNode) GetUUID() string {
+	if m != nil {
+		return m.UUID
+	}
+	return ""
+}
+
+func (m *CNNode) GetAddress() string {
+	if m != nil {
+		return m.Address
+	}
+	return ""
+}
+
+func (m *CNNode) GetTick() uint64 {
+	if m != nil {
+		return m.Tick
+	}
+	return 0
+}
+
+type DNNode struct {
+	UUID                 string   `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"`
+	ShardID              uint64   `protobuf:"varint,2,opt,name=ShardID,proto3" json:"ShardID,omitempty"`
+	ReplicaID            uint64   `protobuf:"varint,3,opt,name=ReplicaID,proto3" json:"ReplicaID,omitempty"`
+	Address              string   `protobuf:"bytes,4,opt,name=Address,proto3" json:"Address,omitempty"`
+	Tick                 uint64   `protobuf:"varint,5,opt,name=Tick,proto3" json:"Tick,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *DNNode) Reset()         { *m = DNNode{} }
+func (m *DNNode) String() string { return proto.CompactTextString(m) }
+func (*DNNode) ProtoMessage()    {}
+func (*DNNode) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{1}
+}
+func (m *DNNode) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *DNNode) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_DNNode.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *DNNode) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DNNode.Merge(m, src)
+}
+func (m *DNNode) XXX_Size() int {
+	return m.Size()
+}
+func (m *DNNode) XXX_DiscardUnknown() {
+	xxx_messageInfo_DNNode.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DNNode proto.InternalMessageInfo
+
+func (m *DNNode) GetUUID() string {
+	if m != nil {
+		return m.UUID
+	}
+	return ""
+}
+
+func (m *DNNode) GetShardID() uint64 {
+	if m != nil {
+		return m.ShardID
+	}
+	return 0
+}
+
+func (m *DNNode) GetReplicaID() uint64 {
+	if m != nil {
+		return m.ReplicaID
+	}
+	return 0
+}
+
+func (m *DNNode) GetAddress() string {
+	if m != nil {
+		return m.Address
+	}
+	return ""
+}
+
+func (m *DNNode) GetTick() uint64 {
+	if m != nil {
+		return m.Tick
+	}
+	return 0
+}
+
 // LogShardInfo contains information a log shard.
 type LogShardInfo struct {
 	// ShardID is the ID of a Log shard.
@@ -356,7 +501,7 @@ func (m *LogShardInfo) Reset()         { *m = LogShardInfo{} }
 func (m *LogShardInfo) String() string { return proto.CompactTextString(m) }
 func (*LogShardInfo) ProtoMessage()    {}
 func (*LogShardInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{0}
+	return fileDescriptor_fd1040c5381ab5a7, []int{2}
 }
 func (m *LogShardInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -434,7 +579,7 @@ func (m *LogReplicaInfo) Reset()         { *m = LogReplicaInfo{} }
 func (m *LogReplicaInfo) String() string { return proto.CompactTextString(m) }
 func (*LogReplicaInfo) ProtoMessage()    {}
 func (*LogReplicaInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{1}
+	return fileDescriptor_fd1040c5381ab5a7, []int{3}
 }
 func (m *LogReplicaInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -482,7 +627,7 @@ func (m *CNStoreHeartbeat) Reset()         { *m = CNStoreHeartbeat{} }
 func (m *CNStoreHeartbeat) String() string { return proto.CompactTextString(m) }
 func (*CNStoreHeartbeat) ProtoMessage()    {}
 func (*CNStoreHeartbeat) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{2}
+	return fileDescriptor_fd1040c5381ab5a7, []int{4}
 }
 func (m *CNStoreHeartbeat) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -539,7 +684,7 @@ func (m *LogStoreHeartbeat) Reset()         { *m = LogStoreHeartbeat{} }
 func (m *LogStoreHeartbeat) String() string { return proto.CompactTextString(m) }
 func (*LogStoreHeartbeat) ProtoMessage()    {}
 func (*LogStoreHeartbeat) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{3}
+	return fileDescriptor_fd1040c5381ab5a7, []int{5}
 }
 func (m *LogStoreHeartbeat) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -621,7 +766,7 @@ func (m *DNShardInfo) Reset()         { *m = DNShardInfo{} }
 func (m *DNShardInfo) String() string { return proto.CompactTextString(m) }
 func (*DNShardInfo) ProtoMessage()    {}
 func (*DNShardInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{4}
+	return fileDescriptor_fd1040c5381ab5a7, []int{6}
 }
 func (m *DNShardInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -680,7 +825,7 @@ func (m *DNStoreHeartbeat) Reset()         { *m = DNStoreHeartbeat{} }
 func (m *DNStoreHeartbeat) String() string { return proto.CompactTextString(m) }
 func (*DNStoreHeartbeat) ProtoMessage()    {}
 func (*DNStoreHeartbeat) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{5}
+	return fileDescriptor_fd1040c5381ab5a7, []int{7}
 }
 func (m *DNStoreHeartbeat) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -738,7 +883,7 @@ func (m *RSMState) Reset()         { *m = RSMState{} }
 func (m *RSMState) String() string { return proto.CompactTextString(m) }
 func (*RSMState) ProtoMessage()    {}
 func (*RSMState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{6}
+	return fileDescriptor_fd1040c5381ab5a7, []int{8}
 }
 func (m *RSMState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -816,7 +961,7 @@ func (m *LogRecord) Reset()         { *m = LogRecord{} }
 func (m *LogRecord) String() string { return proto.CompactTextString(m) }
 func (*LogRecord) ProtoMessage()    {}
 func (*LogRecord) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{7}
+	return fileDescriptor_fd1040c5381ab5a7, []int{9}
 }
 func (m *LogRecord) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -882,7 +1027,7 @@ func (m *LogRequest) Reset()         { *m = LogRequest{} }
 func (m *LogRequest) String() string { return proto.CompactTextString(m) }
 func (*LogRequest) ProtoMessage()    {}
 func (*LogRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{8}
+	return fileDescriptor_fd1040c5381ab5a7, []int{10}
 }
 func (m *LogRequest) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -964,7 +1109,7 @@ func (m *TsoRequest) Reset()         { *m = TsoRequest{} }
 func (m *TsoRequest) String() string { return proto.CompactTextString(m) }
 func (*TsoRequest) ProtoMessage()    {}
 func (*TsoRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{9}
+	return fileDescriptor_fd1040c5381ab5a7, []int{11}
 }
 func (m *TsoRequest) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1018,7 +1163,7 @@ func (m *Request) Reset()         { *m = Request{} }
 func (m *Request) String() string { return proto.CompactTextString(m) }
 func (*Request) ProtoMessage()    {}
 func (*Request) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{10}
+	return fileDescriptor_fd1040c5381ab5a7, []int{12}
 }
 func (m *Request) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1116,7 +1261,7 @@ func (m *LogResponse) Reset()         { *m = LogResponse{} }
 func (m *LogResponse) String() string { return proto.CompactTextString(m) }
 func (*LogResponse) ProtoMessage()    {}
 func (*LogResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{11}
+	return fileDescriptor_fd1040c5381ab5a7, []int{13}
 }
 func (m *LogResponse) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1171,9 +1316,10 @@ type Response struct {
 	Method               MethodType   `protobuf:"varint,2,opt,name=Method,proto3,enum=logservice.MethodType" json:"Method,omitempty"`
 	ErrorCode            ErrorCode    `protobuf:"varint,3,opt,name=ErrorCode,proto3,enum=logservice.ErrorCode" json:"ErrorCode,omitempty"`
 	ErrorMessage         string       `protobuf:"bytes,4,opt,name=ErrorMessage,proto3" json:"ErrorMessage,omitempty"`
-	LogResponse          LogResponse  `protobuf:"bytes,5,opt,name=LogResponse,proto3" json:"LogResponse"`
-	CommandBatch         CommandBatch `protobuf:"bytes,6,opt,name=CommandBatch,proto3" json:"CommandBatch"`
-	TsoResponse          TsoResponse  `protobuf:"bytes,7,opt,name=TsoResponse,proto3" json:"TsoResponse"`
+	IsHAKeeper           bool         `protobuf:"varint,5,opt,name=IsHAKeeper,proto3" json:"IsHAKeeper,omitempty"`
+	LogResponse          LogResponse  `protobuf:"bytes,6,opt,name=LogResponse,proto3" json:"LogResponse"`
+	CommandBatch         CommandBatch `protobuf:"bytes,7,opt,name=CommandBatch,proto3" json:"CommandBatch"`
+	TsoResponse          TsoResponse  `protobuf:"bytes,8,opt,name=TsoResponse,proto3" json:"TsoResponse"`
 	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
 	XXX_unrecognized     []byte       `json:"-"`
 	XXX_sizecache        int32        `json:"-"`
@@ -1183,7 +1329,7 @@ func (m *Response) Reset()         { *m = Response{} }
 func (m *Response) String() string { return proto.CompactTextString(m) }
 func (*Response) ProtoMessage()    {}
 func (*Response) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{12}
+	return fileDescriptor_fd1040c5381ab5a7, []int{14}
 }
 func (m *Response) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1240,6 +1386,13 @@ func (m *Response) GetErrorMessage() string {
 	return ""
 }
 
+func (m *Response) GetIsHAKeeper() bool {
+	if m != nil {
+		return m.IsHAKeeper
+	}
+	return false
+}
+
 func (m *Response) GetLogResponse() LogResponse {
 	if m != nil {
 		return m.LogResponse
@@ -1272,7 +1425,7 @@ func (m *LogRecordResponse) Reset()         { *m = LogRecordResponse{} }
 func (m *LogRecordResponse) String() string { return proto.CompactTextString(m) }
 func (*LogRecordResponse) ProtoMessage()    {}
 func (*LogRecordResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{13}
+	return fileDescriptor_fd1040c5381ab5a7, []int{15}
 }
 func (m *LogRecordResponse) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1319,7 +1472,7 @@ func (m *TsoResponse) Reset()         { *m = TsoResponse{} }
 func (m *TsoResponse) String() string { return proto.CompactTextString(m) }
 func (*TsoResponse) ProtoMessage()    {}
 func (*TsoResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{14}
+	return fileDescriptor_fd1040c5381ab5a7, []int{16}
 }
 func (m *TsoResponse) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1371,7 +1524,7 @@ func (m *Replica) Reset()         { *m = Replica{} }
 func (m *Replica) String() string { return proto.CompactTextString(m) }
 func (*Replica) ProtoMessage()    {}
 func (*Replica) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{15}
+	return fileDescriptor_fd1040c5381ab5a7, []int{17}
 }
 func (m *Replica) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1443,7 +1596,7 @@ func (m *ConfigChange) Reset()         { *m = ConfigChange{} }
 func (m *ConfigChange) String() string { return proto.CompactTextString(m) }
 func (*ConfigChange) ProtoMessage()    {}
 func (*ConfigChange) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{16}
+	return fileDescriptor_fd1040c5381ab5a7, []int{18}
 }
 func (m *ConfigChange) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1505,7 +1658,7 @@ func (m *ShutdownStore) Reset()         { *m = ShutdownStore{} }
 func (m *ShutdownStore) String() string { return proto.CompactTextString(m) }
 func (*ShutdownStore) ProtoMessage()    {}
 func (*ShutdownStore) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{17}
+	return fileDescriptor_fd1040c5381ab5a7, []int{19}
 }
 func (m *ShutdownStore) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1558,7 +1711,7 @@ func (m *ScheduleCommand) Reset()         { *m = ScheduleCommand{} }
 func (m *ScheduleCommand) String() string { return proto.CompactTextString(m) }
 func (*ScheduleCommand) ProtoMessage()    {}
 func (*ScheduleCommand) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{18}
+	return fileDescriptor_fd1040c5381ab5a7, []int{20}
 }
 func (m *ScheduleCommand) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1634,7 +1787,7 @@ func (m *CommandBatch) Reset()         { *m = CommandBatch{} }
 func (m *CommandBatch) String() string { return proto.CompactTextString(m) }
 func (*CommandBatch) ProtoMessage()    {}
 func (*CommandBatch) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{19}
+	return fileDescriptor_fd1040c5381ab5a7, []int{21}
 }
 func (m *CommandBatch) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1689,7 +1842,7 @@ func (m *CNStoreInfo) Reset()         { *m = CNStoreInfo{} }
 func (m *CNStoreInfo) String() string { return proto.CompactTextString(m) }
 func (*CNStoreInfo) ProtoMessage()    {}
 func (*CNStoreInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{20}
+	return fileDescriptor_fd1040c5381ab5a7, []int{22}
 }
 func (m *CNStoreInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1738,7 +1891,7 @@ func (m *CNState) Reset()         { *m = CNState{} }
 func (m *CNState) String() string { return proto.CompactTextString(m) }
 func (*CNState) ProtoMessage()    {}
 func (*CNState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{21}
+	return fileDescriptor_fd1040c5381ab5a7, []int{23}
 }
 func (m *CNState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1787,7 +1940,7 @@ func (m *DNStoreInfo) Reset()         { *m = DNStoreInfo{} }
 func (m *DNStoreInfo) String() string { return proto.CompactTextString(m) }
 func (*DNStoreInfo) ProtoMessage()    {}
 func (*DNStoreInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{22}
+	return fileDescriptor_fd1040c5381ab5a7, []int{24}
 }
 func (m *DNStoreInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1843,7 +1996,7 @@ func (m *DNState) Reset()         { *m = DNState{} }
 func (m *DNState) String() string { return proto.CompactTextString(m) }
 func (*DNState) ProtoMessage()    {}
 func (*DNState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{23}
+	return fileDescriptor_fd1040c5381ab5a7, []int{25}
 }
 func (m *DNState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1879,6 +2032,61 @@ func (m *DNState) GetStores() map[string]DNStoreInfo {
 	return nil
 }
 
+type ClusterDetails struct {
+	DNNodes              []DNNode `protobuf:"bytes,1,rep,name=DNNodes,proto3" json:"DNNodes"`
+	CNNodes              []CNNode `protobuf:"bytes,2,rep,name=CNNodes,proto3" json:"CNNodes"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ClusterDetails) Reset()         { *m = ClusterDetails{} }
+func (m *ClusterDetails) String() string { return proto.CompactTextString(m) }
+func (*ClusterDetails) ProtoMessage()    {}
+func (*ClusterDetails) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{26}
+}
+func (m *ClusterDetails) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ClusterDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ClusterDetails.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ClusterDetails) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ClusterDetails.Merge(m, src)
+}
+func (m *ClusterDetails) XXX_Size() int {
+	return m.Size()
+}
+func (m *ClusterDetails) XXX_DiscardUnknown() {
+	xxx_messageInfo_ClusterDetails.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ClusterDetails proto.InternalMessageInfo
+
+func (m *ClusterDetails) GetDNNodes() []DNNode {
+	if m != nil {
+		return m.DNNodes
+	}
+	return nil
+}
+
+func (m *ClusterDetails) GetCNNodes() []CNNode {
+	if m != nil {
+		return m.CNNodes
+	}
+	return nil
+}
+
 // ClusterInfo provides a global view of all shards in the cluster. It
 // describes the logical sharding of the system, rather than physical
 // distribution of all replicas that belong to those shards.
@@ -1894,7 +2102,7 @@ func (m *ClusterInfo) Reset()         { *m = ClusterInfo{} }
 func (m *ClusterInfo) String() string { return proto.CompactTextString(m) }
 func (*ClusterInfo) ProtoMessage()    {}
 func (*ClusterInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{24}
+	return fileDescriptor_fd1040c5381ab5a7, []int{27}
 }
 func (m *ClusterInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1950,7 +2158,7 @@ func (m *InitialClusterRequest) Reset()         { *m = InitialClusterRequest{} }
 func (m *InitialClusterRequest) String() string { return proto.CompactTextString(m) }
 func (*InitialClusterRequest) ProtoMessage()    {}
 func (*InitialClusterRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{25}
+	return fileDescriptor_fd1040c5381ab5a7, []int{28}
 }
 func (m *InitialClusterRequest) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2016,7 +2224,7 @@ func (m *LogStoreInfo) Reset()         { *m = LogStoreInfo{} }
 func (m *LogStoreInfo) String() string { return proto.CompactTextString(m) }
 func (*LogStoreInfo) ProtoMessage()    {}
 func (*LogStoreInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{26}
+	return fileDescriptor_fd1040c5381ab5a7, []int{29}
 }
 func (m *LogStoreInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2099,7 +2307,7 @@ func (m *LogState) Reset()         { *m = LogState{} }
 func (m *LogState) String() string { return proto.CompactTextString(m) }
 func (*LogState) ProtoMessage()    {}
 func (*LogState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{27}
+	return fileDescriptor_fd1040c5381ab5a7, []int{30}
 }
 func (m *LogState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2159,7 +2367,7 @@ func (m *CheckerState) Reset()         { *m = CheckerState{} }
 func (m *CheckerState) String() string { return proto.CompactTextString(m) }
 func (*CheckerState) ProtoMessage()    {}
 func (*CheckerState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{28}
+	return fileDescriptor_fd1040c5381ab5a7, []int{31}
 }
 func (m *CheckerState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2244,7 +2452,7 @@ func (m *HAKeeperRSMState) Reset()         { *m = HAKeeperRSMState{} }
 func (m *HAKeeperRSMState) String() string { return proto.CompactTextString(m) }
 func (*HAKeeperRSMState) ProtoMessage()    {}
 func (*HAKeeperRSMState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_fd1040c5381ab5a7, []int{29}
+	return fileDescriptor_fd1040c5381ab5a7, []int{32}
 }
 func (m *HAKeeperRSMState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2352,6 +2560,8 @@ func init() {
 	proto.RegisterEnum("logservice.HAKeeperState", HAKeeperState_name, HAKeeperState_value)
 	proto.RegisterEnum("logservice.ConfigChangeType", ConfigChangeType_name, ConfigChangeType_value)
 	proto.RegisterEnum("logservice.ServiceType", ServiceType_name, ServiceType_value)
+	proto.RegisterType((*CNNode)(nil), "logservice.CNNode")
+	proto.RegisterType((*DNNode)(nil), "logservice.DNNode")
 	proto.RegisterType((*LogShardInfo)(nil), "logservice.LogShardInfo")
 	proto.RegisterMapType((map[uint64]string)(nil), "logservice.LogShardInfo.ReplicasEntry")
 	proto.RegisterType((*LogReplicaInfo)(nil), "logservice.LogReplicaInfo")
@@ -2381,6 +2591,7 @@ func init() {
 	proto.RegisterType((*DNStoreInfo)(nil), "logservice.DNStoreInfo")
 	proto.RegisterType((*DNState)(nil), "logservice.DNState")
 	proto.RegisterMapType((map[string]DNStoreInfo)(nil), "logservice.DNState.StoresEntry")
+	proto.RegisterType((*ClusterDetails)(nil), "logservice.ClusterDetails")
 	proto.RegisterType((*ClusterInfo)(nil), "logservice.ClusterInfo")
 	proto.RegisterType((*InitialClusterRequest)(nil), "logservice.InitialClusterRequest")
 	proto.RegisterType((*LogStoreInfo)(nil), "logservice.LogStoreInfo")
@@ -2396,147 +2607,153 @@ func init() {
 func init() { proto.RegisterFile("logservice.proto", fileDescriptor_fd1040c5381ab5a7) }
 
 var fileDescriptor_fd1040c5381ab5a7 = []byte{
-	// 2182 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0xcd, 0x6f, 0x23, 0x49,
-	0x15, 0x4f, 0xfb, 0xdb, 0xcf, 0x49, 0xa6, 0x53, 0x99, 0x4c, 0xbc, 0xd9, 0x65, 0x26, 0xdb, 0x2c,
-	0xa3, 0xc1, 0xec, 0x3a, 0x52, 0xa2, 0x45, 0x2c, 0x64, 0x77, 0xe4, 0xb8, 0xbd, 0x99, 0x2c, 0x89,
-	0x3d, 0x6a, 0x3b, 0x68, 0x85, 0x34, 0x1a, 0x3a, 0xee, 0x8a, 0xdd, 0xc4, 0xee, 0x36, 0xdd, 0xe5,
-	0x30, 0xd9, 0x1b, 0x07, 0x90, 0x90, 0xb8, 0x70, 0x5b, 0x21, 0xce, 0xfc, 0x05, 0xfb, 0x17, 0x20,
-	0x0e, 0x7b, 0x9c, 0x0b, 0xd7, 0x15, 0xcc, 0x89, 0x1b, 0x47, 0xae, 0xa8, 0xbe, 0xba, 0xab, 0x6c,
-	0x27, 0xf3, 0x21, 0x38, 0xec, 0xcd, 0xf5, 0xea, 0xbd, 0x57, 0xef, 0xe3, 0xf7, 0x5e, 0xbd, 0x6a,
-	0x83, 0x39, 0x0a, 0x07, 0x31, 0x8e, 0x2e, 0xfd, 0x3e, 0xae, 0x4f, 0xa2, 0x90, 0x84, 0x08, 0x52,
-	0xca, 0xd6, 0x07, 0x03, 0x9f, 0x0c, 0xa7, 0x67, 0xf5, 0x7e, 0x38, 0xde, 0x19, 0x84, 0x83, 0x70,
-	0x87, 0xb1, 0x9c, 0x4d, 0xcf, 0xd9, 0x8a, 0x2d, 0xd8, 0x2f, 0x2e, 0xba, 0xb5, 0x3a, 0xc6, 0xc4,
-	0xf5, 0x5c, 0xe2, 0xf2, 0xb5, 0xf5, 0x6f, 0x03, 0x96, 0x8f, 0xc3, 0x41, 0x77, 0xe8, 0x46, 0xde,
-	0x51, 0x70, 0x1e, 0xa2, 0x2a, 0x14, 0xf9, 0xc2, 0xae, 0x1a, 0xdb, 0xc6, 0x83, 0x9c, 0x23, 0x97,
-	0xe8, 0x00, 0x4a, 0x0e, 0x9e, 0x8c, 0xfc, 0xbe, 0x1b, 0x57, 0x33, 0xdb, 0xd9, 0x07, 0x95, 0xdd,
-	0xfb, 0x75, 0xc5, 0x34, 0x55, 0x4b, 0x5d, 0x32, 0xb6, 0x02, 0x12, 0x5d, 0x39, 0x89, 0x1c, 0xba,
-	0x0d, 0xf9, 0xd6, 0x24, 0xec, 0x0f, 0xab, 0x59, 0xa6, 0x9b, 0x2f, 0xd0, 0x16, 0x94, 0x8e, 0xb1,
-	0xeb, 0xe1, 0xe8, 0xc8, 0xae, 0xe6, 0xd8, 0x46, 0xb2, 0x46, 0x08, 0x72, 0x3d, 0x1c, 0x8d, 0xab,
-	0x79, 0x46, 0x67, 0xbf, 0xb7, 0x7e, 0x02, 0x2b, 0xda, 0x01, 0xc8, 0x84, 0xec, 0x05, 0xbe, 0x12,
-	0x06, 0xd3, 0x9f, 0xf4, 0xa0, 0x4b, 0x77, 0x34, 0xc5, 0xd5, 0xcc, 0xb6, 0xf1, 0xa0, 0xec, 0xf0,
-	0xc5, 0x8f, 0x33, 0x3f, 0x32, 0xac, 0x4b, 0x58, 0x3d, 0x0e, 0x07, 0x42, 0x9e, 0xb9, 0xfc, 0xa9,
-	0x1e, 0x02, 0xa6, 0xa6, 0xb2, 0x5b, 0xbd, 0xce, 0xb9, 0x83, 0xd2, 0xd7, 0xdf, 0xdc, 0x5b, 0x7a,
-	0xfe, 0xcd, 0x3d, 0xc3, 0xd1, 0x43, 0xf7, 0x0e, 0x94, 0xa5, 0x5a, 0x9b, 0x9d, 0x9b, 0x73, 0x52,
-	0x82, 0x75, 0x1f, 0xcc, 0x66, 0xbb, 0x4b, 0xc2, 0x08, 0x3f, 0xc2, 0x6e, 0x44, 0xce, 0xb0, 0x4b,
-	0xa8, 0x73, 0xa7, 0xa7, 0x22, 0xd2, 0x65, 0x87, 0xfd, 0xb6, 0xfe, 0x6e, 0xc0, 0x1a, 0x55, 0xfb,
-	0x52, 0x4e, 0xb4, 0x0d, 0x15, 0xc7, 0x3d, 0x27, 0x0d, 0xcf, 0x8b, 0x70, 0x1c, 0x0b, 0x4f, 0x55,
-	0x12, 0xba, 0x0f, 0xab, 0x5d, 0xee, 0x81, 0x64, 0xca, 0x32, 0xa6, 0x19, 0x2a, 0x7a, 0x0f, 0x56,
-	0x0e, 0xc3, 0x38, 0xf6, 0x27, 0x92, 0x2d, 0xc7, 0xd8, 0x74, 0x22, 0xda, 0x57, 0x00, 0x90, 0x67,
-	0x00, 0xd8, 0x9a, 0x89, 0x91, 0x12, 0xd5, 0x83, 0x1c, 0x8d, 0x52, 0x9a, 0x7a, 0xab, 0x05, 0x15,
-	0xbb, 0xfd, 0x2a, 0x38, 0xbb, 0x39, 0x8c, 0x4f, 0xc0, 0xb4, 0x5f, 0x21, 0x8c, 0xe8, 0x43, 0x28,
-	0x30, 0x85, 0x12, 0xab, 0x9b, 0xaa, 0xa9, 0x8a, 0x21, 0xc2, 0x4e, 0xc1, 0x6c, 0xfd, 0x3e, 0x03,
-	0x25, 0xa7, 0x7b, 0xd2, 0x25, 0x2e, 0xc1, 0x14, 0x56, 0xbd, 0x38, 0x94, 0xb0, 0xea, 0xc5, 0x21,
-	0x85, 0xd5, 0x51, 0xe0, 0xe1, 0x67, 0xc2, 0x2e, 0xbe, 0xa0, 0xe1, 0x3b, 0xc6, 0x6e, 0x8c, 0x1f,
-	0x85, 0x23, 0x0e, 0x62, 0x8e, 0x6e, 0x9d, 0x88, 0x2c, 0x58, 0xee, 0x45, 0xd3, 0xa0, 0xef, 0x12,
-	0xec, 0x1d, 0xc7, 0x81, 0x40, 0xba, 0x46, 0x43, 0x9f, 0xc1, 0x32, 0x17, 0xf2, 0x63, 0x12, 0x46,
-	0x57, 0x22, 0xcc, 0x5a, 0x9d, 0x49, 0xeb, 0xea, 0x2a, 0x23, 0xaf, 0x33, 0x4d, 0x76, 0xeb, 0x21,
-	0xac, 0xcd, 0xb1, 0xbc, 0xac, 0x52, 0x72, 0x6a, 0xa5, 0x3c, 0x81, 0x32, 0xcb, 0x69, 0x3f, 0x8c,
-	0x3c, 0x2a, 0x48, 0x8d, 0x16, 0x82, 0xd4, 0xd6, 0x1a, 0xe4, 0x7a, 0x57, 0x13, 0x2e, 0xb7, 0xba,
-	0x7b, 0x47, 0xb3, 0x91, 0xc9, 0xd0, 0x5d, 0x87, 0xf1, 0xd0, 0x0c, 0xd9, 0x2e, 0x71, 0x59, 0x60,
-	0x96, 0x1d, 0xf6, 0xdb, 0xfa, 0xd2, 0x00, 0x60, 0xfa, 0x7f, 0x35, 0xc5, 0x31, 0x4b, 0x62, 0xdb,
-	0x1d, 0x63, 0x99, 0x44, 0xfa, 0x5b, 0x05, 0x49, 0x46, 0x07, 0x89, 0x30, 0x27, 0x9b, 0x9a, 0x53,
-	0x85, 0xe2, 0x89, 0xfb, 0xac, 0xeb, 0x7f, 0x81, 0x45, 0x64, 0xe5, 0x92, 0x02, 0x4a, 0x26, 0xdc,
-	0x16, 0x7d, 0x24, 0x25, 0x30, 0xd3, 0xda, 0x47, 0x76, 0xb5, 0xc0, 0x1b, 0x0c, 0xfd, 0x6d, 0x59,
-	0x00, 0xbd, 0x38, 0x94, 0x96, 0xdd, 0x86, 0x7c, 0x33, 0x9c, 0x06, 0x44, 0x38, 0xcf, 0x17, 0xd6,
-	0x5f, 0xb3, 0x50, 0x94, 0x1c, 0x0c, 0xb2, 0xec, 0x67, 0x02, 0xe7, 0x94, 0x80, 0xea, 0x50, 0x38,
-	0xc1, 0x64, 0x18, 0x7a, 0x8b, 0x42, 0xc5, 0x77, 0x58, 0xa8, 0x04, 0x17, 0xf5, 0xa4, 0xe7, 0x8f,
-	0x71, 0x38, 0x25, 0xcc, 0xbf, 0xac, 0x23, 0x97, 0x68, 0x5f, 0x8d, 0x18, 0x73, 0xb3, 0xa2, 0x6b,
-	0x4b, 0x77, 0x05, 0xae, 0xd5, 0x08, 0x1f, 0xb2, 0x3e, 0x97, 0x94, 0x0d, 0x0b, 0x45, 0x65, 0xf7,
-	0x3b, 0xb3, 0x7d, 0x4e, 0xab, 0x2d, 0xa1, 0x46, 0x13, 0x44, 0x36, 0x54, 0x9a, 0xed, 0x54, 0x4f,
-	0x81, 0xe9, 0x79, 0x47, 0xd5, 0x33, 0xdb, 0xe9, 0x84, 0x1a, 0x55, 0x8c, 0x6a, 0xb1, 0x15, 0x2d,
-	0xc5, 0x79, 0x2d, 0xf6, 0x35, 0x5a, 0x14, 0x31, 0x1a, 0x92, 0x34, 0x55, 0xd5, 0xd2, 0x7c, 0x48,
-	0xd2, 0x5d, 0x19, 0x92, 0x94, 0x62, 0x75, 0xa1, 0xc2, 0x02, 0x14, 0x4f, 0xc2, 0x20, 0xc6, 0x37,
-	0x34, 0x25, 0x81, 0xb7, 0x8c, 0x86, 0xb7, 0x63, 0x37, 0x26, 0x29, 0x0a, 0xe5, 0xd2, 0xfa, 0x0f,
-	0xed, 0x21, 0x52, 0xe5, 0xff, 0x16, 0x1a, 0x7b, 0x50, 0x6e, 0x45, 0x51, 0x18, 0x35, 0x43, 0x0f,
-	0xb3, 0x63, 0x57, 0x77, 0x37, 0x54, 0x91, 0x64, 0xd3, 0x49, 0xf9, 0x68, 0xe3, 0x61, 0x8b, 0x13,
-	0x1c, 0xc7, 0xee, 0x00, 0x8b, 0xe6, 0xae, 0xd1, 0xd0, 0x43, 0x2d, 0x10, 0x02, 0x1a, 0x9b, 0x73,
-	0xd0, 0xe2, 0xdb, 0x32, 0x0f, 0x6a, 0xe8, 0x0e, 0x60, 0xb9, 0x19, 0x8e, 0xc7, 0x6e, 0xe0, 0x1d,
-	0xb8, 0xa4, 0x3f, 0x14, 0xa0, 0xd0, 0x2e, 0x51, 0x75, 0x5f, 0xe2, 0x4a, 0xa5, 0x51, 0x23, 0x58,
-	0x6e, 0x84, 0x11, 0xc5, 0x79, 0x23, 0x94, 0x6d, 0x69, 0x84, 0x42, 0xb2, 0x3e, 0x63, 0x57, 0x27,
-	0xef, 0x3e, 0x89, 0x65, 0x1f, 0xd2, 0x3a, 0xa5, 0x94, 0xb8, 0x6a, 0xb0, 0x76, 0xba, 0x31, 0xe7,
-	0x16, 0xdd, 0x15, 0xfa, 0x24, 0xaf, 0xf5, 0x5d, 0xcd, 0x18, 0xda, 0x04, 0x7e, 0xc6, 0xda, 0xa4,
-	0x68, 0x02, 0x6c, 0x61, 0x5d, 0x50, 0xdd, 0xec, 0x6a, 0x5a, 0x78, 0x09, 0x5d, 0xdf, 0xbf, 0xb4,
-	0x4b, 0x2e, 0x3b, 0x73, 0xc9, 0xa5, 0x63, 0x52, 0x4e, 0x19, 0x93, 0xac, 0x3f, 0x65, 0x68, 0x8c,
-	0x83, 0x73, 0x7f, 0xd0, 0x1c, 0xba, 0xc1, 0x00, 0xa3, 0xbd, 0xe4, 0x74, 0x31, 0xb3, 0xac, 0xeb,
-	0x4d, 0x98, 0x6d, 0xa5, 0x7e, 0x71, 0x3b, 0xf7, 0x01, 0xb8, 0xb8, 0xd2, 0xbc, 0xf5, 0xda, 0x55,
-	0x8e, 0x60, 0xe0, 0x53, 0xf8, 0x51, 0x0f, 0x56, 0x8f, 0x02, 0x9f, 0xf8, 0xee, 0xe8, 0x04, 0x8f,
-	0xcf, 0x70, 0x44, 0x27, 0x0a, 0x1a, 0xd3, 0xf7, 0xaf, 0xd3, 0x50, 0xd7, 0xd9, 0xf9, 0x45, 0x35,
-	0xa3, 0x63, 0xab, 0x01, 0xeb, 0x0b, 0xd8, 0x5e, 0x6b, 0xac, 0xfb, 0x3e, 0xac, 0x74, 0x87, 0x53,
-	0xe2, 0x85, 0xbf, 0x0e, 0x58, 0xd3, 0x60, 0xb1, 0xa7, 0x3f, 0x92, 0x94, 0xc8, 0xa5, 0xf5, 0xdb,
-	0x0c, 0xdc, 0xea, 0xf6, 0x87, 0xd8, 0x9b, 0x8e, 0xb0, 0xc0, 0xdf, 0xc2, 0xec, 0xbd, 0x07, 0x2b,
-	0x07, 0x61, 0x48, 0x62, 0x12, 0xb9, 0x93, 0x89, 0x1f, 0x0c, 0xd8, 0xa1, 0x25, 0x47, 0x27, 0xa2,
-	0x7d, 0x3d, 0x29, 0x2c, 0x99, 0x73, 0xc0, 0x4f, 0xf7, 0x1d, 0x3d, 0x85, 0x1f, 0x41, 0x45, 0xcc,
-	0x62, 0x2c, 0x1d, 0x39, 0x96, 0x0e, 0x0d, 0xf2, 0xca, 0xb6, 0xa3, 0xf2, 0xa2, 0x87, 0x33, 0x1e,
-	0x8b, 0xa2, 0x7d, 0x4b, 0x13, 0x56, 0x19, 0x1c, 0x9d, 0xdf, 0x72, 0xf5, 0x92, 0x4d, 0x46, 0x6d,
-	0x23, 0x1d, 0xb5, 0xd1, 0xc7, 0x50, 0x12, 0x3c, 0x72, 0x90, 0x7a, 0x5b, 0xd3, 0xaf, 0x87, 0x51,
-	0x0e, 0x7d, 0x52, 0xc4, 0x7a, 0x97, 0xde, 0x14, 0x3c, 0xee, 0x74, 0xe8, 0xa3, 0x27, 0xf8, 0xfd,
-	0x8b, 0xe4, 0x04, 0xbf, 0x7f, 0x61, 0xfd, 0xd9, 0x80, 0x22, 0xe5, 0xa1, 0x03, 0xd7, 0xc7, 0x50,
-	0x60, 0xcc, 0xb2, 0x52, 0xef, 0xcd, 0xde, 0x29, 0x74, 0xee, 0xe1, 0x1c, 0x0c, 0x21, 0xc9, 0xf0,
-	0xc6, 0x48, 0x5b, 0x0e, 0x54, 0x94, 0x4d, 0x15, 0x3e, 0x65, 0x0e, 0x9f, 0x0f, 0x54, 0xf8, 0xcc,
-	0xb4, 0x16, 0xc5, 0x4e, 0x15, 0x57, 0x9f, 0xb3, 0xb1, 0xf5, 0x26, 0x0f, 0xde, 0x74, 0xd4, 0xa4,
-	0x8e, 0xdb, 0xaf, 0xe2, 0xb8, 0xfd, 0xff, 0x75, 0xdc, 0x5e, 0xec, 0xf8, 0xef, 0x0c, 0xa8, 0x34,
-	0x47, 0xd3, 0x98, 0xe0, 0x88, 0x79, 0xfe, 0x11, 0x94, 0x84, 0x2f, 0xd2, 0xc8, 0xcd, 0x7a, 0xf2,
-	0x98, 0x14, 0x3b, 0x5a, 0x27, 0x4d, 0xd8, 0xd1, 0x3e, 0x1b, 0x24, 0xb5, 0x18, 0x55, 0x53, 0x59,
-	0xb9, 0xa5, 0x09, 0xa7, 0x02, 0xd6, 0x1f, 0x0d, 0xd8, 0x10, 0xdd, 0x41, 0xd8, 0x23, 0x07, 0x9a,
-	0xfb, 0xb0, 0xda, 0x9e, 0x8e, 0x3b, 0xe7, 0xa9, 0x72, 0x9e, 0x96, 0x19, 0x2a, 0x2d, 0x64, 0x46,
-	0x49, 0xec, 0xe7, 0xcd, 0x58, 0x27, 0xa2, 0x1a, 0x98, 0x52, 0x2e, 0x79, 0xe6, 0xf0, 0xce, 0x3c,
-	0x47, 0xb7, 0x9e, 0x8b, 0x67, 0xf3, 0x8d, 0xb8, 0xf8, 0x76, 0xbd, 0xcf, 0xbe, 0xca, 0x40, 0x89,
-	0xb9, 0x44, 0xf1, 0xf8, 0x49, 0x02, 0x69, 0x9e, 0xea, 0xed, 0xb9, 0x21, 0x91, 0x01, 0x92, 0xb1,
-	0xe8, 0x80, 0xe4, 0xb1, 0xfc, 0x24, 0xc1, 0x73, 0xe6, 0x26, 0xf9, 0x6b, 0x01, 0xdd, 0x85, 0x8a,
-	0xa2, 0x7c, 0xc1, 0x45, 0x50, 0xd7, 0x01, 0x7d, 0xed, 0x63, 0x5d, 0x41, 0x34, 0x53, 0x7a, 0x63,
-	0x95, 0xbc, 0x4c, 0xe9, 0xa2, 0x32, 0xf9, 0x0d, 0xbd, 0x94, 0x87, 0xb8, 0x7f, 0x81, 0x23, 0x1e,
-	0xba, 0x45, 0x48, 0x78, 0xa8, 0x95, 0xd2, 0xc2, 0xee, 0x93, 0x6e, 0x27, 0xb3, 0xb2, 0x52, 0x7c,
-	0x7b, 0x49, 0xab, 0x10, 0xf7, 0xcb, 0xfa, 0x82, 0x06, 0x21, 0x6f, 0x7a, 0xd9, 0x54, 0x7e, 0x98,
-	0x26, 0x54, 0xbc, 0x15, 0x6e, 0x2f, 0x4a, 0x83, 0x44, 0x42, 0x92, 0xfc, 0x1d, 0xc8, 0x73, 0xa1,
-	0x3c, 0xbb, 0x8d, 0xb4, 0x0b, 0xe5, 0x51, 0xe3, 0xa7, 0x18, 0x4f, 0x84, 0xaf, 0x0e, 0xe7, 0xb3,
-	0xbe, 0xca, 0x83, 0x29, 0x37, 0x92, 0xc7, 0xf3, 0xa2, 0x38, 0xdc, 0x81, 0x42, 0x1b, 0x3f, 0x23,
-	0xc9, 0x38, 0x24, 0x56, 0xc9, 0xcd, 0x93, 0x55, 0x6e, 0x9e, 0xc4, 0x8a, 0xdc, 0xab, 0x59, 0x81,
-	0x3c, 0x30, 0x67, 0xae, 0x23, 0x59, 0x06, 0xbb, 0x8b, 0x64, 0x93, 0x77, 0xf4, 0xac, 0x90, 0x8a,
-	0xc7, 0x39, 0x8d, 0xe8, 0x48, 0xed, 0x65, 0x05, 0xa6, 0xfe, 0x07, 0x37, 0xaa, 0x4f, 0xb8, 0xf9,
-	0xe8, 0x93, 0x4a, 0xd3, 0xa4, 0x8a, 0x3b, 0x4d, 0x8c, 0xba, 0xeb, 0x0b, 0xae, 0x3b, 0x99, 0x54,
-	0x79, 0x45, 0x2a, 0x48, 0x28, 0xbd, 0x11, 0x12, 0xca, 0xaf, 0x81, 0x84, 0x19, 0xdc, 0xc2, 0xeb,
-	0xe2, 0x76, 0xeb, 0x09, 0x6c, 0x2c, 0x0c, 0xef, 0x6b, 0x16, 0x9f, 0x3a, 0xa6, 0xa8, 0x15, 0xbd,
-	0xcf, 0xbe, 0xe5, 0x5d, 0xd3, 0x29, 0xca, 0x2f, 0xf9, 0xbe, 0x51, 0xfb, 0x05, 0xc0, 0xe9, 0xc4,
-	0x73, 0x09, 0x1f, 0xa7, 0x36, 0x61, 0x5d, 0xfb, 0x5e, 0xc3, 0xb7, 0xcc, 0x25, 0xb4, 0x01, 0x6b,
-	0xf2, 0x1b, 0xcd, 0x71, 0xb7, 0x2d, 0xc8, 0x06, 0x5a, 0x87, 0x5b, 0xa7, 0x31, 0x8e, 0xd8, 0xb1,
-	0x82, 0x98, 0x41, 0x2b, 0x50, 0xee, 0x75, 0x3b, 0x62, 0x99, 0xad, 0xfd, 0xc5, 0x00, 0x48, 0x5f,
-	0x71, 0x68, 0x15, 0xa0, 0xd7, 0xed, 0x3c, 0x3d, 0x7d, 0x6c, 0x37, 0x7a, 0x2d, 0x73, 0x09, 0x01,
-	0x14, 0x1a, 0x8f, 0x1f, 0xb7, 0xda, 0xb6, 0x69, 0xa0, 0x12, 0xe4, 0x9c, 0x56, 0xc3, 0x36, 0x33,
-	0x68, 0x19, 0x4a, 0x3d, 0xe7, 0xb4, 0xdd, 0xa4, 0x3c, 0x59, 0x64, 0xc2, 0xf2, 0x61, 0xab, 0xf7,
-	0x34, 0xa1, 0xe4, 0x50, 0x05, 0x8a, 0xcd, 0x4e, 0xbb, 0xdd, 0x6a, 0xf6, 0xcc, 0x3c, 0x55, 0x29,
-	0x16, 0x4f, 0x9d, 0x8e, 0x59, 0x40, 0x6b, 0xb0, 0x72, 0xdc, 0x39, 0x7c, 0xfa, 0xa8, 0xd5, 0x70,
-	0x7a, 0x07, 0xad, 0x46, 0xcf, 0x2c, 0x52, 0x0d, 0xcd, 0xb6, 0x42, 0x29, 0x51, 0x8a, 0xad, 0x52,
-	0xca, 0xb5, 0x36, 0x40, 0xfa, 0xcd, 0x86, 0x2a, 0xa5, 0xae, 0x71, 0x8a, 0xb9, 0x44, 0x2d, 0x3a,
-	0x0a, 0x08, 0x8e, 0x02, 0x77, 0x64, 0x1a, 0xe8, 0x16, 0x54, 0x58, 0xa0, 0x12, 0xa7, 0xd7, 0xc4,
-	0xe7, 0x2f, 0x07, 0xff, 0x12, 0xf7, 0x09, 0xf6, 0xcc, 0x6c, 0xed, 0x0f, 0x19, 0xe5, 0xa1, 0x4a,
-	0x2d, 0x6e, 0x87, 0x6c, 0x69, 0x2e, 0xd1, 0x85, 0xf8, 0x9c, 0x61, 0x1a, 0x54, 0x73, 0xd3, 0x0d,
-	0xfa, 0x78, 0x84, 0x3d, 0x33, 0x43, 0xed, 0x3a, 0x0a, 0x2e, 0xdd, 0x91, 0xef, 0xb1, 0x94, 0x9a,
-	0x59, 0x84, 0xe8, 0x73, 0x83, 0x51, 0xa4, 0x4c, 0x4e, 0xa1, 0x3d, 0x76, 0xaf, 0x46, 0xa1, 0xeb,
-	0x99, 0x79, 0x74, 0x07, 0x90, 0x4e, 0xeb, 0xfa, 0x5f, 0x60, 0xb3, 0x40, 0xf5, 0x27, 0x56, 0x15,
-	0xa9, 0xa1, 0x4c, 0x71, 0x3b, 0x24, 0x0e, 0x76, 0xbd, 0x2b, 0x1e, 0x8a, 0xee, 0x55, 0x4c, 0xf0,
-	0xb8, 0x39, 0x0a, 0x63, 0xec, 0x99, 0x65, 0x86, 0x83, 0x38, 0x68, 0x8c, 0x22, 0xca, 0x91, 0x7c,
-	0x9c, 0x33, 0x3d, 0x1a, 0x95, 0xce, 0x94, 0x74, 0xce, 0x1d, 0x3a, 0xb8, 0x9b, 0xb4, 0xc1, 0xad,
-	0xb6, 0x43, 0xa2, 0x60, 0xc6, 0x3c, 0x47, 0x1b, 0x60, 0x76, 0xc8, 0x10, 0x47, 0x5c, 0x27, 0x77,
-	0xf9, 0x5f, 0xc5, 0xda, 0xdf, 0x0c, 0x40, 0xb2, 0x31, 0x28, 0x90, 0xa3, 0x78, 0xf0, 0xfb, 0x17,
-	0x2a, 0xd2, 0x94, 0x0f, 0x24, 0x09, 0xd2, 0x36, 0x60, 0xcd, 0x9e, 0x23, 0x67, 0xa8, 0xcf, 0xea,
-	0x57, 0x19, 0x09, 0x3a, 0x9a, 0x9f, 0x43, 0x4c, 0x12, 0x00, 0xe7, 0xd0, 0x5b, 0x73, 0x45, 0x28,
-	0xb6, 0xf2, 0xd4, 0x87, 0x2e, 0x26, 0xac, 0xd8, 0x05, 0xad, 0x80, 0xaa, 0x70, 0x5b, 0x1f, 0xb7,
-	0xc4, 0x4e, 0xb1, 0xf6, 0xa5, 0x01, 0x2b, 0x5a, 0xeb, 0xa5, 0x45, 0x20, 0x09, 0xcd, 0x08, 0xb3,
-	0x40, 0x2d, 0xd1, 0xf3, 0x24, 0x51, 0x7b, 0x2a, 0x99, 0x06, 0xfa, 0x1e, 0xbc, 0x3b, 0xb7, 0x25,
-	0x1b, 0x83, 0x83, 0xfb, 0xd8, 0xbf, 0x64, 0x40, 0x78, 0x1b, 0x36, 0xe7, 0xd8, 0x3e, 0x75, 0x7d,
-	0x8a, 0x92, 0xac, 0x7a, 0xa6, 0x33, 0x0d, 0x02, 0xaa, 0x38, 0x57, 0xfb, 0x1c, 0xcc, 0xd9, 0x77,
-	0x2b, 0x0d, 0x6f, 0xc3, 0xf3, 0xc4, 0x80, 0x63, 0x2e, 0xd1, 0xf4, 0x3b, 0x78, 0x1c, 0x5e, 0x62,
-	0x49, 0x32, 0x58, 0xfa, 0x89, 0x1b, 0x11, 0x49, 0xc9, 0xd0, 0xe8, 0x75, 0x49, 0x38, 0x91, 0x84,
-	0x6c, 0xed, 0x7d, 0xed, 0x85, 0x46, 0x95, 0xd2, 0x96, 0xc3, 0x29, 0xe6, 0x12, 0xad, 0x78, 0x3b,
-	0x90, 0x4b, 0xe3, 0xa0, 0xf5, 0xfc, 0x9f, 0x77, 0x97, 0xbe, 0x7e, 0x71, 0xd7, 0x78, 0xfe, 0xe2,
-	0xae, 0xf1, 0x8f, 0x17, 0x77, 0x8d, 0x9f, 0xef, 0x29, 0x7f, 0xd0, 0x8c, 0x5d, 0x12, 0xf9, 0xcf,
-	0xc2, 0xc8, 0x1f, 0xf8, 0x81, 0x5c, 0x04, 0x78, 0x67, 0x72, 0x31, 0xd8, 0x99, 0x9c, 0xed, 0xa4,
-	0x0d, 0xef, 0xac, 0xc0, 0xfe, 0x9d, 0xd9, 0xfb, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x91, 0xb1,
-	0x67, 0xcb, 0xfc, 0x19, 0x00, 0x00,
-}
-
-func (m *LogShardInfo) Marshal() (dAtA []byte, err error) {
+	// 2280 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0xcd, 0x6f, 0x1b, 0xc7,
+	0x15, 0xd7, 0xf2, 0x9b, 0x8f, 0x12, 0xbd, 0x1a, 0x59, 0x36, 0xa3, 0xa4, 0xb6, 0xb3, 0x4d, 0x0d,
+	0x97, 0x4d, 0x24, 0x40, 0x46, 0x8a, 0xa6, 0x55, 0x62, 0x50, 0x5c, 0xc6, 0xa2, 0x2d, 0x53, 0xc2,
+	0x92, 0x2a, 0x82, 0x02, 0x86, 0xba, 0xe2, 0x8e, 0xc8, 0xad, 0xc8, 0x5d, 0x76, 0x77, 0xa8, 0x4a,
+	0xb9, 0xb5, 0x40, 0x0b, 0x14, 0xe8, 0xa5, 0xa7, 0x06, 0x45, 0xff, 0x8c, 0x9c, 0x7a, 0x2c, 0x7a,
+	0xc8, 0xd1, 0x97, 0x5e, 0x83, 0xd6, 0xa7, 0xde, 0xfa, 0x2f, 0x14, 0xf3, 0xb5, 0x3b, 0x43, 0x52,
+	0xb2, 0x1d, 0xa4, 0x87, 0xde, 0x38, 0x6f, 0xde, 0x7b, 0xf3, 0x3e, 0x7e, 0xef, 0xcd, 0x9b, 0x25,
+	0x98, 0xa3, 0x70, 0x10, 0xe3, 0xe8, 0xdc, 0xef, 0xe3, 0xcd, 0x49, 0x14, 0x92, 0x10, 0x41, 0x4a,
+	0xd9, 0xf8, 0x60, 0xe0, 0x93, 0xe1, 0xf4, 0x64, 0xb3, 0x1f, 0x8e, 0xb7, 0x06, 0xe1, 0x20, 0xdc,
+	0x62, 0x2c, 0x27, 0xd3, 0x53, 0xb6, 0x62, 0x0b, 0xf6, 0x8b, 0x8b, 0x6e, 0x54, 0xc7, 0x98, 0xb8,
+	0x9e, 0x4b, 0x5c, 0xbe, 0xb6, 0x9e, 0x40, 0xa1, 0xd9, 0xe9, 0x84, 0x1e, 0x46, 0x08, 0x72, 0x47,
+	0x47, 0x6d, 0xbb, 0x66, 0xdc, 0x33, 0x1e, 0x94, 0x1d, 0xf6, 0x1b, 0xd5, 0xa0, 0xd8, 0xf0, 0xbc,
+	0x08, 0xc7, 0x71, 0x2d, 0xc3, 0xc8, 0x72, 0x49, 0xb9, 0x7b, 0x7e, 0xff, 0xac, 0x96, 0xbd, 0x67,
+	0x3c, 0xc8, 0x39, 0xec, 0xb7, 0xf5, 0x1b, 0x03, 0x0a, 0xf6, 0xb5, 0xca, 0xba, 0x43, 0x37, 0xf2,
+	0xda, 0x36, 0x53, 0x96, 0x73, 0xe4, 0x12, 0xbd, 0x03, 0x65, 0x07, 0x4f, 0x46, 0x7e, 0xdf, 0x6d,
+	0xdb, 0x42, 0x63, 0x4a, 0x50, 0x8d, 0xc8, 0x2d, 0x36, 0x22, 0xaf, 0x18, 0xf1, 0x1f, 0x03, 0x96,
+	0xf7, 0xc3, 0x01, 0x57, 0x1d, 0x9c, 0x86, 0xea, 0xb1, 0x86, 0x7e, 0xec, 0x2e, 0x94, 0xc4, 0x29,
+	0xd4, 0xbd, 0xec, 0x83, 0xca, 0xf6, 0xfd, 0x4d, 0x25, 0xd6, 0xaa, 0x96, 0x4d, 0xc9, 0xd8, 0x0a,
+	0x48, 0x74, 0xe9, 0x24, 0x72, 0xe8, 0x26, 0xe4, 0x5b, 0x93, 0xb0, 0x3f, 0x14, 0x66, 0xf3, 0x05,
+	0xda, 0x80, 0xd2, 0x3e, 0x76, 0x3d, 0x1c, 0xb5, 0x6d, 0x66, 0x73, 0xce, 0x49, 0xd6, 0xcc, 0x68,
+	0x1c, 0x8d, 0x13, 0xa3, 0x71, 0x34, 0xde, 0xf8, 0x09, 0xac, 0x68, 0x07, 0x20, 0x13, 0xb2, 0x67,
+	0xf8, 0x52, 0x18, 0x4c, 0x7f, 0xd2, 0x83, 0xce, 0xdd, 0xd1, 0x14, 0x8b, 0x44, 0xf0, 0xc5, 0x8f,
+	0x33, 0x3f, 0x32, 0xac, 0x73, 0xa8, 0xee, 0x87, 0x03, 0x19, 0x2f, 0xea, 0xf2, 0xa7, 0x7a, 0x08,
+	0x98, 0x9a, 0xca, 0x76, 0xed, 0x2a, 0xe7, 0x76, 0x4b, 0x5f, 0x7d, 0x7d, 0x77, 0xe9, 0xc5, 0xd7,
+	0x77, 0x0d, 0x47, 0x0f, 0x9d, 0x96, 0x97, 0xcc, 0x4c, 0x5e, 0xac, 0xfb, 0x60, 0x36, 0x3b, 0x5d,
+	0x12, 0x46, 0x78, 0x0f, 0xbb, 0x11, 0x39, 0xc1, 0x2e, 0x59, 0x94, 0x77, 0xeb, 0x1f, 0x06, 0xac,
+	0x52, 0xb5, 0xaf, 0xe4, 0x44, 0xf7, 0xa0, 0xe2, 0xb8, 0xa7, 0x44, 0x87, 0x9c, 0x4a, 0x42, 0xf7,
+	0xa1, 0xda, 0xe5, 0x1e, 0x48, 0xa6, 0x2c, 0x63, 0x9a, 0xa1, 0xa2, 0xf7, 0x60, 0xe5, 0x71, 0x18,
+	0xc7, 0xfe, 0x44, 0x47, 0x8e, 0x4e, 0x44, 0x3b, 0x0a, 0x00, 0xf2, 0x0c, 0x00, 0x1b, 0x33, 0x31,
+	0x52, 0xa2, 0xba, 0x9b, 0xa3, 0x51, 0x4a, 0x53, 0x6f, 0xb5, 0xa0, 0x62, 0x77, 0x5e, 0x07, 0x67,
+	0xd7, 0x87, 0xf1, 0x39, 0x98, 0xf6, 0x6b, 0x84, 0x11, 0x7d, 0x08, 0x05, 0xa6, 0x50, 0x62, 0xf5,
+	0xb6, 0x6a, 0xaa, 0x62, 0x88, 0xb0, 0x53, 0x30, 0x5b, 0xbf, 0xcf, 0x40, 0xc9, 0xe9, 0x3e, 0xeb,
+	0x12, 0x97, 0x60, 0x0a, 0xab, 0x5e, 0x1c, 0x4a, 0x58, 0xf5, 0xe2, 0x90, 0xc2, 0xaa, 0x1d, 0x78,
+	0xf8, 0x42, 0xd8, 0xc5, 0x17, 0x34, 0x7c, 0xfb, 0xd8, 0x8d, 0xf1, 0x5e, 0x38, 0xe2, 0x20, 0xe6,
+	0xe8, 0xd6, 0x89, 0xc8, 0x82, 0xe5, 0x5e, 0x34, 0x0d, 0xfa, 0x2e, 0xc1, 0xde, 0x7e, 0x1c, 0x08,
+	0xa4, 0x6b, 0x34, 0xf4, 0x04, 0x96, 0xb9, 0x90, 0x1f, 0x93, 0x30, 0xba, 0x14, 0x61, 0xd6, 0xea,
+	0x4c, 0x5a, 0xb7, 0xa9, 0x32, 0xf2, 0x3a, 0xd3, 0x64, 0x37, 0x1e, 0xc1, 0xea, 0x1c, 0xcb, 0xab,
+	0x2a, 0x25, 0xa7, 0x56, 0xca, 0x73, 0x28, 0xb3, 0x9c, 0xf6, 0xc3, 0xc8, 0xa3, 0x82, 0xd4, 0x68,
+	0x21, 0x48, 0x6d, 0xad, 0x43, 0xae, 0x77, 0x39, 0xe1, 0x72, 0xd5, 0xed, 0x5b, 0x9a, 0x8d, 0x4c,
+	0x86, 0xee, 0x3a, 0x8c, 0x87, 0x66, 0xc8, 0x76, 0x89, 0xcb, 0x02, 0xb3, 0xec, 0xb0, 0xdf, 0xd6,
+	0x17, 0x06, 0x00, 0xd3, 0xff, 0xcb, 0x29, 0x8e, 0x59, 0x12, 0x3b, 0xee, 0x18, 0xcb, 0x24, 0xd2,
+	0xdf, 0xd7, 0xf4, 0x40, 0x61, 0x4e, 0x36, 0x35, 0xa7, 0x06, 0xc5, 0x67, 0xee, 0x45, 0xd7, 0xff,
+	0x1c, 0x8b, 0xc8, 0xca, 0x25, 0x05, 0x94, 0x4c, 0xb8, 0x2d, 0xfa, 0x48, 0x4a, 0x60, 0xa6, 0x75,
+	0xda, 0x76, 0xad, 0xc0, 0x1b, 0x0c, 0xfd, 0x6d, 0x59, 0x00, 0xbd, 0x38, 0x94, 0x96, 0xdd, 0x84,
+	0x7c, 0x33, 0x9c, 0x06, 0x44, 0x38, 0xcf, 0x17, 0xd6, 0xdf, 0xb2, 0x50, 0x94, 0x1c, 0x0c, 0xb2,
+	0xec, 0x67, 0x02, 0xe7, 0x94, 0x80, 0x36, 0xa1, 0xf0, 0x0c, 0x93, 0x61, 0xe8, 0x2d, 0x0a, 0x15,
+	0xdf, 0x61, 0xa1, 0x12, 0x5c, 0xd4, 0x93, 0x9e, 0x3f, 0xc6, 0xe1, 0x94, 0x30, 0xff, 0xb2, 0x8e,
+	0x5c, 0xa2, 0x1d, 0x35, 0x62, 0xcc, 0xcd, 0x8a, 0xae, 0x2d, 0xdd, 0x15, 0xb8, 0x56, 0x23, 0xfc,
+	0x98, 0xf5, 0xb9, 0xa4, 0x6c, 0x58, 0x28, 0x2a, 0xdb, 0xdf, 0x99, 0xed, 0x73, 0x5a, 0x6d, 0x09,
+	0x35, 0x9a, 0x20, 0xb2, 0xa1, 0xd2, 0xec, 0xa4, 0x7a, 0x0a, 0x4c, 0xcf, 0x3b, 0xaa, 0x9e, 0xd9,
+	0x4e, 0x27, 0xd4, 0xa8, 0x62, 0x54, 0x8b, 0xad, 0x68, 0x29, 0xce, 0x6b, 0xb1, 0xaf, 0xd0, 0xa2,
+	0x88, 0xd1, 0x90, 0xa4, 0xa9, 0xaa, 0x95, 0xe6, 0x43, 0x92, 0xee, 0xca, 0x90, 0xa4, 0x14, 0xab,
+	0x0b, 0x15, 0x16, 0xa0, 0x78, 0x12, 0x06, 0x31, 0xbe, 0xa6, 0x29, 0x09, 0xbc, 0x65, 0x34, 0xbc,
+	0xed, 0xbb, 0x31, 0x49, 0x51, 0x28, 0x97, 0xd6, 0x9f, 0xb2, 0xb4, 0x51, 0x0a, 0x95, 0xdf, 0x2e,
+	0x34, 0x1e, 0x42, 0xb9, 0x15, 0x45, 0x61, 0xd4, 0x0c, 0x3d, 0xcc, 0x8e, 0xad, 0x6e, 0xaf, 0xab,
+	0x22, 0xc9, 0xa6, 0x93, 0xf2, 0xd1, 0xc6, 0xc3, 0x16, 0xcf, 0x70, 0x1c, 0xbb, 0x03, 0x2c, 0x9a,
+	0xbb, 0x46, 0x43, 0x77, 0x00, 0xda, 0xf1, 0x5e, 0xe3, 0x29, 0xc6, 0x13, 0x1c, 0x31, 0x64, 0x94,
+	0x1c, 0x85, 0x82, 0x1e, 0x69, 0x81, 0x12, 0x29, 0xbf, 0x3d, 0x07, 0x3d, 0xbe, 0x2d, 0xf3, 0xa4,
+	0x86, 0x76, 0x17, 0x96, 0x9b, 0xe1, 0x78, 0xec, 0x06, 0xde, 0xae, 0x4b, 0xfa, 0x43, 0x91, 0x6e,
+	0xed, 0x92, 0x55, 0xf7, 0x25, 0xee, 0x54, 0x1a, 0x35, 0x82, 0xe5, 0x4e, 0x18, 0x51, 0x9a, 0x37,
+	0x42, 0xd9, 0x96, 0x46, 0x28, 0x24, 0xeb, 0x09, 0xbb, 0x5a, 0x79, 0x77, 0x4a, 0x2c, 0xfb, 0x90,
+	0xd6, 0x31, 0xa5, 0xc4, 0x35, 0x83, 0xb5, 0xdb, 0xf5, 0x39, 0xb7, 0xe8, 0xae, 0xd0, 0x27, 0x79,
+	0xad, 0xef, 0x6a, 0xc6, 0xd0, 0x26, 0xf1, 0x53, 0xd6, 0x46, 0x45, 0x93, 0x60, 0x0b, 0xeb, 0x8c,
+	0xea, 0x66, 0x57, 0xd7, 0xb7, 0x3a, 0xe3, 0x25, 0x63, 0x54, 0x4e, 0x19, 0xa3, 0xac, 0x3f, 0x67,
+	0x68, 0x8c, 0x83, 0x53, 0x7f, 0xd0, 0x1c, 0xba, 0xc1, 0x00, 0xa3, 0x87, 0xc9, 0xe9, 0x62, 0xa6,
+	0x59, 0xd3, 0x9b, 0x34, 0xdb, 0x4a, 0xfd, 0xe2, 0x76, 0xee, 0x00, 0x70, 0x71, 0xa5, 0xb9, 0xeb,
+	0xb5, 0xad, 0x1c, 0xc1, 0xc0, 0xa9, 0xf0, 0xa3, 0x1e, 0x54, 0xdb, 0x81, 0x4f, 0x7c, 0x77, 0xf4,
+	0x0c, 0x8f, 0x4f, 0x70, 0x44, 0x27, 0x0e, 0x1a, 0xd3, 0xf7, 0xaf, 0xd2, 0xb0, 0xa9, 0xb3, 0xf3,
+	0x8b, 0x6c, 0x46, 0xc7, 0x46, 0x03, 0xd6, 0x16, 0xb0, 0xbd, 0xd1, 0xd8, 0xf7, 0x7d, 0x58, 0xe9,
+	0x0e, 0xa7, 0xc4, 0x0b, 0x7f, 0x15, 0xb0, 0xa6, 0xc2, 0x62, 0x4f, 0x7f, 0x24, 0x29, 0x91, 0x4b,
+	0xeb, 0xb7, 0x19, 0xb8, 0xd1, 0xed, 0x0f, 0xb1, 0x37, 0x1d, 0x61, 0x81, 0xbf, 0x85, 0xd9, 0x7b,
+	0x0f, 0x56, 0x76, 0xc3, 0x90, 0xc4, 0x24, 0x72, 0x27, 0x13, 0x3f, 0x18, 0xb0, 0x43, 0x4b, 0x8e,
+	0x4e, 0x44, 0x3b, 0x7a, 0x52, 0x58, 0x32, 0xe7, 0x80, 0x9f, 0xee, 0x3b, 0x7a, 0x0a, 0x3f, 0x82,
+	0x8a, 0x98, 0xd5, 0x58, 0x3a, 0x72, 0x2c, 0x1d, 0x1a, 0xe4, 0x95, 0x6d, 0x47, 0xe5, 0x45, 0x8f,
+	0x66, 0x3c, 0x16, 0xfd, 0xfe, 0x2d, 0x4d, 0x58, 0x65, 0x70, 0x74, 0x7e, 0xcb, 0xd5, 0x4b, 0x36,
+	0x19, 0xc5, 0x8d, 0x74, 0x14, 0x47, 0x1f, 0x43, 0x49, 0xf0, 0xc8, 0x41, 0xeb, 0x6d, 0x4d, 0xbf,
+	0x1e, 0x46, 0x39, 0x14, 0x4a, 0x11, 0xeb, 0x5d, 0x7a, 0x93, 0xf0, 0xb8, 0xd3, 0xa1, 0x50, 0xbe,
+	0x50, 0x0c, 0xe5, 0x85, 0xf2, 0x17, 0x03, 0x8a, 0x94, 0x87, 0x0e, 0x64, 0x1f, 0x43, 0x81, 0x31,
+	0xcb, 0x4a, 0xbd, 0x3b, 0x7b, 0xe7, 0xd0, 0xb9, 0x88, 0x73, 0x30, 0x84, 0x24, 0xc3, 0x1d, 0x23,
+	0x6d, 0x38, 0x50, 0x51, 0x36, 0x55, 0xf8, 0x94, 0x39, 0x7c, 0x3e, 0x50, 0xe1, 0x33, 0xd3, 0x5a,
+	0x14, 0x3b, 0x55, 0x5c, 0x7d, 0xc6, 0xc6, 0xda, 0xeb, 0x3c, 0xf8, 0xa6, 0xa3, 0x28, 0x75, 0xdc,
+	0x7e, 0x1d, 0xc7, 0xed, 0xff, 0xad, 0xe3, 0xf6, 0x62, 0xc7, 0x2f, 0xa0, 0xda, 0x1c, 0x4d, 0x63,
+	0x82, 0x23, 0x1b, 0x13, 0xd7, 0x1f, 0xc5, 0x68, 0x9b, 0xda, 0x4b, 0xdf, 0xb3, 0xd2, 0x4a, 0xa4,
+	0xab, 0xa1, 0x5b, 0xb2, 0xdb, 0x08, 0x46, 0x2a, 0xd3, 0x14, 0x32, 0x99, 0x79, 0x99, 0xa6, 0x26,
+	0x23, 0x18, 0xad, 0xdf, 0x19, 0x50, 0x11, 0x47, 0xb3, 0x98, 0x7f, 0x04, 0x25, 0x11, 0x45, 0x79,
+	0xf0, 0xed, 0xcd, 0xe4, 0xdd, 0x2e, 0x76, 0xb4, 0x1e, 0x9e, 0xb0, 0xa3, 0x1d, 0x36, 0xe2, 0x6a,
+	0xd9, 0xa9, 0xa5, 0xb2, 0x72, 0x4b, 0x13, 0x4e, 0x05, 0xac, 0x3f, 0x1a, 0xb0, 0x2e, 0xfa, 0x92,
+	0xb0, 0x47, 0x8e, 0x5a, 0xf7, 0xa1, 0xda, 0x99, 0x8e, 0x0f, 0x4e, 0x53, 0xe5, 0x1c, 0x10, 0x33,
+	0x54, 0xda, 0x42, 0x18, 0x25, 0xb1, 0x9f, 0x5f, 0x03, 0x3a, 0x11, 0xd5, 0xc1, 0x94, 0x72, 0xc9,
+	0x03, 0x8c, 0xdf, 0x09, 0x73, 0x74, 0xeb, 0x85, 0x78, 0xd0, 0x5f, 0x8b, 0xc8, 0xff, 0xaf, 0x97,
+	0xe3, 0x97, 0x19, 0x28, 0x31, 0x97, 0x68, 0x25, 0x7c, 0x92, 0x14, 0x13, 0x4f, 0xf5, 0xbd, 0xb9,
+	0xf1, 0x95, 0x95, 0x02, 0x63, 0xd1, 0x4b, 0x81, 0xc7, 0xf2, 0x93, 0xa4, 0x92, 0x32, 0xd7, 0xc9,
+	0x5f, 0x59, 0x4a, 0x5d, 0xa8, 0x28, 0xca, 0x17, 0x5c, 0x41, 0x9b, 0x7a, 0x29, 0x5d, 0xf9, 0x19,
+	0x41, 0xa9, 0x25, 0xa6, 0xf4, 0xda, 0xfa, 0x7c, 0x95, 0xd2, 0x45, 0x05, 0xfa, 0x6b, 0x3a, 0x0e,
+	0x0c, 0x71, 0xff, 0x0c, 0x47, 0x3c, 0x74, 0x8b, 0x90, 0xf0, 0x48, 0x2b, 0xa5, 0x85, 0x7d, 0x2f,
+	0xdd, 0x4e, 0xa6, 0x78, 0xa5, 0xf8, 0x1e, 0x26, 0x4d, 0x4a, 0xdc, 0x6c, 0x6b, 0x0b, 0x5a, 0x53,
+	0x5a, 0xf5, 0xdc, 0x92, 0x1f, 0xa6, 0x09, 0x15, 0xaf, 0x98, 0x9b, 0x8b, 0xd2, 0x20, 0x91, 0x90,
+	0x24, 0x7f, 0x0b, 0xf2, 0x5c, 0x28, 0xcf, 0xee, 0x41, 0xed, 0x2a, 0x93, 0xa3, 0x2a, 0x63, 0x70,
+	0x38, 0x9f, 0xf5, 0x65, 0x1e, 0x4c, 0xb9, 0x91, 0x3c, 0xeb, 0x17, 0xc5, 0xe1, 0x16, 0x14, 0x3a,
+	0xf8, 0x82, 0x24, 0x83, 0x98, 0x58, 0x25, 0x77, 0x5e, 0x56, 0xb9, 0xf3, 0x12, 0x2b, 0x72, 0xaf,
+	0x67, 0x05, 0xf2, 0xc0, 0x9c, 0xb9, 0x08, 0x65, 0x19, 0x6c, 0x2f, 0x92, 0x4d, 0x5e, 0xf8, 0xb3,
+	0x42, 0x2a, 0x1e, 0xe7, 0x34, 0xa2, 0xb6, 0xda, 0xcb, 0x0a, 0x4c, 0xfd, 0x0f, 0xae, 0x55, 0x9f,
+	0x70, 0xf3, 0xa1, 0x2b, 0x95, 0xa6, 0x49, 0x15, 0xb7, 0xa9, 0x98, 0xd3, 0xd7, 0x16, 0x5c, 0xb4,
+	0x69, 0x5b, 0xe6, 0x5e, 0x2a, 0x48, 0x28, 0x7d, 0x23, 0x24, 0x94, 0xdf, 0x00, 0x09, 0x33, 0xb8,
+	0x85, 0x37, 0xc5, 0xed, 0xc6, 0x73, 0x58, 0x5f, 0x18, 0xde, 0x37, 0x2c, 0x3e, 0x75, 0x40, 0x52,
+	0x2b, 0x7a, 0x87, 0x7d, 0x65, 0xbc, 0xa2, 0x53, 0x94, 0x5f, 0xf1, 0xe5, 0xa5, 0xfe, 0x73, 0x80,
+	0xa3, 0x89, 0xe7, 0x12, 0x3e, 0xc8, 0xdd, 0x86, 0x35, 0xed, 0x4b, 0x12, 0xdf, 0x32, 0x97, 0xd0,
+	0x3a, 0xac, 0xca, 0xaf, 0x47, 0xfb, 0xdd, 0x8e, 0x20, 0x1b, 0x68, 0x0d, 0x6e, 0x1c, 0xc5, 0x38,
+	0x62, 0xc7, 0x0a, 0x62, 0x06, 0xad, 0x40, 0xb9, 0xd7, 0x3d, 0x10, 0xcb, 0x6c, 0xfd, 0xaf, 0x06,
+	0x40, 0xfa, 0xbe, 0x44, 0x55, 0x80, 0x5e, 0xf7, 0xe0, 0xf8, 0xe8, 0xd0, 0x6e, 0xf4, 0x5a, 0xe6,
+	0x12, 0x02, 0x28, 0x34, 0x0e, 0x0f, 0x5b, 0x1d, 0xdb, 0x34, 0x50, 0x09, 0x72, 0x4e, 0xab, 0x61,
+	0x9b, 0x19, 0xb4, 0x0c, 0xa5, 0x9e, 0x73, 0xd4, 0x69, 0x52, 0x9e, 0x2c, 0x32, 0x61, 0xf9, 0x71,
+	0xab, 0x77, 0x9c, 0x50, 0x72, 0xa8, 0x02, 0xc5, 0xe6, 0x41, 0xa7, 0xd3, 0x6a, 0xf6, 0xcc, 0x3c,
+	0x55, 0x29, 0x16, 0xc7, 0xce, 0x81, 0x59, 0x40, 0xab, 0xb0, 0xb2, 0x7f, 0xf0, 0xf8, 0x78, 0xaf,
+	0xd5, 0x70, 0x7a, 0xbb, 0xad, 0x46, 0xcf, 0x2c, 0x52, 0x0d, 0xcd, 0x8e, 0x42, 0x29, 0x51, 0x8a,
+	0xad, 0x52, 0xca, 0x08, 0x41, 0xb5, 0xb9, 0xd7, 0x6a, 0x3e, 0x3d, 0xde, 0x6b, 0x3c, 0x6d, 0xb5,
+	0x0e, 0x5b, 0x8e, 0x09, 0xf5, 0x0e, 0x40, 0xfa, 0x85, 0x89, 0x1e, 0x44, 0xdd, 0xe5, 0x14, 0x73,
+	0x89, 0x5a, 0xd9, 0x0e, 0x08, 0x8e, 0x02, 0x77, 0x64, 0x1a, 0xe8, 0x06, 0x54, 0x58, 0xf0, 0x92,
+	0x40, 0xac, 0x8a, 0x8f, 0x75, 0x0e, 0xfe, 0x05, 0xee, 0x13, 0xec, 0x99, 0xd9, 0xfa, 0x1f, 0x32,
+	0xca, 0xb3, 0x9a, 0x7a, 0xd1, 0x09, 0xd9, 0xd2, 0x5c, 0xa2, 0x0b, 0xf1, 0xf1, 0xc5, 0x34, 0xa8,
+	0xe6, 0xa6, 0x1b, 0xf4, 0xf1, 0x08, 0x7b, 0x66, 0x86, 0xda, 0xda, 0x0e, 0xce, 0xdd, 0x91, 0xef,
+	0xb1, 0x34, 0x9b, 0x59, 0x6a, 0xab, 0xa0, 0x48, 0x99, 0x9c, 0x42, 0x3b, 0x74, 0x2f, 0x47, 0xa1,
+	0xeb, 0x99, 0x79, 0x74, 0x0b, 0x90, 0x4e, 0xeb, 0xfa, 0x9f, 0x63, 0xb3, 0x40, 0xf5, 0x27, 0x56,
+	0x15, 0xa9, 0xa1, 0x4c, 0x71, 0x27, 0x24, 0x0e, 0x76, 0xbd, 0x4b, 0x1e, 0x9e, 0xee, 0x65, 0x4c,
+	0xf0, 0xb8, 0x39, 0x0a, 0x63, 0xec, 0x99, 0x65, 0x86, 0x8d, 0x38, 0x68, 0x8c, 0x22, 0xca, 0x91,
+	0x7c, 0x4a, 0x34, 0x3d, 0x1a, 0x95, 0x83, 0x29, 0x39, 0x38, 0x75, 0xe8, 0x33, 0xc2, 0xa4, 0x4d,
+	0xaf, 0xda, 0x09, 0x89, 0x82, 0x23, 0xf3, 0x14, 0xad, 0x83, 0x79, 0x40, 0x86, 0x38, 0xe2, 0x3a,
+	0xb9, 0xcb, 0xff, 0x2e, 0xd6, 0xff, 0x6e, 0x00, 0x92, 0xcd, 0x42, 0x81, 0x21, 0xc5, 0x88, 0xdf,
+	0x3f, 0x53, 0xd1, 0xa7, 0x7c, 0xce, 0x49, 0xd0, 0xb7, 0x0e, 0xab, 0xf6, 0x1c, 0x39, 0x43, 0x7d,
+	0x56, 0xbf, 0x21, 0x49, 0x20, 0xd2, 0xfc, 0x3c, 0xc6, 0x24, 0x01, 0x75, 0x0e, 0xbd, 0x35, 0x57,
+	0x98, 0x62, 0x2b, 0x4f, 0x7d, 0xe8, 0x62, 0xc2, 0x1a, 0x80, 0xa0, 0x15, 0x50, 0x0d, 0x6e, 0xea,
+	0x23, 0x98, 0xd8, 0x29, 0xd6, 0xbf, 0x30, 0x60, 0x45, 0x6b, 0xc7, 0xb4, 0x30, 0x24, 0xa1, 0x19,
+	0x61, 0x16, 0xa8, 0x25, 0x7a, 0x9e, 0x24, 0x6a, 0x0f, 0x37, 0xd3, 0x40, 0xdf, 0x83, 0x77, 0xe7,
+	0xb6, 0x64, 0xb3, 0x70, 0x70, 0x1f, 0xfb, 0xe7, 0x0c, 0x08, 0x6f, 0xc3, 0xed, 0x39, 0xb6, 0x4f,
+	0x5d, 0x9f, 0xa2, 0x24, 0xab, 0x9e, 0xe9, 0x4c, 0x83, 0x80, 0x2a, 0xce, 0xd5, 0x3f, 0x03, 0x73,
+	0xf6, 0x15, 0x4d, 0xc3, 0xdb, 0xf0, 0x3c, 0x31, 0xf4, 0x98, 0x4b, 0x34, 0xfd, 0x0e, 0x1e, 0x87,
+	0xe7, 0x58, 0x92, 0x0c, 0x96, 0x7e, 0xe2, 0x46, 0x44, 0x52, 0x32, 0x34, 0x7a, 0x5d, 0x12, 0x4e,
+	0x24, 0x21, 0x5b, 0x7f, 0x5f, 0x7b, 0x2f, 0x52, 0xa5, 0xb4, 0x0d, 0x71, 0x8a, 0xb9, 0x44, 0xbb,
+	0x80, 0x1d, 0xc8, 0xa5, 0xb1, 0xdb, 0x7a, 0xf1, 0xaf, 0x3b, 0x4b, 0x5f, 0xbd, 0xbc, 0x63, 0xbc,
+	0x78, 0x79, 0xc7, 0xf8, 0xe7, 0xcb, 0x3b, 0xc6, 0xcf, 0x1e, 0x2a, 0xff, 0x8f, 0x8d, 0x5d, 0x12,
+	0xf9, 0x17, 0x61, 0xe4, 0x0f, 0xfc, 0x40, 0x2e, 0x02, 0xbc, 0x35, 0x39, 0x1b, 0x6c, 0x4d, 0x4e,
+	0xb6, 0xd2, 0x26, 0x78, 0x52, 0x60, 0x7f, 0x8e, 0x3d, 0xfc, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff,
+	0xec, 0x36, 0xdb, 0xa1, 0x7b, 0x1b, 0x00, 0x00,
+}
+
+func (m *CNNode) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
 	n, err := m.MarshalToSizedBuffer(dAtA[:size])
@@ -2546,12 +2763,12 @@ func (m *LogShardInfo) Marshal() (dAtA []byte, err error) {
 	return dAtA[:n], nil
 }
 
-func (m *LogShardInfo) MarshalTo(dAtA []byte) (int, error) {
+func (m *CNNode) MarshalTo(dAtA []byte) (int, error) {
 	size := m.Size()
 	return m.MarshalToSizedBuffer(dAtA[:size])
 }
 
-func (m *LogShardInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+func (m *CNNode) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	i := len(dAtA)
 	_ = i
 	var l int
@@ -2560,47 +2777,29 @@ func (m *LogShardInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
-	if m.Term != 0 {
-		i = encodeVarintLogservice(dAtA, i, uint64(m.Term))
-		i--
-		dAtA[i] = 0x28
-	}
-	if m.LeaderID != 0 {
-		i = encodeVarintLogservice(dAtA, i, uint64(m.LeaderID))
-		i--
-		dAtA[i] = 0x20
-	}
-	if m.Epoch != 0 {
-		i = encodeVarintLogservice(dAtA, i, uint64(m.Epoch))
+	if m.Tick != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Tick))
 		i--
 		dAtA[i] = 0x18
 	}
-	if len(m.Replicas) > 0 {
-		for k := range m.Replicas {
-			v := m.Replicas[k]
-			baseI := i
-			i -= len(v)
-			copy(dAtA[i:], v)
-			i = encodeVarintLogservice(dAtA, i, uint64(len(v)))
-			i--
-			dAtA[i] = 0x12
-			i = encodeVarintLogservice(dAtA, i, uint64(k))
-			i--
-			dAtA[i] = 0x8
-			i = encodeVarintLogservice(dAtA, i, uint64(baseI-i))
-			i--
-			dAtA[i] = 0x12
-		}
+	if len(m.Address) > 0 {
+		i -= len(m.Address)
+		copy(dAtA[i:], m.Address)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.Address)))
+		i--
+		dAtA[i] = 0x12
 	}
-	if m.ShardID != 0 {
-		i = encodeVarintLogservice(dAtA, i, uint64(m.ShardID))
+	if len(m.UUID) > 0 {
+		i -= len(m.UUID)
+		copy(dAtA[i:], m.UUID)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.UUID)))
 		i--
-		dAtA[i] = 0x8
+		dAtA[i] = 0xa
 	}
 	return len(dAtA) - i, nil
 }
 
-func (m *LogReplicaInfo) Marshal() (dAtA []byte, err error) {
+func (m *DNNode) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
 	n, err := m.MarshalToSizedBuffer(dAtA[:size])
@@ -2610,12 +2809,12 @@ func (m *LogReplicaInfo) Marshal() (dAtA []byte, err error) {
 	return dAtA[:n], nil
 }
 
-func (m *LogReplicaInfo) MarshalTo(dAtA []byte) (int, error) {
+func (m *DNNode) MarshalTo(dAtA []byte) (int, error) {
 	size := m.Size()
 	return m.MarshalToSizedBuffer(dAtA[:size])
 }
 
-func (m *LogReplicaInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+func (m *DNNode) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	i := len(dAtA)
 	_ = i
 	var l int
@@ -2624,12 +2823,132 @@ func (m *LogReplicaInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if m.Tick != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Tick))
+		i--
+		dAtA[i] = 0x28
+	}
+	if len(m.Address) > 0 {
+		i -= len(m.Address)
+		copy(dAtA[i:], m.Address)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.Address)))
+		i--
+		dAtA[i] = 0x22
+	}
 	if m.ReplicaID != 0 {
 		i = encodeVarintLogservice(dAtA, i, uint64(m.ReplicaID))
 		i--
-		dAtA[i] = 0x10
+		dAtA[i] = 0x18
 	}
-	{
+	if m.ShardID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ShardID))
+		i--
+		dAtA[i] = 0x10
+	}
+	if len(m.UUID) > 0 {
+		i -= len(m.UUID)
+		copy(dAtA[i:], m.UUID)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.UUID)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *LogShardInfo) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *LogShardInfo) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *LogShardInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if m.Term != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Term))
+		i--
+		dAtA[i] = 0x28
+	}
+	if m.LeaderID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.LeaderID))
+		i--
+		dAtA[i] = 0x20
+	}
+	if m.Epoch != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Epoch))
+		i--
+		dAtA[i] = 0x18
+	}
+	if len(m.Replicas) > 0 {
+		for k := range m.Replicas {
+			v := m.Replicas[k]
+			baseI := i
+			i -= len(v)
+			copy(dAtA[i:], v)
+			i = encodeVarintLogservice(dAtA, i, uint64(len(v)))
+			i--
+			dAtA[i] = 0x12
+			i = encodeVarintLogservice(dAtA, i, uint64(k))
+			i--
+			dAtA[i] = 0x8
+			i = encodeVarintLogservice(dAtA, i, uint64(baseI-i))
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	if m.ShardID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ShardID))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *LogReplicaInfo) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *LogReplicaInfo) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *LogReplicaInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if m.ReplicaID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ReplicaID))
+		i--
+		dAtA[i] = 0x10
+	}
+	{
 		size, err := m.LogShardInfo.MarshalToSizedBuffer(dAtA[:i])
 		if err != nil {
 			return 0, err
@@ -3194,7 +3513,7 @@ func (m *Response) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i = encodeVarintLogservice(dAtA, i, uint64(size))
 	}
 	i--
-	dAtA[i] = 0x3a
+	dAtA[i] = 0x42
 	{
 		size, err := m.CommandBatch.MarshalToSizedBuffer(dAtA[:i])
 		if err != nil {
@@ -3204,7 +3523,7 @@ func (m *Response) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i = encodeVarintLogservice(dAtA, i, uint64(size))
 	}
 	i--
-	dAtA[i] = 0x32
+	dAtA[i] = 0x3a
 	{
 		size, err := m.LogResponse.MarshalToSizedBuffer(dAtA[:i])
 		if err != nil {
@@ -3214,7 +3533,17 @@ func (m *Response) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i = encodeVarintLogservice(dAtA, i, uint64(size))
 	}
 	i--
-	dAtA[i] = 0x2a
+	dAtA[i] = 0x32
+	if m.IsHAKeeper {
+		i--
+		if m.IsHAKeeper {
+			dAtA[i] = 1
+		} else {
+			dAtA[i] = 0
+		}
+		i--
+		dAtA[i] = 0x28
+	}
 	if len(m.ErrorMessage) > 0 {
 		i -= len(m.ErrorMessage)
 		copy(dAtA[i:], m.ErrorMessage)
@@ -3754,6 +4083,61 @@ func (m *DNState) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *ClusterDetails) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ClusterDetails) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ClusterDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.CNNodes) > 0 {
+		for iNdEx := len(m.CNNodes) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.CNNodes[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintLogservice(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	if len(m.DNNodes) > 0 {
+		for iNdEx := len(m.DNNodes) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.DNNodes[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintLogservice(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0xa
+		}
+	}
+	return len(dAtA) - i, nil
+}
+
 func (m *ClusterInfo) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -4197,6 +4581,58 @@ func encodeVarintLogservice(dAtA []byte, offset int, v uint64) int {
 	dAtA[offset] = uint8(v)
 	return base
 }
+func (m *CNNode) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.UUID)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	l = len(m.Address)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if m.Tick != 0 {
+		n += 1 + sovLogservice(uint64(m.Tick))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *DNNode) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.UUID)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if m.ShardID != 0 {
+		n += 1 + sovLogservice(uint64(m.ShardID))
+	}
+	if m.ReplicaID != 0 {
+		n += 1 + sovLogservice(uint64(m.ReplicaID))
+	}
+	l = len(m.Address)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if m.Tick != 0 {
+		n += 1 + sovLogservice(uint64(m.Tick))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
 func (m *LogShardInfo) Size() (n int) {
 	if m == nil {
 		return 0
@@ -4507,6 +4943,9 @@ func (m *Response) Size() (n int) {
 	if l > 0 {
 		n += 1 + l + sovLogservice(uint64(l))
 	}
+	if m.IsHAKeeper {
+		n += 2
+	}
 	l = m.LogResponse.Size()
 	n += 1 + l + sovLogservice(uint64(l))
 	l = m.CommandBatch.Size()
@@ -4747,6 +5186,30 @@ func (m *DNState) Size() (n int) {
 	return n
 }
 
+func (m *ClusterDetails) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if len(m.DNNodes) > 0 {
+		for _, e := range m.DNNodes {
+			l = e.Size()
+			n += 1 + l + sovLogservice(uint64(l))
+		}
+	}
+	if len(m.CNNodes) > 0 {
+		for _, e := range m.CNNodes {
+			l = e.Size()
+			n += 1 + l + sovLogservice(uint64(l))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
 func (m *ClusterInfo) Size() (n int) {
 	if m == nil {
 		return 0
@@ -4849,90 +5312,396 @@ func (m *LogState) Size() (n int) {
 			n += mapEntrySize + 1 + sovLogservice(uint64(mapEntrySize))
 		}
 	}
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
-	}
-	return n
-}
-
-func (m *CheckerState) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	if m.Tick != 0 {
-		n += 1 + sovLogservice(uint64(m.Tick))
-	}
-	l = m.ClusterInfo.Size()
-	n += 1 + l + sovLogservice(uint64(l))
-	l = m.DNState.Size()
-	n += 1 + l + sovLogservice(uint64(l))
-	l = m.LogState.Size()
-	n += 1 + l + sovLogservice(uint64(l))
-	if m.State != 0 {
-		n += 1 + sovLogservice(uint64(m.State))
-	}
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
-	}
-	return n
-}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *CheckerState) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Tick != 0 {
+		n += 1 + sovLogservice(uint64(m.Tick))
+	}
+	l = m.ClusterInfo.Size()
+	n += 1 + l + sovLogservice(uint64(l))
+	l = m.DNState.Size()
+	n += 1 + l + sovLogservice(uint64(l))
+	l = m.LogState.Size()
+	n += 1 + l + sovLogservice(uint64(l))
+	if m.State != 0 {
+		n += 1 + sovLogservice(uint64(m.State))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *HAKeeperRSMState) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Tick != 0 {
+		n += 1 + sovLogservice(uint64(m.Tick))
+	}
+	if m.NextID != 0 {
+		n += 1 + sovLogservice(uint64(m.NextID))
+	}
+	if m.Term != 0 {
+		n += 1 + sovLogservice(uint64(m.Term))
+	}
+	if m.State != 0 {
+		n += 1 + sovLogservice(uint64(m.State))
+	}
+	if len(m.ScheduleCommands) > 0 {
+		for k, v := range m.ScheduleCommands {
+			_ = k
+			_ = v
+			l = v.Size()
+			mapEntrySize := 1 + len(k) + sovLogservice(uint64(len(k))) + 1 + l + sovLogservice(uint64(l))
+			n += mapEntrySize + 1 + sovLogservice(uint64(mapEntrySize))
+		}
+	}
+	if len(m.LogShards) > 0 {
+		for k, v := range m.LogShards {
+			_ = k
+			_ = v
+			mapEntrySize := 1 + len(k) + sovLogservice(uint64(len(k))) + 1 + sovLogservice(uint64(v))
+			n += mapEntrySize + 1 + sovLogservice(uint64(mapEntrySize))
+		}
+	}
+	l = m.CNState.Size()
+	n += 1 + l + sovLogservice(uint64(l))
+	l = m.DNState.Size()
+	n += 1 + l + sovLogservice(uint64(l))
+	l = m.LogState.Size()
+	n += 1 + l + sovLogservice(uint64(l))
+	l = m.ClusterInfo.Size()
+	n += 1 + l + sovLogservice(uint64(l))
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func sovLogservice(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozLogservice(x uint64) (n int) {
+	return sovLogservice(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *CNNode) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: CNNode: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: CNNode: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.UUID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Address = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Tick", wireType)
+			}
+			m.Tick = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Tick |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *DNNode) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: DNNode: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: DNNode: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.UUID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ShardID", wireType)
+			}
+			m.ShardID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ShardID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ReplicaID", wireType)
+			}
+			m.ReplicaID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ReplicaID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Address = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 5:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Tick", wireType)
+			}
+			m.Tick = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Tick |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
 
-func (m *HAKeeperRSMState) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	if m.Tick != 0 {
-		n += 1 + sovLogservice(uint64(m.Tick))
-	}
-	if m.NextID != 0 {
-		n += 1 + sovLogservice(uint64(m.NextID))
-	}
-	if m.Term != 0 {
-		n += 1 + sovLogservice(uint64(m.Term))
-	}
-	if m.State != 0 {
-		n += 1 + sovLogservice(uint64(m.State))
-	}
-	if len(m.ScheduleCommands) > 0 {
-		for k, v := range m.ScheduleCommands {
-			_ = k
-			_ = v
-			l = v.Size()
-			mapEntrySize := 1 + len(k) + sovLogservice(uint64(len(k))) + 1 + l + sovLogservice(uint64(l))
-			n += mapEntrySize + 1 + sovLogservice(uint64(mapEntrySize))
-		}
-	}
-	if len(m.LogShards) > 0 {
-		for k, v := range m.LogShards {
-			_ = k
-			_ = v
-			mapEntrySize := 1 + len(k) + sovLogservice(uint64(len(k))) + 1 + sovLogservice(uint64(v))
-			n += mapEntrySize + 1 + sovLogservice(uint64(mapEntrySize))
-		}
-	}
-	l = m.CNState.Size()
-	n += 1 + l + sovLogservice(uint64(l))
-	l = m.DNState.Size()
-	n += 1 + l + sovLogservice(uint64(l))
-	l = m.LogState.Size()
-	n += 1 + l + sovLogservice(uint64(l))
-	l = m.ClusterInfo.Size()
-	n += 1 + l + sovLogservice(uint64(l))
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
 	}
-	return n
-}
-
-func sovLogservice(x uint64) (n int) {
-	return (math_bits.Len64(x|1) + 6) / 7
-}
-func sozLogservice(x uint64) (n int) {
-	return sovLogservice(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+	return nil
 }
 func (m *LogShardInfo) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
@@ -6876,6 +7645,26 @@ func (m *Response) Unmarshal(dAtA []byte) error {
 			m.ErrorMessage = string(dAtA[iNdEx:postIndex])
 			iNdEx = postIndex
 		case 5:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field IsHAKeeper", wireType)
+			}
+			var v int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				v |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			m.IsHAKeeper = bool(v != 0)
+		case 6:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field LogResponse", wireType)
 			}
@@ -6908,7 +7697,7 @@ func (m *Response) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
-		case 6:
+		case 7:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field CommandBatch", wireType)
 			}
@@ -6941,7 +7730,7 @@ func (m *Response) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
-		case 7:
+		case 8:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field TsoResponse", wireType)
 			}
@@ -8422,6 +9211,125 @@ func (m *DNState) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *ClusterDetails) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ClusterDetails: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ClusterDetails: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field DNNodes", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.DNNodes = append(m.DNNodes, DNNode{})
+			if err := m.DNNodes[len(m.DNNodes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field CNNodes", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.CNNodes = append(m.CNNodes, CNNode{})
+			if err := m.CNNodes[len(m.CNNodes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *ClusterInfo) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/proto/logservice.proto b/proto/logservice.proto
index da5eb8ff1..19dbe5024 100644
--- a/proto/logservice.proto
+++ b/proto/logservice.proto
@@ -27,6 +27,20 @@ enum UpdateType {
   TSOUpdate           = 3;
 }
 
+message CNNode {
+  string UUID    = 1;
+  string Address = 2;
+  uint64 Tick    = 3;
+}
+
+message DNNode {
+  string UUID      = 1;
+  uint64 ShardID   = 2;
+  uint64 ReplicaID = 3;
+  string Address   = 4;
+  uint64 Tick      = 5;
+}
+
 // LogShardInfo contains information a log shard.
 message LogShardInfo {
   // ShardID is the ID of a Log shard.
@@ -105,16 +119,17 @@ message RSMState {
 }
 
 enum MethodType {
-  TSO_UPDATE    = 0;
-  APPEND        = 1;
-  READ          = 2;
-  TRUNCATE      = 3;
-  GET_TRUNCATE  = 4;
-  CONNECT       = 5;
-  CONNECT_RO    = 6;
-  LOG_HEARTBEAT = 7;
-  CN_HEARTBEAT  = 8;
-  DN_HEARTBEAT  = 9;
+  TSO_UPDATE     = 0;
+  APPEND         = 1;
+  READ           = 2;
+  TRUNCATE       = 3;
+  GET_TRUNCATE   = 4;
+  CONNECT        = 5;
+  CONNECT_RO     = 6;
+  LOG_HEARTBEAT  = 7;
+  CN_HEARTBEAT   = 8;
+  DN_HEARTBEAT   = 9;
+  CHECK_HAKEEPER = 10;
 };
 
 enum RecordType {
@@ -181,13 +196,14 @@ message LogResponse {
 }
 
 message Response {
-  uint64 RequestID        = 1;
-  MethodType Method       = 2;
-  ErrorCode ErrorCode     = 3;
-  string ErrorMessage     = 4;
-  LogResponse LogResponse = 5 [(gogoproto.nullable) = false];
-  CommandBatch CommandBatch = 6 [(gogoproto.nullable) = false];
-  TsoResponse TsoResponse = 7 [(gogoproto.nullable) = false];
+  uint64 RequestID          = 1;
+  MethodType Method         = 2;
+  ErrorCode ErrorCode       = 3;
+  string ErrorMessage       = 4;
+  bool IsHAKeeper           = 5;
+  LogResponse LogResponse   = 6 [(gogoproto.nullable) = false];
+  CommandBatch CommandBatch = 7 [(gogoproto.nullable) = false];
+  TsoResponse TsoResponse   = 8 [(gogoproto.nullable) = false];
 };
 
 message LogRecordResponse {
@@ -299,6 +315,11 @@ message DNState {
   map<string, DNStoreInfo> Stores = 1 [(gogoproto.nullable) = false];
 }
 
+message ClusterDetails {
+  repeated DNNode DNNodes = 1 [(gogoproto.nullable) = false];
+  repeated CNNode CNNodes = 2 [(gogoproto.nullable) = false]; 
+}
+
 // ClusterInfo provides a global view of all shards in the cluster. It
 // describes the logical sharding of the system, rather than physical
 // distribution of all replicas that belong to those shards.


Test patch:
diff --git a/pkg/logservice/hakeeper_client_test.go b/pkg/logservice/hakeeper_client_test.go
new file mode 100644
index 000000000..a04ca04e6
--- /dev/null
+++ b/pkg/logservice/hakeeper_client_test.go
@@ -0,0 +1,153 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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
+//
+//      http://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.
+
+package logservice
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+func TestHAKeeperClientsCanBeCreated(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		cfg := HAKeeperClientConfig{
+			ServiceAddresses: []string{testServiceAddress},
+		}
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		c1, err := NewCNHAKeeperClient(ctx, cfg)
+		require.NoError(t, err)
+		assert.NoError(t, c1.Close())
+		c2, err := NewDNHAKeeperClient(ctx, cfg)
+		assert.NoError(t, err)
+		assert.NoError(t, c2.Close())
+		c3, err := NewLogHAKeeperClient(ctx, cfg)
+		assert.NoError(t, err)
+		assert.NoError(t, c3.Close())
+	}
+	runServiceTest(t, true, true, fn)
+}
+
+func TestHAKeeperClientCanNotConnectToNonHAKeeperNode(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		cfg := HAKeeperClientConfig{
+			ServiceAddresses: []string{testServiceAddress},
+		}
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		_, err := NewCNHAKeeperClient(ctx, cfg)
+		require.Equal(t, ErrNoHAKeeper, err)
+		_, err = NewDNHAKeeperClient(ctx, cfg)
+		assert.Equal(t, ErrNoHAKeeper, err)
+		_, err = NewLogHAKeeperClient(ctx, cfg)
+		assert.Equal(t, ErrNoHAKeeper, err)
+	}
+	runServiceTest(t, false, true, fn)
+}
+
+func TestHAKeeperClientSendCNHeartbeat(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		cfg := HAKeeperClientConfig{
+			ServiceAddresses: []string{testServiceAddress},
+		}
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		c, err := NewCNHAKeeperClient(ctx, cfg)
+		require.NoError(t, err)
+		defer func() {
+			assert.NoError(t, c.Close())
+		}()
+		hb := pb.CNStoreHeartbeat{
+			UUID: s.ID(),
+		}
+		require.NoError(t, c.SendCNHeartbeat(ctx, hb))
+		// TODO: use GetClusterDetails to see whether this CN itself is included
+		// in the return value
+	}
+	runServiceTest(t, true, true, fn)
+}
+
+func TestHAKeeperClientSendDNHeartbeat(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		cfg := HAKeeperClientConfig{
+			ServiceAddresses: []string{testServiceAddress},
+		}
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		c, err := NewDNHAKeeperClient(ctx, cfg)
+		require.NoError(t, err)
+		defer func() {
+			assert.NoError(t, c.Close())
+		}()
+		hb := pb.DNStoreHeartbeat{
+			UUID: s.ID(),
+		}
+		cb, err := c.SendDNHeartbeat(ctx, hb)
+		require.NoError(t, err)
+		assert.Equal(t, 0, len(cb.Commands))
+
+		sc := pb.ScheduleCommand{
+			UUID:        s.ID(),
+			ServiceType: pb.DnService,
+			ShutdownStore: &pb.ShutdownStore{
+				StoreID: "hello world",
+			},
+		}
+		require.NoError(t, s.store.addScheduleCommands(ctx, 0, []pb.ScheduleCommand{sc}))
+		cb, err = c.SendDNHeartbeat(ctx, hb)
+		require.NoError(t, err)
+		require.Equal(t, 1, len(cb.Commands))
+		require.Equal(t, sc, cb.Commands[0])
+	}
+	runServiceTest(t, true, true, fn)
+}
+
+func TestHAKeeperClientSendLogHeartbeat(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		cfg := HAKeeperClientConfig{
+			ServiceAddresses: []string{testServiceAddress},
+		}
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		c, err := NewLogHAKeeperClient(ctx, cfg)
+		require.NoError(t, err)
+		defer func() {
+			assert.NoError(t, c.Close())
+		}()
+		hb := s.store.getHeartbeatMessage()
+		cb, err := c.SendLogHeartbeat(ctx, hb)
+		require.NoError(t, err)
+		assert.Equal(t, 0, len(cb.Commands))
+
+		sc := pb.ScheduleCommand{
+			UUID:        s.ID(),
+			ServiceType: pb.DnService,
+			ShutdownStore: &pb.ShutdownStore{
+				StoreID: "hello world",
+			},
+		}
+		require.NoError(t, s.store.addScheduleCommands(ctx, 0, []pb.ScheduleCommand{sc}))
+		cb, err = c.SendLogHeartbeat(ctx, hb)
+		require.NoError(t, err)
+		require.Equal(t, 1, len(cb.Commands))
+		require.Equal(t, sc, cb.Commands[0])
+	}
+	runServiceTest(t, true, true, fn)
+}
diff --git a/pkg/logservice/service_test.go b/pkg/logservice/service_test.go
index 13a07cc93..350337d19 100644
--- a/pkg/logservice/service_test.go
+++ b/pkg/logservice/service_test.go
@@ -446,6 +446,33 @@ func TestServiceTsoUpdate(t *testing.T) {
 	runServiceTest(t, false, true, fn)
 }
 
+func TestServiceCheckHAKeeper(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		req := pb.Request{
+			Method:  pb.CHECK_HAKEEPER,
+			Timeout: int64(time.Second),
+		}
+		resp := s.handleCheckHAKeeper(req)
+		assert.Equal(t, pb.NoError, resp.ErrorCode)
+		assert.False(t, resp.IsHAKeeper)
+	}
+	runServiceTest(t, false, false, fn)
+
+	fn = func(t *testing.T, s *Service) {
+		init := make(map[uint64]dragonboat.Target)
+		init[1] = s.ID()
+		s.store.startHAKeeperReplica(1, init, false)
+		req := pb.Request{
+			Method:  pb.CHECK_HAKEEPER,
+			Timeout: int64(time.Second),
+		}
+		resp := s.handleCheckHAKeeper(req)
+		assert.Equal(t, pb.NoError, resp.ErrorCode)
+		assert.True(t, resp.IsHAKeeper)
+	}
+	runServiceTest(t, false, false, fn)
+}
+
 func TestShardInfoCanBeQueried(t *testing.T) {
 	defer leaktest.AfterTest(t)()
 	cfg1 := Config{