From 7dc5451faee5f9486b7139fb9456da492c65af54 Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Thu, 14 Mar 2019 15:00:08 -0700 Subject: [PATCH 1/9] *: Change etcdserver API to support raft learner - Added isLearner flag to MemberAddRequest in Cluster API. - Added isLearner field to StatusResponse in Maintenance API. - Added MemberPromote rpc to Cluster API. --- Documentation/dev-guide/api_reference_v3.md | 21 + .../apispec/swagger/rpc.swagger.json | 150 ++- .../apispec/swagger/v3election.swagger.json | 53 +- etcdserver/etcdserverpb/etcdserver.pb.go | 2 + etcdserver/etcdserverpb/gw/rpc.pb.gw.go | 46 + etcdserver/etcdserverpb/rpc.pb.go | 1129 ++++++++++++----- etcdserver/etcdserverpb/rpc.proto | 25 + 7 files changed, 1097 insertions(+), 329 deletions(-) diff --git a/Documentation/dev-guide/api_reference_v3.md b/Documentation/dev-guide/api_reference_v3.md index 2d2000d8a72..e88906b0edc 100644 --- a/Documentation/dev-guide/api_reference_v3.md +++ b/Documentation/dev-guide/api_reference_v3.md @@ -37,6 +37,7 @@ This is a generated documentation. Please read the proto files for more. | MemberRemove | MemberRemoveRequest | MemberRemoveResponse | MemberRemove removes an existing member from the cluster. | | MemberUpdate | MemberUpdateRequest | MemberUpdateResponse | MemberUpdate updates the member configuration. | | MemberList | MemberListRequest | MemberListResponse | MemberList lists all the members in the cluster. | +| MemberPromote | MemberPromoteRequest | MemberPromoteResponse | MemberPromote promotes a member from raft learner (non-voting) to raft voting member. | @@ -609,6 +610,7 @@ Empty field. | name | name is the human-readable name of the member. If the member is not started, the name will be an empty string. | string | | peerURLs | peerURLs is the list of URLs the member exposes to the cluster for communication. | (slice of) string | | clientURLs | clientURLs is the list of URLs the member exposes to clients for communication. If the member is not started, clientURLs will be empty. | (slice of) string | +| isLearner | isLearner indicates if the member is raft learner. | bool | @@ -617,6 +619,7 @@ Empty field. | Field | Description | Type | | ----- | ----------- | ---- | | peerURLs | peerURLs is the list of URLs the added member will use to communicate with the cluster. | (slice of) string | +| isLearner | isLearner indicates if the added member is raft learner. | bool | @@ -645,6 +648,23 @@ Empty field. +##### message `MemberPromoteRequest` (etcdserver/etcdserverpb/rpc.proto) + +| Field | Description | Type | +| ----- | ----------- | ---- | +| ID | ID is the member ID of the member to promote. | uint64 | + + + +##### message `MemberPromoteResponse` (etcdserver/etcdserverpb/rpc.proto) + +| Field | Description | Type | +| ----- | ----------- | ---- | +| header | | ResponseHeader | +| members | members is a list of all members after promoting the member. | (slice of) Member | + + + ##### message `MemberRemoveRequest` (etcdserver/etcdserverpb/rpc.proto) | Field | Description | Type | @@ -819,6 +839,7 @@ Empty field. | raftAppliedIndex | raftAppliedIndex is the current raft applied index of the responding member. | uint64 | | errors | errors contains alarm/health information and status. | (slice of) string | | dbSizeInUse | dbSizeInUse is the size of the backend database logically in use, in bytes, of the responding member. | int64 | +| isLearner | isLearner indicates if the member is raft learner. | bool | diff --git a/Documentation/dev-guide/apispec/swagger/rpc.swagger.json b/Documentation/dev-guide/apispec/swagger/rpc.swagger.json index a76c9d3a8b1..acb38527ef0 100644 --- a/Documentation/dev-guide/apispec/swagger/rpc.swagger.json +++ b/Documentation/dev-guide/apispec/swagger/rpc.swagger.json @@ -501,6 +501,33 @@ } } }, + "/v3/cluster/member/promote": { + "post": { + "tags": [ + "Cluster" + ], + "summary": "MemberPromote promotes a member from raft learner (non-voting) to raft voting member.", + "operationId": "MemberPromote", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/etcdserverpbMemberPromoteRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/etcdserverpbMemberPromoteResponse" + } + } + } + } + }, "/v3/cluster/member/remove": { "post": { "tags": [ @@ -820,7 +847,7 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/definitions/etcdserverpbLeaseKeepAliveResponse" + "$ref": "#/x-stream-definitions/etcdserverpbLeaseKeepAliveResponse" } } } @@ -1009,7 +1036,7 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/definitions/etcdserverpbSnapshotResponse" + "$ref": "#/x-stream-definitions/etcdserverpbSnapshotResponse" } } } @@ -1091,7 +1118,7 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/definitions/etcdserverpbWatchResponse" + "$ref": "#/x-stream-definitions/etcdserverpbWatchResponse" } } } @@ -1882,6 +1909,11 @@ "type": "string" } }, + "isLearner": { + "description": "isLearner indicates if the member is raft learner.", + "type": "boolean", + "format": "boolean" + }, "name": { "description": "name is the human-readable name of the member. If the member is not started, the name will be an empty string.", "type": "string" @@ -1898,6 +1930,11 @@ "etcdserverpbMemberAddRequest": { "type": "object", "properties": { + "isLearner": { + "description": "isLearner indicates if the added member is raft learner.", + "type": "boolean", + "format": "boolean" + }, "peerURLs": { "description": "peerURLs is the list of URLs the added member will use to communicate with the cluster.", "type": "array", @@ -1944,6 +1981,31 @@ } } }, + "etcdserverpbMemberPromoteRequest": { + "type": "object", + "properties": { + "ID": { + "description": "ID is the member ID of the member to promote.", + "type": "string", + "format": "uint64" + } + } + }, + "etcdserverpbMemberPromoteResponse": { + "type": "object", + "properties": { + "header": { + "$ref": "#/definitions/etcdserverpbResponseHeader" + }, + "members": { + "description": "members is a list of all members after promoting the member.", + "type": "array", + "items": { + "$ref": "#/definitions/etcdserverpbMember" + } + } + } + }, "etcdserverpbMemberRemoveRequest": { "type": "object", "properties": { @@ -2266,6 +2328,11 @@ "header": { "$ref": "#/definitions/etcdserverpbResponseHeader" }, + "isLearner": { + "description": "isLearner indicates if the member is raft learner.", + "type": "boolean", + "format": "boolean" + }, "leader": { "description": "leader is the member ID which the responding member believes is the current leader.", "type": "string", @@ -2508,6 +2575,43 @@ "format": "int64" } } + }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "runtimeStreamError": { + "type": "object", + "properties": { + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + }, + "grpc_code": { + "type": "integer", + "format": "int32" + }, + "http_code": { + "type": "integer", + "format": "int32" + }, + "http_status": { + "type": "string" + }, + "message": { + "type": "string" + } + } } }, "securityDefinitions": { @@ -2521,5 +2625,43 @@ { "ApiKey": [] } - ] + ], + "x-stream-definitions": { + "etcdserverpbLeaseKeepAliveResponse": { + "properties": { + "error": { + "$ref": "#/definitions/runtimeStreamError" + }, + "result": { + "$ref": "#/definitions/etcdserverpbLeaseKeepAliveResponse" + } + }, + "title": "Stream result of etcdserverpbLeaseKeepAliveResponse", + "type": "object" + }, + "etcdserverpbSnapshotResponse": { + "properties": { + "error": { + "$ref": "#/definitions/runtimeStreamError" + }, + "result": { + "$ref": "#/definitions/etcdserverpbSnapshotResponse" + } + }, + "title": "Stream result of etcdserverpbSnapshotResponse", + "type": "object" + }, + "etcdserverpbWatchResponse": { + "properties": { + "error": { + "$ref": "#/definitions/runtimeStreamError" + }, + "result": { + "$ref": "#/definitions/etcdserverpbWatchResponse" + } + }, + "title": "Stream result of etcdserverpbWatchResponse", + "type": "object" + } + } } \ No newline at end of file diff --git a/Documentation/dev-guide/apispec/swagger/v3election.swagger.json b/Documentation/dev-guide/apispec/swagger/v3election.swagger.json index b0d33ad080b..a8d08ceaf11 100644 --- a/Documentation/dev-guide/apispec/swagger/v3election.swagger.json +++ b/Documentation/dev-guide/apispec/swagger/v3election.swagger.json @@ -77,7 +77,7 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/definitions/v3electionpbLeaderResponse" + "$ref": "#/x-stream-definitions/v3electionpbLeaderResponse" } } }, @@ -212,6 +212,43 @@ } } }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "runtimeStreamError": { + "type": "object", + "properties": { + "grpc_code": { + "type": "integer", + "format": "int32" + }, + "http_code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "http_status": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, "v3electionpbCampaignRequest": { "type": "object", "properties": { @@ -330,5 +367,19 @@ } } } + }, + "x-stream-definitions": { + "v3electionpbLeaderResponse": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/v3electionpbLeaderResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of v3electionpbLeaderResponse" + } } } diff --git a/etcdserver/etcdserverpb/etcdserver.pb.go b/etcdserver/etcdserverpb/etcdserver.pb.go index f5134b9f7c4..9e9b42ceac7 100644 --- a/etcdserver/etcdserverpb/etcdserver.pb.go +++ b/etcdserver/etcdserverpb/etcdserver.pb.go @@ -64,6 +64,8 @@ MemberUpdateResponse MemberListRequest MemberListResponse + MemberPromoteRequest + MemberPromoteResponse DefragmentRequest DefragmentResponse MoveLeaderRequest diff --git a/etcdserver/etcdserverpb/gw/rpc.pb.gw.go b/etcdserver/etcdserverpb/gw/rpc.pb.gw.go index 4b1f0e5ebc2..a6a4768b25f 100644 --- a/etcdserver/etcdserverpb/gw/rpc.pb.gw.go +++ b/etcdserver/etcdserverpb/gw/rpc.pb.gw.go @@ -341,6 +341,19 @@ func request_Cluster_MemberList_0(ctx context.Context, marshaler runtime.Marshal } +func request_Cluster_MemberPromote_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.ClusterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq etcdserverpb.MemberPromoteRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.MemberPromote(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_Maintenance_Alarm_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.MaintenanceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq etcdserverpb.AlarmRequest var metadata runtime.ServerMetadata @@ -1399,6 +1412,35 @@ func RegisterClusterHandlerClient(ctx context.Context, mux *runtime.ServeMux, cl }) + mux.Handle("POST", pattern_Cluster_MemberPromote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Cluster_MemberPromote_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Cluster_MemberPromote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1410,6 +1452,8 @@ var ( pattern_Cluster_MemberUpdate_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v3", "cluster", "member", "update"}, "")) pattern_Cluster_MemberList_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v3", "cluster", "member", "list"}, "")) + + pattern_Cluster_MemberPromote_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v3", "cluster", "member", "promote"}, "")) ) var ( @@ -1420,6 +1464,8 @@ var ( forward_Cluster_MemberUpdate_0 = runtime.ForwardResponseMessage forward_Cluster_MemberList_0 = runtime.ForwardResponseMessage + + forward_Cluster_MemberPromote_0 = runtime.ForwardResponseMessage ) // RegisterMaintenanceHandlerFromEndpoint is same as RegisterMaintenanceHandler but diff --git a/etcdserver/etcdserverpb/rpc.pb.go b/etcdserver/etcdserverpb/rpc.pb.go index 0e2c9498150..9f6fac9e5ad 100644 --- a/etcdserver/etcdserverpb/rpc.pb.go +++ b/etcdserver/etcdserverpb/rpc.pb.go @@ -211,7 +211,7 @@ func (x AlarmRequest_AlarmAction) String() string { return proto.EnumName(AlarmRequest_AlarmAction_name, int32(x)) } func (AlarmRequest_AlarmAction) EnumDescriptor() ([]byte, []int) { - return fileDescriptorRpc, []int{52, 0} + return fileDescriptorRpc, []int{54, 0} } type ResponseHeader struct { @@ -2186,6 +2186,8 @@ type Member struct { PeerURLs []string `protobuf:"bytes,3,rep,name=peerURLs" json:"peerURLs,omitempty"` // clientURLs is the list of URLs the member exposes to clients for communication. If the member is not started, clientURLs will be empty. ClientURLs []string `protobuf:"bytes,4,rep,name=clientURLs" json:"clientURLs,omitempty"` + // isLearner indicates if the member is raft learner. + IsLearner bool `protobuf:"varint,5,opt,name=isLearner,proto3" json:"isLearner,omitempty"` } func (m *Member) Reset() { *m = Member{} } @@ -2221,9 +2223,18 @@ func (m *Member) GetClientURLs() []string { return nil } +func (m *Member) GetIsLearner() bool { + if m != nil { + return m.IsLearner + } + return false +} + type MemberAddRequest struct { // peerURLs is the list of URLs the added member will use to communicate with the cluster. PeerURLs []string `protobuf:"bytes,1,rep,name=peerURLs" json:"peerURLs,omitempty"` + // isLearner indicates if the added member is raft learner. + IsLearner bool `protobuf:"varint,2,opt,name=isLearner,proto3" json:"isLearner,omitempty"` } func (m *MemberAddRequest) Reset() { *m = MemberAddRequest{} } @@ -2238,6 +2249,13 @@ func (m *MemberAddRequest) GetPeerURLs() []string { return nil } +func (m *MemberAddRequest) GetIsLearner() bool { + if m != nil { + return m.IsLearner + } + return false +} + type MemberAddResponse struct { Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"` // member is the member information for the added member. @@ -2398,13 +2416,55 @@ func (m *MemberListResponse) GetMembers() []*Member { return nil } +type MemberPromoteRequest struct { + // ID is the member ID of the member to promote. + ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` +} + +func (m *MemberPromoteRequest) Reset() { *m = MemberPromoteRequest{} } +func (m *MemberPromoteRequest) String() string { return proto.CompactTextString(m) } +func (*MemberPromoteRequest) ProtoMessage() {} +func (*MemberPromoteRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{48} } + +func (m *MemberPromoteRequest) GetID() uint64 { + if m != nil { + return m.ID + } + return 0 +} + +type MemberPromoteResponse struct { + Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"` + // members is a list of all members after promoting the member. + Members []*Member `protobuf:"bytes,2,rep,name=members" json:"members,omitempty"` +} + +func (m *MemberPromoteResponse) Reset() { *m = MemberPromoteResponse{} } +func (m *MemberPromoteResponse) String() string { return proto.CompactTextString(m) } +func (*MemberPromoteResponse) ProtoMessage() {} +func (*MemberPromoteResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{49} } + +func (m *MemberPromoteResponse) GetHeader() *ResponseHeader { + if m != nil { + return m.Header + } + return nil +} + +func (m *MemberPromoteResponse) GetMembers() []*Member { + if m != nil { + return m.Members + } + return nil +} + type DefragmentRequest struct { } func (m *DefragmentRequest) Reset() { *m = DefragmentRequest{} } func (m *DefragmentRequest) String() string { return proto.CompactTextString(m) } func (*DefragmentRequest) ProtoMessage() {} -func (*DefragmentRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{48} } +func (*DefragmentRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{50} } type DefragmentResponse struct { Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"` @@ -2413,7 +2473,7 @@ type DefragmentResponse struct { func (m *DefragmentResponse) Reset() { *m = DefragmentResponse{} } func (m *DefragmentResponse) String() string { return proto.CompactTextString(m) } func (*DefragmentResponse) ProtoMessage() {} -func (*DefragmentResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{49} } +func (*DefragmentResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{51} } func (m *DefragmentResponse) GetHeader() *ResponseHeader { if m != nil { @@ -2430,7 +2490,7 @@ type MoveLeaderRequest struct { func (m *MoveLeaderRequest) Reset() { *m = MoveLeaderRequest{} } func (m *MoveLeaderRequest) String() string { return proto.CompactTextString(m) } func (*MoveLeaderRequest) ProtoMessage() {} -func (*MoveLeaderRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{50} } +func (*MoveLeaderRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{52} } func (m *MoveLeaderRequest) GetTargetID() uint64 { if m != nil { @@ -2446,7 +2506,7 @@ type MoveLeaderResponse struct { func (m *MoveLeaderResponse) Reset() { *m = MoveLeaderResponse{} } func (m *MoveLeaderResponse) String() string { return proto.CompactTextString(m) } func (*MoveLeaderResponse) ProtoMessage() {} -func (*MoveLeaderResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{51} } +func (*MoveLeaderResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{53} } func (m *MoveLeaderResponse) GetHeader() *ResponseHeader { if m != nil { @@ -2470,7 +2530,7 @@ type AlarmRequest struct { func (m *AlarmRequest) Reset() { *m = AlarmRequest{} } func (m *AlarmRequest) String() string { return proto.CompactTextString(m) } func (*AlarmRequest) ProtoMessage() {} -func (*AlarmRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{52} } +func (*AlarmRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{54} } func (m *AlarmRequest) GetAction() AlarmRequest_AlarmAction { if m != nil { @@ -2503,7 +2563,7 @@ type AlarmMember struct { func (m *AlarmMember) Reset() { *m = AlarmMember{} } func (m *AlarmMember) String() string { return proto.CompactTextString(m) } func (*AlarmMember) ProtoMessage() {} -func (*AlarmMember) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{53} } +func (*AlarmMember) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{55} } func (m *AlarmMember) GetMemberID() uint64 { if m != nil { @@ -2528,7 +2588,7 @@ type AlarmResponse struct { func (m *AlarmResponse) Reset() { *m = AlarmResponse{} } func (m *AlarmResponse) String() string { return proto.CompactTextString(m) } func (*AlarmResponse) ProtoMessage() {} -func (*AlarmResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{54} } +func (*AlarmResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{56} } func (m *AlarmResponse) GetHeader() *ResponseHeader { if m != nil { @@ -2550,7 +2610,7 @@ type StatusRequest struct { func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} -func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{55} } +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{57} } type StatusResponse struct { Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"` @@ -2570,12 +2630,14 @@ type StatusResponse struct { Errors []string `protobuf:"bytes,8,rep,name=errors" json:"errors,omitempty"` // dbSizeInUse is the size of the backend database logically in use, in bytes, of the responding member. DbSizeInUse int64 `protobuf:"varint,9,opt,name=dbSizeInUse,proto3" json:"dbSizeInUse,omitempty"` + // isLearner indicates if the member is raft learner. + IsLearner bool `protobuf:"varint,10,opt,name=isLearner,proto3" json:"isLearner,omitempty"` } func (m *StatusResponse) Reset() { *m = StatusResponse{} } func (m *StatusResponse) String() string { return proto.CompactTextString(m) } func (*StatusResponse) ProtoMessage() {} -func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{56} } +func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{58} } func (m *StatusResponse) GetHeader() *ResponseHeader { if m != nil { @@ -2640,13 +2702,20 @@ func (m *StatusResponse) GetDbSizeInUse() int64 { return 0 } +func (m *StatusResponse) GetIsLearner() bool { + if m != nil { + return m.IsLearner + } + return false +} + type AuthEnableRequest struct { } func (m *AuthEnableRequest) Reset() { *m = AuthEnableRequest{} } func (m *AuthEnableRequest) String() string { return proto.CompactTextString(m) } func (*AuthEnableRequest) ProtoMessage() {} -func (*AuthEnableRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{57} } +func (*AuthEnableRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{59} } type AuthDisableRequest struct { } @@ -2654,7 +2723,7 @@ type AuthDisableRequest struct { func (m *AuthDisableRequest) Reset() { *m = AuthDisableRequest{} } func (m *AuthDisableRequest) String() string { return proto.CompactTextString(m) } func (*AuthDisableRequest) ProtoMessage() {} -func (*AuthDisableRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{58} } +func (*AuthDisableRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{60} } type AuthenticateRequest struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -2664,7 +2733,7 @@ type AuthenticateRequest struct { func (m *AuthenticateRequest) Reset() { *m = AuthenticateRequest{} } func (m *AuthenticateRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateRequest) ProtoMessage() {} -func (*AuthenticateRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{59} } +func (*AuthenticateRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{61} } func (m *AuthenticateRequest) GetName() string { if m != nil { @@ -2688,7 +2757,7 @@ type AuthUserAddRequest struct { func (m *AuthUserAddRequest) Reset() { *m = AuthUserAddRequest{} } func (m *AuthUserAddRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserAddRequest) ProtoMessage() {} -func (*AuthUserAddRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{60} } +func (*AuthUserAddRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{62} } func (m *AuthUserAddRequest) GetName() string { if m != nil { @@ -2711,7 +2780,7 @@ type AuthUserGetRequest struct { func (m *AuthUserGetRequest) Reset() { *m = AuthUserGetRequest{} } func (m *AuthUserGetRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserGetRequest) ProtoMessage() {} -func (*AuthUserGetRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{61} } +func (*AuthUserGetRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{63} } func (m *AuthUserGetRequest) GetName() string { if m != nil { @@ -2728,7 +2797,7 @@ type AuthUserDeleteRequest struct { func (m *AuthUserDeleteRequest) Reset() { *m = AuthUserDeleteRequest{} } func (m *AuthUserDeleteRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserDeleteRequest) ProtoMessage() {} -func (*AuthUserDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{62} } +func (*AuthUserDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{64} } func (m *AuthUserDeleteRequest) GetName() string { if m != nil { @@ -2748,7 +2817,7 @@ func (m *AuthUserChangePasswordRequest) Reset() { *m = AuthUserChangePas func (m *AuthUserChangePasswordRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserChangePasswordRequest) ProtoMessage() {} func (*AuthUserChangePasswordRequest) Descriptor() ([]byte, []int) { - return fileDescriptorRpc, []int{63} + return fileDescriptorRpc, []int{65} } func (m *AuthUserChangePasswordRequest) GetName() string { @@ -2775,7 +2844,7 @@ type AuthUserGrantRoleRequest struct { func (m *AuthUserGrantRoleRequest) Reset() { *m = AuthUserGrantRoleRequest{} } func (m *AuthUserGrantRoleRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserGrantRoleRequest) ProtoMessage() {} -func (*AuthUserGrantRoleRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{64} } +func (*AuthUserGrantRoleRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{66} } func (m *AuthUserGrantRoleRequest) GetUser() string { if m != nil { @@ -2799,7 +2868,7 @@ type AuthUserRevokeRoleRequest struct { func (m *AuthUserRevokeRoleRequest) Reset() { *m = AuthUserRevokeRoleRequest{} } func (m *AuthUserRevokeRoleRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserRevokeRoleRequest) ProtoMessage() {} -func (*AuthUserRevokeRoleRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{65} } +func (*AuthUserRevokeRoleRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{67} } func (m *AuthUserRevokeRoleRequest) GetName() string { if m != nil { @@ -2823,7 +2892,7 @@ type AuthRoleAddRequest struct { func (m *AuthRoleAddRequest) Reset() { *m = AuthRoleAddRequest{} } func (m *AuthRoleAddRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleAddRequest) ProtoMessage() {} -func (*AuthRoleAddRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{66} } +func (*AuthRoleAddRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{68} } func (m *AuthRoleAddRequest) GetName() string { if m != nil { @@ -2839,7 +2908,7 @@ type AuthRoleGetRequest struct { func (m *AuthRoleGetRequest) Reset() { *m = AuthRoleGetRequest{} } func (m *AuthRoleGetRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleGetRequest) ProtoMessage() {} -func (*AuthRoleGetRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{67} } +func (*AuthRoleGetRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{69} } func (m *AuthRoleGetRequest) GetRole() string { if m != nil { @@ -2854,7 +2923,7 @@ type AuthUserListRequest struct { func (m *AuthUserListRequest) Reset() { *m = AuthUserListRequest{} } func (m *AuthUserListRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserListRequest) ProtoMessage() {} -func (*AuthUserListRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{68} } +func (*AuthUserListRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{70} } type AuthRoleListRequest struct { } @@ -2862,7 +2931,7 @@ type AuthRoleListRequest struct { func (m *AuthRoleListRequest) Reset() { *m = AuthRoleListRequest{} } func (m *AuthRoleListRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleListRequest) ProtoMessage() {} -func (*AuthRoleListRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{69} } +func (*AuthRoleListRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{71} } type AuthRoleDeleteRequest struct { Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` @@ -2871,7 +2940,7 @@ type AuthRoleDeleteRequest struct { func (m *AuthRoleDeleteRequest) Reset() { *m = AuthRoleDeleteRequest{} } func (m *AuthRoleDeleteRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleDeleteRequest) ProtoMessage() {} -func (*AuthRoleDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{70} } +func (*AuthRoleDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{72} } func (m *AuthRoleDeleteRequest) GetRole() string { if m != nil { @@ -2891,7 +2960,7 @@ func (m *AuthRoleGrantPermissionRequest) Reset() { *m = AuthRoleGrantPer func (m *AuthRoleGrantPermissionRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleGrantPermissionRequest) ProtoMessage() {} func (*AuthRoleGrantPermissionRequest) Descriptor() ([]byte, []int) { - return fileDescriptorRpc, []int{71} + return fileDescriptorRpc, []int{73} } func (m *AuthRoleGrantPermissionRequest) GetName() string { @@ -2918,7 +2987,7 @@ func (m *AuthRoleRevokePermissionRequest) Reset() { *m = AuthRoleRevokeP func (m *AuthRoleRevokePermissionRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleRevokePermissionRequest) ProtoMessage() {} func (*AuthRoleRevokePermissionRequest) Descriptor() ([]byte, []int) { - return fileDescriptorRpc, []int{72} + return fileDescriptorRpc, []int{74} } func (m *AuthRoleRevokePermissionRequest) GetRole() string { @@ -2949,7 +3018,7 @@ type AuthEnableResponse struct { func (m *AuthEnableResponse) Reset() { *m = AuthEnableResponse{} } func (m *AuthEnableResponse) String() string { return proto.CompactTextString(m) } func (*AuthEnableResponse) ProtoMessage() {} -func (*AuthEnableResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{73} } +func (*AuthEnableResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{75} } func (m *AuthEnableResponse) GetHeader() *ResponseHeader { if m != nil { @@ -2965,7 +3034,7 @@ type AuthDisableResponse struct { func (m *AuthDisableResponse) Reset() { *m = AuthDisableResponse{} } func (m *AuthDisableResponse) String() string { return proto.CompactTextString(m) } func (*AuthDisableResponse) ProtoMessage() {} -func (*AuthDisableResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{74} } +func (*AuthDisableResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{76} } func (m *AuthDisableResponse) GetHeader() *ResponseHeader { if m != nil { @@ -2983,7 +3052,7 @@ type AuthenticateResponse struct { func (m *AuthenticateResponse) Reset() { *m = AuthenticateResponse{} } func (m *AuthenticateResponse) String() string { return proto.CompactTextString(m) } func (*AuthenticateResponse) ProtoMessage() {} -func (*AuthenticateResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{75} } +func (*AuthenticateResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{77} } func (m *AuthenticateResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3006,7 +3075,7 @@ type AuthUserAddResponse struct { func (m *AuthUserAddResponse) Reset() { *m = AuthUserAddResponse{} } func (m *AuthUserAddResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserAddResponse) ProtoMessage() {} -func (*AuthUserAddResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{76} } +func (*AuthUserAddResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{78} } func (m *AuthUserAddResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3023,7 +3092,7 @@ type AuthUserGetResponse struct { func (m *AuthUserGetResponse) Reset() { *m = AuthUserGetResponse{} } func (m *AuthUserGetResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserGetResponse) ProtoMessage() {} -func (*AuthUserGetResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{77} } +func (*AuthUserGetResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{79} } func (m *AuthUserGetResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3046,7 +3115,7 @@ type AuthUserDeleteResponse struct { func (m *AuthUserDeleteResponse) Reset() { *m = AuthUserDeleteResponse{} } func (m *AuthUserDeleteResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserDeleteResponse) ProtoMessage() {} -func (*AuthUserDeleteResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{78} } +func (*AuthUserDeleteResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{80} } func (m *AuthUserDeleteResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3063,7 +3132,7 @@ func (m *AuthUserChangePasswordResponse) Reset() { *m = AuthUserChangePa func (m *AuthUserChangePasswordResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserChangePasswordResponse) ProtoMessage() {} func (*AuthUserChangePasswordResponse) Descriptor() ([]byte, []int) { - return fileDescriptorRpc, []int{79} + return fileDescriptorRpc, []int{81} } func (m *AuthUserChangePasswordResponse) GetHeader() *ResponseHeader { @@ -3080,7 +3149,7 @@ type AuthUserGrantRoleResponse struct { func (m *AuthUserGrantRoleResponse) Reset() { *m = AuthUserGrantRoleResponse{} } func (m *AuthUserGrantRoleResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserGrantRoleResponse) ProtoMessage() {} -func (*AuthUserGrantRoleResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{80} } +func (*AuthUserGrantRoleResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{82} } func (m *AuthUserGrantRoleResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3096,7 +3165,7 @@ type AuthUserRevokeRoleResponse struct { func (m *AuthUserRevokeRoleResponse) Reset() { *m = AuthUserRevokeRoleResponse{} } func (m *AuthUserRevokeRoleResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserRevokeRoleResponse) ProtoMessage() {} -func (*AuthUserRevokeRoleResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{81} } +func (*AuthUserRevokeRoleResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{83} } func (m *AuthUserRevokeRoleResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3112,7 +3181,7 @@ type AuthRoleAddResponse struct { func (m *AuthRoleAddResponse) Reset() { *m = AuthRoleAddResponse{} } func (m *AuthRoleAddResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleAddResponse) ProtoMessage() {} -func (*AuthRoleAddResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{82} } +func (*AuthRoleAddResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{84} } func (m *AuthRoleAddResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3129,7 +3198,7 @@ type AuthRoleGetResponse struct { func (m *AuthRoleGetResponse) Reset() { *m = AuthRoleGetResponse{} } func (m *AuthRoleGetResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleGetResponse) ProtoMessage() {} -func (*AuthRoleGetResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{83} } +func (*AuthRoleGetResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{85} } func (m *AuthRoleGetResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3153,7 +3222,7 @@ type AuthRoleListResponse struct { func (m *AuthRoleListResponse) Reset() { *m = AuthRoleListResponse{} } func (m *AuthRoleListResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleListResponse) ProtoMessage() {} -func (*AuthRoleListResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{84} } +func (*AuthRoleListResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{86} } func (m *AuthRoleListResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3177,7 +3246,7 @@ type AuthUserListResponse struct { func (m *AuthUserListResponse) Reset() { *m = AuthUserListResponse{} } func (m *AuthUserListResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserListResponse) ProtoMessage() {} -func (*AuthUserListResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{85} } +func (*AuthUserListResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{87} } func (m *AuthUserListResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3200,7 +3269,7 @@ type AuthRoleDeleteResponse struct { func (m *AuthRoleDeleteResponse) Reset() { *m = AuthRoleDeleteResponse{} } func (m *AuthRoleDeleteResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleDeleteResponse) ProtoMessage() {} -func (*AuthRoleDeleteResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{86} } +func (*AuthRoleDeleteResponse) Descriptor() ([]byte, []int) { return fileDescriptorRpc, []int{88} } func (m *AuthRoleDeleteResponse) GetHeader() *ResponseHeader { if m != nil { @@ -3217,7 +3286,7 @@ func (m *AuthRoleGrantPermissionResponse) Reset() { *m = AuthRoleGrantPe func (m *AuthRoleGrantPermissionResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleGrantPermissionResponse) ProtoMessage() {} func (*AuthRoleGrantPermissionResponse) Descriptor() ([]byte, []int) { - return fileDescriptorRpc, []int{87} + return fileDescriptorRpc, []int{89} } func (m *AuthRoleGrantPermissionResponse) GetHeader() *ResponseHeader { @@ -3235,7 +3304,7 @@ func (m *AuthRoleRevokePermissionResponse) Reset() { *m = AuthRoleRevoke func (m *AuthRoleRevokePermissionResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleRevokePermissionResponse) ProtoMessage() {} func (*AuthRoleRevokePermissionResponse) Descriptor() ([]byte, []int) { - return fileDescriptorRpc, []int{88} + return fileDescriptorRpc, []int{90} } func (m *AuthRoleRevokePermissionResponse) GetHeader() *ResponseHeader { @@ -3294,6 +3363,8 @@ func init() { proto.RegisterType((*MemberUpdateResponse)(nil), "etcdserverpb.MemberUpdateResponse") proto.RegisterType((*MemberListRequest)(nil), "etcdserverpb.MemberListRequest") proto.RegisterType((*MemberListResponse)(nil), "etcdserverpb.MemberListResponse") + proto.RegisterType((*MemberPromoteRequest)(nil), "etcdserverpb.MemberPromoteRequest") + proto.RegisterType((*MemberPromoteResponse)(nil), "etcdserverpb.MemberPromoteResponse") proto.RegisterType((*DefragmentRequest)(nil), "etcdserverpb.DefragmentRequest") proto.RegisterType((*DefragmentResponse)(nil), "etcdserverpb.DefragmentResponse") proto.RegisterType((*MoveLeaderRequest)(nil), "etcdserverpb.MoveLeaderRequest") @@ -3938,6 +4009,8 @@ type ClusterClient interface { MemberUpdate(ctx context.Context, in *MemberUpdateRequest, opts ...grpc.CallOption) (*MemberUpdateResponse, error) // MemberList lists all the members in the cluster. MemberList(ctx context.Context, in *MemberListRequest, opts ...grpc.CallOption) (*MemberListResponse, error) + // MemberPromote promotes a member from raft learner (non-voting) to raft voting member. + MemberPromote(ctx context.Context, in *MemberPromoteRequest, opts ...grpc.CallOption) (*MemberPromoteResponse, error) } type clusterClient struct { @@ -3984,6 +4057,15 @@ func (c *clusterClient) MemberList(ctx context.Context, in *MemberListRequest, o return out, nil } +func (c *clusterClient) MemberPromote(ctx context.Context, in *MemberPromoteRequest, opts ...grpc.CallOption) (*MemberPromoteResponse, error) { + out := new(MemberPromoteResponse) + err := grpc.Invoke(ctx, "/etcdserverpb.Cluster/MemberPromote", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Cluster service type ClusterServer interface { @@ -3995,6 +4077,8 @@ type ClusterServer interface { MemberUpdate(context.Context, *MemberUpdateRequest) (*MemberUpdateResponse, error) // MemberList lists all the members in the cluster. MemberList(context.Context, *MemberListRequest) (*MemberListResponse, error) + // MemberPromote promotes a member from raft learner (non-voting) to raft voting member. + MemberPromote(context.Context, *MemberPromoteRequest) (*MemberPromoteResponse, error) } func RegisterClusterServer(s *grpc.Server, srv ClusterServer) { @@ -4073,6 +4157,24 @@ func _Cluster_MemberList_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Cluster_MemberPromote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MemberPromoteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClusterServer).MemberPromote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/etcdserverpb.Cluster/MemberPromote", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClusterServer).MemberPromote(ctx, req.(*MemberPromoteRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Cluster_serviceDesc = grpc.ServiceDesc{ ServiceName: "etcdserverpb.Cluster", HandlerType: (*ClusterServer)(nil), @@ -4093,6 +4195,10 @@ var _Cluster_serviceDesc = grpc.ServiceDesc{ MethodName: "MemberList", Handler: _Cluster_MemberList_Handler, }, + { + MethodName: "MemberPromote", + Handler: _Cluster_MemberPromote_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "rpc.proto", @@ -6741,6 +6847,16 @@ func (m *Member) MarshalTo(dAtA []byte) (int, error) { i += copy(dAtA[i:], s) } } + if m.IsLearner { + dAtA[i] = 0x28 + i++ + if m.IsLearner { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -6774,6 +6890,16 @@ func (m *MemberAddRequest) MarshalTo(dAtA []byte) (int, error) { i += copy(dAtA[i:], s) } } + if m.IsLearner { + dAtA[i] = 0x10 + i++ + if m.IsLearner { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -7026,6 +7152,69 @@ func (m *MemberListResponse) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *MemberPromoteRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MemberPromoteRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.ID != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintRpc(dAtA, i, uint64(m.ID)) + } + return i, nil +} + +func (m *MemberPromoteResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MemberPromoteResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Header != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) + n39, err := m.Header.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n39 + } + if len(m.Members) > 0 { + for _, msg := range m.Members { + dAtA[i] = 0x12 + i++ + i = encodeVarintRpc(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *DefragmentRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -7063,11 +7252,11 @@ func (m *DefragmentResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n39, err := m.Header.MarshalTo(dAtA[i:]) + n40, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n39 + i += n40 } return i, nil } @@ -7114,11 +7303,11 @@ func (m *MoveLeaderResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n40, err := m.Header.MarshalTo(dAtA[i:]) + n41, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n40 + i += n41 } return i, nil } @@ -7203,11 +7392,11 @@ func (m *AlarmResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n41, err := m.Header.MarshalTo(dAtA[i:]) + n42, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n41 + i += n42 } if len(m.Alarms) > 0 { for _, msg := range m.Alarms { @@ -7261,11 +7450,11 @@ func (m *StatusResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n42, err := m.Header.MarshalTo(dAtA[i:]) + n43, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n42 + i += n43 } if len(m.Version) > 0 { dAtA[i] = 0x12 @@ -7318,6 +7507,16 @@ func (m *StatusResponse) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintRpc(dAtA, i, uint64(m.DbSizeInUse)) } + if m.IsLearner { + dAtA[i] = 0x50 + i++ + if m.IsLearner { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -7688,11 +7887,11 @@ func (m *AuthRoleGrantPermissionRequest) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintRpc(dAtA, i, uint64(m.Perm.Size())) - n43, err := m.Perm.MarshalTo(dAtA[i:]) + n44, err := m.Perm.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n43 + i += n44 } return i, nil } @@ -7752,11 +7951,11 @@ func (m *AuthEnableResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n44, err := m.Header.MarshalTo(dAtA[i:]) + n45, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n44 + i += n45 } return i, nil } @@ -7780,11 +7979,11 @@ func (m *AuthDisableResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n45, err := m.Header.MarshalTo(dAtA[i:]) + n46, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n45 + i += n46 } return i, nil } @@ -7808,11 +8007,11 @@ func (m *AuthenticateResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n46, err := m.Header.MarshalTo(dAtA[i:]) + n47, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n46 + i += n47 } if len(m.Token) > 0 { dAtA[i] = 0x12 @@ -7842,11 +8041,11 @@ func (m *AuthUserAddResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n47, err := m.Header.MarshalTo(dAtA[i:]) + n48, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n47 + i += n48 } return i, nil } @@ -7870,11 +8069,11 @@ func (m *AuthUserGetResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n48, err := m.Header.MarshalTo(dAtA[i:]) + n49, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n48 + i += n49 } if len(m.Roles) > 0 { for _, s := range m.Roles { @@ -7913,11 +8112,11 @@ func (m *AuthUserDeleteResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n49, err := m.Header.MarshalTo(dAtA[i:]) + n50, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n49 + i += n50 } return i, nil } @@ -7941,11 +8140,11 @@ func (m *AuthUserChangePasswordResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n50, err := m.Header.MarshalTo(dAtA[i:]) + n51, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n50 + i += n51 } return i, nil } @@ -7969,11 +8168,11 @@ func (m *AuthUserGrantRoleResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n51, err := m.Header.MarshalTo(dAtA[i:]) + n52, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n51 + i += n52 } return i, nil } @@ -7997,11 +8196,11 @@ func (m *AuthUserRevokeRoleResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n52, err := m.Header.MarshalTo(dAtA[i:]) + n53, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n52 + i += n53 } return i, nil } @@ -8025,11 +8224,11 @@ func (m *AuthRoleAddResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n53, err := m.Header.MarshalTo(dAtA[i:]) + n54, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n53 + i += n54 } return i, nil } @@ -8053,11 +8252,11 @@ func (m *AuthRoleGetResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n54, err := m.Header.MarshalTo(dAtA[i:]) + n55, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n54 + i += n55 } if len(m.Perm) > 0 { for _, msg := range m.Perm { @@ -8093,11 +8292,11 @@ func (m *AuthRoleListResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n55, err := m.Header.MarshalTo(dAtA[i:]) + n56, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n55 + i += n56 } if len(m.Roles) > 0 { for _, s := range m.Roles { @@ -8136,11 +8335,11 @@ func (m *AuthUserListResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n56, err := m.Header.MarshalTo(dAtA[i:]) + n57, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n56 + i += n57 } if len(m.Users) > 0 { for _, s := range m.Users { @@ -8179,11 +8378,11 @@ func (m *AuthRoleDeleteResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n57, err := m.Header.MarshalTo(dAtA[i:]) + n58, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n57 + i += n58 } return i, nil } @@ -8207,11 +8406,11 @@ func (m *AuthRoleGrantPermissionResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n58, err := m.Header.MarshalTo(dAtA[i:]) + n59, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n58 + i += n59 } return i, nil } @@ -8235,11 +8434,11 @@ func (m *AuthRoleRevokePermissionResponse) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintRpc(dAtA, i, uint64(m.Header.Size())) - n59, err := m.Header.MarshalTo(dAtA[i:]) + n60, err := m.Header.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n59 + i += n60 } return i, nil } @@ -9016,6 +9215,9 @@ func (m *Member) Size() (n int) { n += 1 + l + sovRpc(uint64(l)) } } + if m.IsLearner { + n += 2 + } return n } @@ -9028,6 +9230,9 @@ func (m *MemberAddRequest) Size() (n int) { n += 1 + l + sovRpc(uint64(l)) } } + if m.IsLearner { + n += 2 + } return n } @@ -9129,6 +9334,31 @@ func (m *MemberListResponse) Size() (n int) { return n } +func (m *MemberPromoteRequest) Size() (n int) { + var l int + _ = l + if m.ID != 0 { + n += 1 + sovRpc(uint64(m.ID)) + } + return n +} + +func (m *MemberPromoteResponse) Size() (n int) { + var l int + _ = l + if m.Header != nil { + l = m.Header.Size() + n += 1 + l + sovRpc(uint64(l)) + } + if len(m.Members) > 0 { + for _, e := range m.Members { + l = e.Size() + n += 1 + l + sovRpc(uint64(l)) + } + } + return n +} + func (m *DefragmentRequest) Size() (n int) { var l int _ = l @@ -9248,6 +9478,9 @@ func (m *StatusResponse) Size() (n int) { if m.DbSizeInUse != 0 { n += 1 + sovRpc(uint64(m.DbSizeInUse)) } + if m.IsLearner { + n += 2 + } return n } @@ -14629,6 +14862,26 @@ func (m *Member) Unmarshal(dAtA []byte) error { } m.ClientURLs = append(m.ClientURLs, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsLearner", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.IsLearner = bool(v != 0) default: iNdEx = preIndex skippy, err := skipRpc(dAtA[iNdEx:]) @@ -14708,6 +14961,26 @@ func (m *MemberAddRequest) Unmarshal(dAtA []byte) error { } m.PeerURLs = append(m.PeerURLs, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsLearner", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.IsLearner = bool(v != 0) default: iNdEx = preIndex skippy, err := skipRpc(dAtA[iNdEx:]) @@ -15435,6 +15708,189 @@ func (m *MemberListResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MemberPromoteRequest) 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 ErrIntOverflowRpc + } + 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: MemberPromoteRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MemberPromoteRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) + } + m.ID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ID |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipRpc(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRpc + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MemberPromoteResponse) 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 ErrIntOverflowRpc + } + 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: MemberPromoteResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MemberPromoteResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Header", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRpc + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Header == nil { + m.Header = &ResponseHeader{} + } + if err := m.Header.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Members", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRpc + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Members = append(m.Members, &Member{}) + if err := m.Members[len(m.Members)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRpc(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRpc + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DefragmentRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -16313,6 +16769,26 @@ func (m *StatusResponse) Unmarshal(dAtA []byte) error { break } } + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsLearner", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.IsLearner = bool(v != 0) default: iNdEx = preIndex skippy, err := skipRpc(dAtA[iNdEx:]) @@ -19305,245 +19781,250 @@ var ( func init() { proto.RegisterFile("rpc.proto", fileDescriptorRpc) } var fileDescriptorRpc = []byte{ - // 3836 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5b, 0xdd, 0x6f, 0x23, 0xc9, - 0x71, 0xd7, 0x90, 0xe2, 0x57, 0xf1, 0x43, 0x54, 0xeb, 0x63, 0x29, 0xee, 0xae, 0x56, 0xd7, 0xbb, - 0x7b, 0xab, 0xdb, 0xbd, 0x13, 0x6d, 0xd9, 0x4e, 0x80, 0x4d, 0xe2, 0x58, 0x2b, 0xf1, 0x56, 0x3a, - 0x69, 0x45, 0xdd, 0x88, 0xda, 0xfb, 0x80, 0x11, 0x61, 0x44, 0xf6, 0x4a, 0x13, 0x91, 0x33, 0xf4, - 0xcc, 0x90, 0x2b, 0x5d, 0x82, 0x38, 0x30, 0x9c, 0x00, 0xc9, 0xa3, 0x0d, 0x04, 0xc9, 0x43, 0x9e, - 0x82, 0x20, 0xf0, 0x43, 0x80, 0xbc, 0x05, 0xc8, 0x5f, 0x90, 0xb7, 0x24, 0xc8, 0x3f, 0x10, 0x5c, - 0xfc, 0x92, 0xff, 0x22, 0xe8, 0xaf, 0x99, 0x9e, 0x2f, 0x69, 0x6d, 0xfa, 0xfc, 0x22, 0x4d, 0x57, - 0x57, 0x57, 0x55, 0x57, 0x77, 0x57, 0x55, 0xff, 0x66, 0x08, 0x25, 0x67, 0xd4, 0xdb, 0x18, 0x39, - 0xb6, 0x67, 0xa3, 0x0a, 0xf1, 0x7a, 0x7d, 0x97, 0x38, 0x13, 0xe2, 0x8c, 0xce, 0x9a, 0x8b, 0xe7, - 0xf6, 0xb9, 0xcd, 0x3a, 0x5a, 0xf4, 0x89, 0xf3, 0x34, 0x57, 0x28, 0x4f, 0x6b, 0x38, 0xe9, 0xf5, - 0xd8, 0x9f, 0xd1, 0x59, 0xeb, 0x72, 0x22, 0xba, 0xee, 0xb2, 0x2e, 0x63, 0xec, 0x5d, 0xb0, 0x3f, - 0xa3, 0x33, 0xf6, 0x4f, 0x74, 0xde, 0x3b, 0xb7, 0xed, 0xf3, 0x01, 0x69, 0x19, 0x23, 0xb3, 0x65, - 0x58, 0x96, 0xed, 0x19, 0x9e, 0x69, 0x5b, 0x2e, 0xef, 0xc5, 0x7f, 0xa1, 0x41, 0x4d, 0x27, 0xee, - 0xc8, 0xb6, 0x5c, 0xb2, 0x4b, 0x8c, 0x3e, 0x71, 0xd0, 0x7d, 0x80, 0xde, 0x60, 0xec, 0x7a, 0xc4, - 0x39, 0x35, 0xfb, 0x0d, 0x6d, 0x4d, 0x5b, 0x9f, 0xd5, 0x4b, 0x82, 0xb2, 0xd7, 0x47, 0x77, 0xa1, - 0x34, 0x24, 0xc3, 0x33, 0xde, 0x9b, 0x61, 0xbd, 0x45, 0x4e, 0xd8, 0xeb, 0xa3, 0x26, 0x14, 0x1d, - 0x32, 0x31, 0x5d, 0xd3, 0xb6, 0x1a, 0xd9, 0x35, 0x6d, 0x3d, 0xab, 0xfb, 0x6d, 0x3a, 0xd0, 0x31, - 0xde, 0x78, 0xa7, 0x1e, 0x71, 0x86, 0x8d, 0x59, 0x3e, 0x90, 0x12, 0xba, 0xc4, 0x19, 0xe2, 0x9f, + // 3907 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5b, 0x5b, 0x6f, 0x23, 0xc9, + 0x75, 0x56, 0x93, 0xe2, 0xed, 0xf0, 0x22, 0xaa, 0x24, 0xcd, 0x70, 0x38, 0x33, 0x1a, 0x6d, 0xcd, + 0xce, 0xae, 0x76, 0x66, 0x57, 0xb4, 0x65, 0x3b, 0x01, 0x26, 0x89, 0x63, 0x8d, 0xc4, 0x9d, 0xd1, + 0x4a, 0x23, 0x6a, 0x5b, 0xd4, 0xec, 0x05, 0x46, 0x84, 0x16, 0x59, 0x92, 0x3a, 0x22, 0xbb, 0xe9, + 0xee, 0x26, 0x47, 0xda, 0x5c, 0x1c, 0x18, 0x8e, 0x81, 0xe4, 0xd1, 0x06, 0x82, 0xe4, 0x21, 0x4f, + 0x41, 0x10, 0xf8, 0x21, 0xcf, 0x01, 0xf2, 0x0b, 0xf2, 0x94, 0x0b, 0xf2, 0x07, 0x82, 0x8d, 0x5f, + 0x92, 0x5f, 0x61, 0xd4, 0xad, 0xbb, 0xfa, 0x46, 0x8d, 0xcd, 0xdd, 0x7d, 0x91, 0xba, 0x4e, 0x9d, + 0x3a, 0xe7, 0xd4, 0xa9, 0xaa, 0x73, 0x4e, 0x7f, 0x5d, 0x84, 0x92, 0x33, 0xea, 0x6d, 0x8c, 0x1c, + 0xdb, 0xb3, 0x51, 0x85, 0x78, 0xbd, 0xbe, 0x4b, 0x9c, 0x09, 0x71, 0x46, 0xa7, 0xcd, 0xe5, 0x73, + 0xfb, 0xdc, 0x66, 0x1d, 0x2d, 0xfa, 0xc4, 0x79, 0x9a, 0x77, 0x28, 0x4f, 0x6b, 0x38, 0xe9, 0xf5, + 0xd8, 0x9f, 0xd1, 0x69, 0xeb, 0x72, 0x22, 0xba, 0xee, 0xb2, 0x2e, 0x63, 0xec, 0x5d, 0xb0, 0x3f, + 0xa3, 0x53, 0xf6, 0x4f, 0x74, 0xde, 0x3b, 0xb7, 0xed, 0xf3, 0x01, 0x69, 0x19, 0x23, 0xb3, 0x65, + 0x58, 0x96, 0xed, 0x19, 0x9e, 0x69, 0x5b, 0x2e, 0xef, 0xc5, 0x7f, 0xa9, 0x41, 0x4d, 0x27, 0xee, + 0xc8, 0xb6, 0x5c, 0xf2, 0x82, 0x18, 0x7d, 0xe2, 0xa0, 0xfb, 0x00, 0xbd, 0xc1, 0xd8, 0xf5, 0x88, + 0x73, 0x62, 0xf6, 0x1b, 0xda, 0x9a, 0xb6, 0x3e, 0xaf, 0x97, 0x04, 0x65, 0xb7, 0x8f, 0xee, 0x42, + 0x69, 0x48, 0x86, 0xa7, 0xbc, 0x37, 0xc3, 0x7a, 0x8b, 0x9c, 0xb0, 0xdb, 0x47, 0x4d, 0x28, 0x3a, + 0x64, 0x62, 0xba, 0xa6, 0x6d, 0x35, 0xb2, 0x6b, 0xda, 0x7a, 0x56, 0xf7, 0xdb, 0x74, 0xa0, 0x63, + 0x9c, 0x79, 0x27, 0x1e, 0x71, 0x86, 0x8d, 0x79, 0x3e, 0x90, 0x12, 0xba, 0xc4, 0x19, 0xe2, 0x9f, 0xe6, 0xa0, 0xa2, 0x1b, 0xd6, 0x39, 0xd1, 0xc9, 0x8f, 0xc6, 0xc4, 0xf5, 0x50, 0x1d, 0xb2, 0x97, - 0xe4, 0x9a, 0xa9, 0xaf, 0xe8, 0xf4, 0x91, 0x8f, 0xb7, 0xce, 0xc9, 0x29, 0xb1, 0xb8, 0xe2, 0x0a, - 0x1d, 0x6f, 0x9d, 0x93, 0xb6, 0xd5, 0x47, 0x8b, 0x90, 0x1b, 0x98, 0x43, 0xd3, 0x13, 0x5a, 0x79, - 0x23, 0x64, 0xce, 0x6c, 0xc4, 0x9c, 0x6d, 0x00, 0xd7, 0x76, 0xbc, 0x53, 0xdb, 0xe9, 0x13, 0xa7, - 0x91, 0x5b, 0xd3, 0xd6, 0x6b, 0x9b, 0x8f, 0x36, 0xd4, 0x85, 0xd8, 0x50, 0x0d, 0xda, 0x38, 0xb6, - 0x1d, 0xaf, 0x43, 0x79, 0xf5, 0x92, 0x2b, 0x1f, 0xd1, 0xc7, 0x50, 0x66, 0x42, 0x3c, 0xc3, 0x39, - 0x27, 0x5e, 0x23, 0xcf, 0xa4, 0x3c, 0xbe, 0x45, 0x4a, 0x97, 0x31, 0xeb, 0x4c, 0x3d, 0x7f, 0x46, - 0x18, 0x2a, 0x2e, 0x71, 0x4c, 0x63, 0x60, 0x7e, 0x65, 0x9c, 0x0d, 0x48, 0xa3, 0xb0, 0xa6, 0xad, - 0x17, 0xf5, 0x10, 0x8d, 0xce, 0xff, 0x92, 0x5c, 0xbb, 0xa7, 0xb6, 0x35, 0xb8, 0x6e, 0x14, 0x19, - 0x43, 0x91, 0x12, 0x3a, 0xd6, 0xe0, 0x9a, 0x2d, 0x9a, 0x3d, 0xb6, 0x3c, 0xde, 0x5b, 0x62, 0xbd, - 0x25, 0x46, 0x61, 0xdd, 0xeb, 0x50, 0x1f, 0x9a, 0xd6, 0xe9, 0xd0, 0xee, 0x9f, 0xfa, 0x0e, 0x01, - 0xe6, 0x90, 0xda, 0xd0, 0xb4, 0x5e, 0xd9, 0x7d, 0x5d, 0xba, 0x85, 0x72, 0x1a, 0x57, 0x61, 0xce, - 0xb2, 0xe0, 0x34, 0xae, 0x54, 0xce, 0x0d, 0x58, 0xa0, 0x32, 0x7b, 0x0e, 0x31, 0x3c, 0x12, 0x30, - 0x57, 0x18, 0xf3, 0xfc, 0xd0, 0xb4, 0xb6, 0x59, 0x4f, 0x88, 0xdf, 0xb8, 0x8a, 0xf1, 0x57, 0x05, - 0xbf, 0x71, 0x15, 0xe6, 0xc7, 0x1b, 0x50, 0xf2, 0x7d, 0x8e, 0x8a, 0x30, 0x7b, 0xd8, 0x39, 0x6c, - 0xd7, 0x67, 0x10, 0x40, 0x7e, 0xeb, 0x78, 0xbb, 0x7d, 0xb8, 0x53, 0xd7, 0x50, 0x19, 0x0a, 0x3b, - 0x6d, 0xde, 0xc8, 0xe0, 0x17, 0x00, 0x81, 0x77, 0x51, 0x01, 0xb2, 0xfb, 0xed, 0x2f, 0xea, 0x33, - 0x94, 0xe7, 0x75, 0x5b, 0x3f, 0xde, 0xeb, 0x1c, 0xd6, 0x35, 0x3a, 0x78, 0x5b, 0x6f, 0x6f, 0x75, - 0xdb, 0xf5, 0x0c, 0xe5, 0x78, 0xd5, 0xd9, 0xa9, 0x67, 0x51, 0x09, 0x72, 0xaf, 0xb7, 0x0e, 0x4e, - 0xda, 0xf5, 0x59, 0xfc, 0x73, 0x0d, 0xaa, 0x62, 0xbd, 0xf8, 0x99, 0x40, 0xdf, 0x85, 0xfc, 0x05, - 0x3b, 0x17, 0x6c, 0x2b, 0x96, 0x37, 0xef, 0x45, 0x16, 0x37, 0x74, 0x76, 0x74, 0xc1, 0x8b, 0x30, - 0x64, 0x2f, 0x27, 0x6e, 0x23, 0xb3, 0x96, 0x5d, 0x2f, 0x6f, 0xd6, 0x37, 0xf8, 0x81, 0xdd, 0xd8, - 0x27, 0xd7, 0xaf, 0x8d, 0xc1, 0x98, 0xe8, 0xb4, 0x13, 0x21, 0x98, 0x1d, 0xda, 0x0e, 0x61, 0x3b, - 0xb6, 0xa8, 0xb3, 0x67, 0xba, 0x8d, 0xd9, 0xa2, 0x89, 0xdd, 0xca, 0x1b, 0xf8, 0x17, 0x1a, 0xc0, - 0xd1, 0xd8, 0x4b, 0x3f, 0x1a, 0x8b, 0x90, 0x9b, 0x50, 0xc1, 0xe2, 0x58, 0xf0, 0x06, 0x3b, 0x13, - 0xc4, 0x70, 0x89, 0x7f, 0x26, 0x68, 0x03, 0xdd, 0x81, 0xc2, 0xc8, 0x21, 0x93, 0xd3, 0xcb, 0x09, - 0x53, 0x52, 0xd4, 0xf3, 0xb4, 0xb9, 0x3f, 0x41, 0xef, 0x41, 0xc5, 0x3c, 0xb7, 0x6c, 0x87, 0x9c, - 0x72, 0x59, 0x39, 0xd6, 0x5b, 0xe6, 0x34, 0x66, 0xb7, 0xc2, 0xc2, 0x05, 0xe7, 0x55, 0x96, 0x03, - 0x4a, 0xc2, 0x16, 0x94, 0x99, 0xa9, 0x53, 0xb9, 0xef, 0x83, 0xc0, 0xc6, 0x0c, 0x1b, 0x16, 0x77, - 0xa1, 0xb0, 0x1a, 0xff, 0x10, 0xd0, 0x0e, 0x19, 0x10, 0x8f, 0x4c, 0x13, 0x3d, 0x14, 0x9f, 0x64, - 0x55, 0x9f, 0xe0, 0x9f, 0x69, 0xb0, 0x10, 0x12, 0x3f, 0xd5, 0xb4, 0x1a, 0x50, 0xe8, 0x33, 0x61, - 0xdc, 0x82, 0xac, 0x2e, 0x9b, 0xe8, 0x19, 0x14, 0x85, 0x01, 0x6e, 0x23, 0x9b, 0xb2, 0x69, 0x0a, - 0xdc, 0x26, 0x17, 0xff, 0x22, 0x03, 0x25, 0x31, 0xd1, 0xce, 0x08, 0x6d, 0x41, 0xd5, 0xe1, 0x8d, - 0x53, 0x36, 0x1f, 0x61, 0x51, 0x33, 0x3d, 0x08, 0xed, 0xce, 0xe8, 0x15, 0x31, 0x84, 0x91, 0xd1, - 0xef, 0x41, 0x59, 0x8a, 0x18, 0x8d, 0x3d, 0xe1, 0xf2, 0x46, 0x58, 0x40, 0xb0, 0xff, 0x76, 0x67, - 0x74, 0x10, 0xec, 0x47, 0x63, 0x0f, 0x75, 0x61, 0x51, 0x0e, 0xe6, 0xb3, 0x11, 0x66, 0x64, 0x99, - 0x94, 0xb5, 0xb0, 0x94, 0xf8, 0x52, 0xed, 0xce, 0xe8, 0x48, 0x8c, 0x57, 0x3a, 0x55, 0x93, 0xbc, - 0x2b, 0x1e, 0xbc, 0x63, 0x26, 0x75, 0xaf, 0xac, 0xb8, 0x49, 0xdd, 0x2b, 0xeb, 0x45, 0x09, 0x0a, - 0xa2, 0x85, 0xff, 0x35, 0x03, 0x20, 0x57, 0xa3, 0x33, 0x42, 0x3b, 0x50, 0x73, 0x44, 0x2b, 0xe4, - 0xad, 0xbb, 0x89, 0xde, 0x12, 0x8b, 0x38, 0xa3, 0x57, 0xe5, 0x20, 0x6e, 0xdc, 0xf7, 0xa1, 0xe2, - 0x4b, 0x09, 0x1c, 0xb6, 0x92, 0xe0, 0x30, 0x5f, 0x42, 0x59, 0x0e, 0xa0, 0x2e, 0xfb, 0x0c, 0x96, - 0xfc, 0xf1, 0x09, 0x3e, 0x7b, 0xef, 0x06, 0x9f, 0xf9, 0x02, 0x17, 0xa4, 0x04, 0xd5, 0x6b, 0xaa, - 0x61, 0x81, 0xdb, 0x56, 0x12, 0xdc, 0x16, 0x37, 0x8c, 0x3a, 0x0e, 0x68, 0xbe, 0xe4, 0x4d, 0xfc, - 0x7f, 0x59, 0x28, 0x6c, 0xdb, 0xc3, 0x91, 0xe1, 0xd0, 0xd5, 0xc8, 0x3b, 0xc4, 0x1d, 0x0f, 0x3c, - 0xe6, 0xae, 0xda, 0xe6, 0xc3, 0xb0, 0x44, 0xc1, 0x26, 0xff, 0xeb, 0x8c, 0x55, 0x17, 0x43, 0xe8, - 0x60, 0x91, 0x1e, 0x33, 0xef, 0x30, 0x58, 0x24, 0x47, 0x31, 0x44, 0x1e, 0xe4, 0x6c, 0x70, 0x90, - 0x9b, 0x50, 0x98, 0x10, 0x27, 0x48, 0xe9, 0xbb, 0x33, 0xba, 0x24, 0xa0, 0x0f, 0x60, 0x2e, 0x9a, - 0x5e, 0x72, 0x82, 0xa7, 0xd6, 0x0b, 0x67, 0xa3, 0x87, 0x50, 0x09, 0xe5, 0xb8, 0xbc, 0xe0, 0x2b, - 0x0f, 0x95, 0x14, 0xb7, 0x2c, 0xe3, 0x2a, 0xcd, 0xc7, 0x95, 0xdd, 0x19, 0x19, 0x59, 0x97, 0x65, - 0x64, 0x2d, 0x8a, 0x51, 0x22, 0xb6, 0x86, 0x82, 0xcc, 0x0f, 0xc2, 0x41, 0x06, 0xff, 0x00, 0xaa, - 0x21, 0x07, 0xd1, 0xbc, 0xd3, 0xfe, 0xf4, 0x64, 0xeb, 0x80, 0x27, 0xa9, 0x97, 0x2c, 0x2f, 0xe9, - 0x75, 0x8d, 0xe6, 0xba, 0x83, 0xf6, 0xf1, 0x71, 0x3d, 0x83, 0xaa, 0x50, 0x3a, 0xec, 0x74, 0x4f, - 0x39, 0x57, 0x16, 0xbf, 0xf4, 0x25, 0x88, 0x24, 0xa7, 0xe4, 0xb6, 0x19, 0x25, 0xb7, 0x69, 0x32, - 0xb7, 0x65, 0x82, 0xdc, 0xc6, 0xd2, 0xdc, 0x41, 0x7b, 0xeb, 0xb8, 0x5d, 0x9f, 0x7d, 0x51, 0x83, - 0x0a, 0xf7, 0xef, 0xe9, 0xd8, 0xa2, 0xa9, 0xf6, 0x1f, 0x34, 0x80, 0xe0, 0x34, 0xa1, 0x16, 0x14, - 0x7a, 0x5c, 0x4f, 0x43, 0x63, 0xc1, 0x68, 0x29, 0x71, 0xc9, 0x74, 0xc9, 0x85, 0xbe, 0x0d, 0x05, - 0x77, 0xdc, 0xeb, 0x11, 0x57, 0xa6, 0xbc, 0x3b, 0xd1, 0x78, 0x28, 0xa2, 0x95, 0x2e, 0xf9, 0xe8, - 0x90, 0x37, 0x86, 0x39, 0x18, 0xb3, 0x04, 0x78, 0xf3, 0x10, 0xc1, 0x87, 0xff, 0x4e, 0x83, 0xb2, - 0xb2, 0x79, 0x7f, 0xcd, 0x20, 0x7c, 0x0f, 0x4a, 0xcc, 0x06, 0xd2, 0x17, 0x61, 0xb8, 0xa8, 0x07, - 0x04, 0xf4, 0x3b, 0x50, 0x92, 0x27, 0x40, 0x46, 0xe2, 0x46, 0xb2, 0xd8, 0xce, 0x48, 0x0f, 0x58, - 0xf1, 0x3e, 0xcc, 0x33, 0xaf, 0xf4, 0x68, 0x71, 0x2d, 0xfd, 0xa8, 0x96, 0x9f, 0x5a, 0xa4, 0xfc, - 0x6c, 0x42, 0x71, 0x74, 0x71, 0xed, 0x9a, 0x3d, 0x63, 0x20, 0xac, 0xf0, 0xdb, 0xf8, 0x13, 0x40, - 0xaa, 0xb0, 0x69, 0xa6, 0x8b, 0xab, 0x50, 0xde, 0x35, 0xdc, 0x0b, 0x61, 0x12, 0x7e, 0x06, 0x55, - 0xda, 0xdc, 0x7f, 0xfd, 0x0e, 0x36, 0xb2, 0xcb, 0x81, 0xe4, 0x9e, 0xca, 0xe7, 0x08, 0x66, 0x2f, - 0x0c, 0xf7, 0x82, 0x4d, 0xb4, 0xaa, 0xb3, 0x67, 0xf4, 0x01, 0xd4, 0x7b, 0x7c, 0x92, 0xa7, 0x91, - 0x2b, 0xc3, 0x9c, 0xa0, 0xfb, 0x95, 0xe0, 0xe7, 0x50, 0xe1, 0x73, 0xf8, 0x4d, 0x1b, 0x81, 0xe7, - 0x61, 0xee, 0xd8, 0x32, 0x46, 0xee, 0x85, 0x2d, 0xb3, 0x1b, 0x9d, 0x74, 0x3d, 0xa0, 0x4d, 0xa5, - 0xf1, 0x09, 0xcc, 0x39, 0x64, 0x68, 0x98, 0x96, 0x69, 0x9d, 0x9f, 0x9e, 0x5d, 0x7b, 0xc4, 0x15, - 0x17, 0xa6, 0x9a, 0x4f, 0x7e, 0x41, 0xa9, 0xd4, 0xb4, 0xb3, 0x81, 0x7d, 0x26, 0xc2, 0x1c, 0x7b, - 0xc6, 0x7f, 0x99, 0x81, 0xca, 0x67, 0x86, 0xd7, 0x93, 0x4b, 0x87, 0xf6, 0xa0, 0xe6, 0x07, 0x37, - 0x46, 0x11, 0xb6, 0x44, 0x52, 0x2c, 0x1b, 0x23, 0x4b, 0x69, 0x99, 0x1d, 0xab, 0x3d, 0x95, 0xc0, - 0x44, 0x19, 0x56, 0x8f, 0x0c, 0x7c, 0x51, 0x99, 0x74, 0x51, 0x8c, 0x51, 0x15, 0xa5, 0x12, 0x50, - 0x07, 0xea, 0x23, 0xc7, 0x3e, 0x77, 0x88, 0xeb, 0xfa, 0xc2, 0x78, 0x1a, 0xc3, 0x09, 0xc2, 0x8e, - 0x04, 0x6b, 0x20, 0x6e, 0x6e, 0x14, 0x26, 0xbd, 0x98, 0x0b, 0xea, 0x19, 0x1e, 0x9c, 0xfe, 0x2b, - 0x03, 0x28, 0x3e, 0xa9, 0x5f, 0xb5, 0xc4, 0x7b, 0x0c, 0x35, 0xd7, 0x33, 0x9c, 0xd8, 0x66, 0xab, - 0x32, 0xaa, 0x1f, 0xf1, 0x9f, 0x80, 0x6f, 0xd0, 0xa9, 0x65, 0x7b, 0xe6, 0x9b, 0x6b, 0x51, 0x25, - 0xd7, 0x24, 0xf9, 0x90, 0x51, 0x51, 0x1b, 0x0a, 0x6f, 0xcc, 0x81, 0x47, 0x1c, 0xb7, 0x91, 0x5b, - 0xcb, 0xae, 0xd7, 0x36, 0x9f, 0xdd, 0xb6, 0x0c, 0x1b, 0x1f, 0x33, 0xfe, 0xee, 0xf5, 0x88, 0xe8, - 0x72, 0xac, 0x5a, 0x79, 0xe6, 0x43, 0xd5, 0xf8, 0x0a, 0x14, 0xdf, 0x52, 0x11, 0xf4, 0x96, 0x5d, - 0xe0, 0xc5, 0x22, 0x6b, 0xf3, 0x4b, 0xf6, 0x1b, 0xc7, 0x38, 0x1f, 0x12, 0xcb, 0x93, 0xf7, 0x40, - 0xd9, 0xc6, 0x8f, 0x01, 0x02, 0x35, 0x34, 0xe4, 0x1f, 0x76, 0x8e, 0x4e, 0xba, 0xf5, 0x19, 0x54, - 0x81, 0xe2, 0x61, 0x67, 0xa7, 0x7d, 0xd0, 0xa6, 0xf9, 0x01, 0xb7, 0xa4, 0x4b, 0x43, 0x6b, 0xa9, - 0xea, 0xd4, 0x42, 0x3a, 0xf1, 0x32, 0x2c, 0x26, 0x2d, 0x20, 0xad, 0x45, 0xab, 0x62, 0x97, 0x4e, - 0x75, 0x54, 0x54, 0xd5, 0x99, 0xf0, 0x74, 0x1b, 0x50, 0xe0, 0xbb, 0xb7, 0x2f, 0x8a, 0x73, 0xd9, - 0xa4, 0x8e, 0xe0, 0x9b, 0x91, 0xf4, 0xc5, 0x2a, 0xf9, 0xed, 0xc4, 0xf0, 0x92, 0x4b, 0x0c, 0x2f, - 0xe8, 0x21, 0x54, 0xfd, 0xd3, 0x60, 0xb8, 0xa2, 0x16, 0x28, 0xe9, 0x15, 0xb9, 0xd1, 0x29, 0x2d, - 0xe4, 0xf4, 0x42, 0xd8, 0xe9, 0xe8, 0x31, 0xe4, 0xc9, 0x84, 0x58, 0x9e, 0xdb, 0x28, 0xb3, 0x8c, - 0x51, 0x95, 0xb5, 0x7b, 0x9b, 0x52, 0x75, 0xd1, 0x89, 0xbf, 0x07, 0xf3, 0xec, 0x8e, 0xf4, 0xd2, - 0x31, 0x2c, 0xf5, 0x32, 0xd7, 0xed, 0x1e, 0x08, 0x77, 0xd3, 0x47, 0x54, 0x83, 0xcc, 0xde, 0x8e, - 0x70, 0x42, 0x66, 0x6f, 0x07, 0xff, 0x44, 0x03, 0xa4, 0x8e, 0x9b, 0xca, 0xcf, 0x11, 0xe1, 0x52, - 0x7d, 0x36, 0x50, 0xbf, 0x08, 0x39, 0xe2, 0x38, 0xb6, 0xc3, 0x3c, 0x5a, 0xd2, 0x79, 0x03, 0x3f, - 0x12, 0x36, 0xe8, 0x64, 0x62, 0x5f, 0xfa, 0x67, 0x90, 0x4b, 0xd3, 0x7c, 0x53, 0xf7, 0x61, 0x21, - 0xc4, 0x35, 0x55, 0xe6, 0xfa, 0x18, 0xe6, 0x98, 0xb0, 0xed, 0x0b, 0xd2, 0xbb, 0x1c, 0xd9, 0xa6, - 0x15, 0xd3, 0x47, 0x57, 0x2e, 0x08, 0xb0, 0x74, 0x1e, 0x7c, 0x62, 0x15, 0x9f, 0xd8, 0xed, 0x1e, - 0xe0, 0x2f, 0x60, 0x39, 0x22, 0x47, 0x9a, 0xff, 0x87, 0x50, 0xee, 0xf9, 0x44, 0x57, 0xd4, 0x3a, - 0xf7, 0xc3, 0xc6, 0x45, 0x87, 0xaa, 0x23, 0x70, 0x07, 0xee, 0xc4, 0x44, 0x4f, 0x35, 0xe7, 0x27, - 0xb0, 0xc4, 0x04, 0xee, 0x13, 0x32, 0xda, 0x1a, 0x98, 0x93, 0x54, 0x4f, 0x8f, 0xc4, 0xa4, 0x14, - 0xc6, 0x6f, 0x76, 0x5f, 0xe0, 0xdf, 0x17, 0x1a, 0xbb, 0xe6, 0x90, 0x74, 0xed, 0x83, 0x74, 0xdb, - 0x68, 0x36, 0xbb, 0x24, 0xd7, 0xae, 0x28, 0x6b, 0xd8, 0x33, 0xfe, 0x47, 0x4d, 0xb8, 0x4a, 0x1d, - 0xfe, 0x0d, 0xef, 0xe4, 0x55, 0x80, 0x73, 0x7a, 0x64, 0x48, 0x9f, 0x76, 0x70, 0x44, 0x45, 0xa1, - 0xf8, 0x76, 0xd2, 0xf8, 0x5d, 0x11, 0x76, 0x2e, 0x8a, 0x7d, 0xce, 0xfe, 0xf8, 0x51, 0xee, 0x3e, - 0x94, 0x19, 0xe1, 0xd8, 0x33, 0xbc, 0xb1, 0x1b, 0x5b, 0x8c, 0x3f, 0x13, 0xdb, 0x5e, 0x0e, 0x9a, - 0x6a, 0x5e, 0xdf, 0x86, 0x3c, 0xbb, 0x4c, 0xc8, 0x52, 0x7a, 0x25, 0x61, 0x3f, 0x72, 0x3b, 0x74, - 0xc1, 0x88, 0x2f, 0x20, 0xff, 0x8a, 0x21, 0xb0, 0x8a, 0x65, 0xb3, 0x72, 0x29, 0x2c, 0x63, 0xc8, - 0x71, 0xa1, 0x92, 0xce, 0x9e, 0x59, 0xe5, 0x49, 0x88, 0x73, 0xa2, 0x1f, 0xf0, 0x0a, 0xb7, 0xa4, - 0xfb, 0x6d, 0xea, 0xb2, 0xde, 0xc0, 0x24, 0x96, 0xc7, 0x7a, 0x67, 0x59, 0xaf, 0x42, 0xc1, 0x1b, - 0x50, 0xe7, 0x9a, 0xb6, 0xfa, 0x7d, 0xa5, 0x82, 0xf4, 0xe5, 0x69, 0x61, 0x79, 0xf8, 0x9f, 0x34, - 0x98, 0x57, 0x06, 0x4c, 0xe5, 0x98, 0x0f, 0x21, 0xcf, 0x71, 0x66, 0x51, 0xac, 0x2c, 0x86, 0x47, - 0x71, 0x35, 0xba, 0xe0, 0x41, 0x1b, 0x50, 0xe0, 0x4f, 0xb2, 0x8c, 0x4f, 0x66, 0x97, 0x4c, 0xf8, - 0x31, 0x2c, 0x08, 0x12, 0x19, 0xda, 0x49, 0x7b, 0x9b, 0x39, 0x14, 0xff, 0x29, 0x2c, 0x86, 0xd9, - 0xa6, 0x9a, 0x92, 0x62, 0x64, 0xe6, 0x5d, 0x8c, 0xdc, 0x92, 0x46, 0x9e, 0x8c, 0xfa, 0x4a, 0x29, - 0x14, 0x5d, 0x75, 0x75, 0x45, 0x32, 0x91, 0x15, 0xf1, 0x27, 0x20, 0x45, 0xfc, 0x56, 0x27, 0xb0, - 0x20, 0xb7, 0xc3, 0x81, 0xe9, 0xfa, 0x15, 0xf7, 0x57, 0x80, 0x54, 0xe2, 0x6f, 0xdb, 0xa0, 0x1d, - 0x22, 0x13, 0xb9, 0x34, 0xe8, 0x13, 0x40, 0x2a, 0x71, 0xaa, 0x88, 0xde, 0x82, 0xf9, 0x57, 0xf6, - 0x84, 0x86, 0x06, 0x4a, 0x0d, 0x8e, 0x0c, 0xbf, 0x7f, 0xfb, 0xcb, 0xe6, 0xb7, 0xa9, 0x72, 0x75, - 0xc0, 0x54, 0xca, 0xff, 0x43, 0x83, 0xca, 0xd6, 0xc0, 0x70, 0x86, 0x52, 0xf1, 0xf7, 0x21, 0xcf, - 0x6f, 0x95, 0x02, 0xc8, 0x79, 0x3f, 0x2c, 0x46, 0xe5, 0xe5, 0x8d, 0x2d, 0x7e, 0x07, 0x15, 0xa3, - 0xa8, 0xe1, 0xe2, 0x5d, 0xcf, 0x4e, 0xe4, 0xdd, 0xcf, 0x0e, 0xfa, 0x08, 0x72, 0x06, 0x1d, 0xc2, - 0x42, 0x70, 0x2d, 0x7a, 0x9f, 0x67, 0xd2, 0x58, 0xed, 0xcb, 0xb9, 0xf0, 0x77, 0xa1, 0xac, 0x68, - 0x40, 0x05, 0xc8, 0xbe, 0x6c, 0x8b, 0x42, 0x75, 0x6b, 0xbb, 0xbb, 0xf7, 0x9a, 0x03, 0x19, 0x35, - 0x80, 0x9d, 0xb6, 0xdf, 0xce, 0xe0, 0xcf, 0xc5, 0x28, 0x11, 0xef, 0x54, 0x7b, 0xb4, 0x34, 0x7b, - 0x32, 0xef, 0x64, 0xcf, 0x15, 0x54, 0xc5, 0xf4, 0xa7, 0x0d, 0xdf, 0x4c, 0x5e, 0x4a, 0xf8, 0x56, - 0x8c, 0xd7, 0x05, 0x23, 0x9e, 0x83, 0xaa, 0x08, 0xe8, 0x62, 0xff, 0xfd, 0x4b, 0x06, 0x6a, 0x92, - 0x32, 0x2d, 0xe0, 0x2c, 0xb1, 0x32, 0x9e, 0x01, 0x7c, 0xa4, 0x6c, 0x19, 0xf2, 0xfd, 0xb3, 0x63, - 0xf3, 0x2b, 0xf9, 0x72, 0x40, 0xb4, 0x28, 0x7d, 0xc0, 0xf5, 0xf0, 0x37, 0x74, 0xa2, 0x85, 0xee, - 0xf1, 0x97, 0x77, 0x7b, 0x56, 0x9f, 0x5c, 0xb1, 0x3a, 0x7a, 0x56, 0x0f, 0x08, 0x0c, 0x44, 0x10, - 0x6f, 0xf2, 0x58, 0xf1, 0xac, 0xbc, 0xd9, 0x43, 0x4f, 0xa1, 0x4e, 0x9f, 0xb7, 0x46, 0xa3, 0x81, - 0x49, 0xfa, 0x5c, 0x40, 0x81, 0xf1, 0xc4, 0xe8, 0x54, 0x3b, 0x2b, 0x37, 0xdd, 0x46, 0x91, 0x85, - 0x2d, 0xd1, 0x42, 0x6b, 0x50, 0xe6, 0xf6, 0xed, 0x59, 0x27, 0x2e, 0x61, 0xaf, 0xb7, 0xb2, 0xba, - 0x4a, 0xa2, 0xe7, 0x78, 0x6b, 0xec, 0x5d, 0xb4, 0x2d, 0xe3, 0x6c, 0x20, 0xe3, 0x22, 0x4d, 0xe6, - 0x94, 0xb8, 0x63, 0xba, 0x2a, 0xb5, 0x0d, 0x0b, 0x94, 0x4a, 0x2c, 0xcf, 0xec, 0x29, 0x41, 0x54, - 0xa6, 0x4a, 0x2d, 0x92, 0x2a, 0x0d, 0xd7, 0x7d, 0x6b, 0x3b, 0x7d, 0xe1, 0x40, 0xbf, 0x8d, 0x77, - 0xb8, 0xf0, 0x13, 0x37, 0x94, 0x0c, 0x7f, 0x55, 0x29, 0xeb, 0x81, 0x94, 0x97, 0xc4, 0xbb, 0x41, - 0x0a, 0x7e, 0x06, 0x4b, 0x92, 0x53, 0x40, 0xbe, 0x37, 0x30, 0x77, 0xe0, 0xbe, 0x64, 0xde, 0xbe, - 0xa0, 0x57, 0xe0, 0x23, 0xa1, 0xf0, 0xd7, 0xb5, 0xf3, 0x05, 0x34, 0x7c, 0x3b, 0xd9, 0x35, 0xc4, - 0x1e, 0xa8, 0x06, 0x8c, 0x5d, 0xb1, 0x33, 0x4b, 0x3a, 0x7b, 0xa6, 0x34, 0xc7, 0x1e, 0xf8, 0x85, - 0x07, 0x7d, 0xc6, 0xdb, 0xb0, 0x22, 0x65, 0x88, 0x0b, 0x42, 0x58, 0x48, 0xcc, 0xa0, 0x24, 0x21, - 0xc2, 0x61, 0x74, 0xe8, 0xcd, 0x6e, 0x57, 0x39, 0xc3, 0xae, 0x65, 0x32, 0x35, 0x45, 0xe6, 0x12, - 0xdf, 0x11, 0xd4, 0x30, 0x35, 0x2f, 0x09, 0x32, 0x15, 0xa0, 0x92, 0xc5, 0x42, 0x50, 0x72, 0x6c, - 0x21, 0x62, 0xa2, 0x7f, 0x08, 0xab, 0xbe, 0x11, 0xd4, 0x6f, 0x47, 0xc4, 0x19, 0x9a, 0xae, 0xab, - 0x80, 0x84, 0x49, 0x13, 0x7f, 0x1f, 0x66, 0x47, 0x44, 0x44, 0xae, 0xf2, 0x26, 0xda, 0xe0, 0x6f, - 0xf5, 0x37, 0x94, 0xc1, 0xac, 0x1f, 0xf7, 0xe1, 0x81, 0x94, 0xce, 0x3d, 0x9a, 0x28, 0x3e, 0x6a, - 0x94, 0x84, 0x4e, 0x32, 0x29, 0xd0, 0x49, 0x36, 0x02, 0x5c, 0x7f, 0xc2, 0x1d, 0x29, 0xcf, 0xd6, - 0x54, 0x19, 0x69, 0x9f, 0xfb, 0xd4, 0x3f, 0x92, 0x53, 0x09, 0x3b, 0x83, 0xc5, 0xf0, 0x49, 0x9e, - 0x2a, 0x58, 0x2e, 0x42, 0xce, 0xb3, 0x2f, 0x89, 0x0c, 0x95, 0xbc, 0x21, 0x0d, 0xf6, 0x8f, 0xf9, - 0x54, 0x06, 0x1b, 0x81, 0x30, 0xb6, 0x25, 0xa7, 0xb5, 0x97, 0xae, 0xa6, 0x2c, 0xf1, 0x78, 0x03, - 0x1f, 0xc2, 0x72, 0x34, 0x4c, 0x4c, 0x65, 0xf2, 0x6b, 0xbe, 0x81, 0x93, 0x22, 0xc9, 0x54, 0x72, - 0x3f, 0x0d, 0x82, 0x81, 0x12, 0x50, 0xa6, 0x12, 0xa9, 0x43, 0x33, 0x29, 0xbe, 0xfc, 0x26, 0xf6, - 0xab, 0x1f, 0x6e, 0xa6, 0x12, 0xe6, 0x06, 0xc2, 0xa6, 0x5f, 0xfe, 0x20, 0x46, 0x64, 0x6f, 0x8c, - 0x11, 0xe2, 0x90, 0x04, 0x51, 0xec, 0x1b, 0xd8, 0x74, 0x42, 0x47, 0x10, 0x40, 0xa7, 0xd5, 0x41, - 0x73, 0x88, 0xaf, 0x83, 0x35, 0xe4, 0xc6, 0x56, 0xc3, 0xee, 0x54, 0x8b, 0xf1, 0x59, 0x10, 0x3b, - 0x63, 0x91, 0x79, 0x2a, 0xc1, 0x9f, 0xc3, 0x5a, 0x7a, 0x50, 0x9e, 0x46, 0xf2, 0xd3, 0x16, 0x94, - 0xfc, 0xb2, 0x55, 0xf9, 0x22, 0xa6, 0x0c, 0x85, 0xc3, 0xce, 0xf1, 0xd1, 0xd6, 0x76, 0x9b, 0x7f, - 0x12, 0xb3, 0xdd, 0xd1, 0xf5, 0x93, 0xa3, 0x6e, 0x3d, 0xb3, 0xf9, 0xcb, 0x2c, 0x64, 0xf6, 0x5f, - 0xa3, 0x2f, 0x20, 0xc7, 0xdf, 0x0f, 0xdf, 0xf0, 0x51, 0x40, 0xf3, 0xa6, 0x57, 0xe0, 0xf8, 0xce, - 0x4f, 0xfe, 0xfb, 0x97, 0x3f, 0xcf, 0xcc, 0xe3, 0x4a, 0x6b, 0xf2, 0x9d, 0xd6, 0xe5, 0xa4, 0xc5, - 0x72, 0xc3, 0x73, 0xed, 0x29, 0xfa, 0x14, 0xb2, 0x47, 0x63, 0x0f, 0xa5, 0x7e, 0x2c, 0xd0, 0x4c, - 0x7f, 0x2b, 0x8e, 0x97, 0x98, 0xd0, 0x39, 0x0c, 0x42, 0xe8, 0x68, 0xec, 0x51, 0x91, 0x3f, 0x82, - 0xb2, 0xfa, 0x4e, 0xfb, 0xd6, 0x2f, 0x08, 0x9a, 0xb7, 0xbf, 0x2f, 0xc7, 0xf7, 0x99, 0xaa, 0x3b, - 0x18, 0x09, 0x55, 0xfc, 0xad, 0xbb, 0x3a, 0x8b, 0xee, 0x95, 0x85, 0x52, 0xbf, 0x2f, 0x68, 0xa6, - 0xbf, 0x42, 0x8f, 0xcd, 0xc2, 0xbb, 0xb2, 0xa8, 0xc8, 0x3f, 0x16, 0x6f, 0xcf, 0x7b, 0x1e, 0x7a, - 0x90, 0xf0, 0xf6, 0x54, 0x7d, 0x4f, 0xd8, 0x5c, 0x4b, 0x67, 0x10, 0x4a, 0xee, 0x31, 0x25, 0xcb, - 0x78, 0x5e, 0x28, 0xe9, 0xf9, 0x2c, 0xcf, 0xb5, 0xa7, 0x9b, 0x3d, 0xc8, 0x31, 0x0c, 0x1e, 0x7d, - 0x29, 0x1f, 0x9a, 0x09, 0x2f, 0x23, 0x52, 0x16, 0x3a, 0x84, 0xde, 0xe3, 0x45, 0xa6, 0xa8, 0x86, - 0x4b, 0x54, 0x11, 0x43, 0xe0, 0x9f, 0x6b, 0x4f, 0xd7, 0xb5, 0x6f, 0x69, 0x9b, 0xff, 0x9c, 0x83, - 0x1c, 0x03, 0x9f, 0xd0, 0x25, 0x40, 0x80, 0x47, 0x47, 0x67, 0x17, 0x43, 0xb8, 0xa3, 0xb3, 0x8b, - 0x43, 0xd9, 0xb8, 0xc9, 0x94, 0x2e, 0xe2, 0x39, 0xaa, 0x94, 0x61, 0x5a, 0x2d, 0x06, 0xd3, 0x51, - 0x3f, 0xfe, 0x95, 0x26, 0xb0, 0x37, 0x7e, 0x96, 0x50, 0x92, 0xb4, 0x10, 0x28, 0x1d, 0xdd, 0x0e, - 0x09, 0x80, 0x34, 0xfe, 0x1e, 0x53, 0xd8, 0xc2, 0xf5, 0x40, 0xa1, 0xc3, 0x38, 0x9e, 0x6b, 0x4f, - 0xbf, 0x6c, 0xe0, 0x05, 0xe1, 0xe5, 0x48, 0x0f, 0xfa, 0x31, 0xd4, 0xc2, 0xa0, 0x2b, 0x7a, 0x98, - 0xa0, 0x2b, 0x8a, 0xdd, 0x36, 0x1f, 0xdd, 0xcc, 0x24, 0x6c, 0x5a, 0x65, 0x36, 0x09, 0xe5, 0x5c, - 0xf3, 0x25, 0x21, 0x23, 0x83, 0x32, 0x89, 0x35, 0x40, 0x7f, 0xaf, 0x09, 0x4c, 0x3c, 0x40, 0x51, - 0x51, 0x92, 0xf4, 0x18, 0x46, 0xdb, 0x7c, 0x7c, 0x0b, 0x97, 0x30, 0xe2, 0x0f, 0x98, 0x11, 0xbf, - 0x8b, 0x17, 0x03, 0x23, 0x3c, 0x73, 0x48, 0x3c, 0x5b, 0x58, 0xf1, 0xe5, 0x3d, 0x7c, 0x27, 0xe4, - 0x9c, 0x50, 0x6f, 0xb0, 0x58, 0x1c, 0x09, 0x4d, 0x5c, 0xac, 0x10, 0xb2, 0x9a, 0xb8, 0x58, 0x61, - 0x18, 0x35, 0x69, 0xb1, 0x38, 0xee, 0x99, 0xb4, 0x58, 0x7e, 0xcf, 0x26, 0xfb, 0x7e, 0x85, 0x7f, - 0xb5, 0x8a, 0x6c, 0x28, 0xf9, 0x28, 0x24, 0x5a, 0x4d, 0x42, 0x84, 0x82, 0xbb, 0x44, 0xf3, 0x41, - 0x6a, 0xbf, 0x30, 0xe8, 0x3d, 0x66, 0xd0, 0x5d, 0xbc, 0x4c, 0x35, 0x8b, 0x0f, 0x63, 0x5b, 0x1c, - 0x76, 0x68, 0x19, 0xfd, 0x3e, 0x75, 0xc4, 0x9f, 0x40, 0x45, 0x85, 0x09, 0xd1, 0x7b, 0x89, 0x28, - 0x94, 0x8a, 0x34, 0x36, 0xf1, 0x4d, 0x2c, 0x42, 0xf3, 0x23, 0xa6, 0x79, 0x15, 0xaf, 0x24, 0x68, - 0x76, 0x18, 0x6b, 0x48, 0x39, 0x87, 0xf8, 0x92, 0x95, 0x87, 0x10, 0xc4, 0x64, 0xe5, 0x61, 0x84, - 0xf0, 0x46, 0xe5, 0x63, 0xc6, 0x4a, 0x95, 0xbb, 0x00, 0x01, 0x98, 0x87, 0x12, 0x7d, 0xa9, 0x5c, - 0xa6, 0xa2, 0xc1, 0x21, 0x8e, 0x03, 0x62, 0xcc, 0xd4, 0x8a, 0x7d, 0x17, 0x51, 0x3b, 0x30, 0x5d, - 0x1a, 0x24, 0x36, 0xff, 0x3a, 0x0f, 0xe5, 0x57, 0x86, 0x69, 0x79, 0xc4, 0x32, 0xac, 0x1e, 0x41, - 0x67, 0x90, 0x63, 0x89, 0x32, 0x1a, 0x07, 0x55, 0x7c, 0x2b, 0x1a, 0x07, 0x43, 0xe0, 0x0f, 0x5e, - 0x63, 0x5a, 0x9b, 0x78, 0x89, 0x6a, 0x1d, 0x06, 0xa2, 0x5b, 0x0c, 0xb3, 0xa1, 0x13, 0x7d, 0x03, - 0x79, 0xf1, 0x3a, 0x20, 0x22, 0x28, 0x84, 0xe5, 0x34, 0xef, 0x25, 0x77, 0x26, 0x6d, 0x25, 0x55, - 0x8d, 0xcb, 0xf8, 0xa8, 0x9e, 0x09, 0x40, 0x00, 0x46, 0x46, 0x1d, 0x1a, 0xc3, 0x2e, 0x9b, 0x6b, - 0xe9, 0x0c, 0x42, 0xe7, 0x63, 0xa6, 0xf3, 0x01, 0x6e, 0x46, 0x75, 0xf6, 0x7d, 0x5e, 0xaa, 0xf7, - 0x8f, 0x60, 0x76, 0xd7, 0x70, 0x2f, 0x50, 0x24, 0xf5, 0x29, 0x1f, 0x93, 0x34, 0x9b, 0x49, 0x5d, - 0x42, 0xcb, 0x03, 0xa6, 0x65, 0x85, 0x47, 0x12, 0x55, 0xcb, 0x85, 0xe1, 0xd2, 0x9c, 0x82, 0xfa, - 0x90, 0xe7, 0xdf, 0x96, 0x44, 0xfd, 0x17, 0xfa, 0x3e, 0x25, 0xea, 0xbf, 0xf0, 0xe7, 0x28, 0xb7, - 0x6b, 0x19, 0x41, 0x51, 0x7e, 0xcc, 0x81, 0x22, 0x6f, 0xf6, 0x22, 0x1f, 0x7e, 0x34, 0x57, 0xd3, - 0xba, 0x85, 0xae, 0x87, 0x4c, 0xd7, 0x7d, 0xdc, 0x88, 0xad, 0x95, 0xe0, 0x7c, 0xae, 0x3d, 0xfd, - 0x96, 0x86, 0x7e, 0x0c, 0x10, 0xe0, 0xb7, 0xb1, 0x03, 0x10, 0x85, 0x82, 0x63, 0x07, 0x20, 0x06, - 0xfd, 0xe2, 0x0d, 0xa6, 0x77, 0x1d, 0x3f, 0x8c, 0xea, 0xf5, 0x1c, 0xc3, 0x72, 0xdf, 0x10, 0xe7, - 0x23, 0x8e, 0xd1, 0xb9, 0x17, 0xe6, 0x88, 0x1e, 0x86, 0x7f, 0x9b, 0x83, 0x59, 0x5a, 0x80, 0xd2, - 0x3c, 0x1d, 0xdc, 0xdb, 0xa3, 0x96, 0xc4, 0xd0, 0xb2, 0xa8, 0x25, 0xf1, 0x2b, 0x7f, 0x38, 0x4f, - 0xb3, 0x9f, 0x1b, 0x10, 0xc6, 0x40, 0x1d, 0x6d, 0x43, 0x59, 0xb9, 0xd8, 0xa3, 0x04, 0x61, 0x61, - 0x18, 0x2e, 0x1a, 0xf9, 0x13, 0x50, 0x01, 0x7c, 0x97, 0xe9, 0x5b, 0xe2, 0x91, 0x9f, 0xe9, 0xeb, - 0x73, 0x0e, 0xaa, 0xf0, 0x2d, 0x54, 0xd4, 0xcb, 0x3f, 0x4a, 0x90, 0x17, 0x81, 0xf8, 0xa2, 0x51, - 0x2e, 0x09, 0x3b, 0x08, 0x1f, 0x7c, 0xff, 0x27, 0x15, 0x92, 0x8d, 0x2a, 0x1e, 0x40, 0x41, 0xa0, - 0x01, 0x49, 0xb3, 0x0c, 0xe3, 0x81, 0x49, 0xb3, 0x8c, 0x40, 0x09, 0xe1, 0xda, 0x8e, 0x69, 0xa4, - 0x17, 0x1e, 0x99, 0x49, 0x84, 0xb6, 0x97, 0xc4, 0x4b, 0xd3, 0x16, 0x80, 0x5b, 0x69, 0xda, 0x94, - 0xcb, 0x66, 0x9a, 0xb6, 0x73, 0xe2, 0x89, 0xe3, 0x22, 0x2f, 0x71, 0x28, 0x45, 0x98, 0x1a, 0xbd, - 0xf1, 0x4d, 0x2c, 0x49, 0xa5, 0x77, 0xa0, 0x50, 0x84, 0x6e, 0x74, 0x05, 0x10, 0x60, 0x15, 0xd1, - 0x7a, 0x2a, 0x11, 0xf0, 0x8c, 0xd6, 0x53, 0xc9, 0x70, 0x47, 0x38, 0x34, 0x04, 0x7a, 0x79, 0xe5, - 0x4f, 0x35, 0xff, 0x4c, 0x03, 0x14, 0x87, 0x35, 0xd0, 0xb3, 0x64, 0xe9, 0x89, 0x30, 0x6a, 0xf3, - 0xc3, 0x77, 0x63, 0x4e, 0x8a, 0xf6, 0x81, 0x49, 0x3d, 0xc6, 0x3d, 0x7a, 0x4b, 0x8d, 0xfa, 0x73, - 0x0d, 0xaa, 0x21, 0x4c, 0x04, 0xbd, 0x9f, 0xb2, 0xa6, 0x11, 0x14, 0xb6, 0xf9, 0xe4, 0x56, 0xbe, - 0xa4, 0x42, 0x53, 0xd9, 0x01, 0xb2, 0xe2, 0xfe, 0xa9, 0x06, 0xb5, 0x30, 0x86, 0x82, 0x52, 0x64, - 0xc7, 0x50, 0xdc, 0xe6, 0xfa, 0xed, 0x8c, 0x37, 0x2f, 0x4f, 0x50, 0x6c, 0x0f, 0xa0, 0x20, 0x50, - 0x97, 0xa4, 0x8d, 0x1f, 0xc6, 0x7f, 0x93, 0x36, 0x7e, 0x04, 0xb2, 0x49, 0xd8, 0xf8, 0x8e, 0x3d, - 0x20, 0xca, 0x31, 0x13, 0xb0, 0x4c, 0x9a, 0xb6, 0x9b, 0x8f, 0x59, 0x04, 0xd3, 0x49, 0xd3, 0x16, - 0x1c, 0x33, 0x89, 0xc7, 0xa0, 0x14, 0x61, 0xb7, 0x1c, 0xb3, 0x28, 0x9c, 0x93, 0x70, 0xcc, 0x98, - 0x42, 0xe5, 0x98, 0x05, 0xc8, 0x49, 0xd2, 0x31, 0x8b, 0xc1, 0xd9, 0x49, 0xc7, 0x2c, 0x0e, 0xbe, - 0x24, 0xac, 0x23, 0xd3, 0x1b, 0x3a, 0x66, 0x0b, 0x09, 0x20, 0x0b, 0xfa, 0x30, 0xc5, 0x89, 0x89, - 0x28, 0x79, 0xf3, 0xa3, 0x77, 0xe4, 0x4e, 0xdd, 0xe3, 0xdc, 0xfd, 0x72, 0x8f, 0xff, 0x8d, 0x06, - 0x8b, 0x49, 0x00, 0x0d, 0x4a, 0xd1, 0x93, 0x82, 0xae, 0x37, 0x37, 0xde, 0x95, 0xfd, 0x66, 0x6f, - 0xf9, 0xbb, 0xfe, 0x45, 0xfd, 0xdf, 0xbf, 0x5e, 0xd5, 0xfe, 0xf3, 0xeb, 0x55, 0xed, 0x7f, 0xbe, - 0x5e, 0xd5, 0xfe, 0xf6, 0x7f, 0x57, 0x67, 0xce, 0xf2, 0xec, 0x87, 0x7a, 0xdf, 0xf9, 0xff, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xc6, 0xc3, 0xa2, 0xb2, 0x2f, 0x38, 0x00, 0x00, + 0xe4, 0x9a, 0xa9, 0xaf, 0xe8, 0xf4, 0x91, 0x8f, 0xb7, 0xce, 0xc9, 0x09, 0xb1, 0xb8, 0xe2, 0x0a, + 0x1d, 0x6f, 0x9d, 0x93, 0xb6, 0xd5, 0x47, 0xcb, 0x90, 0x1b, 0x98, 0x43, 0xd3, 0x13, 0x5a, 0x79, + 0x23, 0x64, 0xce, 0x7c, 0xc4, 0x9c, 0x6d, 0x00, 0xd7, 0x76, 0xbc, 0x13, 0xdb, 0xe9, 0x13, 0xa7, + 0x91, 0x5b, 0xd3, 0xd6, 0x6b, 0x9b, 0x6f, 0x6f, 0xa8, 0x0b, 0xb1, 0xa1, 0x1a, 0xb4, 0x71, 0x64, + 0x3b, 0x5e, 0x87, 0xf2, 0xea, 0x25, 0x57, 0x3e, 0xa2, 0x0f, 0xa1, 0xcc, 0x84, 0x78, 0x86, 0x73, + 0x4e, 0xbc, 0x46, 0x9e, 0x49, 0x79, 0x74, 0x83, 0x94, 0x2e, 0x63, 0xd6, 0x99, 0x7a, 0xfe, 0x8c, + 0x30, 0x54, 0x5c, 0xe2, 0x98, 0xc6, 0xc0, 0xfc, 0xc2, 0x38, 0x1d, 0x90, 0x46, 0x61, 0x4d, 0x5b, + 0x2f, 0xea, 0x21, 0x1a, 0x9d, 0xff, 0x25, 0xb9, 0x76, 0x4f, 0x6c, 0x6b, 0x70, 0xdd, 0x28, 0x32, + 0x86, 0x22, 0x25, 0x74, 0xac, 0xc1, 0x35, 0x5b, 0x34, 0x7b, 0x6c, 0x79, 0xbc, 0xb7, 0xc4, 0x7a, + 0x4b, 0x8c, 0xc2, 0xba, 0xd7, 0xa1, 0x3e, 0x34, 0xad, 0x93, 0xa1, 0xdd, 0x3f, 0xf1, 0x1d, 0x02, + 0xcc, 0x21, 0xb5, 0xa1, 0x69, 0xbd, 0xb4, 0xfb, 0xba, 0x74, 0x0b, 0xe5, 0x34, 0xae, 0xc2, 0x9c, + 0x65, 0xc1, 0x69, 0x5c, 0xa9, 0x9c, 0x1b, 0xb0, 0x44, 0x65, 0xf6, 0x1c, 0x62, 0x78, 0x24, 0x60, + 0xae, 0x30, 0xe6, 0xc5, 0xa1, 0x69, 0x6d, 0xb3, 0x9e, 0x10, 0xbf, 0x71, 0x15, 0xe3, 0xaf, 0x0a, + 0x7e, 0xe3, 0x2a, 0xcc, 0x8f, 0x37, 0xa0, 0xe4, 0xfb, 0x1c, 0x15, 0x61, 0xfe, 0xa0, 0x73, 0xd0, + 0xae, 0xcf, 0x21, 0x80, 0xfc, 0xd6, 0xd1, 0x76, 0xfb, 0x60, 0xa7, 0xae, 0xa1, 0x32, 0x14, 0x76, + 0xda, 0xbc, 0x91, 0xc1, 0xcf, 0x00, 0x02, 0xef, 0xa2, 0x02, 0x64, 0xf7, 0xda, 0x9f, 0xd5, 0xe7, + 0x28, 0xcf, 0xab, 0xb6, 0x7e, 0xb4, 0xdb, 0x39, 0xa8, 0x6b, 0x74, 0xf0, 0xb6, 0xde, 0xde, 0xea, + 0xb6, 0xeb, 0x19, 0xca, 0xf1, 0xb2, 0xb3, 0x53, 0xcf, 0xa2, 0x12, 0xe4, 0x5e, 0x6d, 0xed, 0x1f, + 0xb7, 0xeb, 0xf3, 0xf8, 0x17, 0x1a, 0x54, 0xc5, 0x7a, 0xf1, 0x33, 0x81, 0xbe, 0x0b, 0xf9, 0x0b, + 0x76, 0x2e, 0xd8, 0x56, 0x2c, 0x6f, 0xde, 0x8b, 0x2c, 0x6e, 0xe8, 0xec, 0xe8, 0x82, 0x17, 0x61, + 0xc8, 0x5e, 0x4e, 0xdc, 0x46, 0x66, 0x2d, 0xbb, 0x5e, 0xde, 0xac, 0x6f, 0xf0, 0x03, 0xbb, 0xb1, + 0x47, 0xae, 0x5f, 0x19, 0x83, 0x31, 0xd1, 0x69, 0x27, 0x42, 0x30, 0x3f, 0xb4, 0x1d, 0xc2, 0x76, + 0x6c, 0x51, 0x67, 0xcf, 0x74, 0x1b, 0xb3, 0x45, 0x13, 0xbb, 0x95, 0x37, 0xf0, 0x2f, 0x35, 0x80, + 0xc3, 0xb1, 0x97, 0x7e, 0x34, 0x96, 0x21, 0x37, 0xa1, 0x82, 0xc5, 0xb1, 0xe0, 0x0d, 0x76, 0x26, + 0x88, 0xe1, 0x12, 0xff, 0x4c, 0xd0, 0x06, 0xba, 0x0d, 0x85, 0x91, 0x43, 0x26, 0x27, 0x97, 0x13, + 0xa6, 0xa4, 0xa8, 0xe7, 0x69, 0x73, 0x6f, 0x82, 0xde, 0x82, 0x8a, 0x79, 0x6e, 0xd9, 0x0e, 0x39, + 0xe1, 0xb2, 0x72, 0xac, 0xb7, 0xcc, 0x69, 0xcc, 0x6e, 0x85, 0x85, 0x0b, 0xce, 0xab, 0x2c, 0xfb, + 0x94, 0x84, 0x2d, 0x28, 0x33, 0x53, 0x67, 0x72, 0xdf, 0x7b, 0x81, 0x8d, 0x19, 0x36, 0x2c, 0xee, + 0x42, 0x61, 0x35, 0xfe, 0x21, 0xa0, 0x1d, 0x32, 0x20, 0x1e, 0x99, 0x25, 0x7a, 0x28, 0x3e, 0xc9, + 0xaa, 0x3e, 0xc1, 0x3f, 0xd7, 0x60, 0x29, 0x24, 0x7e, 0xa6, 0x69, 0x35, 0xa0, 0xd0, 0x67, 0xc2, + 0xb8, 0x05, 0x59, 0x5d, 0x36, 0xd1, 0x13, 0x28, 0x0a, 0x03, 0xdc, 0x46, 0x36, 0x65, 0xd3, 0x14, + 0xb8, 0x4d, 0x2e, 0xfe, 0x65, 0x06, 0x4a, 0x62, 0xa2, 0x9d, 0x11, 0xda, 0x82, 0xaa, 0xc3, 0x1b, + 0x27, 0x6c, 0x3e, 0xc2, 0xa2, 0x66, 0x7a, 0x10, 0x7a, 0x31, 0xa7, 0x57, 0xc4, 0x10, 0x46, 0x46, + 0xbf, 0x07, 0x65, 0x29, 0x62, 0x34, 0xf6, 0x84, 0xcb, 0x1b, 0x61, 0x01, 0xc1, 0xfe, 0x7b, 0x31, + 0xa7, 0x83, 0x60, 0x3f, 0x1c, 0x7b, 0xa8, 0x0b, 0xcb, 0x72, 0x30, 0x9f, 0x8d, 0x30, 0x23, 0xcb, + 0xa4, 0xac, 0x85, 0xa5, 0xc4, 0x97, 0xea, 0xc5, 0x9c, 0x8e, 0xc4, 0x78, 0xa5, 0x53, 0x35, 0xc9, + 0xbb, 0xe2, 0xc1, 0x3b, 0x66, 0x52, 0xf7, 0xca, 0x8a, 0x9b, 0xd4, 0xbd, 0xb2, 0x9e, 0x95, 0xa0, + 0x20, 0x5a, 0xf8, 0x5f, 0x32, 0x00, 0x72, 0x35, 0x3a, 0x23, 0xb4, 0x03, 0x35, 0x47, 0xb4, 0x42, + 0xde, 0xba, 0x9b, 0xe8, 0x2d, 0xb1, 0x88, 0x73, 0x7a, 0x55, 0x0e, 0xe2, 0xc6, 0x7d, 0x1f, 0x2a, + 0xbe, 0x94, 0xc0, 0x61, 0x77, 0x12, 0x1c, 0xe6, 0x4b, 0x28, 0xcb, 0x01, 0xd4, 0x65, 0x9f, 0xc0, + 0x8a, 0x3f, 0x3e, 0xc1, 0x67, 0x6f, 0x4d, 0xf1, 0x99, 0x2f, 0x70, 0x49, 0x4a, 0x50, 0xbd, 0xa6, + 0x1a, 0x16, 0xb8, 0xed, 0x4e, 0x82, 0xdb, 0xe2, 0x86, 0x51, 0xc7, 0x01, 0xcd, 0x97, 0xbc, 0x89, + 0xff, 0x2f, 0x0b, 0x85, 0x6d, 0x7b, 0x38, 0x32, 0x1c, 0xba, 0x1a, 0x79, 0x87, 0xb8, 0xe3, 0x81, + 0xc7, 0xdc, 0x55, 0xdb, 0x7c, 0x18, 0x96, 0x28, 0xd8, 0xe4, 0x7f, 0x9d, 0xb1, 0xea, 0x62, 0x08, + 0x1d, 0x2c, 0xd2, 0x63, 0xe6, 0x0d, 0x06, 0x8b, 0xe4, 0x28, 0x86, 0xc8, 0x83, 0x9c, 0x0d, 0x0e, + 0x72, 0x13, 0x0a, 0x13, 0xe2, 0x04, 0x29, 0xfd, 0xc5, 0x9c, 0x2e, 0x09, 0xe8, 0x3d, 0x58, 0x88, + 0xa6, 0x97, 0x9c, 0xe0, 0xa9, 0xf5, 0xc2, 0xd9, 0xe8, 0x21, 0x54, 0x42, 0x39, 0x2e, 0x2f, 0xf8, + 0xca, 0x43, 0x25, 0xc5, 0xdd, 0x92, 0x71, 0x95, 0xe6, 0xe3, 0xca, 0x8b, 0x39, 0x19, 0x59, 0x6f, + 0xc9, 0xc8, 0x5a, 0x14, 0xa3, 0x44, 0x6c, 0x0d, 0x05, 0x99, 0x1f, 0x84, 0x83, 0x0c, 0xfe, 0x01, + 0x54, 0x43, 0x0e, 0xa2, 0x79, 0xa7, 0xfd, 0xf1, 0xf1, 0xd6, 0x3e, 0x4f, 0x52, 0xcf, 0x59, 0x5e, + 0xd2, 0xeb, 0x1a, 0xcd, 0x75, 0xfb, 0xed, 0xa3, 0xa3, 0x7a, 0x06, 0x55, 0xa1, 0x74, 0xd0, 0xe9, + 0x9e, 0x70, 0xae, 0x2c, 0x7e, 0xee, 0x4b, 0x10, 0x49, 0x4e, 0xc9, 0x6d, 0x73, 0x4a, 0x6e, 0xd3, + 0x64, 0x6e, 0xcb, 0x04, 0xb9, 0x8d, 0xa5, 0xb9, 0xfd, 0xf6, 0xd6, 0x51, 0xbb, 0x3e, 0xff, 0xac, + 0x06, 0x15, 0xee, 0xdf, 0x93, 0xb1, 0x45, 0x53, 0xed, 0x3f, 0x68, 0x00, 0xc1, 0x69, 0x42, 0x2d, + 0x28, 0xf4, 0xb8, 0x9e, 0x86, 0xc6, 0x82, 0xd1, 0x4a, 0xe2, 0x92, 0xe9, 0x92, 0x0b, 0x7d, 0x1b, + 0x0a, 0xee, 0xb8, 0xd7, 0x23, 0xae, 0x4c, 0x79, 0xb7, 0xa3, 0xf1, 0x50, 0x44, 0x2b, 0x5d, 0xf2, + 0xd1, 0x21, 0x67, 0x86, 0x39, 0x18, 0xb3, 0x04, 0x38, 0x7d, 0x88, 0xe0, 0xc3, 0x7f, 0xa7, 0x41, + 0x59, 0xd9, 0xbc, 0xbf, 0x65, 0x10, 0xbe, 0x07, 0x25, 0x66, 0x03, 0xe9, 0x8b, 0x30, 0x5c, 0xd4, + 0x03, 0x02, 0xfa, 0x1d, 0x28, 0xc9, 0x13, 0x20, 0x23, 0x71, 0x23, 0x59, 0x6c, 0x67, 0xa4, 0x07, + 0xac, 0x78, 0x0f, 0x16, 0x99, 0x57, 0x7a, 0xb4, 0xb8, 0x96, 0x7e, 0x54, 0xcb, 0x4f, 0x2d, 0x52, + 0x7e, 0x36, 0xa1, 0x38, 0xba, 0xb8, 0x76, 0xcd, 0x9e, 0x31, 0x10, 0x56, 0xf8, 0x6d, 0xfc, 0x11, + 0x20, 0x55, 0xd8, 0x2c, 0xd3, 0xc5, 0x55, 0x28, 0xbf, 0x30, 0xdc, 0x0b, 0x61, 0x12, 0x7e, 0x02, + 0x55, 0xda, 0xdc, 0x7b, 0xf5, 0x06, 0x36, 0xb2, 0x97, 0x03, 0xc9, 0x3d, 0x93, 0xcf, 0x11, 0xcc, + 0x5f, 0x18, 0xee, 0x05, 0x9b, 0x68, 0x55, 0x67, 0xcf, 0xe8, 0x3d, 0xa8, 0xf7, 0xf8, 0x24, 0x4f, + 0x22, 0xaf, 0x0c, 0x0b, 0x82, 0xee, 0x57, 0x82, 0x9f, 0x42, 0x85, 0xcf, 0xe1, 0xab, 0x36, 0x02, + 0x2f, 0xc2, 0xc2, 0x91, 0x65, 0x8c, 0xdc, 0x0b, 0x5b, 0x66, 0x37, 0x3a, 0xe9, 0x7a, 0x40, 0x9b, + 0x49, 0xe3, 0xbb, 0xb0, 0xe0, 0x90, 0xa1, 0x61, 0x5a, 0xa6, 0x75, 0x7e, 0x72, 0x7a, 0xed, 0x11, + 0x57, 0xbc, 0x30, 0xd5, 0x7c, 0xf2, 0x33, 0x4a, 0xa5, 0xa6, 0x9d, 0x0e, 0xec, 0x53, 0x11, 0xe6, + 0xd8, 0x33, 0xfe, 0x59, 0x06, 0x2a, 0x9f, 0x18, 0x5e, 0x4f, 0x2e, 0x1d, 0xda, 0x85, 0x9a, 0x1f, + 0xdc, 0x18, 0x45, 0xd8, 0x12, 0x49, 0xb1, 0x6c, 0x8c, 0x2c, 0xa5, 0x65, 0x76, 0xac, 0xf6, 0x54, + 0x02, 0x13, 0x65, 0x58, 0x3d, 0x32, 0xf0, 0x45, 0x65, 0xd2, 0x45, 0x31, 0x46, 0x55, 0x94, 0x4a, + 0x40, 0x1d, 0xa8, 0x8f, 0x1c, 0xfb, 0xdc, 0x21, 0xae, 0xeb, 0x0b, 0xe3, 0x69, 0x0c, 0x27, 0x08, + 0x3b, 0x14, 0xac, 0x81, 0xb8, 0x85, 0x51, 0x98, 0xf4, 0x6c, 0x21, 0xa8, 0x67, 0x78, 0x70, 0xfa, + 0xaf, 0x0c, 0xa0, 0xf8, 0xa4, 0x7e, 0xd3, 0x12, 0xef, 0x11, 0xd4, 0x5c, 0xcf, 0x70, 0x62, 0x9b, + 0xad, 0xca, 0xa8, 0x7e, 0xc4, 0x7f, 0x17, 0x7c, 0x83, 0x4e, 0x2c, 0xdb, 0x33, 0xcf, 0xae, 0x45, + 0x95, 0x5c, 0x93, 0xe4, 0x03, 0x46, 0x45, 0x6d, 0x28, 0x9c, 0x99, 0x03, 0x8f, 0x38, 0x6e, 0x23, + 0xb7, 0x96, 0x5d, 0xaf, 0x6d, 0x3e, 0xb9, 0x69, 0x19, 0x36, 0x3e, 0x64, 0xfc, 0xdd, 0xeb, 0x11, + 0xd1, 0xe5, 0x58, 0xb5, 0xf2, 0xcc, 0x87, 0xaa, 0xf1, 0x3b, 0x50, 0x7c, 0x4d, 0x45, 0xd0, 0xb7, + 0xec, 0x02, 0x2f, 0x16, 0x59, 0x9b, 0xbf, 0x64, 0x9f, 0x39, 0xc6, 0xf9, 0x90, 0x58, 0x9e, 0x7c, + 0x0f, 0x94, 0x6d, 0xfc, 0x08, 0x20, 0x50, 0x43, 0x43, 0xfe, 0x41, 0xe7, 0xf0, 0xb8, 0x5b, 0x9f, + 0x43, 0x15, 0x28, 0x1e, 0x74, 0x76, 0xda, 0xfb, 0x6d, 0x9a, 0x1f, 0x70, 0x4b, 0xba, 0x34, 0xb4, + 0x96, 0xaa, 0x4e, 0x2d, 0xa4, 0x13, 0xdf, 0x82, 0xe5, 0xa4, 0x05, 0xa4, 0xb5, 0x68, 0x55, 0xec, + 0xd2, 0x99, 0x8e, 0x8a, 0xaa, 0x3a, 0x13, 0x9e, 0x6e, 0x03, 0x0a, 0x7c, 0xf7, 0xf6, 0x45, 0x71, + 0x2e, 0x9b, 0xd4, 0x11, 0x7c, 0x33, 0x92, 0xbe, 0x58, 0x25, 0xbf, 0x9d, 0x18, 0x5e, 0x72, 0x89, + 0xe1, 0x05, 0x3d, 0x84, 0xaa, 0x7f, 0x1a, 0x0c, 0x57, 0xd4, 0x02, 0x25, 0xbd, 0x22, 0x37, 0x3a, + 0xa5, 0x85, 0x9c, 0x5e, 0x08, 0x3b, 0x1d, 0x3d, 0x82, 0x3c, 0x99, 0x10, 0xcb, 0x73, 0x1b, 0x65, + 0x96, 0x31, 0xaa, 0xb2, 0x76, 0x6f, 0x53, 0xaa, 0x2e, 0x3a, 0xf1, 0xf7, 0x60, 0x91, 0xbd, 0x23, + 0x3d, 0x77, 0x0c, 0x4b, 0x7d, 0x99, 0xeb, 0x76, 0xf7, 0x85, 0xbb, 0xe9, 0x23, 0xaa, 0x41, 0x66, + 0x77, 0x47, 0x38, 0x21, 0xb3, 0xbb, 0x83, 0x7f, 0xa2, 0x01, 0x52, 0xc7, 0xcd, 0xe4, 0xe7, 0x88, + 0x70, 0xa9, 0x3e, 0x1b, 0xa8, 0x5f, 0x86, 0x1c, 0x71, 0x1c, 0xdb, 0x61, 0x1e, 0x2d, 0xe9, 0xbc, + 0x81, 0xdf, 0x16, 0x36, 0xe8, 0x64, 0x62, 0x5f, 0xfa, 0x67, 0x90, 0x4b, 0xd3, 0x7c, 0x53, 0xf7, + 0x60, 0x29, 0xc4, 0x35, 0x53, 0xe6, 0xfa, 0x10, 0x16, 0x98, 0xb0, 0xed, 0x0b, 0xd2, 0xbb, 0x1c, + 0xd9, 0xa6, 0x15, 0xd3, 0x47, 0x57, 0x2e, 0x08, 0xb0, 0x74, 0x1e, 0x7c, 0x62, 0x15, 0x9f, 0xd8, + 0xed, 0xee, 0xe3, 0xcf, 0xe0, 0x56, 0x44, 0x8e, 0x34, 0xff, 0x0f, 0xa1, 0xdc, 0xf3, 0x89, 0xae, + 0xa8, 0x75, 0xee, 0x87, 0x8d, 0x8b, 0x0e, 0x55, 0x47, 0xe0, 0x0e, 0xdc, 0x8e, 0x89, 0x9e, 0x69, + 0xce, 0xef, 0xc2, 0x0a, 0x13, 0xb8, 0x47, 0xc8, 0x68, 0x6b, 0x60, 0x4e, 0x52, 0x3d, 0x3d, 0x12, + 0x93, 0x52, 0x18, 0xbf, 0xde, 0x7d, 0x81, 0x7f, 0x5f, 0x68, 0xec, 0x9a, 0x43, 0xd2, 0xb5, 0xf7, + 0xd3, 0x6d, 0xa3, 0xd9, 0xec, 0x92, 0x5c, 0xbb, 0xa2, 0xac, 0x61, 0xcf, 0xf8, 0x1f, 0x35, 0xe1, + 0x2a, 0x75, 0xf8, 0xd7, 0xbc, 0x93, 0x57, 0x01, 0xce, 0xe9, 0x91, 0x21, 0x7d, 0xda, 0xc1, 0x11, + 0x15, 0x85, 0xe2, 0xdb, 0x49, 0xe3, 0x77, 0x45, 0xd8, 0xb9, 0x2c, 0xf6, 0x39, 0xfb, 0xe3, 0x47, + 0xb9, 0xfb, 0x50, 0x66, 0x84, 0x23, 0xcf, 0xf0, 0xc6, 0x6e, 0x6c, 0x31, 0xfe, 0x5c, 0x6c, 0x7b, + 0x39, 0x68, 0xa6, 0x79, 0x7d, 0x1b, 0xf2, 0xec, 0x65, 0x42, 0x96, 0xd2, 0x77, 0x12, 0xf6, 0x23, + 0xb7, 0x43, 0x17, 0x8c, 0xf8, 0x67, 0x1a, 0xe4, 0x5f, 0x32, 0x08, 0x56, 0x31, 0x6d, 0x5e, 0xae, + 0x85, 0x65, 0x0c, 0x39, 0x30, 0x54, 0xd2, 0xd9, 0x33, 0x2b, 0x3d, 0x09, 0x71, 0x8e, 0xf5, 0x7d, + 0x5e, 0xe2, 0x96, 0x74, 0xbf, 0x4d, 0x7d, 0xd6, 0x1b, 0x98, 0xc4, 0xf2, 0x58, 0xef, 0x3c, 0xeb, + 0x55, 0x28, 0xb4, 0x7a, 0x36, 0xdd, 0x7d, 0x62, 0x38, 0x96, 0x00, 0x4d, 0x8b, 0x7a, 0x40, 0xc0, + 0xfb, 0x50, 0xe7, 0x76, 0x6c, 0xf5, 0xfb, 0x4a, 0x81, 0xe9, 0x6b, 0xd3, 0x22, 0xda, 0x42, 0xd2, + 0x32, 0x51, 0x69, 0xff, 0xa4, 0xc1, 0xa2, 0x22, 0x6e, 0x26, 0xaf, 0xbe, 0x0f, 0x79, 0x0e, 0x52, + 0x8b, 0x4a, 0x67, 0x39, 0x3c, 0x8a, 0xab, 0xd1, 0x05, 0x0f, 0xda, 0x80, 0x02, 0x7f, 0x92, 0xef, + 0x00, 0xc9, 0xec, 0x92, 0x09, 0x3f, 0x82, 0x25, 0x41, 0x22, 0x43, 0x3b, 0xe9, 0x60, 0xb0, 0xc5, + 0xc0, 0x7f, 0x0a, 0xcb, 0x61, 0xb6, 0x99, 0xa6, 0xa4, 0x18, 0x99, 0x79, 0x13, 0x23, 0xb7, 0xa4, + 0x91, 0xc7, 0xa3, 0xbe, 0x52, 0x47, 0x45, 0x77, 0x8c, 0xba, 0x5e, 0x99, 0xf0, 0x7a, 0x05, 0x13, + 0x90, 0x22, 0xbe, 0xd1, 0x09, 0x2c, 0xc9, 0xed, 0xb0, 0x6f, 0xba, 0x7e, 0xb9, 0xfe, 0x05, 0x20, + 0x95, 0xf8, 0x8d, 0x1a, 0xf4, 0x8e, 0x74, 0xc7, 0xa1, 0x63, 0x0f, 0xed, 0x54, 0x97, 0xe2, 0x3f, + 0x83, 0x95, 0x08, 0xdf, 0x37, 0xed, 0xb7, 0x1d, 0x22, 0x8b, 0x15, 0xe9, 0xb7, 0x8f, 0x00, 0xa9, + 0xc4, 0x99, 0xb2, 0x56, 0x0b, 0x16, 0x5f, 0xda, 0x13, 0x1a, 0xfe, 0x28, 0x35, 0x38, 0xf7, 0x1c, + 0x63, 0xf0, 0x5d, 0xe1, 0xb7, 0xa9, 0x72, 0x75, 0xc0, 0x4c, 0xca, 0xff, 0x43, 0x83, 0xca, 0xd6, + 0xc0, 0x70, 0x86, 0x52, 0xf1, 0xf7, 0x21, 0xcf, 0xdf, 0x9c, 0x05, 0x58, 0xf5, 0x4e, 0x58, 0x8c, + 0xca, 0xcb, 0x1b, 0x5b, 0xfc, 0x3d, 0x5b, 0x8c, 0xa2, 0x86, 0x8b, 0xef, 0x59, 0x3b, 0x91, 0xef, + 0x5b, 0x3b, 0xe8, 0x03, 0xc8, 0x19, 0x74, 0x08, 0x4b, 0x33, 0xb5, 0x28, 0x66, 0xc1, 0xa4, 0xb1, + 0xfa, 0x9e, 0x73, 0xe1, 0xef, 0x42, 0x59, 0xd1, 0x80, 0x0a, 0x90, 0x7d, 0xde, 0x16, 0xc5, 0xf8, + 0xd6, 0x76, 0x77, 0xf7, 0x15, 0x07, 0x6b, 0x6a, 0x00, 0x3b, 0x6d, 0xbf, 0x9d, 0xc1, 0x9f, 0x8a, + 0x51, 0x22, 0xa4, 0xab, 0xf6, 0x68, 0x69, 0xf6, 0x64, 0xde, 0xc8, 0x9e, 0x2b, 0xa8, 0x8a, 0xe9, + 0xcf, 0x9a, 0xa2, 0x98, 0xbc, 0x94, 0x14, 0xa5, 0x18, 0xaf, 0x0b, 0x46, 0xbc, 0x00, 0x55, 0x91, + 0xb4, 0xc4, 0xfe, 0xfb, 0xf7, 0x0c, 0xd4, 0x24, 0x65, 0x56, 0x50, 0x5d, 0xe2, 0x81, 0x3c, 0xc9, + 0xf9, 0x68, 0xe0, 0x2d, 0xc8, 0xf7, 0x4f, 0x8f, 0xcc, 0x2f, 0xe4, 0x07, 0x10, 0xd1, 0xa2, 0xf4, + 0x01, 0xd7, 0xc3, 0xbf, 0x42, 0x8a, 0x16, 0xcd, 0x46, 0x8e, 0x71, 0xe6, 0xed, 0x5a, 0x7d, 0x72, + 0xc5, 0x72, 0xdb, 0xbc, 0x1e, 0x10, 0x18, 0x50, 0x22, 0xbe, 0x56, 0xb2, 0x17, 0x04, 0xe5, 0xeb, + 0x25, 0x7a, 0x0c, 0x75, 0xfa, 0xbc, 0x35, 0x1a, 0x0d, 0x4c, 0xd2, 0xe7, 0x02, 0x0a, 0x8c, 0x27, + 0x46, 0xa7, 0xda, 0x59, 0x49, 0xed, 0x36, 0x8a, 0x2c, 0xba, 0x8a, 0x16, 0x5a, 0x83, 0x32, 0xb7, + 0x6f, 0xd7, 0x3a, 0x76, 0x09, 0xfb, 0x84, 0x97, 0xd5, 0x55, 0x52, 0x38, 0x5b, 0x42, 0x34, 0x5b, + 0x2e, 0xc1, 0xe2, 0xd6, 0xd8, 0xbb, 0x68, 0x5b, 0xc6, 0xe9, 0x40, 0x46, 0x22, 0x5a, 0xce, 0x50, + 0xe2, 0x8e, 0xe9, 0xaa, 0xd4, 0x36, 0x2c, 0x51, 0x2a, 0xb1, 0x3c, 0xb3, 0xa7, 0x64, 0x02, 0x59, + 0x2b, 0x68, 0x91, 0x5a, 0xc1, 0x70, 0xdd, 0xd7, 0xb6, 0xd3, 0x17, 0xee, 0xf5, 0xdb, 0x78, 0x87, + 0x0b, 0x3f, 0x76, 0x43, 0xf9, 0xfe, 0x37, 0x95, 0xb2, 0x1e, 0x48, 0x79, 0x4e, 0xbc, 0x29, 0x52, + 0xf0, 0x13, 0x58, 0x91, 0x9c, 0x02, 0xf4, 0x9e, 0xc2, 0xdc, 0x81, 0xfb, 0x92, 0x79, 0xfb, 0xc2, + 0xb0, 0xce, 0xc9, 0xa1, 0x50, 0xf8, 0xdb, 0xda, 0xf9, 0x0c, 0x1a, 0xbe, 0x9d, 0xec, 0x45, 0xcc, + 0x1e, 0xa8, 0x06, 0x8c, 0x5d, 0xb1, 0x6f, 0x4b, 0x3a, 0x7b, 0xa6, 0x34, 0xc7, 0x1e, 0xf8, 0x95, + 0x17, 0x7d, 0xc6, 0xdb, 0x70, 0x47, 0xca, 0x10, 0xaf, 0x48, 0x61, 0x21, 0x31, 0x83, 0x92, 0x84, + 0x08, 0x87, 0xd1, 0xa1, 0xd3, 0xdd, 0xae, 0x72, 0x86, 0x5d, 0xcb, 0x64, 0x6a, 0x8a, 0xcc, 0x15, + 0xbe, 0x23, 0xa8, 0x61, 0x6a, 0x72, 0x15, 0x64, 0x2a, 0x40, 0x25, 0x8b, 0x85, 0xa0, 0xe4, 0xd8, + 0x42, 0xc4, 0x44, 0xff, 0x10, 0x56, 0x7d, 0x23, 0xa8, 0xdf, 0x0e, 0x89, 0x33, 0x34, 0x5d, 0x57, + 0x81, 0x49, 0x93, 0x26, 0xfe, 0x0e, 0xcc, 0x8f, 0x88, 0x88, 0x6b, 0xe5, 0x4d, 0xb4, 0xc1, 0xef, + 0x35, 0x6c, 0x28, 0x83, 0x59, 0x3f, 0xee, 0xc3, 0x03, 0x29, 0x9d, 0x7b, 0x34, 0x51, 0x7c, 0xd4, + 0x28, 0x09, 0x1e, 0x65, 0x52, 0xc0, 0xa3, 0x6c, 0x04, 0xba, 0xff, 0x88, 0x3b, 0x52, 0x9e, 0xad, + 0x99, 0xf2, 0xd5, 0x1e, 0xf7, 0xa9, 0x7f, 0x24, 0x67, 0x12, 0x76, 0x0a, 0xcb, 0xe1, 0x93, 0x3c, + 0x53, 0x28, 0x5d, 0x86, 0x9c, 0x67, 0x5f, 0x12, 0x19, 0x48, 0x79, 0x43, 0x1a, 0xec, 0x1f, 0xf3, + 0x99, 0x0c, 0x36, 0x02, 0x61, 0x6c, 0x4b, 0xce, 0x6a, 0x2f, 0x5d, 0x4d, 0x59, 0xa7, 0xf2, 0x06, + 0x3e, 0x80, 0x5b, 0xd1, 0x30, 0x31, 0x93, 0xc9, 0xaf, 0xf8, 0x06, 0x4e, 0x8a, 0x24, 0x33, 0xc9, + 0xfd, 0x38, 0x08, 0x06, 0x4a, 0x40, 0x99, 0x49, 0xa4, 0x0e, 0xcd, 0xa4, 0xf8, 0xf2, 0x55, 0xec, + 0x57, 0x3f, 0xdc, 0xcc, 0x24, 0xcc, 0x0d, 0x84, 0xcd, 0xbe, 0xfc, 0x41, 0x8c, 0xc8, 0x4e, 0x8d, + 0x11, 0xe2, 0x90, 0x04, 0x51, 0xec, 0x6b, 0xd8, 0x74, 0x42, 0x47, 0x10, 0x40, 0x67, 0xd5, 0x41, + 0x73, 0x88, 0xaf, 0x83, 0x35, 0xe4, 0xc6, 0x56, 0xc3, 0xee, 0x4c, 0x8b, 0xf1, 0x49, 0x10, 0x3b, + 0x63, 0x91, 0x79, 0x26, 0xc1, 0x9f, 0xc2, 0x5a, 0x7a, 0x50, 0x9e, 0x45, 0xf2, 0xe3, 0x16, 0x94, + 0xfc, 0xa2, 0x56, 0xb9, 0x13, 0x54, 0x86, 0xc2, 0x41, 0xe7, 0xe8, 0x70, 0x6b, 0xbb, 0xcd, 0x2f, + 0x05, 0x6d, 0x77, 0x74, 0xfd, 0xf8, 0xb0, 0x5b, 0xcf, 0x6c, 0xfe, 0x2a, 0x0b, 0x99, 0xbd, 0x57, + 0xe8, 0x33, 0xc8, 0xf1, 0x2f, 0xe4, 0x53, 0xae, 0x45, 0x34, 0xa7, 0x5d, 0x02, 0xc0, 0xb7, 0x7f, + 0xf2, 0xdf, 0xbf, 0xfa, 0x45, 0x66, 0x11, 0x57, 0x5a, 0x93, 0xef, 0xb4, 0x2e, 0x27, 0x2d, 0x96, + 0x1b, 0x9e, 0x6a, 0x8f, 0xd1, 0xc7, 0x90, 0x3d, 0x1c, 0x7b, 0x28, 0xf5, 0xba, 0x44, 0x33, 0xfd, + 0x5e, 0x00, 0x5e, 0x61, 0x42, 0x17, 0x30, 0x08, 0xa1, 0xa3, 0xb1, 0x47, 0x45, 0xfe, 0x08, 0xca, + 0xea, 0x57, 0xfd, 0x1b, 0xef, 0x50, 0x34, 0x6f, 0xbe, 0x31, 0x80, 0xef, 0x33, 0x55, 0xb7, 0x31, + 0x12, 0xaa, 0xf8, 0xbd, 0x03, 0x75, 0x16, 0xdd, 0x2b, 0x0b, 0xa5, 0xde, 0xb0, 0x68, 0xa6, 0x5f, + 0x22, 0x88, 0xcd, 0xc2, 0xbb, 0xb2, 0xa8, 0xc8, 0x3f, 0x16, 0xf7, 0x07, 0x7a, 0x1e, 0x7a, 0x90, + 0xf0, 0xfd, 0x58, 0xfd, 0x52, 0xda, 0x5c, 0x4b, 0x67, 0x10, 0x4a, 0xee, 0x31, 0x25, 0xb7, 0xf0, + 0xa2, 0x50, 0xd2, 0xf3, 0x59, 0x9e, 0x6a, 0x8f, 0x37, 0x7b, 0x90, 0x63, 0x5f, 0x21, 0xd0, 0xe7, + 0xf2, 0xa1, 0x99, 0xf0, 0x39, 0x26, 0x65, 0xa1, 0x43, 0xdf, 0x2f, 0xf0, 0x32, 0x53, 0x54, 0xc3, + 0x25, 0xaa, 0x88, 0x7d, 0x83, 0x78, 0xaa, 0x3d, 0x5e, 0xd7, 0xbe, 0xa5, 0x6d, 0xfe, 0x73, 0x0e, + 0x72, 0x0c, 0x7e, 0x43, 0x97, 0x00, 0x01, 0x22, 0x1f, 0x9d, 0x5d, 0x0c, 0xe3, 0x8f, 0xce, 0x2e, + 0x0e, 0xe6, 0xe3, 0x26, 0x53, 0xba, 0x8c, 0x17, 0xa8, 0x52, 0x86, 0xea, 0xb5, 0x18, 0x50, 0x49, + 0xfd, 0xf8, 0x57, 0x9a, 0x40, 0x1f, 0xf9, 0x59, 0x42, 0x49, 0xd2, 0x42, 0xb0, 0x7c, 0x74, 0x3b, + 0x24, 0x40, 0xf2, 0xf8, 0x7b, 0x4c, 0x61, 0x0b, 0xd7, 0x03, 0x85, 0x0e, 0xe3, 0x78, 0xaa, 0x3d, + 0xfe, 0xbc, 0x81, 0x97, 0x84, 0x97, 0x23, 0x3d, 0xe8, 0xc7, 0x50, 0x0b, 0xc3, 0xce, 0xe8, 0x61, + 0x82, 0xae, 0x28, 0x7a, 0xdd, 0x7c, 0x7b, 0x3a, 0x93, 0xb0, 0x69, 0x95, 0xd9, 0x24, 0x94, 0x73, + 0xcd, 0x97, 0x84, 0x8c, 0x0c, 0xca, 0x24, 0xd6, 0x00, 0xfd, 0xbd, 0x26, 0xbe, 0x0a, 0x04, 0x38, + 0x32, 0x4a, 0x92, 0x1e, 0x43, 0xa9, 0x9b, 0x8f, 0x6e, 0xe0, 0x12, 0x46, 0xfc, 0x01, 0x33, 0xe2, + 0x77, 0xf1, 0x72, 0x60, 0x84, 0x67, 0x0e, 0x89, 0x67, 0x0b, 0x2b, 0x3e, 0xbf, 0x87, 0x6f, 0x87, + 0x9c, 0x13, 0xea, 0x0d, 0x16, 0x8b, 0x63, 0xc1, 0x89, 0x8b, 0x15, 0xc2, 0x96, 0x13, 0x17, 0x2b, + 0x0c, 0x24, 0x27, 0x2d, 0x16, 0x47, 0x7e, 0x93, 0x16, 0xcb, 0xef, 0xd9, 0xfc, 0xff, 0x79, 0x28, + 0x6c, 0xf3, 0x7b, 0xbb, 0xc8, 0x86, 0x92, 0x0f, 0xa5, 0xa2, 0xd5, 0x24, 0xbc, 0x28, 0x78, 0x97, + 0x68, 0x3e, 0x48, 0xed, 0x17, 0x06, 0xbd, 0xc5, 0x0c, 0xba, 0x8b, 0x6f, 0x51, 0xcd, 0xe2, 0x6a, + 0x70, 0x8b, 0x83, 0x12, 0x2d, 0xa3, 0xdf, 0xa7, 0x8e, 0xf8, 0x13, 0xa8, 0xa8, 0x58, 0x27, 0x7a, + 0x2b, 0x11, 0xa3, 0x52, 0xe1, 0xd2, 0x26, 0x9e, 0xc6, 0x22, 0x34, 0xbf, 0xcd, 0x34, 0xaf, 0xe2, + 0x3b, 0x09, 0x9a, 0x1d, 0xc6, 0x1a, 0x52, 0xce, 0x71, 0xca, 0x64, 0xe5, 0x21, 0x18, 0x34, 0x59, + 0x79, 0x18, 0xe6, 0x9c, 0xaa, 0x7c, 0xcc, 0x58, 0xa9, 0x72, 0x17, 0x20, 0x40, 0x24, 0x51, 0xa2, + 0x2f, 0x95, 0x97, 0xa9, 0x68, 0x70, 0x88, 0x83, 0x99, 0x18, 0x33, 0xb5, 0x62, 0xdf, 0x45, 0xd4, + 0x0e, 0x4c, 0xd7, 0xe3, 0x07, 0xb3, 0x1a, 0x82, 0x18, 0x51, 0xe2, 0x7c, 0xc2, 0x38, 0x65, 0xf3, + 0xe1, 0x54, 0x1e, 0xa1, 0xfd, 0x11, 0xd3, 0xfe, 0x00, 0x37, 0x13, 0xb4, 0x8f, 0x38, 0x2f, 0xdd, + 0x6c, 0x7f, 0x9d, 0x87, 0xf2, 0x4b, 0xc3, 0xb4, 0x3c, 0x62, 0x19, 0x56, 0x8f, 0xa0, 0x53, 0xc8, + 0xb1, 0x4c, 0x1d, 0x0d, 0xc4, 0x2a, 0xfc, 0x16, 0x0d, 0xc4, 0x21, 0x6c, 0x0a, 0xaf, 0x31, 0xc5, + 0x4d, 0xbc, 0x42, 0x15, 0x0f, 0x03, 0xd1, 0x2d, 0x06, 0x29, 0xd1, 0x49, 0x9f, 0x41, 0x5e, 0x7c, + 0x91, 0x89, 0x08, 0x0a, 0x41, 0x4d, 0xcd, 0x7b, 0xc9, 0x9d, 0x49, 0x7b, 0x59, 0x55, 0xe3, 0x32, + 0x3e, 0xaa, 0x67, 0x02, 0x10, 0x60, 0xa5, 0xd1, 0x15, 0x8d, 0x41, 0xab, 0xcd, 0xb5, 0x74, 0x86, + 0x24, 0x9f, 0xaa, 0x3a, 0xfb, 0x3e, 0x2f, 0xd5, 0xfb, 0x47, 0x30, 0xff, 0xc2, 0x70, 0x2f, 0x50, + 0x24, 0xf7, 0x2a, 0xf7, 0x79, 0x9a, 0xcd, 0xa4, 0x2e, 0xa1, 0xe5, 0x01, 0xd3, 0x72, 0x87, 0x87, + 0x32, 0x55, 0xcb, 0x85, 0xe1, 0xd2, 0xa4, 0x86, 0xfa, 0x90, 0xe7, 0xd7, 0x7b, 0xa2, 0xfe, 0x0b, + 0x5d, 0x11, 0x8a, 0xfa, 0x2f, 0x7c, 0x23, 0xe8, 0x66, 0x2d, 0x23, 0x28, 0xca, 0xfb, 0x34, 0x28, + 0xf2, 0x71, 0x35, 0x72, 0xf7, 0xa6, 0xb9, 0x9a, 0xd6, 0x2d, 0x74, 0x3d, 0x64, 0xba, 0xee, 0xe3, + 0x46, 0x6c, 0xad, 0x04, 0xe7, 0x53, 0xed, 0xf1, 0xb7, 0x34, 0xf4, 0x63, 0x80, 0x00, 0x5e, 0x8e, + 0x9d, 0xc0, 0x28, 0x52, 0x1d, 0x3b, 0x81, 0x31, 0x64, 0x1a, 0x6f, 0x30, 0xbd, 0xeb, 0xf8, 0x61, + 0x54, 0xaf, 0xe7, 0x18, 0x96, 0x7b, 0x46, 0x9c, 0x0f, 0x38, 0x84, 0xe8, 0x5e, 0x98, 0x23, 0x7a, + 0x18, 0xfe, 0x75, 0x01, 0xe6, 0x69, 0x05, 0x4c, 0x0b, 0x85, 0x00, 0x38, 0x88, 0x5a, 0x12, 0x83, + 0xeb, 0xa2, 0x96, 0xc4, 0x31, 0x87, 0x70, 0xa1, 0xc0, 0x7e, 0xf1, 0x41, 0x18, 0x03, 0x75, 0xb4, + 0x0d, 0x65, 0x05, 0x59, 0x40, 0x09, 0xc2, 0xc2, 0x38, 0x60, 0x34, 0xf5, 0x24, 0xc0, 0x12, 0xf8, + 0x2e, 0xd3, 0xb7, 0xc2, 0x53, 0x0f, 0xd3, 0xd7, 0xe7, 0x1c, 0x54, 0xe1, 0x6b, 0xa8, 0xa8, 0xe8, + 0x03, 0x4a, 0x90, 0x17, 0xc1, 0x18, 0xa3, 0x61, 0x36, 0x09, 0xbc, 0x08, 0x1f, 0x7c, 0xff, 0x57, + 0x2d, 0x92, 0x8d, 0x2a, 0x1e, 0x40, 0x41, 0xc0, 0x11, 0x49, 0xb3, 0x0c, 0x03, 0x92, 0x49, 0xb3, + 0x8c, 0x60, 0x19, 0xe1, 0xe2, 0x92, 0x69, 0xa4, 0x6f, 0x5c, 0x32, 0x95, 0x09, 0x6d, 0xcf, 0x89, + 0x97, 0xa6, 0x2d, 0x40, 0xd7, 0xd2, 0xb4, 0x29, 0x6f, 0xbb, 0x69, 0xda, 0xce, 0x89, 0x27, 0x8e, + 0x8b, 0x7c, 0x8b, 0x44, 0x29, 0xc2, 0xd4, 0xf4, 0x81, 0xa7, 0xb1, 0x24, 0xd5, 0xfe, 0x81, 0x42, + 0x99, 0x3b, 0xae, 0x00, 0x02, 0xb0, 0x24, 0x5a, 0xd0, 0x25, 0x22, 0xae, 0xd1, 0x82, 0x2e, 0x19, + 0x6f, 0x09, 0x87, 0x86, 0x40, 0x2f, 0x7f, 0xf5, 0xa0, 0x9a, 0x7f, 0xae, 0x01, 0x8a, 0xe3, 0x2a, + 0xe8, 0x49, 0xb2, 0xf4, 0x44, 0x1c, 0xb7, 0xf9, 0xfe, 0x9b, 0x31, 0x27, 0x45, 0xfb, 0xc0, 0xa4, + 0x1e, 0xe3, 0x1e, 0xbd, 0xa6, 0x46, 0xfd, 0x85, 0x06, 0xd5, 0x10, 0x28, 0x83, 0xde, 0x49, 0x59, + 0xd3, 0x08, 0x0c, 0xdc, 0x7c, 0xf7, 0x46, 0xbe, 0xa4, 0x4a, 0x57, 0xd9, 0x01, 0xb2, 0xe4, 0xff, + 0xa9, 0x06, 0xb5, 0x30, 0x88, 0x83, 0x52, 0x64, 0xc7, 0x60, 0xe4, 0xe6, 0xfa, 0xcd, 0x8c, 0xd3, + 0x97, 0x27, 0xa8, 0xf6, 0x07, 0x50, 0x10, 0xb0, 0x4f, 0xd2, 0xc6, 0x0f, 0x03, 0xd0, 0x49, 0x1b, + 0x3f, 0x82, 0x19, 0x25, 0x6c, 0x7c, 0xc7, 0x1e, 0x10, 0xe5, 0x98, 0x09, 0x5c, 0x28, 0x4d, 0xdb, + 0xf4, 0x63, 0x16, 0x01, 0x95, 0xd2, 0xb4, 0x05, 0xc7, 0x4c, 0x02, 0x42, 0x28, 0x45, 0xd8, 0x0d, + 0xc7, 0x2c, 0x8a, 0x27, 0x25, 0x1c, 0x33, 0xa6, 0x50, 0x39, 0x66, 0x01, 0x74, 0x93, 0x74, 0xcc, + 0x62, 0x78, 0x7a, 0xd2, 0x31, 0x8b, 0xa3, 0x3f, 0x09, 0xeb, 0xc8, 0xf4, 0x86, 0x8e, 0xd9, 0x52, + 0x02, 0xca, 0x83, 0xde, 0x4f, 0x71, 0x62, 0x22, 0x4c, 0xdf, 0xfc, 0xe0, 0x0d, 0xb9, 0x53, 0xf7, + 0x38, 0x77, 0xbf, 0xdc, 0xe3, 0x7f, 0xa3, 0xc1, 0x72, 0x12, 0x42, 0x84, 0x52, 0xf4, 0xa4, 0xc0, + 0xfb, 0xcd, 0x8d, 0x37, 0x65, 0x9f, 0xee, 0x2d, 0x7f, 0xd7, 0x3f, 0xab, 0xff, 0xdb, 0x97, 0xab, + 0xda, 0x7f, 0x7e, 0xb9, 0xaa, 0xfd, 0xcf, 0x97, 0xab, 0xda, 0xdf, 0xfe, 0xef, 0xea, 0xdc, 0x69, + 0x9e, 0xfd, 0x56, 0xf2, 0x3b, 0xbf, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xad, 0x27, 0xa6, 0xe1, 0xb2, + 0x39, 0x00, 0x00, } diff --git a/etcdserver/etcdserverpb/rpc.proto b/etcdserver/etcdserverpb/rpc.proto index 81c306b3f06..78ecd48ade5 100644 --- a/etcdserver/etcdserverpb/rpc.proto +++ b/etcdserver/etcdserverpb/rpc.proto @@ -165,6 +165,14 @@ service Cluster { body: "*" }; } + + // MemberPromote promotes a member from raft learner (non-voting) to raft voting member. + rpc MemberPromote(MemberPromoteRequest) returns (MemberPromoteResponse) { + option (google.api.http) = { + post: "/v3/cluster/member/promote" + body: "*" + }; + } } service Maintenance { @@ -846,11 +854,15 @@ message Member { repeated string peerURLs = 3; // clientURLs is the list of URLs the member exposes to clients for communication. If the member is not started, clientURLs will be empty. repeated string clientURLs = 4; + // isLearner indicates if the member is raft learner. + bool isLearner = 5; } message MemberAddRequest { // peerURLs is the list of URLs the added member will use to communicate with the cluster. repeated string peerURLs = 1; + // isLearner indicates if the added member is raft learner. + bool isLearner = 2; } message MemberAddResponse { @@ -894,6 +906,17 @@ message MemberListResponse { repeated Member members = 2; } +message MemberPromoteRequest { + // ID is the member ID of the member to promote. + uint64 ID = 1; +} + +message MemberPromoteResponse { + ResponseHeader header = 1; + // members is a list of all members after promoting the member. + repeated Member members = 2; +} + message DefragmentRequest { } @@ -967,6 +990,8 @@ message StatusResponse { repeated string errors = 8; // dbSizeInUse is the size of the backend database logically in use, in bytes, of the responding member. int64 dbSizeInUse = 9; + // isLearner indicates if the member is raft learner. + bool isLearner = 10; } message AuthEnableRequest { From a0d3c4d6417fc5ca573ee8ddf7c979c4c4e57b76 Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Thu, 14 Mar 2019 20:39:21 -0700 Subject: [PATCH 2/9] *: fix compilation after API change Fixed compilation erros after API change for learner. --- clientv3/cluster.go | 20 ++++++++++++++----- clientv3/retry.go | 4 ++++ etcdserver/api/v3rpc/member.go | 6 ++++++ .../adapter/cluster_client_adapter.go | 4 ++++ proxy/grpcproxy/cluster.go | 6 ++++++ 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/clientv3/cluster.go b/clientv3/cluster.go index c8680558b1c..6d612cf6d9c 100644 --- a/clientv3/cluster.go +++ b/clientv3/cluster.go @@ -20,15 +20,17 @@ import ( pb "go.etcd.io/etcd/v3/etcdserver/etcdserverpb" "go.etcd.io/etcd/v3/pkg/types" + "errors" "google.golang.org/grpc" ) type ( - Member pb.Member - MemberListResponse pb.MemberListResponse - MemberAddResponse pb.MemberAddResponse - MemberRemoveResponse pb.MemberRemoveResponse - MemberUpdateResponse pb.MemberUpdateResponse + Member pb.Member + MemberListResponse pb.MemberListResponse + MemberAddResponse pb.MemberAddResponse + MemberRemoveResponse pb.MemberRemoveResponse + MemberUpdateResponse pb.MemberUpdateResponse + MemberPromoteResponse pb.MemberPromoteResponse ) type Cluster interface { @@ -43,6 +45,9 @@ type Cluster interface { // MemberUpdate updates the peer addresses of the member. MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error) + + // MemberPromote promotes a member from raft learner (non-voting) to raft voting member. + MemberPromote(ctx context.Context, id uint64) (*MemberPromoteResponse, error) } type cluster struct { @@ -112,3 +117,8 @@ func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) { } return nil, toErr(ctx, err) } + +func (c *cluster) MemberPromote(ctx context.Context, id uint64) (*MemberPromoteResponse, error) { + // TODO: implement + return nil, errors.New("not implemented") +} diff --git a/clientv3/retry.go b/clientv3/retry.go index 6a1f238742c..df69e918d39 100644 --- a/clientv3/retry.go +++ b/clientv3/retry.go @@ -183,6 +183,10 @@ func (rcc *retryClusterClient) MemberUpdate(ctx context.Context, in *pb.MemberUp return rcc.cc.MemberUpdate(ctx, in, opts...) } +func (rcc *retryClusterClient) MemberPromote(ctx context.Context, in *pb.MemberPromoteRequest, opts ...grpc.CallOption) (resp *pb.MemberPromoteResponse, err error) { + return rcc.cc.MemberPromote(ctx, in, opts...) +} + type retryMaintenanceClient struct { mc pb.MaintenanceClient } diff --git a/etcdserver/api/v3rpc/member.go b/etcdserver/api/v3rpc/member.go index 914cf1e92b1..d1bd00b0c35 100644 --- a/etcdserver/api/v3rpc/member.go +++ b/etcdserver/api/v3rpc/member.go @@ -16,6 +16,7 @@ package v3rpc import ( "context" + "errors" "time" "go.etcd.io/etcd/v3/etcdserver" @@ -83,6 +84,11 @@ func (cs *ClusterServer) MemberList(ctx context.Context, r *pb.MemberListRequest return &pb.MemberListResponse{Header: cs.header(), Members: membs}, nil } +func (cs *ClusterServer) MemberPromote(ctx context.Context, r *pb.MemberPromoteRequest) (*pb.MemberPromoteResponse, error) { + // TODO: implement + return nil, errors.New("not implemented") +} + func (cs *ClusterServer) header() *pb.ResponseHeader { return &pb.ResponseHeader{ClusterId: uint64(cs.cluster.ID()), MemberId: uint64(cs.server.ID()), RaftTerm: cs.server.Term()} } diff --git a/proxy/grpcproxy/adapter/cluster_client_adapter.go b/proxy/grpcproxy/adapter/cluster_client_adapter.go index 112f6e414e9..b5dab26ca26 100644 --- a/proxy/grpcproxy/adapter/cluster_client_adapter.go +++ b/proxy/grpcproxy/adapter/cluster_client_adapter.go @@ -43,3 +43,7 @@ func (s *cls2clc) MemberUpdate(ctx context.Context, r *pb.MemberUpdateRequest, o func (s *cls2clc) MemberRemove(ctx context.Context, r *pb.MemberRemoveRequest, opts ...grpc.CallOption) (*pb.MemberRemoveResponse, error) { return s.cls.MemberRemove(ctx, r) } + +func (s *cls2clc) MemberPromote(ctx context.Context, r *pb.MemberPromoteRequest, opts ...grpc.CallOption) (*pb.MemberPromoteResponse, error) { + return s.cls.MemberPromote(ctx, r) +} diff --git a/proxy/grpcproxy/cluster.go b/proxy/grpcproxy/cluster.go index d502e238d2a..cf6145fe072 100644 --- a/proxy/grpcproxy/cluster.go +++ b/proxy/grpcproxy/cluster.go @@ -25,6 +25,7 @@ import ( "go.etcd.io/etcd/v3/etcdserver/api/v3rpc/rpctypes" pb "go.etcd.io/etcd/v3/etcdserver/etcdserverpb" + "errors" "golang.org/x/time/rate" gnaming "google.golang.org/grpc/naming" ) @@ -175,3 +176,8 @@ func (cp *clusterProxy) MemberList(ctx context.Context, r *pb.MemberListRequest) resp := (pb.MemberListResponse)(*mresp) return &resp, err } + +func (cp *clusterProxy) MemberPromote(ctx context.Context, r *pb.MemberPromoteRequest) (*pb.MemberPromoteResponse, error) { + // TODO: implement + return nil, errors.New("not implemented") +} From 604bc04f707a144d4b5fe078970acc01776f792b Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Sun, 17 Mar 2019 00:33:28 -0700 Subject: [PATCH 3/9] etcdserver: support MemberAdd for learner Added IsLearner field to etcdserver internal Member type. Routed learner MemberAdd request from server API to raft. Apply learner MemberAdd result to server after the request is passed through Raft. --- etcdserver/api/membership/cluster.go | 6 ++++-- etcdserver/api/membership/cluster_test.go | 23 +++++++++++++++++++++++ etcdserver/api/membership/member.go | 14 +++++++++++--- etcdserver/api/membership/member_test.go | 22 +++++++++++++++------- etcdserver/api/v2http/client.go | 2 +- etcdserver/api/v3rpc/member.go | 11 ++++++++--- etcdserver/server.go | 8 +++++++- etcdserver/server_test.go | 16 ++++++++-------- 8 files changed, 77 insertions(+), 25 deletions(-) diff --git a/etcdserver/api/membership/cluster.go b/etcdserver/api/membership/cluster.go index 5553f289093..43d0ec008a2 100644 --- a/etcdserver/api/membership/cluster.go +++ b/etcdserver/api/membership/cluster.go @@ -59,10 +59,12 @@ type RaftCluster struct { removed map[types.ID]bool } +// NewClusterFromURLsMap creates a new raft cluster using provided urls map. Currently, it does not support creating +// cluster with raft learner member. func NewClusterFromURLsMap(lg *zap.Logger, token string, urlsmap types.URLsMap) (*RaftCluster, error) { c := NewCluster(lg, token) for name, urls := range urlsmap { - m := NewMember(name, urls, token, nil) + m := NewMember(name, urls, token, nil, false) if _, ok := c.members[m.ID]; ok { return nil, fmt.Errorf("member exists with identical ID %v", m) } @@ -259,7 +261,7 @@ func (c *RaftCluster) ValidateConfigurationChange(cc raftpb.ConfChange) error { return ErrIDRemoved } switch cc.Type { - case raftpb.ConfChangeAddNode: + case raftpb.ConfChangeAddNode, raftpb.ConfChangeAddLearnerNode: if members[id] != nil { return ErrIDExists } diff --git a/etcdserver/api/membership/cluster_test.go b/etcdserver/api/membership/cluster_test.go index 04939b49a11..e6a23e35c44 100644 --- a/etcdserver/api/membership/cluster_test.go +++ b/etcdserver/api/membership/cluster_test.go @@ -472,6 +472,29 @@ func TestClusterAddMember(t *testing.T) { } } +func TestClusterAddMemberAsLearner(t *testing.T) { + st := mockstore.NewRecorder() + c := newTestCluster(nil) + c.SetStore(st) + c.AddMember(newTestMemberAsLearner(1, nil, "node1", nil)) + + wactions := []testutil.Action{ + { + Name: "Create", + Params: []interface{}{ + path.Join(StoreMembersPrefix, "1", "raftAttributes"), + false, + `{"peerURLs":null,"isLearner":true}`, + false, + v2store.TTLOptionSet{ExpireTime: v2store.Permanent}, + }, + }, + } + if g := st.Action(); !reflect.DeepEqual(g, wactions) { + t.Errorf("actions = %v, want %v", g, wactions) + } +} + func TestClusterMembers(t *testing.T) { cls := &RaftCluster{ members: map[types.ID]*Member{ diff --git a/etcdserver/api/membership/member.go b/etcdserver/api/membership/member.go index 3b360e662fa..ca751a1cd8d 100644 --- a/etcdserver/api/membership/member.go +++ b/etcdserver/api/membership/member.go @@ -35,6 +35,8 @@ type RaftAttributes struct { // PeerURLs is the list of peers in the raft cluster. // TODO(philips): ensure these are URLs PeerURLs []string `json:"peerURLs"` + // IsLearner indicates if the member is raft learner. + IsLearner bool `json:"isLearner,omitempty"` } // Attributes represents all the non-raft related attributes of an etcd member. @@ -51,10 +53,13 @@ type Member struct { // NewMember creates a Member without an ID and generates one based on the // cluster name, peer URLs, and time. This is used for bootstrapping/adding new member. -func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member { +func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time, isLearner bool) *Member { m := &Member{ - RaftAttributes: RaftAttributes{PeerURLs: peerURLs.StringSlice()}, - Attributes: Attributes{Name: name}, + RaftAttributes: RaftAttributes{ + PeerURLs: peerURLs.StringSlice(), + IsLearner: isLearner, + }, + Attributes: Attributes{Name: name}, } var b []byte @@ -88,6 +93,9 @@ func (m *Member) Clone() *Member { } mm := &Member{ ID: m.ID, + RaftAttributes: RaftAttributes{ + IsLearner: m.IsLearner, + }, Attributes: Attributes{ Name: m.Name, }, diff --git a/etcdserver/api/membership/member_test.go b/etcdserver/api/membership/member_test.go index bdecfbed9c6..415946ef08c 100644 --- a/etcdserver/api/membership/member_test.go +++ b/etcdserver/api/membership/member_test.go @@ -36,17 +36,17 @@ func TestMemberTime(t *testing.T) { mem *Member id types.ID }{ - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil), 14544069596553697298}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil, false), 14544069596553697298}, // Same ID, different name (names shouldn't matter) - {NewMember("memfoo", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil), 14544069596553697298}, + {NewMember("memfoo", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil, false), 14544069596553697298}, // Same ID, different Time - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", timeParse("1984-12-23T15:04:05Z")), 2448790162483548276}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", timeParse("1984-12-23T15:04:05Z"), false), 2448790162483548276}, // Different cluster name - {NewMember("mcm1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "etcd", timeParse("1984-12-23T15:04:05Z")), 6973882743191604649}, - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}}, "", timeParse("1984-12-23T15:04:05Z")), 1466075294948436910}, + {NewMember("mcm1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "etcd", timeParse("1984-12-23T15:04:05Z"), false), 6973882743191604649}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}}, "", timeParse("1984-12-23T15:04:05Z"), false), 1466075294948436910}, // Order shouldn't matter - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "10.0.0.2:2379"}}, "", nil), 16552244735972308939}, - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.2:2379"}, {Scheme: "http", Host: "10.0.0.1:2379"}}, "", nil), 16552244735972308939}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "10.0.0.2:2379"}}, "", nil, false), 16552244735972308939}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.2:2379"}, {Scheme: "http", Host: "10.0.0.1:2379"}}, "", nil, false), 16552244735972308939}, } for i, tt := range tests { if tt.mem.ID != tt.id { @@ -113,3 +113,11 @@ func newTestMember(id uint64, peerURLs []string, name string, clientURLs []strin Attributes: Attributes{Name: name, ClientURLs: clientURLs}, } } + +func newTestMemberAsLearner(id uint64, peerURLs []string, name string, clientURLs []string) *Member { + return &Member{ + ID: types.ID(id), + RaftAttributes: RaftAttributes{PeerURLs: peerURLs, IsLearner: true}, + Attributes: Attributes{Name: name, ClientURLs: clientURLs}, + } +} diff --git a/etcdserver/api/v2http/client.go b/etcdserver/api/v2http/client.go index 4b6fa9f8644..58ee325465d 100644 --- a/etcdserver/api/v2http/client.go +++ b/etcdserver/api/v2http/client.go @@ -238,7 +238,7 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } now := h.clock.Now() - m := membership.NewMember("", req.PeerURLs, "", &now) + m := membership.NewMember("", req.PeerURLs, "", &now, false) // does not support adding learner via v2http _, err := h.server.AddMember(ctx, *m) switch { case err == membership.ErrIDExists || err == membership.ErrPeerURLexists: diff --git a/etcdserver/api/v3rpc/member.go b/etcdserver/api/v3rpc/member.go index d1bd00b0c35..96ebdbf55aa 100644 --- a/etcdserver/api/v3rpc/member.go +++ b/etcdserver/api/v3rpc/member.go @@ -46,15 +46,19 @@ func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) } now := time.Now() - m := membership.NewMember("", urls, "", &now) + m := membership.NewMember("", urls, "", &now, r.IsLearner) membs, merr := cs.server.AddMember(ctx, *m) if merr != nil { return nil, togRPCError(merr) } return &pb.MemberAddResponse{ - Header: cs.header(), - Member: &pb.Member{ID: uint64(m.ID), PeerURLs: m.PeerURLs}, + Header: cs.header(), + Member: &pb.Member{ + ID: uint64(m.ID), + PeerURLs: m.PeerURLs, + IsLearner: m.IsLearner, + }, Members: membersToProtoMembers(membs), }, nil } @@ -101,6 +105,7 @@ func membersToProtoMembers(membs []*membership.Member) []*pb.Member { ID: uint64(membs[i].ID), PeerURLs: membs[i].PeerURLs, ClientURLs: membs[i].ClientURLs, + IsLearner: membs[i].IsLearner, } } return protoMembs diff --git a/etcdserver/server.go b/etcdserver/server.go index d12c94770a1..ed0941fd2c9 100644 --- a/etcdserver/server.go +++ b/etcdserver/server.go @@ -1544,6 +1544,7 @@ func (s *EtcdServer) AddMember(ctx context.Context, memb membership.Member) ([]* return nil, err } + // TODO: might switch to less strict check when adding raft learner if s.Cfg.StrictReconfigCheck { // by default StrictReconfigCheck is enabled; reject new members if unhealthy if !s.cluster.IsReadyToAddNewMember() { @@ -1585,6 +1586,11 @@ func (s *EtcdServer) AddMember(ctx context.Context, memb membership.Member) ([]* NodeID: uint64(memb.ID), Context: b, } + + if memb.IsLearner { + cc.Type = raftpb.ConfChangeAddLearnerNode + } + return s.configure(ctx, cc) } @@ -2054,7 +2060,7 @@ func (s *EtcdServer) applyConfChange(cc raftpb.ConfChange, confState *raftpb.Con lg := s.getLogger() *confState = *s.r.ApplyConfChange(cc) switch cc.Type { - case raftpb.ConfChangeAddNode: + case raftpb.ConfChangeAddNode, raftpb.ConfChangeAddLearnerNode: m := new(membership.Member) if err := json.Unmarshal(cc.Context, m); err != nil { if lg != nil { diff --git a/etcdserver/server_test.go b/etcdserver/server_test.go index a8dbbb33306..3ba0237cbbf 100644 --- a/etcdserver/server_test.go +++ b/etcdserver/server_test.go @@ -634,7 +634,7 @@ func TestApplyConfigChangeUpdatesConsistIndex(t *testing.T) { if err != nil { t.Fatal(err) } - m := membership.NewMember("", urls, "", &now) + m := membership.NewMember("", urls, "", &now, false) m.ID = types.ID(2) b, err := json.Marshal(m) if err != nil { @@ -1564,23 +1564,23 @@ func TestGetOtherPeerURLs(t *testing.T) { }{ { []*membership.Member{ - membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), + membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil, false), }, []string{}, }, { []*membership.Member{ - membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), - membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil), - membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil), + membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil, false), + membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil, false), + membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil, false), }, []string{"http://10.0.0.2:2", "http://10.0.0.3:3"}, }, { []*membership.Member{ - membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), - membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil), - membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil), + membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil, false), + membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil, false), + membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil, false), }, []string{"http://10.0.0.2:2", "http://10.0.0.3:3"}, }, From fc14608cb73c8cf59b9a3d3a733217a2a28b60c2 Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Tue, 19 Mar 2019 17:22:15 -0700 Subject: [PATCH 4/9] clientv3: support MemberAdd for learner Added IsLearner flag to clientv3 MemberAdd API. --- clientv3/cluster.go | 9 +++-- clientv3/example_cluster_test.go | 2 +- clientv3/integration/cluster_test.go | 49 ++++++++++++++++++++++++- clientv3/snapshot/member_test.go | 2 +- etcdctl/ctlv3/command/member_command.go | 2 +- etcdserver/api/v2v3/server.go | 5 ++- proxy/grpcproxy/cluster.go | 2 +- 7 files changed, 60 insertions(+), 11 deletions(-) diff --git a/clientv3/cluster.go b/clientv3/cluster.go index 6d612cf6d9c..8a407f5624d 100644 --- a/clientv3/cluster.go +++ b/clientv3/cluster.go @@ -38,7 +38,7 @@ type Cluster interface { MemberList(ctx context.Context) (*MemberListResponse, error) // MemberAdd adds a new member into the cluster. - MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) + MemberAdd(ctx context.Context, peerAddrs []string, isLearner bool) (*MemberAddResponse, error) // MemberRemove removes an existing member from the cluster. MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) @@ -71,13 +71,16 @@ func NewClusterFromClusterClient(remote pb.ClusterClient, c *Client) Cluster { return api } -func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { +func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string, isLearner bool) (*MemberAddResponse, error) { // fail-fast before panic in rafthttp if _, err := types.NewURLs(peerAddrs); err != nil { return nil, err } - r := &pb.MemberAddRequest{PeerURLs: peerAddrs} + r := &pb.MemberAddRequest{ + PeerURLs: peerAddrs, + IsLearner: isLearner, + } resp, err := c.remote.MemberAdd(ctx, r, c.callOpts...) if err != nil { return nil, toErr(ctx, err) diff --git a/clientv3/example_cluster_test.go b/clientv3/example_cluster_test.go index 7841d4facdc..398fd3829f8 100644 --- a/clientv3/example_cluster_test.go +++ b/clientv3/example_cluster_test.go @@ -51,7 +51,7 @@ func ExampleCluster_memberAdd() { defer cli.Close() peerURLs := endpoints[2:] - mresp, err := cli.MemberAdd(context.Background(), peerURLs) + mresp, err := cli.MemberAdd(context.Background(), peerURLs, false) if err != nil { log.Fatal(err) } diff --git a/clientv3/integration/cluster_test.go b/clientv3/integration/cluster_test.go index 229395795f9..280e9812411 100644 --- a/clientv3/integration/cluster_test.go +++ b/clientv3/integration/cluster_test.go @@ -16,6 +16,7 @@ package integration import ( "context" + "fmt" "reflect" "strings" "testing" @@ -52,7 +53,7 @@ func TestMemberAdd(t *testing.T) { capi := clus.RandClient() urls := []string{"http://127.0.0.1:1234"} - resp, err := capi.MemberAdd(context.Background(), urls) + resp, err := capi.MemberAdd(context.Background(), urls, false) if err != nil { t.Fatalf("failed to add member %v", err) } @@ -174,7 +175,7 @@ func TestMemberAddUpdateWrongURLs(t *testing.T) { {"localhost:1234"}, } for i := range tt { - _, err := capi.MemberAdd(context.Background(), tt[i]) + _, err := capi.MemberAdd(context.Background(), tt[i], false) if err == nil { t.Errorf("#%d: MemberAdd err = nil, but error", i) } @@ -184,3 +185,47 @@ func TestMemberAddUpdateWrongURLs(t *testing.T) { } } } + +func TestMemberAddForLearner(t *testing.T) { + defer testutil.AfterTest(t) + + clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3}) + defer clus.Terminate(t) + + capi := clus.RandClient() + + urls := []string{"http://127.0.0.1:1234"} + isLearner := true + resp, err := capi.MemberAdd(context.Background(), urls, isLearner) + if err != nil { + t.Fatalf("failed to add member %v", err) + } + + if resp.Member.IsLearner != isLearner { + t.Errorf("Added a member with IsLearner = %v, got %v", isLearner, resp.Member.IsLearner) + } + + numOfLearners, err := getNumberOfLearners(clus) + if err != nil { + t.Fatalf("failed to get the number of learners in cluster: %v", err) + } + if numOfLearners != 1 { + t.Errorf("Added 1 learner node to cluster, got %d", numOfLearners) + } +} + +// getNumberOfLearners return the number of learner nodes in cluster using MemberList API +func getNumberOfLearners(clus *integration.ClusterV3) (int, error) { + cli := clus.RandClient() + resp, err := cli.MemberList(context.Background()) + if err != nil { + return 0, fmt.Errorf("failed to list member %v", err) + } + numberOfLearners := 0 + for _, m := range resp.Members { + if m.IsLearner { + numberOfLearners++ + } + } + return numberOfLearners, nil +} diff --git a/clientv3/snapshot/member_test.go b/clientv3/snapshot/member_test.go index 1260eccdaa5..d9c0eb7e447 100644 --- a/clientv3/snapshot/member_test.go +++ b/clientv3/snapshot/member_test.go @@ -55,7 +55,7 @@ func TestSnapshotV3RestoreMultiMemberAdd(t *testing.T) { urls := newEmbedURLs(2) newCURLs, newPURLs := urls[:1], urls[1:] - if _, err = cli.MemberAdd(context.Background(), []string{newPURLs[0].String()}); err != nil { + if _, err = cli.MemberAdd(context.Background(), []string{newPURLs[0].String()}, false); err != nil { t.Fatal(err) } diff --git a/etcdctl/ctlv3/command/member_command.go b/etcdctl/ctlv3/command/member_command.go index 5b2119aba50..dc69a5cb042 100644 --- a/etcdctl/ctlv3/command/member_command.go +++ b/etcdctl/ctlv3/command/member_command.go @@ -118,7 +118,7 @@ func memberAddCommandFunc(cmd *cobra.Command, args []string) { urls := strings.Split(memberPeerURLs, ",") ctx, cancel := commandCtx(cmd) cli := mustClientFromCmd(cmd) - resp, err := cli.MemberAdd(ctx, urls) + resp, err := cli.MemberAdd(ctx, urls, false) cancel() if err != nil { ExitWithError(ExitError, err) diff --git a/etcdserver/api/v2v3/server.go b/etcdserver/api/v2v3/server.go index f1cf3b33ba8..cc817ccd5bc 100644 --- a/etcdserver/api/v2v3/server.go +++ b/etcdserver/api/v2v3/server.go @@ -63,7 +63,7 @@ func (s *v2v3Server) Leader() types.ID { } func (s *v2v3Server) AddMember(ctx context.Context, memb membership.Member) ([]*membership.Member, error) { - resp, err := s.c.MemberAdd(ctx, memb.PeerURLs) + resp, err := s.c.MemberAdd(ctx, memb.PeerURLs, memb.IsLearner) if err != nil { return nil, err } @@ -92,7 +92,8 @@ func v3MembersToMembership(v3membs []*pb.Member) []*membership.Member { membs[i] = &membership.Member{ ID: types.ID(m.ID), RaftAttributes: membership.RaftAttributes{ - PeerURLs: m.PeerURLs, + PeerURLs: m.PeerURLs, + IsLearner: m.IsLearner, }, Attributes: membership.Attributes{ Name: m.Name, diff --git a/proxy/grpcproxy/cluster.go b/proxy/grpcproxy/cluster.go index cf6145fe072..6e69d2337d5 100644 --- a/proxy/grpcproxy/cluster.go +++ b/proxy/grpcproxy/cluster.go @@ -109,7 +109,7 @@ func (cp *clusterProxy) monitor(wa gnaming.Watcher) { } func (cp *clusterProxy) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) { - mresp, err := cp.clus.MemberAdd(ctx, r.PeerURLs) + mresp, err := cp.clus.MemberAdd(ctx, r.PeerURLs, r.IsLearner) if err != nil { return nil, err } From a67d9344103f48d49775c9ab4f21faad7b4e23cb Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Tue, 19 Mar 2019 18:55:48 -0700 Subject: [PATCH 5/9] etcdctl: support MemberAdd for learner Added support for "etcdctl member add --learner". --- etcdctl/ctlv3/command/member_command.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/etcdctl/ctlv3/command/member_command.go b/etcdctl/ctlv3/command/member_command.go index dc69a5cb042..cbe2be4ff3f 100644 --- a/etcdctl/ctlv3/command/member_command.go +++ b/etcdctl/ctlv3/command/member_command.go @@ -23,7 +23,10 @@ import ( "github.com/spf13/cobra" ) -var memberPeerURLs string +var ( + memberPeerURLs string + isLearner bool +) // NewMemberCommand returns the cobra command for "member". func NewMemberCommand() *cobra.Command { @@ -50,6 +53,7 @@ func NewMemberAddCommand() *cobra.Command { } cc.Flags().StringVar(&memberPeerURLs, "peer-urls", "", "comma separated peer URLs for the new member.") + cc.Flags().BoolVar(&isLearner, "learner", false, "indicates if the new member is raft learner") return cc } @@ -118,7 +122,7 @@ func memberAddCommandFunc(cmd *cobra.Command, args []string) { urls := strings.Split(memberPeerURLs, ",") ctx, cancel := commandCtx(cmd) cli := mustClientFromCmd(cmd) - resp, err := cli.MemberAdd(ctx, urls, false) + resp, err := cli.MemberAdd(ctx, urls, isLearner) cancel() if err != nil { ExitWithError(ExitError, err) From e4296bbad9151846ab7203a994aa36f8bcf4599d Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Wed, 20 Mar 2019 15:06:05 -0700 Subject: [PATCH 6/9] tests/e2e: Add test for learner member add Added an e2e test to exercise "etcdctl member add --learner". --- tests/e2e/ctl_v3_auth_test.go | 4 ++-- tests/e2e/ctl_v3_member_test.go | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/tests/e2e/ctl_v3_auth_test.go b/tests/e2e/ctl_v3_auth_test.go index da9258966d3..22832c71891 100644 --- a/tests/e2e/ctl_v3_auth_test.go +++ b/tests/e2e/ctl_v3_auth_test.go @@ -510,13 +510,13 @@ func authTestMemberAdd(cx ctlCtx) { peerURL := fmt.Sprintf("http://localhost:%d", etcdProcessBasePort+11) // ordinary user cannot add a new member cx.user, cx.pass = "test-user", "pass" - if err := ctlV3MemberAdd(cx, peerURL); err == nil { + if err := ctlV3MemberAdd(cx, peerURL, false); err == nil { cx.t.Fatalf("ordinary user must not be allowed to add a member") } // root can add a new member cx.user, cx.pass = "root", "root" - if err := ctlV3MemberAdd(cx, peerURL); err != nil { + if err := ctlV3MemberAdd(cx, peerURL, false); err != nil { cx.t.Fatal(err) } } diff --git a/tests/e2e/ctl_v3_member_test.go b/tests/e2e/ctl_v3_member_test.go index 1eeeebdf8be..06da3b0f7c4 100644 --- a/tests/e2e/ctl_v3_member_test.go +++ b/tests/e2e/ctl_v3_member_test.go @@ -59,9 +59,10 @@ func TestCtlV3MemberAddClientTLS(t *testing.T) { testCtl(t, memberAddTest, withC func TestCtlV3MemberAddClientAutoTLS(t *testing.T) { testCtl(t, memberAddTest, withCfg(configClientAutoTLS)) } -func TestCtlV3MemberAddPeerTLS(t *testing.T) { testCtl(t, memberAddTest, withCfg(configPeerTLS)) } -func TestCtlV3MemberUpdate(t *testing.T) { testCtl(t, memberUpdateTest) } -func TestCtlV3MemberUpdateNoTLS(t *testing.T) { testCtl(t, memberUpdateTest, withCfg(configNoTLS)) } +func TestCtlV3MemberAddPeerTLS(t *testing.T) { testCtl(t, memberAddTest, withCfg(configPeerTLS)) } +func TestCtlV3MemberAddForLearner(t *testing.T) { testCtl(t, memberAddForLearnerTest) } +func TestCtlV3MemberUpdate(t *testing.T) { testCtl(t, memberUpdateTest) } +func TestCtlV3MemberUpdateNoTLS(t *testing.T) { testCtl(t, memberUpdateTest, withCfg(configNoTLS)) } func TestCtlV3MemberUpdateClientTLS(t *testing.T) { testCtl(t, memberUpdateTest, withCfg(configClientTLS)) } @@ -122,13 +123,22 @@ func ctlV3MemberRemove(cx ctlCtx, ep, memberID, clusterID string) error { } func memberAddTest(cx ctlCtx) { - if err := ctlV3MemberAdd(cx, fmt.Sprintf("http://localhost:%d", etcdProcessBasePort+11)); err != nil { + if err := ctlV3MemberAdd(cx, fmt.Sprintf("http://localhost:%d", etcdProcessBasePort+11), false); err != nil { cx.t.Fatal(err) } } -func ctlV3MemberAdd(cx ctlCtx, peerURL string) error { +func memberAddForLearnerTest(cx ctlCtx) { + if err := ctlV3MemberAdd(cx, fmt.Sprintf("http://localhost:%d", etcdProcessBasePort+11), true); err != nil { + cx.t.Fatal(err) + } +} + +func ctlV3MemberAdd(cx ctlCtx, peerURL string, isLearner bool) error { cmdArgs := append(cx.PrefixArgs(), "member", "add", "newmember", fmt.Sprintf("--peer-urls=%s", peerURL)) + if isLearner { + cmdArgs = append(cmdArgs, "--learner") + } return spawnWithExpect(cmdArgs, " added to cluster ") } From 1e38de5b9d92911adfdbd638f86160a71ae3cb20 Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Wed, 20 Mar 2019 17:02:00 -0700 Subject: [PATCH 7/9] etcdctl: add learner field in member list output --- etcdctl/ctlv3/command/member_command.go | 2 +- etcdctl/ctlv3/command/printer.go | 7 ++++++- etcdctl/ctlv3/command/printer_fields.go | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/etcdctl/ctlv3/command/member_command.go b/etcdctl/ctlv3/command/member_command.go index cbe2be4ff3f..3aefe9da9d6 100644 --- a/etcdctl/ctlv3/command/member_command.go +++ b/etcdctl/ctlv3/command/member_command.go @@ -90,7 +90,7 @@ func NewMemberListCommand() *cobra.Command { Use: "list", Short: "Lists all members in the cluster", Long: `When --write-out is set to simple, this command prints out comma-separated member lists for each endpoint. -The items in the lists are ID, Status, Name, Peer Addrs, Client Addrs. +The items in the lists are ID, Status, Name, Peer Addrs, Client Addrs, Is Learner. `, Run: memberListCommandFunc, diff --git a/etcdctl/ctlv3/command/printer.go b/etcdctl/ctlv3/command/printer.go index 365af9188f3..55586a41384 100644 --- a/etcdctl/ctlv3/command/printer.go +++ b/etcdctl/ctlv3/command/printer.go @@ -158,18 +158,23 @@ func (p *printerUnsupported) DBStatus(snapshot.Status) { p.p(nil) } func (p *printerUnsupported) MoveLeader(leader, target uint64, r v3.MoveLeaderResponse) { p.p(nil) } func makeMemberListTable(r v3.MemberListResponse) (hdr []string, rows [][]string) { - hdr = []string{"ID", "Status", "Name", "Peer Addrs", "Client Addrs"} + hdr = []string{"ID", "Status", "Name", "Peer Addrs", "Client Addrs", "Is Learner"} for _, m := range r.Members { status := "started" if len(m.Name) == 0 { status = "unstarted" } + isLearner := "false" + if m.IsLearner { + isLearner = "true" + } rows = append(rows, []string{ fmt.Sprintf("%x", m.ID), status, m.Name, strings.Join(m.PeerURLs, ","), strings.Join(m.ClientURLs, ","), + isLearner, }) } return hdr, rows diff --git a/etcdctl/ctlv3/command/printer_fields.go b/etcdctl/ctlv3/command/printer_fields.go index 0f5a3f076f8..220eb491f75 100644 --- a/etcdctl/ctlv3/command/printer_fields.go +++ b/etcdctl/ctlv3/command/printer_fields.go @@ -137,6 +137,7 @@ func (p *fieldsPrinter) MemberList(r v3.MemberListResponse) { for _, u := range m.ClientURLs { fmt.Printf("\"ClientURL\" : %q\n", u) } + fmt.Println(`"IsLearner" :`, m.IsLearner) fmt.Println() } } From 2b76200f702b838c4a21f51ccd64df945ca1095f Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Tue, 14 May 2019 16:56:44 -0700 Subject: [PATCH 8/9] *: add MemberAddAsLearner to clientv3 Cluster API Made changes to Clientv3 Cluster API: - Added MemberAddAsLearner. - Reverted changes to MemberAdd - removed input parameter isLearner. --- clientv3/cluster.go | 17 ++++++++++++++--- clientv3/example_cluster_test.go | 23 ++++++++++++++++++++++- clientv3/integration/cluster_test.go | 11 +++++------ clientv3/snapshot/member_test.go | 2 +- etcdctl/ctlv3/command/member_command.go | 11 ++++++++++- etcdserver/api/v2v3/server.go | 3 ++- proxy/grpcproxy/cluster.go | 18 +++++++++++++++++- 7 files changed, 71 insertions(+), 14 deletions(-) diff --git a/clientv3/cluster.go b/clientv3/cluster.go index 8a407f5624d..43ceb79c33c 100644 --- a/clientv3/cluster.go +++ b/clientv3/cluster.go @@ -16,11 +16,11 @@ package clientv3 import ( "context" + "errors" pb "go.etcd.io/etcd/v3/etcdserver/etcdserverpb" "go.etcd.io/etcd/v3/pkg/types" - "errors" "google.golang.org/grpc" ) @@ -38,7 +38,10 @@ type Cluster interface { MemberList(ctx context.Context) (*MemberListResponse, error) // MemberAdd adds a new member into the cluster. - MemberAdd(ctx context.Context, peerAddrs []string, isLearner bool) (*MemberAddResponse, error) + MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) + + // MemberAddAsLearner adds a new learner member into the cluster. + MemberAddAsLearner(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) // MemberRemove removes an existing member from the cluster. MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) @@ -71,7 +74,15 @@ func NewClusterFromClusterClient(remote pb.ClusterClient, c *Client) Cluster { return api } -func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string, isLearner bool) (*MemberAddResponse, error) { +func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { + return c.memberAdd(ctx, peerAddrs, false) +} + +func (c *cluster) MemberAddAsLearner(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { + return c.memberAdd(ctx, peerAddrs, true) +} + +func (c *cluster) memberAdd(ctx context.Context, peerAddrs []string, isLearner bool) (*MemberAddResponse, error) { // fail-fast before panic in rafthttp if _, err := types.NewURLs(peerAddrs); err != nil { return nil, err diff --git a/clientv3/example_cluster_test.go b/clientv3/example_cluster_test.go index 398fd3829f8..bb180782378 100644 --- a/clientv3/example_cluster_test.go +++ b/clientv3/example_cluster_test.go @@ -51,7 +51,7 @@ func ExampleCluster_memberAdd() { defer cli.Close() peerURLs := endpoints[2:] - mresp, err := cli.MemberAdd(context.Background(), peerURLs, false) + mresp, err := cli.MemberAdd(context.Background(), peerURLs) if err != nil { log.Fatal(err) } @@ -59,6 +59,27 @@ func ExampleCluster_memberAdd() { // added member.PeerURLs: [http://localhost:32380] } +func ExampleCluster_memberAddAsLearner() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints[:2], + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + peerURLs := endpoints[2:] + mresp, err := cli.MemberAddAsLearner(context.Background(), peerURLs) + if err != nil { + log.Fatal(err) + } + fmt.Println("added member.PeerURLs:", mresp.Member.PeerURLs) + fmt.Println("added member.IsLearner:", mresp.Member.IsLearner) + // added member.PeerURLs: [http://localhost:32380] + // added member.IsLearner: true +} + func ExampleCluster_memberRemove() { cli, err := clientv3.New(clientv3.Config{ Endpoints: endpoints[1:], diff --git a/clientv3/integration/cluster_test.go b/clientv3/integration/cluster_test.go index 280e9812411..fbc35e51f94 100644 --- a/clientv3/integration/cluster_test.go +++ b/clientv3/integration/cluster_test.go @@ -53,7 +53,7 @@ func TestMemberAdd(t *testing.T) { capi := clus.RandClient() urls := []string{"http://127.0.0.1:1234"} - resp, err := capi.MemberAdd(context.Background(), urls, false) + resp, err := capi.MemberAdd(context.Background(), urls) if err != nil { t.Fatalf("failed to add member %v", err) } @@ -175,7 +175,7 @@ func TestMemberAddUpdateWrongURLs(t *testing.T) { {"localhost:1234"}, } for i := range tt { - _, err := capi.MemberAdd(context.Background(), tt[i], false) + _, err := capi.MemberAdd(context.Background(), tt[i]) if err == nil { t.Errorf("#%d: MemberAdd err = nil, but error", i) } @@ -195,14 +195,13 @@ func TestMemberAddForLearner(t *testing.T) { capi := clus.RandClient() urls := []string{"http://127.0.0.1:1234"} - isLearner := true - resp, err := capi.MemberAdd(context.Background(), urls, isLearner) + resp, err := capi.MemberAddAsLearner(context.Background(), urls) if err != nil { t.Fatalf("failed to add member %v", err) } - if resp.Member.IsLearner != isLearner { - t.Errorf("Added a member with IsLearner = %v, got %v", isLearner, resp.Member.IsLearner) + if !resp.Member.IsLearner { + t.Errorf("Added a member as learner, got resp.Member.IsLearner = %v", resp.Member.IsLearner) } numOfLearners, err := getNumberOfLearners(clus) diff --git a/clientv3/snapshot/member_test.go b/clientv3/snapshot/member_test.go index d9c0eb7e447..1260eccdaa5 100644 --- a/clientv3/snapshot/member_test.go +++ b/clientv3/snapshot/member_test.go @@ -55,7 +55,7 @@ func TestSnapshotV3RestoreMultiMemberAdd(t *testing.T) { urls := newEmbedURLs(2) newCURLs, newPURLs := urls[:1], urls[1:] - if _, err = cli.MemberAdd(context.Background(), []string{newPURLs[0].String()}, false); err != nil { + if _, err = cli.MemberAdd(context.Background(), []string{newPURLs[0].String()}); err != nil { t.Fatal(err) } diff --git a/etcdctl/ctlv3/command/member_command.go b/etcdctl/ctlv3/command/member_command.go index 3aefe9da9d6..5c4ca2a72b8 100644 --- a/etcdctl/ctlv3/command/member_command.go +++ b/etcdctl/ctlv3/command/member_command.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/spf13/cobra" + "go.etcd.io/etcd/v3/clientv3" ) var ( @@ -122,7 +123,15 @@ func memberAddCommandFunc(cmd *cobra.Command, args []string) { urls := strings.Split(memberPeerURLs, ",") ctx, cancel := commandCtx(cmd) cli := mustClientFromCmd(cmd) - resp, err := cli.MemberAdd(ctx, urls, isLearner) + var ( + resp *clientv3.MemberAddResponse + err error + ) + if isLearner { + resp, err = cli.MemberAddAsLearner(ctx, urls) + } else { + resp, err = cli.MemberAdd(ctx, urls) + } cancel() if err != nil { ExitWithError(ExitError, err) diff --git a/etcdserver/api/v2v3/server.go b/etcdserver/api/v2v3/server.go index cc817ccd5bc..fa9d66d75f1 100644 --- a/etcdserver/api/v2v3/server.go +++ b/etcdserver/api/v2v3/server.go @@ -63,7 +63,8 @@ func (s *v2v3Server) Leader() types.ID { } func (s *v2v3Server) AddMember(ctx context.Context, memb membership.Member) ([]*membership.Member, error) { - resp, err := s.c.MemberAdd(ctx, memb.PeerURLs, memb.IsLearner) + // adding member as learner is not supported by V2 Server. + resp, err := s.c.MemberAdd(ctx, memb.PeerURLs) if err != nil { return nil, err } diff --git a/proxy/grpcproxy/cluster.go b/proxy/grpcproxy/cluster.go index 6e69d2337d5..61b5daa4689 100644 --- a/proxy/grpcproxy/cluster.go +++ b/proxy/grpcproxy/cluster.go @@ -109,7 +109,23 @@ func (cp *clusterProxy) monitor(wa gnaming.Watcher) { } func (cp *clusterProxy) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) { - mresp, err := cp.clus.MemberAdd(ctx, r.PeerURLs, r.IsLearner) + if r.IsLearner { + return cp.memberAddAsLearner(ctx, r.PeerURLs) + } + return cp.memberAdd(ctx, r.PeerURLs) +} + +func (cp *clusterProxy) memberAdd(ctx context.Context, peerURLs []string) (*pb.MemberAddResponse, error) { + mresp, err := cp.clus.MemberAdd(ctx, peerURLs) + if err != nil { + return nil, err + } + resp := (pb.MemberAddResponse)(*mresp) + return &resp, err +} + +func (cp *clusterProxy) memberAddAsLearner(ctx context.Context, peerURLs []string) (*pb.MemberAddResponse, error) { + mresp, err := cp.clus.MemberAddAsLearner(ctx, peerURLs) if err != nil { return nil, err } From e1acf244c1e1a864107f0537e940b7db16fe36c4 Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Tue, 14 May 2019 17:31:56 -0700 Subject: [PATCH 9/9] etcdserver: Add MemberAddAsLearner Made changes to api/membership: - Added MemberAddAsLearner - Reverted changes to MemberAdd - removed input parameter isLearner --- etcdserver/api/membership/cluster.go | 2 +- etcdserver/api/membership/member.go | 12 +++++++++++- etcdserver/api/membership/member_test.go | 14 +++++++------- etcdserver/api/v2http/client.go | 2 +- etcdserver/api/v3rpc/member.go | 7 ++++++- etcdserver/server_test.go | 16 ++++++++-------- proxy/grpcproxy/cluster.go | 2 +- 7 files changed, 35 insertions(+), 20 deletions(-) diff --git a/etcdserver/api/membership/cluster.go b/etcdserver/api/membership/cluster.go index 43d0ec008a2..cc8e171bd3a 100644 --- a/etcdserver/api/membership/cluster.go +++ b/etcdserver/api/membership/cluster.go @@ -64,7 +64,7 @@ type RaftCluster struct { func NewClusterFromURLsMap(lg *zap.Logger, token string, urlsmap types.URLsMap) (*RaftCluster, error) { c := NewCluster(lg, token) for name, urls := range urlsmap { - m := NewMember(name, urls, token, nil, false) + m := NewMember(name, urls, token, nil) if _, ok := c.members[m.ID]; ok { return nil, fmt.Errorf("member exists with identical ID %v", m) } diff --git a/etcdserver/api/membership/member.go b/etcdserver/api/membership/member.go index ca751a1cd8d..8e45d80421f 100644 --- a/etcdserver/api/membership/member.go +++ b/etcdserver/api/membership/member.go @@ -53,7 +53,17 @@ type Member struct { // NewMember creates a Member without an ID and generates one based on the // cluster name, peer URLs, and time. This is used for bootstrapping/adding new member. -func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time, isLearner bool) *Member { +func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member { + return newMember(name, peerURLs, clusterName, now, false) +} + +// NewMemberAsLearner creates a learner Member without an ID and generates one based on the +// cluster name, peer URLs, and time. This is used for adding new learner member. +func NewMemberAsLearner(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member { + return newMember(name, peerURLs, clusterName, now, true) +} + +func newMember(name string, peerURLs types.URLs, clusterName string, now *time.Time, isLearner bool) *Member { m := &Member{ RaftAttributes: RaftAttributes{ PeerURLs: peerURLs.StringSlice(), diff --git a/etcdserver/api/membership/member_test.go b/etcdserver/api/membership/member_test.go index 415946ef08c..c67eb0a6dad 100644 --- a/etcdserver/api/membership/member_test.go +++ b/etcdserver/api/membership/member_test.go @@ -36,17 +36,17 @@ func TestMemberTime(t *testing.T) { mem *Member id types.ID }{ - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil, false), 14544069596553697298}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil), 14544069596553697298}, // Same ID, different name (names shouldn't matter) - {NewMember("memfoo", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil, false), 14544069596553697298}, + {NewMember("memfoo", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil), 14544069596553697298}, // Same ID, different Time - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", timeParse("1984-12-23T15:04:05Z"), false), 2448790162483548276}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", timeParse("1984-12-23T15:04:05Z")), 2448790162483548276}, // Different cluster name - {NewMember("mcm1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "etcd", timeParse("1984-12-23T15:04:05Z"), false), 6973882743191604649}, - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}}, "", timeParse("1984-12-23T15:04:05Z"), false), 1466075294948436910}, + {NewMember("mcm1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "etcd", timeParse("1984-12-23T15:04:05Z")), 6973882743191604649}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}}, "", timeParse("1984-12-23T15:04:05Z")), 1466075294948436910}, // Order shouldn't matter - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "10.0.0.2:2379"}}, "", nil, false), 16552244735972308939}, - {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.2:2379"}, {Scheme: "http", Host: "10.0.0.1:2379"}}, "", nil, false), 16552244735972308939}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "10.0.0.2:2379"}}, "", nil), 16552244735972308939}, + {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.2:2379"}, {Scheme: "http", Host: "10.0.0.1:2379"}}, "", nil), 16552244735972308939}, } for i, tt := range tests { if tt.mem.ID != tt.id { diff --git a/etcdserver/api/v2http/client.go b/etcdserver/api/v2http/client.go index 58ee325465d..4b6fa9f8644 100644 --- a/etcdserver/api/v2http/client.go +++ b/etcdserver/api/v2http/client.go @@ -238,7 +238,7 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } now := h.clock.Now() - m := membership.NewMember("", req.PeerURLs, "", &now, false) // does not support adding learner via v2http + m := membership.NewMember("", req.PeerURLs, "", &now) _, err := h.server.AddMember(ctx, *m) switch { case err == membership.ErrIDExists || err == membership.ErrPeerURLexists: diff --git a/etcdserver/api/v3rpc/member.go b/etcdserver/api/v3rpc/member.go index 96ebdbf55aa..16813836dec 100644 --- a/etcdserver/api/v3rpc/member.go +++ b/etcdserver/api/v3rpc/member.go @@ -46,7 +46,12 @@ func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) } now := time.Now() - m := membership.NewMember("", urls, "", &now, r.IsLearner) + var m *membership.Member + if r.IsLearner { + m = membership.NewMemberAsLearner("", urls, "", &now) + } else { + m = membership.NewMember("", urls, "", &now) + } membs, merr := cs.server.AddMember(ctx, *m) if merr != nil { return nil, togRPCError(merr) diff --git a/etcdserver/server_test.go b/etcdserver/server_test.go index 3ba0237cbbf..a8dbbb33306 100644 --- a/etcdserver/server_test.go +++ b/etcdserver/server_test.go @@ -634,7 +634,7 @@ func TestApplyConfigChangeUpdatesConsistIndex(t *testing.T) { if err != nil { t.Fatal(err) } - m := membership.NewMember("", urls, "", &now, false) + m := membership.NewMember("", urls, "", &now) m.ID = types.ID(2) b, err := json.Marshal(m) if err != nil { @@ -1564,23 +1564,23 @@ func TestGetOtherPeerURLs(t *testing.T) { }{ { []*membership.Member{ - membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil, false), + membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), }, []string{}, }, { []*membership.Member{ - membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil, false), - membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil, false), - membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil, false), + membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), + membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil), + membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil), }, []string{"http://10.0.0.2:2", "http://10.0.0.3:3"}, }, { []*membership.Member{ - membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil, false), - membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil, false), - membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil, false), + membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), + membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil), + membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil), }, []string{"http://10.0.0.2:2", "http://10.0.0.3:3"}, }, diff --git a/proxy/grpcproxy/cluster.go b/proxy/grpcproxy/cluster.go index 61b5daa4689..b472181265f 100644 --- a/proxy/grpcproxy/cluster.go +++ b/proxy/grpcproxy/cluster.go @@ -16,6 +16,7 @@ package grpcproxy import ( "context" + "errors" "fmt" "os" "sync" @@ -25,7 +26,6 @@ import ( "go.etcd.io/etcd/v3/etcdserver/api/v3rpc/rpctypes" pb "go.etcd.io/etcd/v3/etcdserver/etcdserverpb" - "errors" "golang.org/x/time/rate" gnaming "google.golang.org/grpc/naming" )