Add active session count to the worker (#2145)

This commit is contained in:
Todd authored and GitHub committed 2022-06-07 11:21:39 -07:00
1 parent fdf43fc4fe
commit 1e3c941be1
9 files changed
+167 -79

No files matched your search

+1
View File
@@ -81,4 +81,5 @@ const (
ConfigurationField = "configuration"
WorkerAuthTokenField = "worker_auth_token"
WorkerConfigField = "worker_config"
ActiveConnectionCountField = "active_session_count"
)
@@ -462,6 +462,9 @@ func toProto(ctx context.Context, in *servers.Worker, opt ...handlers.Option) (*
if outputFields.Has(globals.LastStatusTimeField) {
out.LastStatusTime = in.GetLastStatusTime().GetTimestamp()
}
if outputFields.Has(globals.ActiveConnectionCountField) {
out.ActiveConnectionCount = in.ActiveConnectionCount()
}
if outputFields.Has(globals.CanonicalTagsField) && len(in.CanonicalTags()) > 0 {
var err error
out.CanonicalTags, err = tagsToMapProto(in.CanonicalTags())
@@ -150,41 +150,6 @@ create table server_worker_tag (
primary key(worker_id, key, value, source)
);
-- worker_aggregate view allows the worker and configuration to be read at the
-- same time.
create view server_worker_aggregate as
with worker_config_tags(worker_id, source, tags) as (
select
ct.worker_id,
ct.source,
-- keys and tags can be any lowercase printable character so use uppercase characters as delimitors.
string_agg(distinct concat_ws('Y', ct.key, ct.value), 'Z') as tags
from server_worker_tag ct
group by ct.worker_id, ct.source
)
select
w.public_id,
w.scope_id,
w.description,
w.name,
w.address,
w.create_time,
w.update_time,
w.version,
w.worker_reported_name,
w.worker_reported_address,
w.last_status_time,
-- keys and tags can be any lowercase printable character so use uppercase characters as delimitors.
wt.tags as api_tags,
ct.tags as worker_config_tags
from server_worker w
left join worker_config_tags wt on
w.public_id = wt.worker_id and wt.source = 'api'
left join worker_config_tags ct on
w.public_id = ct.worker_id and ct.source = 'configuration';
comment on view server_worker_aggregate is
'server_worker_aggregate contains the worker resource with its worker provided config values and its configuration and api provided tags.';
-- Aaand drop server_tag
drop table server_tag;
@@ -245,26 +210,6 @@ set
where
status = 'running';
-- Replaces the view created in 9/01.
-- Remove the worker id from this view. In actuality this is almost a no-op
-- because no server information was ever getting populated here due to a bug
-- in the update mask when updating a session at the time we activate a session.
create view session_list as
select
s.public_id, s.user_id, s.host_id, s.target_id,
s.host_set_id, s.auth_token_id, s.scope_id, s.certificate,s.expiration_time,
s.connection_limit, s.tofu_token, s.key_id, s.termination_reason, s.version,
s.create_time, s.update_time, s.endpoint, s.worker_filter,
ss.state, ss.previous_end_time, ss.start_time, ss.end_time, sc.public_id as connection_id,
sc.client_tcp_address, sc.client_tcp_port, sc.endpoint_tcp_address, sc.endpoint_tcp_port,
sc.bytes_up, sc.bytes_down, sc.closed_reason
from session s
join session_state ss on
s.public_id = ss.session_id
left join session_connection sc on
s.public_id = sc.session_id;
-- Drop the server and server_type_enm tables
drop table server;
drop table server_type_enm;
@@ -0,0 +1,69 @@
begin;
-- worker_aggregate view allows the worker and configuration to be read at the
-- same time.
create view server_worker_aggregate as
with worker_config_tags(worker_id, source, tags) as (
select
ct.worker_id,
ct.source,
-- keys and tags can be any lowercase printable character so use uppercase characters as delimitors.
string_agg(distinct concat_ws('Y', ct.key, ct.value), 'Z') as tags
from server_worker_tag ct
group by ct.worker_id, ct.source
),
connection_count (worker_id, count) as (
select
worker_id,
count(1) as count
from session_connection
where closed_reason is null
group by worker_id
)
select
w.public_id,
w.scope_id,
w.description,
w.name,
w.address,
w.create_time,
w.update_time,
w.version,
w.worker_reported_name,
w.worker_reported_address,
w.last_status_time,
cc.count as active_connection_count,
-- keys and tags can be any lowercase printable character so use uppercase characters as delimitors.
wt.tags as api_tags,
ct.tags as worker_config_tags
from server_worker w
left join worker_config_tags wt on
w.public_id = wt.worker_id and wt.source = 'api'
left join worker_config_tags ct on
w.public_id = ct.worker_id and ct.source = 'configuration'
left join connection_count as cc on
w.public_id = cc.worker_id;
comment on view server_worker_aggregate is
'server_worker_aggregate contains the worker resource with its worker provided config values and its configuration and api provided tags.';
-- Replaces the view created in 9/01.
-- Remove the worker id from this view. In actuality this is almost a no-op
-- because no server information was ever getting populated here due to a bug
-- in the update mask when updating a session at the time we activate a session.
create view session_list as
select
s.public_id, s.user_id, s.host_id, s.target_id,
s.host_set_id, s.auth_token_id, s.scope_id, s.certificate,s.expiration_time,
s.connection_limit, s.tofu_token, s.key_id, s.termination_reason, s.version,
s.create_time, s.update_time, s.endpoint, s.worker_filter,
ss.state, ss.previous_end_time, ss.start_time, ss.end_time, sc.public_id as connection_id,
sc.client_tcp_address, sc.client_tcp_port, sc.endpoint_tcp_address, sc.endpoint_tcp_port,
sc.bytes_up, sc.bytes_down, sc.closed_reason
from session s
join session_state ss on
s.public_id = ss.session_id
left join session_connection sc on
s.public_id = sc.session_id;
commit;
+6
View File
@@ -5586,6 +5586,12 @@
"type": "string",
"title": "worker_auth_token is input only. Supports the worker led node\nenrollment flow where this credentials token is produced by a worker. This\ntoken is a base58 encoded types.FetchNodeCredentialsRequest from\nhttps://github.com/hashicorp/nodeenrollment"
},
"active_connection_count": {
"type": "integer",
"format": "int64",
"description": "Output only. The number of connections that this worker is currently handling.",
"readOnly": true
},
"authorized_actions": {
"type": "array",
"items": {
@@ -96,6 +96,9 @@ message Worker {
// https://github.com/hashicorp/nodeenrollment
google.protobuf.StringValue worker_auth_token = 150 [json_name = "worker_auth_token"]; // @gotags: `class:"public"`
// Output only. The number of connections that this worker is currently handling.
uint32 active_connection_count = 160 [json_name = "active_connection_count"]; // @gotags: `class:"public"`
// Output only. The available actions on this resource for the requester.
repeated string authorized_actions = 300 [json_name = "authorized_actions"]; // @gotags: `class:"public"`
}
@@ -16,6 +16,7 @@ import (
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/servers"
"github.com/hashicorp/boundary/internal/servers/store"
"github.com/hashicorp/boundary/internal/session"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-dbw"
"github.com/hashicorp/go-kms-wrapping/extras/kms/v2/migrations"
@@ -25,6 +26,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestDeleteWorker(t *testing.T) {
@@ -162,10 +164,43 @@ func TestLookupWorker(t *testing.T) {
servers.WithPublicId(w.GetPublicId()))
require.NoError(t, err)
sessRepo, err := session.NewRepository(rw, rw, kms)
require.NoError(t, err)
connRepo, err := session.NewConnectionRepository(ctx, rw, rw, kms, session.WithWorkerStateDelay(0))
require.NoError(t, err)
// 1 session with 2 connections
{
composedOf := session.TestSessionParams(t, conn, wrapper, iam.TestRepo(t, conn, wrapper))
future := timestamppb.New(time.Now().Add(time.Hour))
exp := &timestamp.Timestamp{Timestamp: future}
composedOf.ConnectionLimit = -1
sess := session.TestSession(t, conn, wrapper, composedOf, session.WithDbOpts(db.WithSkipVetForWrite(true)), session.WithExpirationTime(exp))
sess, _, err = sessRepo.ActivateSession(ctx, sess.GetPublicId(), sess.Version, []byte("foo"))
require.NoError(t, err)
c, _, err := connRepo.AuthorizeConnection(ctx, sess.GetPublicId(), w.GetPublicId())
require.NoError(t, err)
require.NotNil(t, c)
c, _, err = connRepo.AuthorizeConnection(ctx, sess.GetPublicId(), w.GetPublicId())
require.NoError(t, err)
require.NotNil(t, c)
}
// 1 session with 1 connection
{
sess2 := session.TestDefaultSession(t, conn, wrapper, iam.TestRepo(t, conn, wrapper),
session.WithDbOpts(db.WithSkipVetForWrite(true)))
sess2, _, err = sessRepo.ActivateSession(ctx, sess2.GetPublicId(), sess2.Version, []byte("foo"))
require.NoError(t, err)
c, _, err := connRepo.AuthorizeConnection(ctx, sess2.GetPublicId(), w.GetPublicId())
require.NoError(t, err)
require.NotNil(t, c)
}
t.Run("success", func(t *testing.T) {
got, err := repo.LookupWorker(ctx, w.GetPublicId())
require.NoError(t, err)
assert.Empty(t, cmp.Diff(w, got, protocmp.Transform()))
assert.Equal(t, uint32(3), got.ActiveConnectionCount())
assert.Equal(t, map[string][]string{"key": {"val"}}, got.GetApiTags())
assert.Equal(t, map[string][]string{"config": {"test"}}, got.GetConfigTags())
assert.Equal(t, map[string][]string{
+15 -2
View File
@@ -36,8 +36,9 @@ func AttachWorkerIdToState(ctx context.Context, workerId string) (*structpb.Stru
type Worker struct {
*store.Worker
apiTags []*Tag `gorm:"-"`
configTags []*Tag `gorm:"-"`
activeConnectionCount uint32 `gorm:"-"`
apiTags []*Tag `gorm:"-"`
configTags []*Tag `gorm:"-"`
}
// NewWorker returns a new Worker. Valid options are WithName, WithDescription
@@ -112,6 +113,12 @@ func (w *Worker) CanonicalAddress() string {
return w.GetWorkerReportedAddress()
}
// ActiveConnectionCount is the current number of sessions this worker is handling
// according to the controllers.
func (w *Worker) ActiveConnectionCount() uint32 {
return w.activeConnectionCount
}
// CanonicalTags is the deduplicated set of tags contained on both the resource
// set over the API as well as the tags reported by the worker itself.
func (w *Worker) CanonicalTags() map[string][]string {
@@ -129,6 +136,7 @@ func (w *Worker) CanonicalTags() map[string][]string {
return tags
}
// GetApiTags returns the tags for this worker which has been set by the api.
func (w *Worker) GetApiTags() map[string][]string {
tags := make(map[string][]string)
for _, t := range w.apiTags {
@@ -137,6 +145,8 @@ func (w *Worker) GetApiTags() map[string][]string {
return tags
}
// GetConfigTags returns the tags for this worker which has been set through
// the worker daemon's configuration file.
func (w *Worker) GetConfigTags() map[string][]string {
tags := make(map[string][]string)
for _, t := range w.configTags {
@@ -172,6 +182,8 @@ type workerAggregate struct {
Address string
Version uint32
ApiTags string
ActiveConnectionCount uint32
// Config Fields
WorkerReportedName string
WorkerReportedAddress string
LastStatusTime *timestamp.Timestamp
@@ -194,6 +206,7 @@ func (a *workerAggregate) toWorker(ctx context.Context) (*Worker, error) {
WorkerReportedName: a.WorkerReportedName,
LastStatusTime: a.LastStatusTime,
},
activeConnectionCount: a.ActiveConnectionCount,
}
tags, err := tagsFromAggregatedTagString(ctx, a.ApiTags)
if err != nil {
@@ -137,6 +137,8 @@ type Worker struct {
// token is a base58 encoded types.FetchNodeCredentialsRequest from
// https://github.com/hashicorp/nodeenrollment
WorkerAuthToken *wrapperspb.StringValue `protobuf:"bytes,150,opt,name=worker_auth_token,proto3" json:"worker_auth_token,omitempty"` // @gotags: `class:"public"`
// Output only. The number of connections that this worker is currently handling.
ActiveConnectionCount uint32 `protobuf:"varint,160,opt,name=active_connection_count,proto3" json:"active_connection_count,omitempty"` // @gotags: `class:"public"`
// Output only. The available actions on this resource for the requester.
AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` // @gotags: `class:"public"`
}
@@ -278,6 +280,13 @@ func (x *Worker) GetWorkerAuthToken() *wrapperspb.StringValue {
return nil
}
func (x *Worker) GetActiveConnectionCount() uint32 {
if x != nil {
return x.ActiveConnectionCount
}
return 0
}
func (x *Worker) GetAuthorizedActions() []string {
if x != nil {
return x.AuthorizedActions
@@ -319,7 +328,7 @@ var file_controller_api_resources_workers_v1_worker_proto_rawDesc = []byte{
0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xc6, 0x09, 0x0a, 0x06, 0x57, 0x6f, 0x72, 0x6b,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x81, 0x0a, 0x0a, 0x06, 0x57, 0x6f, 0x72, 0x6b,
0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43,
@@ -381,27 +390,31 @@ var file_controller_api_resources_workers_v1_worker_proto_rawDesc = []byte{
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x96, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x11, 0x77, 0x6f, 0x72, 0x6b, 0x65,
0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2f, 0x0a, 0x12,
0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x53, 0x0a,
0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x1a, 0x5c, 0x0a, 0x12, 0x43, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x54,
0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x42, 0x50, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68,
0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72,
0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x73, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x3b, 0x77, 0x6f, 0x72, 0x6b, 0x65,
0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x39, 0x0a, 0x17,
0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0xa0, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x17,
0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02,
0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x53, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c,
0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5c, 0x0a,
0x12, 0x43, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x50, 0x5a, 0x4e, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63,
0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b,
0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x77, 0x6f,
0x72, 0x6b, 0x65, 0x72, 0x73, 0x3b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (