From 7314a402f244bd8b74f37f62ebdda7a4c3ce8d45 Mon Sep 17 00:00:00 2001 From: Ahmad Kashif Date: Sun, 21 May 2023 15:03:59 +0500 Subject: [PATCH 01/27] using fully qualified module path --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b55c9d08..c2775249 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module juno +module github.com/paypal/junodb go 1.18 From 3c7ae7fa18eb5ef9fab4fb37249ed1bfad3ebf48 Mon Sep 17 00:00:00 2001 From: Ahmad Kashif Date: Sun, 21 May 2023 15:20:15 +0500 Subject: [PATCH 02/27] Adding comments for documentation for clientImpl.go --- pkg/client/clientimpl.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/client/clientimpl.go b/pkg/client/clientimpl.go index 0648fd65..1e02733e 100644 --- a/pkg/client/clientimpl.go +++ b/pkg/client/clientimpl.go @@ -17,6 +17,7 @@ // limitations under the License. // +// Package client provides interfaces and implementations for communicating with a Juno server. package client import ( @@ -31,6 +32,7 @@ import ( "juno/pkg/proto" ) +// clientImplT is the default implementation of the IClient interface. type clientImplT struct { config Config appName string @@ -38,6 +40,7 @@ type clientImplT struct { processor *cli.Processor } +// newProcessorWithConfig initializes a new Processor with the given configuration. func newProcessorWithConfig(conf *Config) *cli.Processor { if conf == nil { return nil @@ -51,6 +54,7 @@ func newProcessorWithConfig(conf *Config) *cli.Processor { return c } +// New initializes a new IClient with the given configuration. Returns an error if configuration validation fails. func New(conf Config) (IClient, error) { if err := conf.validate(); err != nil { return nil, err @@ -68,6 +72,7 @@ func New(conf Config) (IClient, error) { return client, nil } +// NewClient initializes a new IClient with the provided server address, namespace and app name. func NewClient(server string, ns string, app string) (IClient, error) { c := &clientImplT{ config: Config{ @@ -99,6 +104,8 @@ func NewClient(server string, ns string, app string) (IClient, error) { } ///TODO to revisit + +// Close closes the client and cleans up resources. func (c *clientImplT) Close() { if c.processor != nil { c.processor.Close() @@ -106,6 +113,7 @@ func (c *clientImplT) Close() { } } +// getOptions collects all provided options into an optionData object. func (c *clientImplT) getOptions(opts ...IOption) *optionData { data := &optionData{} for _, op := range opts { @@ -114,12 +122,14 @@ func (c *clientImplT) getOptions(opts ...IOption) *optionData { return data } +// newContext creates a new context from the provided operational message. func newContext(resp *proto.OperationalMessage) IContext { recInfo := &cli.RecordInfo{} recInfo.SetFromOpMsg(resp) return recInfo } +// Create sends a Create operation request to the server. func (c *clientImplT) Create(key []byte, value []byte, opts ...IOption) (context IContext, err error) { glog.Verbosef("Create ") var resp *proto.OperationalMessage @@ -138,6 +148,7 @@ func (c *clientImplT) Create(key []byte, value []byte, opts ...IOption) (context return } +// Get sends a Get operation request to the server. func (c *clientImplT) Get(key []byte, opts ...IOption) (value []byte, context IContext, err error) { var resp *proto.OperationalMessage options := newOptionData(opts...) @@ -161,6 +172,7 @@ func (c *clientImplT) Get(key []byte, opts ...IOption) (value []byte, context IC return } +// Update sends an Update operation request to the server. func (c *clientImplT) Update(key []byte, value []byte, opts ...IOption) (context IContext, err error) { var resp *proto.OperationalMessage options := newOptionData(opts...) @@ -183,6 +195,7 @@ func (c *clientImplT) Update(key []byte, value []byte, opts ...IOption) (context return } +// Set sends a Set operation request to the server. func (c *clientImplT) Set(key []byte, value []byte, opts ...IOption) (context IContext, err error) { var resp *proto.OperationalMessage options := newOptionData(opts...) @@ -200,6 +213,7 @@ func (c *clientImplT) Set(key []byte, value []byte, opts ...IOption) (context IC return } +// Destroy sends a Destroy operation request to the server. func (c *clientImplT) Destroy(key []byte, opts ...IOption) (err error) { var resp *proto.OperationalMessage options := newOptionData(opts...) @@ -215,6 +229,7 @@ func (c *clientImplT) Destroy(key []byte, opts ...IOption) (err error) { return } +// UDFGet sends a UDFGet operation request to the server. func (c *clientImplT) UDFGet(key []byte, fname []byte, params []byte, opts ...IOption) (value []byte, context IContext, err error) { var resp *proto.OperationalMessage options := newOptionData(opts...) @@ -239,6 +254,7 @@ func (c *clientImplT) UDFGet(key []byte, fname []byte, params []byte, opts ...IO return } +// UDFSet sends a UDFSet operation request to the server. func (c *clientImplT) UDFSet(key []byte, fname []byte, params []byte, opts ...IOption) (context IContext, err error) { var resp *proto.OperationalMessage options := newOptionData(opts...) @@ -258,10 +274,13 @@ func (c *clientImplT) UDFSet(key []byte, fname []byte, params []byte, opts ...IO } ///TODO temporary + +// Batch sends a batch of operation requests to the server. func (c *clientImplT) Batch(requests []*proto.OperationalMessage) (responses []*proto.OperationalMessage, err error) { return c.processor.ProcessBatchRequests(requests) } +// NewRequest creates a new OperationalMessage with the provided parameters. func (c *clientImplT) NewRequest(op proto.OpCode, key []byte, value []byte, ttl uint32) (request *proto.OperationalMessage) { ///TODO: validate op request = &proto.OperationalMessage{} @@ -272,6 +291,7 @@ func (c *clientImplT) NewRequest(op proto.OpCode, key []byte, value []byte, ttl return } +// NewUDFRequest creates a new UDF OperationalMessage with the provided parameters. func (c *clientImplT) NewUDFRequest(op proto.OpCode, key []byte, fname []byte, params []byte, ttl uint32) (request *proto.OperationalMessage) { ///TODO: validate op request = &proto.OperationalMessage{} @@ -284,6 +304,7 @@ func (c *clientImplT) NewUDFRequest(op proto.OpCode, key []byte, fname []byte, p return } +// checkResponse validates the response from the server against the original request. func checkResponse(request *proto.OperationalMessage, response *proto.OperationalMessage, recInfo *cli.RecordInfo) (err error) { opCode := request.GetOpCode() if opCode != response.GetOpCode() { From 34b2e122e2398764827e1773af058a71cf93be6c Mon Sep 17 00:00:00 2001 From: Ahmad Kashif Date: Sun, 21 May 2023 15:32:35 +0500 Subject: [PATCH 03/27] Adding comments for documentation for config.go --- pkg/client/config.go | 59 +++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/pkg/client/config.go b/pkg/client/config.go index 22a4d90e..c683579d 100644 --- a/pkg/client/config.go +++ b/pkg/client/config.go @@ -1,22 +1,21 @@ +// Copyright 2023 PayPal Inc. // -// Copyright 2023 PayPal Inc. -// -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// Package client handles the configuration for a Juno client. package client import ( @@ -27,21 +26,24 @@ import ( "juno/pkg/util" ) +// Duration is a type alias for util.Duration. type Duration = util.Duration +// Config holds the configuration values for the Juno client. type Config struct { - Server io.ServiceEndpoint - Appname string - Namespace string - RetryCount int - DefaultTimeToLive int - ConnectTimeout Duration - ReadTimeout Duration - WriteTimeout Duration - RequestTimeout Duration - ConnRecycleTimeout Duration + Server io.ServiceEndpoint // Server defines the ServiceEndpoint of the Juno server. + Appname string // Appname is the name of the application. + Namespace string // Namespace is the namespace of the application. + RetryCount int // RetryCount is the maximum number of retries. + DefaultTimeToLive int // DefaultTimeToLive is the default TTL (time to live) for requests. + ConnectTimeout Duration // ConnectTimeout is the timeout for establishing connections. + ReadTimeout Duration // ReadTimeout is the timeout for read operations. + WriteTimeout Duration // WriteTimeout is the timeout for write operations. + RequestTimeout Duration // RequestTimeout is the timeout for each request. + ConnRecycleTimeout Duration // ConnRecycleTimeout is the timeout for connection recycling. } +// defaultConfig defines the default configuration values. var defaultConfig = Config{ RetryCount: 1, DefaultTimeToLive: 1800, @@ -52,10 +54,12 @@ var defaultConfig = Config{ ConnRecycleTimeout: Duration{9 * time.Second}, } +// SetDefaultTimeToLive sets the default time to live (TTL) for the configuration. func SetDefaultTimeToLive(ttl int) { defaultConfig.DefaultTimeToLive = ttl } +// SetDefaultTimeout sets the default timeout durations for the configuration. func SetDefaultTimeout(connect, read, write, request, connRecycle time.Duration) { defaultConfig.ConnectTimeout.Duration = connect defaultConfig.ReadTimeout.Duration = read @@ -64,10 +68,14 @@ func SetDefaultTimeout(connect, read, write, request, connRecycle time.Duration) defaultConfig.ConnRecycleTimeout.Duration = connRecycle } +// SetDefault updates the current Config to match the default Config. func (c *Config) SetDefault() { *c = defaultConfig } +// validate checks if the required fields of the Config are correctly populated. +// It validates the Server field and checks if Appname and Namespace are specified. +// It returns an error if any of the above conditions are not met. func (c *Config) validate() error { if err := c.Server.Validate(); err != nil { return err @@ -78,6 +86,7 @@ func (c *Config) validate() error { if len(c.Namespace) == 0 { return fmt.Errorf("Config.Namespace not specified.") } - /// TODO to validate others + // TODO to validate others return nil } + From 873ce04cc9edac8692e1147e5a27f6fdf96985b7 Mon Sep 17 00:00:00 2001 From: Ahmad Kashif Date: Sun, 21 May 2023 15:59:07 +0500 Subject: [PATCH 04/27] Adding comments for documentation for error.go --- pkg/client/error.go | 79 ++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/pkg/client/error.go b/pkg/client/error.go index 18c7332b..89661b1b 100644 --- a/pkg/client/error.go +++ b/pkg/client/error.go @@ -17,6 +17,7 @@ // limitations under the License. // +// client is a package that handles various error situations in the Juno application. package client import ( @@ -24,54 +25,58 @@ import ( "juno/pkg/proto" ) +// Error variables for different scenarios in the application. var ( - ErrNoKey error - ErrUniqueKeyViolation error - ErrBadParam error - ErrConditionViolation error + ErrNoKey error // Error when no key is found. + ErrUniqueKeyViolation error // Error when there is a violation of a unique key. + ErrBadParam error // Error when a bad parameter is provided. + ErrConditionViolation error // Error when a condition violation occurs. - ErrBadMsg error - ErrNoStorage error - ErrRecordLocked error - ErrTTLExtendFailure error - ErrBusy error + ErrBadMsg error // Error when a bad message is encountered. + ErrNoStorage error // Error when no storage is available. + ErrRecordLocked error // Error when a record is locked. + ErrTTLExtendFailure error // Error when TTL extension fails. + ErrBusy error // Error when the server is busy. - ErrWriteFailure error - ErrInternal error - ErrOpNotSupported error + ErrWriteFailure error // Error when a write operation fails. + ErrInternal error // Error when an internal problem occurs. + ErrOpNotSupported error // Error when the operation is not supported. ) +// errorMapping is a map between different operation status and their corresponding errors. var errorMapping map[proto.OpStatus]error +// init function initializes the error variables and the errorMapping map. func init() { - ErrNoKey = &cli.Error{"no key"} - ErrUniqueKeyViolation = &cli.Error{"unique key violation"} - ErrBadParam = &cli.Error{"bad parameter"} - ErrConditionViolation = &cli.Error{"condition violation"} //version too old - ErrTTLExtendFailure = &cli.Error{"fail to extend TTL"} + ErrNoKey = &cli.Error{"no key"} // Error when the key does not exist. + ErrUniqueKeyViolation = &cli.Error{"unique key violation"} // Error when unique key constraint is violated. + ErrBadParam = &cli.Error{"bad parameter"} // Error when a bad parameter is passed. + ErrConditionViolation = &cli.Error{"condition violation"} // Error when there is a condition violation. + ErrTTLExtendFailure = &cli.Error{"fail to extend TTL"} // Error when TTL extension fails. - ErrBadMsg = &cli.RetryableError{"bad message"} - ErrNoStorage = &cli.RetryableError{"no storage"} - ErrRecordLocked = &cli.RetryableError{"record locked"} - ErrBusy = &cli.RetryableError{"server busy"} + ErrBadMsg = &cli.RetryableError{"bad message"} // Error when an inappropriate message is received. + ErrNoStorage = &cli.RetryableError{"no storage"} // Error when there is no storage available. + ErrRecordLocked = &cli.RetryableError{"record locked"} // Error when a record is locked. + ErrBusy = &cli.RetryableError{"server busy"} // Error when the server is busy. - ErrWriteFailure = &cli.Error{"write failure"} - ErrInternal = &cli.Error{"internal error"} - ErrOpNotSupported = &cli.Error{"Op not supported"} + ErrWriteFailure = &cli.Error{"write failure"} // Error when a write operation fails. + ErrInternal = &cli.Error{"internal error"} // Error when an internal error occurs. + ErrOpNotSupported = &cli.Error{"Op not supported"} // Error when the operation is not supported. + // Mapping between the operation status and the corresponding errors. errorMapping = map[proto.OpStatus]error{ - proto.OpStatusNoError: nil, - proto.OpStatusInconsistent: nil, - proto.OpStatusBadMsg: ErrBadMsg, - proto.OpStatusNoKey: ErrNoKey, - proto.OpStatusDupKey: ErrUniqueKeyViolation, - proto.OpStatusNoStorageServer: ErrNoStorage, - proto.OpStatusBadParam: ErrBadParam, - proto.OpStatusRecordLocked: ErrRecordLocked, - proto.OpStatusVersionConflict: ErrConditionViolation, - proto.OpStatusSSReadTTLExtendErr: ErrTTLExtendFailure, - proto.OpStatusCommitFailure: ErrWriteFailure, - proto.OpStatusBusy: ErrBusy, - proto.OpStatusNotSupported: ErrOpNotSupported, + proto.OpStatusNoError: nil, // Status when there is no error. + proto.OpStatusInconsistent: nil, // Status when there is an inconsistency. + proto.OpStatusBadMsg: ErrBadMsg, // Status when a bad message is received. + proto.OpStatusNoKey: ErrNoKey, // Status when the key is not present. + proto.OpStatusDupKey: ErrUniqueKeyViolation, // Status when unique key constraint is violated. + proto.OpStatusNoStorageServer: ErrNoStorage, // Status when there is no storage server available. + proto.OpStatusBadParam: ErrBadParam, // Status when a bad parameter is passed. + proto.OpStatusRecordLocked: ErrRecordLocked, // Status when a record is locked. + proto.OpStatusVersionConflict: ErrConditionViolation, // Status when there is a version conflict. + proto.OpStatusSSReadTTLExtendErr: ErrTTLExtendFailure, // Status when TTL extension fails. + proto.OpStatusCommitFailure: ErrWriteFailure, // Status when a commit operation fails. + proto.OpStatusBusy: ErrBusy, // Status when the server is busy. + proto.OpStatusNotSupported: ErrOpNotSupported, // Status when the operation is not supported. } } From 9325722ff43fba2c34516928c6d88eb31accab2a Mon Sep 17 00:00:00 2001 From: Ahmad Kashif Date: Sun, 21 May 2023 16:11:06 +0500 Subject: [PATCH 05/27] Adding comments for documentation for option.go --- pkg/client/option.go | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/pkg/client/option.go b/pkg/client/option.go index 849400b2..2e77f42c 100644 --- a/pkg/client/option.go +++ b/pkg/client/option.go @@ -17,16 +17,19 @@ // limitations under the License. // +// Package client provides functionalities for client configurations. package client import () +// optionData struct contains client options. type optionData struct { - ttl uint32 - context IContext - correlationId string + ttl uint32 // Time to live value. + context IContext // Client context. + correlationId string // Correlation ID for tracking. } +// IOption type represents a function that applies options on optionData. //type IOption interface { // Apply(data *optionData) error //} @@ -46,34 +49,41 @@ type optionData struct { type IOption func(data interface{}) +// WithTTL function returns an IOption that sets a TTL value. func WithTTL(ttl uint32) IOption { return func(i interface{}) { + // Check if the passed interface can be casted to *optionData if data, ok := i.(*optionData); ok { - data.ttl = ttl + data.ttl = ttl // Set the TTL value. } } } +// WithCond function returns an IOption that sets a context. func WithCond(context IContext) IOption { return func(i interface{}) { + // Check if the passed interface can be casted to *optionData if data, ok := i.(*optionData); ok { - data.context = context + data.context = context // Set the context. } } } +// WithCorrelationId function returns an IOption that sets a correlationId. func WithCorrelationId(id string) IOption { return func(i interface{}) { + // Check if the passed interface can be casted to *optionData if data, ok := i.(*optionData); ok { - data.correlationId = id + data.correlationId = id // Set the correlation ID. } } } +// newOptionData function applies the options passed in and returns an initialized optionData. func newOptionData(opts ...IOption) *optionData { - data := &optionData{} + data := &optionData{} // Initialize a new optionData. for _, op := range opts { - op(data) + op(data) // Apply each option on the optionData. } - return data + return data // Return the initialized optionData. } From 77c68934c3c126d1bb51049fc3a469131472f84c Mon Sep 17 00:00:00 2001 From: Ahmad Kashif Date: Sun, 21 May 2023 16:36:23 +0500 Subject: [PATCH 06/27] Updating imports for clustermgr, clusterctl, ctx, proc, processor, recinfo, tracker, cfg --- cmd/clustermgr/clusterctl/clusterctl.go | 6 +++--- cmd/clustermgr/clustermgr.go | 6 +++--- internal/cli/ctx.go | 4 ++-- internal/cli/proc.go | 8 ++++---- internal/cli/processor.go | 10 +++++----- internal/cli/recinfo.go | 2 +- internal/cli/tracker.go | 4 ++-- pkg/cfg/cfg.go | 2 +- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/clustermgr/clusterctl/clusterctl.go b/cmd/clustermgr/clusterctl/clusterctl.go index 24a3d289..e37ff392 100644 --- a/cmd/clustermgr/clusterctl/clusterctl.go +++ b/cmd/clustermgr/clusterctl/clusterctl.go @@ -28,11 +28,11 @@ import ( "path/filepath" "strings" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/version" + "github.com/paypal/junodb/pkg/version" - "juno/cmd/clustermgr/cmd" + "github.com/paypal/junodb/cmd/clustermgr/cmd" ) // diff --git a/cmd/clustermgr/clustermgr.go b/cmd/clustermgr/clustermgr.go index 4bc94f1f..ec4c50ee 100644 --- a/cmd/clustermgr/clustermgr.go +++ b/cmd/clustermgr/clustermgr.go @@ -27,11 +27,11 @@ import ( "os" "path/filepath" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/version" + "github.com/paypal/junodb/pkg/version" - "juno/cmd/clustermgr/cmd" + "github.com/paypal/junodb/cmd/clustermgr/cmd" ) var ( diff --git a/internal/cli/ctx.go b/internal/cli/ctx.go index 022dbfd5..56965d87 100644 --- a/internal/cli/ctx.go +++ b/internal/cli/ctx.go @@ -20,9 +20,9 @@ package cli import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) //GetResponse() != nil and GetError() != nil are mutually exclusive diff --git a/internal/cli/proc.go b/internal/cli/proc.go index 5a4719ce..d67c13b9 100644 --- a/internal/cli/proc.go +++ b/internal/cli/proc.go @@ -26,11 +26,11 @@ import ( "syscall" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - junoio "juno/pkg/io" - "juno/pkg/proto" - "juno/pkg/util" + junoio "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) type ( diff --git a/internal/cli/processor.go b/internal/cli/processor.go index be505974..cb7ee3ad 100644 --- a/internal/cli/processor.go +++ b/internal/cli/processor.go @@ -25,12 +25,12 @@ import ( "sync" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/io" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" ) type IOError struct { diff --git a/internal/cli/recinfo.go b/internal/cli/recinfo.go index 6e3a752e..728bfb42 100644 --- a/internal/cli/recinfo.go +++ b/internal/cli/recinfo.go @@ -22,7 +22,7 @@ package cli import ( "fmt" "io" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) type RecordInfo struct { diff --git a/internal/cli/tracker.go b/internal/cli/tracker.go index 54a30021..28af2b13 100644 --- a/internal/cli/tracker.go +++ b/internal/cli/tracker.go @@ -23,9 +23,9 @@ import ( "fmt" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" ) type PendingRequest struct { diff --git a/pkg/cfg/cfg.go b/pkg/cfg/cfg.go index fb2eb5f1..fdaeca59 100644 --- a/pkg/cfg/cfg.go +++ b/pkg/cfg/cfg.go @@ -29,7 +29,7 @@ import ( "github.com/BurntSushi/toml" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type ( From 10eb923ce7f1e337c6358346bea89a20af557daf Mon Sep 17 00:00:00 2001 From: Ahmad Kashif Date: Sun, 21 May 2023 17:15:38 +0500 Subject: [PATCH 07/27] Updating imports for all golang files --- binary_build/build.sh | 20 ++--- cmd/clustermgr/cmd/cmd.go | 6 +- cmd/clustermgr/cmd/config.go | 6 +- cmd/dbscanserv/app/cmd.go | 10 +-- cmd/dbscanserv/app/collect.go | 6 +- cmd/dbscanserv/app/init.go | 18 ++-- cmd/dbscanserv/app/patch.go | 16 ++-- cmd/dbscanserv/app/remote.go | 8 +- cmd/dbscanserv/app/rpcclient.go | 6 +- cmd/dbscanserv/app/scan.go | 8 +- cmd/dbscanserv/junoctl/junoctl.go | 6 +- cmd/dbscanserv/main.go | 8 +- cmd/dbscanserv/patch/relay.go | 10 +-- cmd/dbscanserv/prime/db.go | 6 +- cmd/dbscanserv/prime/log.go | 2 +- cmd/dbscanserv/prime/meta.go | 4 +- cmd/dbscanserv/prime/opdata.go | 6 +- cmd/dbscanserv/prime/replicate.go | 10 +-- cmd/dbscanserv/prime/result.go | 6 +- cmd/proxy/app/proxy.go | 12 +-- cmd/proxy/app/proxymgr.go | 22 ++--- cmd/proxy/app/proxymonwkr.go | 16 ++-- cmd/proxy/app/proxywkr.go | 36 ++++---- cmd/proxy/config/config.go | 26 +++--- cmd/proxy/config/limitscfg.go | 4 +- cmd/proxy/handler/requestHandler.go | 18 ++-- cmd/proxy/main.go | 2 +- cmd/proxy/proc/base.go | 32 +++---- cmd/proxy/proc/create.go | 8 +- cmd/proxy/proc/destroy.go | 2 +- cmd/proxy/proc/destroy2.go | 4 +- cmd/proxy/proc/get.go | 6 +- cmd/proxy/proc/inbreqctx.go | 20 ++--- cmd/proxy/proc/pconfig.go | 10 +-- cmd/proxy/proc/proc_logging.go | 8 +- cmd/proxy/proc/processor.go | 10 +-- cmd/proxy/proc/reqProcessorPool.go | 6 +- cmd/proxy/proc/set.go | 6 +- cmd/proxy/proc/state.go | 10 +-- cmd/proxy/proc/udfget.go | 6 +- cmd/proxy/proc/update.go | 8 +- cmd/proxy/replication/config/repcfg.go | 4 +- cmd/proxy/replication/pingreqctx.go | 10 +-- cmd/proxy/replication/replicaterequest.go | 18 ++-- cmd/proxy/replication/replicator.go | 20 ++--- cmd/proxy/stats/clsConTmpl.go | 2 +- cmd/proxy/stats/htmlsect.go | 8 +- cmd/proxy/stats/httphandler.go | 10 +-- cmd/proxy/stats/monhandler.go | 10 +-- cmd/proxy/stats/qry/infoquery.go | 2 +- cmd/proxy/stats/shmstats/shmstats.go | 12 +-- cmd/proxy/stats/shmstatswr.go | 6 +- cmd/proxy/stats/statelog.go | 12 +-- cmd/proxy/stats/statsinit.go | 10 +-- cmd/proxy/stats/statslogger.go | 12 +-- cmd/proxy/watcher/watcher.go | 10 +-- cmd/storageserv/app/ssmgr.go | 18 ++-- cmd/storageserv/app/ssmonwkr.go | 16 ++-- cmd/storageserv/app/sswkr.go | 36 ++++---- cmd/storageserv/app/storagemgr.go | 6 +- cmd/storageserv/app/storageserv.go | 8 +- cmd/storageserv/compact/dbclient.go | 8 +- cmd/storageserv/compact/filter.go | 2 +- cmd/storageserv/config/config.go | 28 +++--- cmd/storageserv/handler/reqhandler.go | 10 +-- cmd/storageserv/main.go | 2 +- cmd/storageserv/redist/config.go | 5 +- cmd/storageserv/redist/ratelimiter.go | 3 +- cmd/storageserv/redist/redist.go | 12 +-- cmd/storageserv/redist/replicator.go | 16 ++-- cmd/storageserv/redist/reqctx.go | 10 +-- cmd/storageserv/stats/htmlsect.go | 4 +- cmd/storageserv/stats/htmltmpl.go | 2 +- cmd/storageserv/stats/httphandler.go | 10 +-- cmd/storageserv/stats/monhandler.go | 10 +-- cmd/storageserv/stats/shmstats/shmstats.go | 6 +- cmd/storageserv/stats/shmstatswr.go | 4 +- cmd/storageserv/stats/statelog.go | 8 +- cmd/storageserv/stats/statsinit.go | 8 +- cmd/storageserv/stats/statslogger.go | 12 +-- cmd/storageserv/storage/bench_test.go | 2 +- cmd/storageserv/storage/create_test.go | 2 +- cmd/storageserv/storage/db/config.go | 6 +- cmd/storageserv/storage/db/db.go | 4 +- cmd/storageserv/storage/db/dbcopy/cmd.go | 2 +- cmd/storageserv/storage/db/dbcopy/compact.go | 3 +- cmd/storageserv/storage/db/dbcopy/config.go | 6 +- cmd/storageserv/storage/db/dbcopy/dbclient.go | 8 +- cmd/storageserv/storage/db/dbcopy/main.go | 2 +- cmd/storageserv/storage/db/debug.go | 2 +- cmd/storageserv/storage/db/record.go | 71 ++++++++------- cmd/storageserv/storage/db/recordid.go | 4 +- cmd/storageserv/storage/db/recordid_test.go | 3 +- cmd/storageserv/storage/db/rocksdb.go | 20 ++--- cmd/storageserv/storage/db/sharding.go | 6 +- .../storage/db/shardingByInstance.go | 12 +-- .../storage/db/shardingByPrefix.go | 12 +-- cmd/storageserv/storage/delete_test.go | 4 +- cmd/storageserv/storage/get_test.go | 4 +- cmd/storageserv/storage/lock.go | 10 +-- cmd/storageserv/storage/proc.go | 28 +++--- cmd/storageserv/storage/set_test.go | 2 +- cmd/storageserv/storage/setup_test.go | 8 +- cmd/storageserv/storage/storage.go | 32 +++---- cmd/storageserv/storage/storage_test.go | 10 +-- cmd/storageserv/storage/update_test.go | 4 +- cmd/storageserv/watcher/watcher.go | 8 +- cmd/tools/cmd/cfg/cfggen.go | 6 +- cmd/tools/cmd/cfg/conf.go | 4 +- cmd/tools/cmd/cfg/rtcfg.go | 10 +-- cmd/tools/cmd/cli/cli.go | 14 +-- cmd/tools/cmd/cli/sscli.go | 14 +-- cmd/tools/cmd/insp/inspmsg.go | 4 +- cmd/tools/cmd/insp/insprid.go | 8 +- cmd/tools/cmd/insp/ssgrp.go | 8 +- cmd/tools/cmd/stats/proxystats.go | 4 +- cmd/tools/cmd/stats/storagestats.go | 4 +- cmd/tools/goldendata/client.go | 6 +- cmd/tools/goldendata/goldenset.go | 3 +- cmd/tools/goldendata/redistset.go | 3 +- cmd/tools/goldentool/goldentool.go | 4 +- cmd/tools/junocfg/junocfg.go | 4 +- cmd/tools/junocli/junocli.go | 10 +-- cmd/tools/junostats/junostats.go | 4 +- pkg/client/clientimpl.go | 10 +-- pkg/client/config.go | 7 +- pkg/client/error.go | 80 ++++++++--------- pkg/client/example_test.go | 2 +- pkg/cluster/cluster.go | 2 +- pkg/cluster/clusterstats.go | 4 +- pkg/cluster/lock_linux.go | 3 +- pkg/cluster/node.go | 2 +- pkg/cluster/shardmgr.go | 20 ++--- pkg/cluster/util.go | 2 +- pkg/cluster/zone.go | 2 +- pkg/cmd/cmd.go | 4 +- pkg/etcd/config.go | 2 +- pkg/etcd/etcd.go | 3 +- pkg/etcd/etcdclient.go | 2 +- pkg/etcd/etcdreader.go | 8 +- pkg/etcd/etcdwriter.go | 4 +- pkg/initmgr/init.go | 4 +- pkg/io/conn.go | 10 +-- pkg/io/connmgr.go | 2 +- pkg/io/inboundconnector.go | 12 +-- pkg/io/ioconfig.go | 2 +- pkg/io/ioutil/logerr.go | 2 +- pkg/io/listener.go | 8 +- pkg/io/mayflyreqctx.go | 12 +-- pkg/io/outboundconnector.go | 12 +-- pkg/io/outboundprocessor.go | 18 ++-- pkg/io/requestcontext.go | 8 +- pkg/io/ssllistener.go | 8 +- pkg/logging/cal/config/calconfig.go | 2 +- pkg/logging/cal/logger.go | 10 +-- pkg/logging/cal/net/io/client.go | 14 ++- pkg/logging/cal/net/io/clientapi.go | 3 +- pkg/logging/cal/net/protocol/message.go | 3 +- pkg/logging/cal/test/main.go | 12 +-- pkg/logging/cal/util/util.go | 3 +- pkg/logging/calstatus.go | 4 +- pkg/logging/logging.go | 10 +-- pkg/logging/otel/config/otelconfig.go | 30 +++---- pkg/logging/otel/logger.go | 32 ++++--- pkg/logging/otel/statsLogger.go | 31 ++++--- pkg/logging/otel/test/logger_test.go | 7 +- pkg/net/netutil/netutil.go | 2 +- pkg/proto/cryptoks.go | 2 +- pkg/proto/decode.go | 6 +- pkg/proto/mayfly/mapping.go | 6 +- pkg/proto/mayfly/msg.go | 2 +- pkg/proto/mayfly/msg_test.go | 2 +- pkg/proto/mayfly/opmsg.go | 4 +- pkg/proto/metaField.go | 90 +++++++++---------- pkg/proto/opMsg.go | 2 +- pkg/proto/opMsg_test.go | 3 +- pkg/proto/payload.go | 6 +- pkg/proto/rawmessage.go | 10 +-- pkg/sec/certs.go | 3 +- pkg/sec/certs_test.go | 3 +- pkg/sec/gotlsimpl.go | 5 +- pkg/sec/keystore.go | 5 +- pkg/sec/keystore_test.go | 3 +- pkg/sec/seccfg.go | 2 +- pkg/sec/secinit.go | 18 ++-- pkg/service/servermgr.go | 2 +- pkg/service/service.go | 6 +- pkg/service/svccfg.go | 4 +- pkg/stats/appnsstats.go | 2 +- pkg/stats/redist/stats.go | 5 +- pkg/stats/sharedstats.go | 4 +- pkg/stats/statelog.go | 4 +- pkg/udf/udfmgr_test.go | 3 +- pkg/udf/udfplugin.go | 2 +- pkg/util/cmap.go | 5 +- pkg/version/version.go | 2 +- test/drv/bulkload/bulkload.go | 8 +- test/drv/junoload/dbstats.go | 6 +- test/drv/junoload/junoload.go | 12 +-- test/drv/junoload/tdscfg.go | 6 +- test/drv/junoload/tsteng.go | 4 +- test/fakess/config.go | 23 ++--- test/fakess/main.go | 22 ++--- test/fakess/requesthandler.go | 24 ++--- test/functest/create_test.go | 8 +- test/functest/delete_test.go | 5 +- test/functest/get_test.go | 9 +- test/functest/set_test.go | 7 +- test/functest/setup_test.go | 20 ++--- test/functest/update_test.go | 6 +- test/mockss/mockss.go | 5 +- test/testutil/log/frwk/frwk.go | 2 +- test/testutil/mock/client.go | 8 +- test/testutil/mock/common.go | 2 +- test/testutil/mock/config.go | 6 +- test/testutil/mock/ssreqhandler.go | 12 +-- test/testutil/server/cluster.go | 20 ++--- test/testutil/server/config.go | 10 +-- test/testutil/server/inprocsrv.go | 6 +- test/testutil/server/server.go | 13 +-- test/testutil/server/ssnode.go | 6 +- test/testutil/ssclient/advssclient.go | 8 +- test/testutil/ssclient/ssclient.go | 10 +-- test/testutil/testhelper.go | 38 ++++---- test/testutil/tmkeeper.go | 2 +- test/unittest/create_test.go | 11 +-- test/unittest/destroy_test.go | 37 ++++---- test/unittest/get_test.go | 9 +- test/unittest/set_test.go | 9 +- test/unittest/setup_test.go | 18 ++-- test/unittest/update_test.go | 11 +-- 231 files changed, 1130 insertions(+), 1111 deletions(-) diff --git a/binary_build/build.sh b/binary_build/build.sh index 87f2b489..072c2ef6 100755 --- a/binary_build/build.sh +++ b/binary_build/build.sh @@ -97,16 +97,16 @@ export CGO_LDFLAGS="-L$rocksdb_dir/lib -L/usr/local/lib -lrocksdb -lstdc++ -lm - juno_version_info="-X juno/pkg/version.BuildTime=$build_time -X juno/pkg/version.Revision=$code_revision -X juno/pkg/version.BuildId=$JUNO_BUILD_NUMBER" juno_executables="\ - juno/cmd/proxy \ - juno/cmd/storageserv \ - juno/cmd/storageserv/storage/db/dbcopy \ - juno/cmd/tools/junocli \ - juno/cmd/clustermgr \ - juno/cmd/dbscanserv \ - juno/cmd/dbscanserv/junoctl \ - juno/test/drv/junoload \ - juno/cmd/tools/junostats\ - juno/cmd/tools/junocfg\ + github.com/paypal/junodb/cmd/proxy \ + github.com/paypal/junodb/cmd/storageserv \ + github.com/paypal/junodb/cmd/storageserv/storage/db/dbcopy \ + github.com/paypal/junodb/cmd/tools/junocli \ + github.com/paypal/junodb/cmd/clustermgr \ + github.com/paypal/junodb/cmd/dbscanserv \ + github.com/paypal/junodb/cmd/dbscanserv/junoctl \ + github.com/paypal/junodb/test/drv/junoload \ + github.com/paypal/junodb/cmd/tools/junostats\ + github.com/paypal/junodb/cmd/tools/junocfg\ " # DO NOT include junoload in any package diff --git a/cmd/clustermgr/cmd/cmd.go b/cmd/clustermgr/cmd/cmd.go index f9fce42f..f684f9fa 100644 --- a/cmd/clustermgr/cmd/cmd.go +++ b/cmd/clustermgr/cmd/cmd.go @@ -27,10 +27,10 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/cluster" - "juno/pkg/etcd" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" ) func GetStatus(configFile string) { diff --git a/cmd/clustermgr/cmd/config.go b/cmd/clustermgr/cmd/config.go index 8d67d49a..4089b994 100644 --- a/cmd/clustermgr/cmd/config.go +++ b/cmd/clustermgr/cmd/config.go @@ -26,10 +26,10 @@ import ( "github.com/BurntSushi/toml" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/cluster" - "juno/pkg/etcd" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" ) var ( diff --git a/cmd/dbscanserv/app/cmd.go b/cmd/dbscanserv/app/cmd.go index a642a978..59b3beeb 100644 --- a/cmd/dbscanserv/app/cmd.go +++ b/cmd/dbscanserv/app/cmd.go @@ -28,12 +28,12 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" - "juno/cmd/dbscanserv/prime" - "juno/cmd/storageserv/compact" - "juno/cmd/storageserv/storage/db" + "github.com/paypal/junodb/cmd/dbscanserv/prime" + "github.com/paypal/junodb/cmd/storageserv/compact" + "github.com/paypal/junodb/cmd/storageserv/storage/db" ) type CmdLine struct { diff --git a/cmd/dbscanserv/app/collect.go b/cmd/dbscanserv/app/collect.go index 5d8a49d9..7cd2f358 100644 --- a/cmd/dbscanserv/app/collect.go +++ b/cmd/dbscanserv/app/collect.go @@ -25,10 +25,10 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/dbscanserv/prime" - "juno/pkg/logging" + "github.com/paypal/junodb/cmd/dbscanserv/prime" + "github.com/paypal/junodb/pkg/logging" ) type Collector struct { diff --git a/cmd/dbscanserv/app/init.go b/cmd/dbscanserv/app/init.go index d81a98c8..1feb7864 100644 --- a/cmd/dbscanserv/app/init.go +++ b/cmd/dbscanserv/app/init.go @@ -27,15 +27,15 @@ import ( "github.com/BurntSushi/toml" - "juno/third_party/forked/golang/glog" - - "juno/cmd/clustermgr/cmd" - "juno/cmd/dbscanserv/config" - "juno/cmd/dbscanserv/prime" - "juno/cmd/storageserv/storage/db" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/sec" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/clustermgr/cmd" + "github.com/paypal/junodb/cmd/dbscanserv/config" + "github.com/paypal/junodb/cmd/dbscanserv/prime" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/sec" ) type CmdConfig cmd.Config diff --git a/cmd/dbscanserv/app/patch.go b/cmd/dbscanserv/app/patch.go index 70bf2e93..d1f8acbe 100644 --- a/cmd/dbscanserv/app/patch.go +++ b/cmd/dbscanserv/app/patch.go @@ -27,14 +27,14 @@ import ( "sync" "time" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" - - "juno/cmd/dbscanserv/config" - "juno/cmd/dbscanserv/prime" - "juno/cmd/storageserv/storage/db" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" + + "github.com/paypal/junodb/cmd/dbscanserv/config" + "github.com/paypal/junodb/cmd/dbscanserv/prime" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) type CmdUpdatePatch struct { diff --git a/cmd/dbscanserv/app/remote.go b/cmd/dbscanserv/app/remote.go index 46d4ca4d..b6aa6f8f 100644 --- a/cmd/dbscanserv/app/remote.go +++ b/cmd/dbscanserv/app/remote.go @@ -30,11 +30,11 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/dbscanserv/config" - "juno/cmd/dbscanserv/prime" - "juno/pkg/net/netutil" + "github.com/paypal/junodb/cmd/dbscanserv/config" + "github.com/paypal/junodb/cmd/dbscanserv/prime" + "github.com/paypal/junodb/pkg/net/netutil" ) type Remote int diff --git a/cmd/dbscanserv/app/rpcclient.go b/cmd/dbscanserv/app/rpcclient.go index e468557e..b02449fd 100644 --- a/cmd/dbscanserv/app/rpcclient.go +++ b/cmd/dbscanserv/app/rpcclient.go @@ -27,10 +27,10 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/dbscanserv/config" - "juno/cmd/dbscanserv/prime" + "github.com/paypal/junodb/cmd/dbscanserv/config" + "github.com/paypal/junodb/cmd/dbscanserv/prime" ) type RpcClient struct { diff --git a/cmd/dbscanserv/app/scan.go b/cmd/dbscanserv/app/scan.go index 331a63d4..0321514f 100644 --- a/cmd/dbscanserv/app/scan.go +++ b/cmd/dbscanserv/app/scan.go @@ -24,11 +24,11 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" - "juno/cmd/dbscanserv/prime" - "juno/cmd/storageserv/storage/db" + "github.com/paypal/junodb/cmd/dbscanserv/prime" + "github.com/paypal/junodb/cmd/storageserv/storage/db" ) // Map from zoneid, shardid to Scanner diff --git a/cmd/dbscanserv/junoctl/junoctl.go b/cmd/dbscanserv/junoctl/junoctl.go index c03045c3..2d6894fe 100644 --- a/cmd/dbscanserv/junoctl/junoctl.go +++ b/cmd/dbscanserv/junoctl/junoctl.go @@ -25,9 +25,9 @@ import ( "os" "path/filepath" - "juno/cmd/dbscanserv/app" - "juno/pkg/version" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/cmd/dbscanserv/app" + "github.com/paypal/junodb/pkg/version" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) func main() { diff --git a/cmd/dbscanserv/main.go b/cmd/dbscanserv/main.go index de9fe60a..1a8461ff 100644 --- a/cmd/dbscanserv/main.go +++ b/cmd/dbscanserv/main.go @@ -27,11 +27,11 @@ import ( "strconv" "strings" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/dbscanserv/app" - "juno/cmd/dbscanserv/prime" - "juno/pkg/version" + "github.com/paypal/junodb/cmd/dbscanserv/app" + "github.com/paypal/junodb/cmd/dbscanserv/prime" + "github.com/paypal/junodb/pkg/version" ) func parseKeyRange(keyRange string, cmd string) (start int, stop int, skip int) { diff --git a/cmd/dbscanserv/patch/relay.go b/cmd/dbscanserv/patch/relay.go index a3014f34..8c73a2ab 100644 --- a/cmd/dbscanserv/patch/relay.go +++ b/cmd/dbscanserv/patch/relay.go @@ -22,12 +22,12 @@ package patch import ( "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/dbscanserv/app" - "juno/cmd/dbscanserv/config" - "juno/cmd/storageserv/storage/db" - "juno/pkg/proto" + "github.com/paypal/junodb/cmd/dbscanserv/app" + "github.com/paypal/junodb/cmd/dbscanserv/config" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/proto" ) // Called by storageserv. diff --git a/cmd/dbscanserv/prime/db.go b/cmd/dbscanserv/prime/db.go index 5e240a5f..42719e01 100644 --- a/cmd/dbscanserv/prime/db.go +++ b/cmd/dbscanserv/prime/db.go @@ -26,10 +26,10 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" - "juno/cmd/storageserv/storage/db" + "github.com/paypal/junodb/cmd/storageserv/storage/db" ) // Map from zoneid, nodeid to db handle diff --git a/cmd/dbscanserv/prime/log.go b/cmd/dbscanserv/prime/log.go index 6cf9312c..efb1de63 100644 --- a/cmd/dbscanserv/prime/log.go +++ b/cmd/dbscanserv/prime/log.go @@ -23,7 +23,7 @@ import ( "fmt" "os" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var ( diff --git a/cmd/dbscanserv/prime/meta.go b/cmd/dbscanserv/prime/meta.go index b1036384..61d752b8 100644 --- a/cmd/dbscanserv/prime/meta.go +++ b/cmd/dbscanserv/prime/meta.go @@ -28,9 +28,9 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/storage/db" + "github.com/paypal/junodb/cmd/storageserv/storage/db" ) type Meta struct { diff --git a/cmd/dbscanserv/prime/opdata.go b/cmd/dbscanserv/prime/opdata.go index 58e198b8..20176e6e 100644 --- a/cmd/dbscanserv/prime/opdata.go +++ b/cmd/dbscanserv/prime/opdata.go @@ -26,10 +26,10 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/storage/db" - "juno/pkg/proto" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/proto" ) type OpRecord struct { diff --git a/cmd/dbscanserv/prime/replicate.go b/cmd/dbscanserv/prime/replicate.go index abde2776..2a4d392c 100644 --- a/cmd/dbscanserv/prime/replicate.go +++ b/cmd/dbscanserv/prime/replicate.go @@ -24,12 +24,12 @@ import ( "runtime" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/internal/cli" - "juno/pkg/io" - "juno/pkg/proto" - "juno/pkg/sec" + "github.com/paypal/junodb/internal/cli" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/sec" ) var ( diff --git a/cmd/dbscanserv/prime/result.go b/cmd/dbscanserv/prime/result.go index d68b9251..9e589903 100644 --- a/cmd/dbscanserv/prime/result.go +++ b/cmd/dbscanserv/prime/result.go @@ -23,10 +23,10 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/client" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/util" ) type KeyList struct { diff --git a/cmd/proxy/app/proxy.go b/cmd/proxy/app/proxy.go index bfc55518..6855cf23 100644 --- a/cmd/proxy/app/proxy.go +++ b/cmd/proxy/app/proxy.go @@ -30,13 +30,13 @@ import ( "os/exec" "path/filepath" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/proxy/config" - "juno/pkg/cmd" - "juno/pkg/initmgr" - "juno/pkg/sec" - "juno/pkg/version" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/sec" + "github.com/paypal/junodb/pkg/version" ) func Main() { diff --git a/cmd/proxy/app/proxymgr.go b/cmd/proxy/app/proxymgr.go index 19cc1433..f1692190 100644 --- a/cmd/proxy/app/proxymgr.go +++ b/cmd/proxy/app/proxymgr.go @@ -28,17 +28,17 @@ import ( "strings" "syscall" - "juno/third_party/forked/golang/glog" - - "juno/cmd/proxy/config" - "juno/cmd/proxy/stats" - "juno/pkg/cmd" - "juno/pkg/initmgr" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/sec" - "juno/pkg/service" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/stats" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/sec" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/pkg/util" ) const ( diff --git a/cmd/proxy/app/proxymonwkr.go b/cmd/proxy/app/proxymonwkr.go index 28ed4e7e..73ef97e7 100644 --- a/cmd/proxy/app/proxymonwkr.go +++ b/cmd/proxy/app/proxymonwkr.go @@ -24,14 +24,14 @@ import ( "strings" "sync" - "juno/third_party/forked/golang/glog" - - "juno/cmd/proxy/config" - "juno/cmd/proxy/stats" - "juno/pkg/initmgr" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/stats" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" ) type ( diff --git a/cmd/proxy/app/proxywkr.go b/cmd/proxy/app/proxywkr.go index 56107f29..b2f8d92b 100644 --- a/cmd/proxy/app/proxywkr.go +++ b/cmd/proxy/app/proxywkr.go @@ -28,24 +28,24 @@ import ( "strconv" "time" - "juno/third_party/forked/golang/glog" - - "juno/cmd/proxy/config" - "juno/cmd/proxy/handler" - "juno/cmd/proxy/replication" - "juno/cmd/proxy/stats" - "juno/cmd/proxy/stats/shmstats" - "juno/cmd/proxy/watcher" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/initmgr" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/sec" - "juno/pkg/service" - "juno/pkg/udf" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/handler" + "github.com/paypal/junodb/cmd/proxy/replication" + "github.com/paypal/junodb/cmd/proxy/stats" + "github.com/paypal/junodb/cmd/proxy/stats/shmstats" + "github.com/paypal/junodb/cmd/proxy/watcher" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/sec" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/pkg/udf" + "github.com/paypal/junodb/pkg/util" ) type ( diff --git a/cmd/proxy/config/config.go b/cmd/proxy/config/config.go index 8c399ad6..b00c5994 100644 --- a/cmd/proxy/config/config.go +++ b/cmd/proxy/config/config.go @@ -26,21 +26,21 @@ import ( "path/filepath" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" - repconfig "juno/cmd/proxy/replication/config" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/initmgr" - "juno/pkg/io" - cal "juno/pkg/logging/cal/config" - otel "juno/pkg/logging/otel/config" - "juno/pkg/sec" - "juno/pkg/service" - "juno/pkg/util" - "juno/pkg/version" + repconfig "github.com/paypal/junodb/cmd/proxy/replication/config" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/io" + cal "github.com/paypal/junodb/pkg/logging/cal/config" + otel "github.com/paypal/junodb/pkg/logging/otel/config" + "github.com/paypal/junodb/pkg/sec" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/pkg/version" ) var ( @@ -211,7 +211,7 @@ func (c *Config) IsEncryptionEnabled() bool { return c.ReplicationEncryptionEnabled || c.PayloadEncryptionEnabled } -///TODO find a better name +// /TODO find a better name func (c *Config) GetSecFlag() (f sec.Flag) { if c.IsTLSEnabled(true) { f |= sec.KFlagServerTlsEnabled diff --git a/cmd/proxy/config/limitscfg.go b/cmd/proxy/config/limitscfg.go index 9d475c02..51d23fdd 100644 --- a/cmd/proxy/config/limitscfg.go +++ b/cmd/proxy/config/limitscfg.go @@ -24,9 +24,9 @@ import ( "math" "sync" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/cfg" + "github.com/paypal/junodb/pkg/cfg" ) var ( diff --git a/cmd/proxy/handler/requestHandler.go b/cmd/proxy/handler/requestHandler.go index 6d55adad..85d67abc 100644 --- a/cmd/proxy/handler/requestHandler.go +++ b/cmd/proxy/handler/requestHandler.go @@ -22,15 +22,15 @@ package handler import ( "os" - "juno/third_party/forked/golang/glog" - - "juno/cmd/proxy/config" - "juno/cmd/proxy/proc" - "juno/cmd/proxy/stats" - "juno/pkg/io" - "juno/pkg/net/netutil" - "juno/pkg/proto" - "juno/pkg/service" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/proc" + "github.com/paypal/junodb/cmd/proxy/stats" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/net/netutil" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/service" ) var _ io.IRequestHandler = (*RequestHandler)(nil) diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 5e18bcd4..540b7e91 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -19,7 +19,7 @@ package main -import "juno/cmd/proxy/app" +import "github.com/paypal/junodb/cmd/proxy/app" func main() { app.Main() diff --git a/cmd/proxy/proc/base.go b/cmd/proxy/proc/base.go index 76d6197f..547b5c48 100644 --- a/cmd/proxy/proc/base.go +++ b/cmd/proxy/proc/base.go @@ -28,22 +28,22 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" - - "juno/cmd/proxy/config" - "juno/cmd/proxy/replication" - proxystats "juno/cmd/proxy/stats" - "juno/pkg/cluster" - "juno/pkg/debug" - "juno/pkg/errors" - "juno/pkg/io" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/proto" - "juno/pkg/shard" - "juno/pkg/stats" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/replication" + proxystats "github.com/paypal/junodb/cmd/proxy/stats" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/debug" + "github.com/paypal/junodb/pkg/errors" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" + "github.com/paypal/junodb/pkg/stats" + "github.com/paypal/junodb/pkg/util" ) const ( diff --git a/cmd/proxy/proc/create.go b/cmd/proxy/proc/create.go index e73e3895..af4c1275 100644 --- a/cmd/proxy/proc/create.go +++ b/cmd/proxy/proc/create.go @@ -23,11 +23,11 @@ import ( "fmt" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/proto" ) var _ ITwoPhaseProcessor = (*CreateProcessor)(nil) diff --git a/cmd/proxy/proc/destroy.go b/cmd/proxy/proc/destroy.go index e5c17c57..2c2fe5dc 100644 --- a/cmd/proxy/proc/destroy.go +++ b/cmd/proxy/proc/destroy.go @@ -20,7 +20,7 @@ package proc import ( - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) var _ IOnePhaseProcessor = (*DestroyProcessor)(nil) diff --git a/cmd/proxy/proc/destroy2.go b/cmd/proxy/proc/destroy2.go index 2cd78632..82ab5129 100644 --- a/cmd/proxy/proc/destroy2.go +++ b/cmd/proxy/proc/destroy2.go @@ -20,9 +20,9 @@ package proc import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) // SUCCESS: NoError, AlreadyFulfilled, NoKey diff --git a/cmd/proxy/proc/get.go b/cmd/proxy/proc/get.go index 5cfdf4ba..561c6ec8 100644 --- a/cmd/proxy/proc/get.go +++ b/cmd/proxy/proc/get.go @@ -20,10 +20,10 @@ package proc import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/proto" ) // SUCCESS: NoError, NoKey, MarkedDelete diff --git a/cmd/proxy/proc/inbreqctx.go b/cmd/proxy/proc/inbreqctx.go index 7e96b1ed..ec0e614e 100644 --- a/cmd/proxy/proc/inbreqctx.go +++ b/cmd/proxy/proc/inbreqctx.go @@ -24,15 +24,15 @@ import ( goio "io" "time" - "juno/third_party/forked/golang/glog" - - "juno/cmd/proxy/config" - "juno/cmd/proxy/replication" - "juno/pkg/io" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/proto" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/replication" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/proto" ) type IInbRequestContext interface { @@ -47,7 +47,7 @@ type IInbRequestContext interface { ReplyStatus(st proto.OpStatus) } -//InboundRequestContext Proxy Inbound request context +// InboundRequestContext Proxy Inbound request context type InboundRequestContext struct { io.InboundRequestContext proto.OperationalMessage diff --git a/cmd/proxy/proc/pconfig.go b/cmd/proxy/proc/pconfig.go index 93beff00..c9a2c269 100644 --- a/cmd/proxy/proc/pconfig.go +++ b/cmd/proxy/proc/pconfig.go @@ -22,12 +22,12 @@ package proc import ( "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/proxy/config" - "juno/pkg/cfg" - "juno/pkg/io" - "juno/pkg/proto" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/pkg/cfg" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" ) var ( diff --git a/cmd/proxy/proc/proc_logging.go b/cmd/proxy/proc/proc_logging.go index b1eab84a..b332433a 100644 --- a/cmd/proxy/proc/proc_logging.go +++ b/cmd/proxy/proc/proc_logging.go @@ -20,11 +20,11 @@ package proc import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" ) var ( diff --git a/cmd/proxy/proc/processor.go b/cmd/proxy/proc/processor.go index 18b1599b..3d446789 100644 --- a/cmd/proxy/proc/processor.go +++ b/cmd/proxy/proc/processor.go @@ -22,12 +22,12 @@ package proc import ( "fmt" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/proto" ) type IOnePhaseProcessor interface { diff --git a/cmd/proxy/proc/reqProcessorPool.go b/cmd/proxy/proc/reqProcessorPool.go index b62ffec3..6e7b1238 100644 --- a/cmd/proxy/proc/reqProcessorPool.go +++ b/cmd/proxy/proc/reqProcessorPool.go @@ -20,9 +20,9 @@ package proc import ( - "juno/cmd/proxy/stats" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/proxy/stats" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) type ReqProcessorPool struct { diff --git a/cmd/proxy/proc/set.go b/cmd/proxy/proc/set.go index c3c5fb8d..7f8e3e85 100644 --- a/cmd/proxy/proc/set.go +++ b/cmd/proxy/proc/set.go @@ -22,10 +22,10 @@ package proc import ( "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging/cal" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" ) var _ ITwoPhaseProcessor = (*SetProcessor)(nil) diff --git a/cmd/proxy/proc/state.go b/cmd/proxy/proc/state.go index 62547cc7..ac8588ea 100644 --- a/cmd/proxy/proc/state.go +++ b/cmd/proxy/proc/state.go @@ -20,11 +20,11 @@ package proc import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/debug" - "juno/pkg/errors" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/debug" + "github.com/paypal/junodb/pkg/errors" + "github.com/paypal/junodb/pkg/proto" ) type RequestAndStats struct { @@ -42,7 +42,7 @@ type RequestAndStats struct { funcIsSuccess func(proto.OpStatus) bool } -///TODO may just change to *SSRequestContext +// /TODO may just change to *SSRequestContext type ResponseWrapper struct { ssRequest *SSRequestContext } diff --git a/cmd/proxy/proc/udfget.go b/cmd/proxy/proc/udfget.go index 3d3c9108..91cfe219 100644 --- a/cmd/proxy/proc/udfget.go +++ b/cmd/proxy/proc/udfget.go @@ -20,9 +20,9 @@ package proc import ( - "juno/pkg/proto" - "juno/pkg/udf" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/udf" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var _ IOnePhaseProcessor = (*UDFGetProcessor)(nil) diff --git a/cmd/proxy/proc/update.go b/cmd/proxy/proc/update.go index c38503c4..9f3d6d15 100644 --- a/cmd/proxy/proc/update.go +++ b/cmd/proxy/proc/update.go @@ -22,11 +22,11 @@ package proc import ( "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" ) var _ ITwoPhaseProcessor = (*UpdateProcessor)(nil) diff --git a/cmd/proxy/replication/config/repcfg.go b/cmd/proxy/replication/config/repcfg.go index 4278f208..93a00a74 100644 --- a/cmd/proxy/replication/config/repcfg.go +++ b/cmd/proxy/replication/config/repcfg.go @@ -24,8 +24,8 @@ import ( "strings" "time" - "juno/pkg/io" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/util" ) var ( diff --git a/cmd/proxy/replication/pingreqctx.go b/cmd/proxy/replication/pingreqctx.go index 0d715d62..c7abf39f 100644 --- a/cmd/proxy/replication/pingreqctx.go +++ b/cmd/proxy/replication/pingreqctx.go @@ -24,12 +24,12 @@ import ( goio "io" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/io" - "juno/pkg/proto" - "juno/pkg/proto/mayfly" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/proto/mayfly" + "github.com/paypal/junodb/pkg/util" ) type ( diff --git a/cmd/proxy/replication/replicaterequest.go b/cmd/proxy/replication/replicaterequest.go index 5ce53dc8..875df265 100644 --- a/cmd/proxy/replication/replicaterequest.go +++ b/cmd/proxy/replication/replicaterequest.go @@ -24,15 +24,15 @@ import ( goio "io" "time" - "juno/third_party/forked/golang/glog" - - "juno/pkg/io" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/proto" - "juno/pkg/proto/mayfly" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/proto/mayfly" + "github.com/paypal/junodb/pkg/util" ) var ( diff --git a/cmd/proxy/replication/replicator.go b/cmd/proxy/replication/replicator.go index 4abcd607..b0a36b80 100644 --- a/cmd/proxy/replication/replicator.go +++ b/cmd/proxy/replication/replicator.go @@ -28,16 +28,16 @@ import ( "sync" "time" - "juno/third_party/forked/golang/glog" - - repconfig "juno/cmd/proxy/replication/config" - proxystats "juno/cmd/proxy/stats" - "juno/cmd/proxy/stats/shmstats" - "juno/pkg/io" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + repconfig "github.com/paypal/junodb/cmd/proxy/replication/config" + proxystats "github.com/paypal/junodb/cmd/proxy/stats" + "github.com/paypal/junodb/cmd/proxy/stats/shmstats" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) var ( diff --git a/cmd/proxy/stats/clsConTmpl.go b/cmd/proxy/stats/clsConTmpl.go index ad3fc5c0..a41efdaa 100644 --- a/cmd/proxy/stats/clsConTmpl.go +++ b/cmd/proxy/stats/clsConTmpl.go @@ -24,7 +24,7 @@ import ( "fmt" "html/template" - "juno/pkg/version" + "github.com/paypal/junodb/pkg/version" ) var ( diff --git a/cmd/proxy/stats/htmlsect.go b/cmd/proxy/stats/htmlsect.go index d490aade..b9cfdc43 100644 --- a/cmd/proxy/stats/htmlsect.go +++ b/cmd/proxy/stats/htmlsect.go @@ -25,10 +25,10 @@ import ( "html/template" "time" - "juno/cmd/proxy/config" - "juno/cmd/proxy/stats/shmstats" - "juno/pkg/cluster" - "juno/pkg/stats" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/stats/shmstats" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/stats" ) type ( diff --git a/cmd/proxy/stats/httphandler.go b/cmd/proxy/stats/httphandler.go index 46637934..9db66df8 100644 --- a/cmd/proxy/stats/httphandler.go +++ b/cmd/proxy/stats/httphandler.go @@ -23,13 +23,13 @@ import ( "fmt" "net/http" - //"juno/third_party/forked/golang/glog" + //"github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" - "juno/cmd/proxy/config" - "juno/cmd/proxy/stats/qry" - "juno/pkg/stats" - "juno/pkg/version" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/stats/qry" + "github.com/paypal/junodb/pkg/stats" + "github.com/paypal/junodb/pkg/version" ) var ( diff --git a/cmd/proxy/stats/monhandler.go b/cmd/proxy/stats/monhandler.go index aac7d7a8..cbf728eb 100644 --- a/cmd/proxy/stats/monhandler.go +++ b/cmd/proxy/stats/monhandler.go @@ -31,12 +31,12 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/proxy/config" - "juno/cmd/proxy/stats/shmstats" - "juno/pkg/stats" - "juno/pkg/version" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/stats/shmstats" + "github.com/paypal/junodb/pkg/stats" + "github.com/paypal/junodb/pkg/version" ) var () diff --git a/cmd/proxy/stats/qry/infoquery.go b/cmd/proxy/stats/qry/infoquery.go index 0b6e6fc9..a3f363af 100644 --- a/cmd/proxy/stats/qry/infoquery.go +++ b/cmd/proxy/stats/qry/infoquery.go @@ -27,7 +27,7 @@ import ( "strconv" "strings" - "juno/pkg/cluster" + "github.com/paypal/junodb/pkg/cluster" ) var ( diff --git a/cmd/proxy/stats/shmstats/shmstats.go b/cmd/proxy/stats/shmstats/shmstats.go index 29b5878f..21dc4cda 100644 --- a/cmd/proxy/stats/shmstats/shmstats.go +++ b/cmd/proxy/stats/shmstats/shmstats.go @@ -34,12 +34,12 @@ import ( "unsafe" // "github.com/BurntSushi/toml" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/proxy/config" - "juno/pkg/io" - "juno/pkg/stats" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/stats" + "github.com/paypal/junodb/pkg/util" ) const ( @@ -944,7 +944,7 @@ func GetListenerStats() (stats []ListenerStats) { return } -//TODO make sure shmStats has been initialized.... +// TODO make sure shmStats has been initialized.... func GetCurrentWorkerStatsManager() *workerStatsManagerT { return shmStats.current } diff --git a/cmd/proxy/stats/shmstatswr.go b/cmd/proxy/stats/shmstatswr.go index e8b2aba8..b2ab3d90 100644 --- a/cmd/proxy/stats/shmstatswr.go +++ b/cmd/proxy/stats/shmstatswr.go @@ -25,9 +25,9 @@ import ( "time" "unsafe" - "juno/cmd/proxy/stats/shmstats" - "juno/pkg/logging/cal" - "juno/pkg/stats" + "github.com/paypal/junodb/cmd/proxy/stats/shmstats" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/stats" ) var ( diff --git a/cmd/proxy/stats/statelog.go b/cmd/proxy/stats/statelog.go index bf604907..d8a86328 100644 --- a/cmd/proxy/stats/statelog.go +++ b/cmd/proxy/stats/statelog.go @@ -31,13 +31,13 @@ import ( "syscall" "time" - // "juno/third_party/forked/golang/glog" + // "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/cluster" - "juno/pkg/io" - "juno/pkg/proto" - "juno/pkg/stats" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/stats" + "github.com/paypal/junodb/pkg/util" ) // counters diff --git a/cmd/proxy/stats/statsinit.go b/cmd/proxy/stats/statsinit.go index c06d369a..55d6da3c 100644 --- a/cmd/proxy/stats/statsinit.go +++ b/cmd/proxy/stats/statsinit.go @@ -22,9 +22,9 @@ package stats import ( "fmt" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/proxy/stats/shmstats" + "github.com/paypal/junodb/cmd/proxy/stats/shmstats" ) const ( @@ -39,9 +39,9 @@ type ( ) /* - Arguments - arg 0: Type stats.Type - arg 1: WorkerId int, if Type == KTypeWorker +Arguments +arg 0: Type stats.Type +arg 1: WorkerId int, if Type == KTypeWorker */ func Initialize(args ...interface{}) (err error) { var ( diff --git a/cmd/proxy/stats/statslogger.go b/cmd/proxy/stats/statslogger.go index 80cd7d0f..bff0eba5 100644 --- a/cmd/proxy/stats/statslogger.go +++ b/cmd/proxy/stats/statslogger.go @@ -29,12 +29,12 @@ import ( "path/filepath" "time" - "juno/cmd/proxy/config" - "juno/cmd/proxy/stats/shmstats" - "juno/pkg/io" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/stats" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/cmd/proxy/stats/shmstats" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/stats" ) var ( diff --git a/cmd/proxy/watcher/watcher.go b/cmd/proxy/watcher/watcher.go index 35befb7d..056b44c7 100644 --- a/cmd/proxy/watcher/watcher.go +++ b/cmd/proxy/watcher/watcher.go @@ -28,12 +28,12 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/proxy/proc" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/proxy/proc" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/util" ) type Watcher struct { diff --git a/cmd/storageserv/app/ssmgr.go b/cmd/storageserv/app/ssmgr.go index 4ee27b01..2a1f4d18 100644 --- a/cmd/storageserv/app/ssmgr.go +++ b/cmd/storageserv/app/ssmgr.go @@ -26,14 +26,14 @@ import ( "net" "os" - "juno/third_party/forked/golang/glog" - - "juno/cmd/storageserv/config" - "juno/pkg/cmd" - "juno/pkg/initmgr" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/net/netutil" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/net/netutil" ) const ( @@ -97,7 +97,7 @@ func (c *Manager) Parse(args []string) (err error) { return } -///TODO refactoring +// /TODO refactoring func (c *Manager) Exec() { initmgr.Register(config.Initializer, c.optConfigFile) diff --git a/cmd/storageserv/app/ssmonwkr.go b/cmd/storageserv/app/ssmonwkr.go index dbe9ada7..6e2311f5 100644 --- a/cmd/storageserv/app/ssmonwkr.go +++ b/cmd/storageserv/app/ssmonwkr.go @@ -24,14 +24,14 @@ import ( "strings" "sync" - "juno/third_party/forked/golang/glog" - - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/stats" - "juno/pkg/initmgr" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/stats" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" ) type ( diff --git a/cmd/storageserv/app/sswkr.go b/cmd/storageserv/app/sswkr.go index 73c7474f..10aa38c2 100644 --- a/cmd/storageserv/app/sswkr.go +++ b/cmd/storageserv/app/sswkr.go @@ -26,24 +26,24 @@ import ( "os" "strconv" - "juno/third_party/forked/golang/glog" - - "juno/cmd/dbscanserv/patch" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/handler" - "juno/cmd/storageserv/redist" - "juno/cmd/storageserv/stats" - "juno/cmd/storageserv/storage" - - "juno/cmd/storageserv/compact" - "juno/cmd/storageserv/watcher" - "juno/pkg/cluster" - "juno/pkg/initmgr" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/service" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/dbscanserv/patch" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/handler" + "github.com/paypal/junodb/cmd/storageserv/redist" + "github.com/paypal/junodb/cmd/storageserv/stats" + "github.com/paypal/junodb/cmd/storageserv/storage" + + "github.com/paypal/junodb/cmd/storageserv/compact" + "github.com/paypal/junodb/cmd/storageserv/watcher" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/pkg/util" ) type Worker struct { diff --git a/cmd/storageserv/app/storagemgr.go b/cmd/storageserv/app/storagemgr.go index ee9c288d..20b88626 100644 --- a/cmd/storageserv/app/storagemgr.go +++ b/cmd/storageserv/app/storagemgr.go @@ -34,10 +34,10 @@ import ( "syscall" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/stats/shmstats" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/storageserv/stats/shmstats" + "github.com/paypal/junodb/pkg/util" ) type ( diff --git a/cmd/storageserv/app/storageserv.go b/cmd/storageserv/app/storageserv.go index 11d8ac57..f2d1b4bd 100644 --- a/cmd/storageserv/app/storageserv.go +++ b/cmd/storageserv/app/storageserv.go @@ -26,9 +26,9 @@ import ( "path/filepath" "strings" - "juno/pkg/cmd" - "juno/pkg/initmgr" - "juno/pkg/version" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/version" ) func init() { @@ -96,7 +96,7 @@ func Main() { } -//TODO may customize this or remove inappalicable glob flags +// TODO may customize this or remove inappalicable glob flags func printUsage() { progName := filepath.Base(os.Args[0]) fmt.Printf(` diff --git a/cmd/storageserv/compact/dbclient.go b/cmd/storageserv/compact/dbclient.go index 6d92d985..4428530a 100644 --- a/cmd/storageserv/compact/dbclient.go +++ b/cmd/storageserv/compact/dbclient.go @@ -28,10 +28,10 @@ import ( "path/filepath" "time" - "juno/cmd/storageserv/storage/db" - "juno/pkg/service" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" ) type DbClient struct { diff --git a/cmd/storageserv/compact/filter.go b/cmd/storageserv/compact/filter.go index 226e4aa6..53d0cace 100644 --- a/cmd/storageserv/compact/filter.go +++ b/cmd/storageserv/compact/filter.go @@ -24,7 +24,7 @@ import ( "github.com/BurntSushi/toml" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type namespaceFilter struct { diff --git a/cmd/storageserv/config/config.go b/cmd/storageserv/config/config.go index 524a3de3..253d1f70 100644 --- a/cmd/storageserv/config/config.go +++ b/cmd/storageserv/config/config.go @@ -29,23 +29,23 @@ import ( "path/filepath" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" - dbscan "juno/cmd/dbscanserv/config" - "juno/cmd/storageserv/redist" - "juno/cmd/storageserv/storage/db" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/initmgr" - "juno/pkg/io" - cal "juno/pkg/logging/cal/config" - otel "juno/pkg/logging/otel/config" - "juno/pkg/service" - "juno/pkg/shard" - "juno/pkg/util" - "juno/pkg/version" + dbscan "github.com/paypal/junodb/cmd/dbscanserv/config" + "github.com/paypal/junodb/cmd/storageserv/redist" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/io" + cal "github.com/paypal/junodb/pkg/logging/cal/config" + otel "github.com/paypal/junodb/pkg/logging/otel/config" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/pkg/shard" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/pkg/version" ) var Initializer initmgr.IInitializer = initmgr.NewInitializer(initialize, finalize) diff --git a/cmd/storageserv/handler/reqhandler.go b/cmd/storageserv/handler/reqhandler.go index 9c8fe70e..a029b790 100644 --- a/cmd/storageserv/handler/reqhandler.go +++ b/cmd/storageserv/handler/reqhandler.go @@ -20,12 +20,12 @@ package handler import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/storage" - "juno/pkg/io" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/storage" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/util" ) var _ io.IRequestHandler = (*RequestHandler)(nil) diff --git a/cmd/storageserv/main.go b/cmd/storageserv/main.go index 271a0523..1c7fb879 100644 --- a/cmd/storageserv/main.go +++ b/cmd/storageserv/main.go @@ -19,7 +19,7 @@ package main -import "juno/cmd/storageserv/app" +import "github.com/paypal/junodb/cmd/storageserv/app" func main() { app.Main() diff --git a/cmd/storageserv/redist/config.go b/cmd/storageserv/redist/config.go index 48aa4ea5..4134fc8a 100644 --- a/cmd/storageserv/redist/config.go +++ b/cmd/storageserv/redist/config.go @@ -20,9 +20,10 @@ package redist import ( - "juno/pkg/io" - "juno/pkg/util" "time" + + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/util" ) type Config struct { diff --git a/cmd/storageserv/redist/ratelimiter.go b/cmd/storageserv/redist/ratelimiter.go index b98359a8..5cf06cc0 100644 --- a/cmd/storageserv/redist/ratelimiter.go +++ b/cmd/storageserv/redist/ratelimiter.go @@ -20,8 +20,9 @@ package redist import ( - "juno/third_party/forked/golang/glog" "time" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) // A variantion of token bucket algorithm diff --git a/cmd/storageserv/redist/redist.go b/cmd/storageserv/redist/redist.go index d4d6a9d7..baf3fd43 100644 --- a/cmd/storageserv/redist/redist.go +++ b/cmd/storageserv/redist/redist.go @@ -26,13 +26,13 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/etcd" - "juno/pkg/io" - "juno/pkg/proto" - "juno/pkg/shard" - redistst "juno/pkg/stats/redist" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" + redistst "github.com/paypal/junodb/pkg/stats/redist" ) type IDBRedistHandler interface { diff --git a/cmd/storageserv/redist/replicator.go b/cmd/storageserv/redist/replicator.go index c7a28a56..4ea068c8 100644 --- a/cmd/storageserv/redist/replicator.go +++ b/cmd/storageserv/redist/replicator.go @@ -24,14 +24,14 @@ import ( "sync" "time" - cerr "juno/pkg/errors" - "juno/pkg/etcd" - "juno/pkg/io" - "juno/pkg/proto" - "juno/pkg/shard" - redistst "juno/pkg/stats/redist" - - "juno/third_party/forked/golang/glog" + cerr "github.com/paypal/junodb/pkg/errors" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" + redistst "github.com/paypal/junodb/pkg/stats/redist" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type Replicator struct { diff --git a/cmd/storageserv/redist/reqctx.go b/cmd/storageserv/redist/reqctx.go index 249abd99..f0edfaec 100644 --- a/cmd/storageserv/redist/reqctx.go +++ b/cmd/storageserv/redist/reqctx.go @@ -24,12 +24,12 @@ import ( "io" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - . "juno/pkg/io" - "juno/pkg/proto" - redistst "juno/pkg/stats/redist" - "juno/pkg/util" + . "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + redistst "github.com/paypal/junodb/pkg/stats/redist" + "github.com/paypal/junodb/pkg/util" ) type RedistRequestContext struct { diff --git a/cmd/storageserv/stats/htmlsect.go b/cmd/storageserv/stats/htmlsect.go index 86d45177..11d02fbc 100644 --- a/cmd/storageserv/stats/htmlsect.go +++ b/cmd/storageserv/stats/htmlsect.go @@ -25,8 +25,8 @@ import ( "html/template" "time" - "juno/cmd/storageserv/stats/shmstats" - "juno/pkg/stats" + "github.com/paypal/junodb/cmd/storageserv/stats/shmstats" + "github.com/paypal/junodb/pkg/stats" ) type ( diff --git a/cmd/storageserv/stats/htmltmpl.go b/cmd/storageserv/stats/htmltmpl.go index 5fbe57ed..687ce5fd 100644 --- a/cmd/storageserv/stats/htmltmpl.go +++ b/cmd/storageserv/stats/htmltmpl.go @@ -23,7 +23,7 @@ import ( "fmt" "html/template" - "juno/pkg/stats" + "github.com/paypal/junodb/pkg/stats" ) var ( diff --git a/cmd/storageserv/stats/httphandler.go b/cmd/storageserv/stats/httphandler.go index e4b1608a..b36d8eed 100644 --- a/cmd/storageserv/stats/httphandler.go +++ b/cmd/storageserv/stats/httphandler.go @@ -25,11 +25,11 @@ import ( "github.com/BurntSushi/toml" - "juno/cmd/proxy/stats/qry" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/storage/db" - "juno/pkg/stats" - "juno/pkg/version" + "github.com/paypal/junodb/cmd/proxy/stats/qry" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/stats" + "github.com/paypal/junodb/pkg/version" ) var ( diff --git a/cmd/storageserv/stats/monhandler.go b/cmd/storageserv/stats/monhandler.go index d965aa1b..0eb335b5 100644 --- a/cmd/storageserv/stats/monhandler.go +++ b/cmd/storageserv/stats/monhandler.go @@ -29,12 +29,12 @@ import ( "strconv" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/stats/shmstats" - "juno/pkg/stats" - "juno/pkg/version" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/stats/shmstats" + "github.com/paypal/junodb/pkg/stats" + "github.com/paypal/junodb/pkg/version" ) type ( diff --git a/cmd/storageserv/stats/shmstats/shmstats.go b/cmd/storageserv/stats/shmstats/shmstats.go index 66b1e7d3..87ecfdc8 100644 --- a/cmd/storageserv/stats/shmstats/shmstats.go +++ b/cmd/storageserv/stats/shmstats/shmstats.go @@ -32,10 +32,10 @@ import ( "time" "unsafe" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/config" - "juno/pkg/stats" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/pkg/stats" ) const ( diff --git a/cmd/storageserv/stats/shmstatswr.go b/cmd/storageserv/stats/shmstatswr.go index 042e0b39..d2fb50c7 100644 --- a/cmd/storageserv/stats/shmstatswr.go +++ b/cmd/storageserv/stats/shmstatswr.go @@ -25,8 +25,8 @@ import ( "time" "unsafe" - "juno/cmd/storageserv/stats/shmstats" - "juno/pkg/stats" + "github.com/paypal/junodb/cmd/storageserv/stats/shmstats" + "github.com/paypal/junodb/pkg/stats" ) var ( diff --git a/cmd/storageserv/stats/statelog.go b/cmd/storageserv/stats/statelog.go index 723cf7e9..66d27b6f 100644 --- a/cmd/storageserv/stats/statelog.go +++ b/cmd/storageserv/stats/statelog.go @@ -31,9 +31,9 @@ import ( "syscall" "time" - "juno/cmd/storageserv/storage/db" - "juno/pkg/proto" - "juno/pkg/stats" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/stats" ) const ( @@ -46,7 +46,7 @@ const ( kNumRequestTypes ) -//Exponential Moving Average (EMA) +// Exponential Moving Average (EMA) var ( statsNumRequests uint64 statsNumRequestsPrev uint64 diff --git a/cmd/storageserv/stats/statsinit.go b/cmd/storageserv/stats/statsinit.go index d1e2ed95..8b7f4cfe 100644 --- a/cmd/storageserv/stats/statsinit.go +++ b/cmd/storageserv/stats/statsinit.go @@ -22,11 +22,11 @@ package stats import ( "fmt" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/stats/shmstats" - "juno/pkg/debug" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/stats/shmstats" + "github.com/paypal/junodb/pkg/debug" ) func InitForManager(numChildren int) (err error) { diff --git a/cmd/storageserv/stats/statslogger.go b/cmd/storageserv/stats/statslogger.go index a36ca100..79ca3643 100644 --- a/cmd/storageserv/stats/statslogger.go +++ b/cmd/storageserv/stats/statslogger.go @@ -29,13 +29,13 @@ import ( "path/filepath" "time" - // "juno/third_party/forked/golang/glog" + // "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/stats/shmstats" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/stats" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/stats/shmstats" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/stats" ) var ( diff --git a/cmd/storageserv/storage/bench_test.go b/cmd/storageserv/storage/bench_test.go index 90070c04..580fce6e 100644 --- a/cmd/storageserv/storage/bench_test.go +++ b/cmd/storageserv/storage/bench_test.go @@ -23,7 +23,7 @@ import ( "testing" "time" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) func BenchmarkSet(b *testing.B) { diff --git a/cmd/storageserv/storage/create_test.go b/cmd/storageserv/storage/create_test.go index 25dd5d47..77cd1d26 100644 --- a/cmd/storageserv/storage/create_test.go +++ b/cmd/storageserv/storage/create_test.go @@ -24,7 +24,7 @@ import ( "testing" "time" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) var ( diff --git a/cmd/storageserv/storage/db/config.go b/cmd/storageserv/storage/db/config.go index f7df81f7..eb7ddd95 100644 --- a/cmd/storageserv/storage/db/config.go +++ b/cmd/storageserv/storage/db/config.go @@ -26,8 +26,8 @@ import ( "math/rand" "os" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" ) ///TODO need to add validation @@ -242,7 +242,7 @@ var defaultFlashConfig = Config{ var DBConfig = defaultFlashConfig -//Note: rocksdb C binding does not support getters for option types +// Note: rocksdb C binding does not support getters for option types func NewRocksDBptions() *gorocksdb.Options { options := gorocksdb.NewDefaultOptions() diff --git a/cmd/storageserv/storage/db/db.go b/cmd/storageserv/storage/db/db.go index 60b4d8b1..ed67979a 100644 --- a/cmd/storageserv/storage/db/db.go +++ b/cmd/storageserv/storage/db/db.go @@ -22,8 +22,8 @@ package db import ( "io" - "juno/cmd/storageserv/redist" - "juno/pkg/shard" + "github.com/paypal/junodb/cmd/storageserv/redist" + "github.com/paypal/junodb/pkg/shard" ) type IDatabase interface { diff --git a/cmd/storageserv/storage/db/dbcopy/cmd.go b/cmd/storageserv/storage/db/dbcopy/cmd.go index 3c66333a..c1fde68d 100644 --- a/cmd/storageserv/storage/db/dbcopy/cmd.go +++ b/cmd/storageserv/storage/db/dbcopy/cmd.go @@ -22,7 +22,7 @@ package main import ( "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type CmdLine struct { diff --git a/cmd/storageserv/storage/db/dbcopy/compact.go b/cmd/storageserv/storage/db/dbcopy/compact.go index c5541641..fc280869 100644 --- a/cmd/storageserv/storage/db/dbcopy/compact.go +++ b/cmd/storageserv/storage/db/dbcopy/compact.go @@ -21,7 +21,8 @@ package main import ( "bytes" - "juno/third_party/forked/golang/glog" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type CompactionFilter struct { diff --git a/cmd/storageserv/storage/db/dbcopy/config.go b/cmd/storageserv/storage/db/dbcopy/config.go index a1834ee4..7fe3da6b 100644 --- a/cmd/storageserv/storage/db/dbcopy/config.go +++ b/cmd/storageserv/storage/db/dbcopy/config.go @@ -28,9 +28,9 @@ import ( "github.com/BurntSushi/toml" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/storage/db" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var cfg = config.Config{ diff --git a/cmd/storageserv/storage/db/dbcopy/dbclient.go b/cmd/storageserv/storage/db/dbcopy/dbclient.go index d6b27c85..bfeae710 100644 --- a/cmd/storageserv/storage/db/dbcopy/dbclient.go +++ b/cmd/storageserv/storage/db/dbcopy/dbclient.go @@ -27,11 +27,11 @@ import ( "path/filepath" "time" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" - "juno/cmd/storageserv/storage/db" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/util" ) type DbClient struct { diff --git a/cmd/storageserv/storage/db/dbcopy/main.go b/cmd/storageserv/storage/db/dbcopy/main.go index d0a767db..6c288aee 100644 --- a/cmd/storageserv/storage/db/dbcopy/main.go +++ b/cmd/storageserv/storage/db/dbcopy/main.go @@ -31,7 +31,7 @@ import ( "strings" "sync" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) func parseKeyRange(key string) (start int, stop int) { diff --git a/cmd/storageserv/storage/db/debug.go b/cmd/storageserv/storage/db/debug.go index 57f32f91..e73a7485 100644 --- a/cmd/storageserv/storage/db/debug.go +++ b/cmd/storageserv/storage/db/debug.go @@ -28,7 +28,7 @@ import ( "sync" "unsafe" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" ) var ( diff --git a/cmd/storageserv/storage/db/record.go b/cmd/storageserv/storage/db/record.go index 9ae2675d..86557a3c 100644 --- a/cmd/storageserv/storage/db/record.go +++ b/cmd/storageserv/storage/db/record.go @@ -22,39 +22,38 @@ Package db implements Juno storage interfaces with gorocksdb. Record Encoding Format - Offset | Field | Size - --------+---------------------------------+--------------- - 0 | encoding version | 1 byte - --------+---------------------------------+--------------- - 1 | flag | 1 byte - --------+---------------------------------+--------------- - 2 | reserved | 2 bytes - --------+---------------------------------+--------------- - 4 | expiration time | 4 bytes - --------+---------------------------------+--------------- - 8 | version | 4 bytes - --------+---------------------------------+--------------- - 12 | creation time | 4 bytes - --------+---------------------------------+--------------- - 16 | last modification time | 8 bytes - --------+---------------------------------+--------------- - 24 | request Id of the last modifier | 16 bytes - --------+---------------------------------+--------------- - 40 | request Id of the originator | 16 bytes - --------+---------------------------------+--------------- - 56 | encapsulating payload | ... - - Record Flag - bit | 0| 1| 2| 3| 4| 5| 6| 7 - ------+------------+------------+------------+------------+------------+------------+------------+------------+ - | MarkDelete | - + Offset | Field | Size + --------+---------------------------------+--------------- + 0 | encoding version | 1 byte + --------+---------------------------------+--------------- + 1 | flag | 1 byte + --------+---------------------------------+--------------- + 2 | reserved | 2 bytes + --------+---------------------------------+--------------- + 4 | expiration time | 4 bytes + --------+---------------------------------+--------------- + 8 | version | 4 bytes + --------+---------------------------------+--------------- + 12 | creation time | 4 bytes + --------+---------------------------------+--------------- + 16 | last modification time | 8 bytes + --------+---------------------------------+--------------- + 24 | request Id of the last modifier | 16 bytes + --------+---------------------------------+--------------- + 40 | request Id of the originator | 16 bytes + --------+---------------------------------+--------------- + 56 | encapsulating payload | ... + + Record Flag + bit | 0| 1| 2| 3| 4| 5| 6| 7 + ------+------------+------------+------------+------------+------------+------------+------------+------------+ + | MarkDelete | Storage Key Format - ----------------------------+----------- +-------- - namespace length (1 byte) | namespace | key - ----------------------------+----------- +-------- + ----------------------------+----------- +-------- + namespace length (1 byte) | namespace | key + ----------------------------+----------- +-------- */ package db @@ -66,12 +65,12 @@ import ( "io" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging" - "juno/pkg/proto" - "juno/pkg/shard" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" + "github.com/paypal/junodb/pkg/util" ) const ( @@ -234,7 +233,7 @@ func (rec *Record) EncodeToBuffer(buffer *bytes.Buffer) error { return nil } -///TODO validation. the slices +// /TODO validation. the slices func (rec *Record) Decode(data []byte) error { if data == nil || len(data) < kSzHeader { return errors.New("Decoding error: empty") diff --git a/cmd/storageserv/storage/db/recordid.go b/cmd/storageserv/storage/db/recordid.go index 8ff6103e..b46c5c1f 100644 --- a/cmd/storageserv/storage/db/recordid.go +++ b/cmd/storageserv/storage/db/recordid.go @@ -23,9 +23,9 @@ import ( "bytes" "encoding/binary" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/shard" + "github.com/paypal/junodb/pkg/shard" ) var enableMircoShardId bool = false // default false for backward compatibility diff --git a/cmd/storageserv/storage/db/recordid_test.go b/cmd/storageserv/storage/db/recordid_test.go index 29f07345..1da0f86d 100644 --- a/cmd/storageserv/storage/db/recordid_test.go +++ b/cmd/storageserv/storage/db/recordid_test.go @@ -22,8 +22,9 @@ package db import ( "bytes" "fmt" - "juno/pkg/shard" "testing" + + "github.com/paypal/junodb/pkg/shard" ) func TestNoMicroShard(t *testing.T) { diff --git a/cmd/storageserv/storage/db/rocksdb.go b/cmd/storageserv/storage/db/rocksdb.go index 11b1d13c..dd5a29bb 100644 --- a/cmd/storageserv/storage/db/rocksdb.go +++ b/cmd/storageserv/storage/db/rocksdb.go @@ -28,14 +28,14 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" - - "juno/cmd/storageserv/redist" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/proto" - "juno/pkg/shard" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" + + "github.com/paypal/junodb/cmd/storageserv/redist" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" ) var ( @@ -92,7 +92,7 @@ func GetPrefixDB() *ShardingByPrefix { return nil } -///TODO xuli dbDir... +// /TODO xuli dbDir... func newDBSharding(numShards int, numMicroShards int, numMicroShardGroups int, numPrefixDbs int, dbnamePrefix string) (sharding IDBSharding) { if numPrefixDbs > 0 { // Use prefix key shardFilters := make([]*ShardFilter, numPrefixDbs, numPrefixDbs) @@ -115,7 +115,7 @@ func newDBSharding(numShards int, numMicroShards int, numMicroShardGroups int, n return } -///TODO dbDir... +// /TODO dbDir... func newRocksDB(numShards int, numMicroShards int, numMicroShardGroups int, numPrefixDbs int, zoneId int, nodeId int, shardMap shard.Map) *RocksDB { db := &RocksDB{ zoneId: zoneId, diff --git a/cmd/storageserv/storage/db/sharding.go b/cmd/storageserv/storage/db/sharding.go index bfc3eedf..b24723bb 100644 --- a/cmd/storageserv/storage/db/sharding.go +++ b/cmd/storageserv/storage/db/sharding.go @@ -23,10 +23,10 @@ import ( "io" "time" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" - "juno/cmd/storageserv/redist" - "juno/pkg/shard" + "github.com/paypal/junodb/cmd/storageserv/redist" + "github.com/paypal/junodb/pkg/shard" ) type IDBSharding interface { diff --git a/cmd/storageserv/storage/db/shardingByInstance.go b/cmd/storageserv/storage/db/shardingByInstance.go index fcd95a99..0a7dfbe4 100644 --- a/cmd/storageserv/storage/db/shardingByInstance.go +++ b/cmd/storageserv/storage/db/shardingByInstance.go @@ -25,13 +25,13 @@ import ( "sync" "time" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" - "juno/cmd/storageserv/redist" - "juno/pkg/shard" - redistst "juno/pkg/stats/redist" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/storageserv/redist" + "github.com/paypal/junodb/pkg/shard" + redistst "github.com/paypal/junodb/pkg/stats/redist" + "github.com/paypal/junodb/pkg/util" ) type ShardingByInstance struct { diff --git a/cmd/storageserv/storage/db/shardingByPrefix.go b/cmd/storageserv/storage/db/shardingByPrefix.go index fefd006d..e32988dc 100644 --- a/cmd/storageserv/storage/db/shardingByPrefix.go +++ b/cmd/storageserv/storage/db/shardingByPrefix.go @@ -28,13 +28,13 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" - "juno/cmd/storageserv/redist" - "juno/pkg/shard" - redistst "juno/pkg/stats/redist" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/storageserv/redist" + "github.com/paypal/junodb/pkg/shard" + redistst "github.com/paypal/junodb/pkg/stats/redist" + "github.com/paypal/junodb/pkg/util" ) type ShardFilter struct { diff --git a/cmd/storageserv/storage/delete_test.go b/cmd/storageserv/storage/delete_test.go index 791add9c..969cb74c 100644 --- a/cmd/storageserv/storage/delete_test.go +++ b/cmd/storageserv/storage/delete_test.go @@ -24,8 +24,8 @@ import ( "testing" "time" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) var ( diff --git a/cmd/storageserv/storage/get_test.go b/cmd/storageserv/storage/get_test.go index 1143f49d..150bb479 100644 --- a/cmd/storageserv/storage/get_test.go +++ b/cmd/storageserv/storage/get_test.go @@ -25,8 +25,8 @@ package storage import ( "testing" - "juno/pkg/proto" - "juno/test/testutil" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/test/testutil" ) var ( diff --git a/cmd/storageserv/storage/lock.go b/cmd/storageserv/storage/lock.go index 8b781cf8..19c14ccd 100644 --- a/cmd/storageserv/storage/lock.go +++ b/cmd/storageserv/storage/lock.go @@ -22,12 +22,12 @@ package storage import ( "sync" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/storage/db" - "juno/pkg/logging" - "juno/pkg/proto" - "juno/pkg/shard" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" ) var ( diff --git a/cmd/storageserv/storage/proc.go b/cmd/storageserv/storage/proc.go index 647cbbe8..aba6d671 100644 --- a/cmd/storageserv/storage/proc.go +++ b/cmd/storageserv/storage/proc.go @@ -26,20 +26,20 @@ import ( "runtime" "time" - "juno/third_party/forked/golang/glog" - - "juno/cmd/storageserv/config" - ssstats "juno/cmd/storageserv/stats" - "juno/cmd/storageserv/storage/db" - "juno/pkg/debug" - "juno/pkg/io" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/proto" - "juno/pkg/shard" - "juno/pkg/stats" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/storageserv/config" + ssstats "github.com/paypal/junodb/cmd/storageserv/stats" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/debug" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" + "github.com/paypal/junodb/pkg/stats" + "github.com/paypal/junodb/pkg/util" ) const ( diff --git a/cmd/storageserv/storage/set_test.go b/cmd/storageserv/storage/set_test.go index 4cba2f4b..89b5b24e 100644 --- a/cmd/storageserv/storage/set_test.go +++ b/cmd/storageserv/storage/set_test.go @@ -23,7 +23,7 @@ import ( "testing" "time" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) var ( diff --git a/cmd/storageserv/storage/setup_test.go b/cmd/storageserv/storage/setup_test.go index ad72be99..1eebff06 100644 --- a/cmd/storageserv/storage/setup_test.go +++ b/cmd/storageserv/storage/setup_test.go @@ -24,11 +24,11 @@ import ( "os" "testing" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/storage/db" - "juno/pkg/shard" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/shard" ) func testSetup() { diff --git a/cmd/storageserv/storage/storage.go b/cmd/storageserv/storage/storage.go index 62d576c7..f2010843 100644 --- a/cmd/storageserv/storage/storage.go +++ b/cmd/storageserv/storage/storage.go @@ -27,21 +27,21 @@ import ( "sync" "time" - "juno/third_party/forked/golang/glog" - - "juno/cmd/dbscanserv/patch" - "juno/cmd/storageserv/config" - "juno/cmd/storageserv/redist" - "juno/cmd/storageserv/storage/db" - "juno/cmd/storageserv/watcher" - "juno/pkg/cluster" - "juno/pkg/debug" - "juno/pkg/etcd" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/proto" - "juno/pkg/shard" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/dbscanserv/patch" + "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/cmd/storageserv/redist" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/cmd/storageserv/watcher" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/debug" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" + "github.com/paypal/junodb/pkg/util" ) var ( @@ -1125,7 +1125,7 @@ func TruncateExpired() { } // TODO: revisit... -//Used to detect replication conflict. Not for conditional update +// Used to detect replication conflict. Not for conditional update func isConflict(request *proto.OperationalMessage, rec *db.Record) (conflict bool) { lmt := request.GetLastModificationTime() if lmt != 0 { diff --git a/cmd/storageserv/storage/storage_test.go b/cmd/storageserv/storage/storage_test.go index 3a30b8e9..e3b89ac2 100644 --- a/cmd/storageserv/storage/storage_test.go +++ b/cmd/storageserv/storage/storage_test.go @@ -27,12 +27,12 @@ import ( "testing" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/storageserv/storage/db" - "juno/pkg/io" - "juno/pkg/proto" - "juno/pkg/shard" + "github.com/paypal/junodb/cmd/storageserv/storage/db" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/shard" ) const ( diff --git a/cmd/storageserv/storage/update_test.go b/cmd/storageserv/storage/update_test.go index 7ec45b3a..25703ef0 100644 --- a/cmd/storageserv/storage/update_test.go +++ b/cmd/storageserv/storage/update_test.go @@ -23,8 +23,8 @@ import ( "testing" "time" - "juno/pkg/proto" - // "juno/test/testutil" + "github.com/paypal/junodb/pkg/proto" + // "github.com/paypal/junodb/test/testutil" ) var ( diff --git a/cmd/storageserv/watcher/watcher.go b/cmd/storageserv/watcher/watcher.go index b4a12206..f7ac87ef 100644 --- a/cmd/storageserv/watcher/watcher.go +++ b/cmd/storageserv/watcher/watcher.go @@ -27,13 +27,13 @@ import ( "strconv" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" clientv3 "go.etcd.io/etcd/client/v3" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/shard" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/shard" ) // watch for the following etcd changes diff --git a/cmd/tools/cmd/cfg/cfggen.go b/cmd/tools/cmd/cfg/cfggen.go index 856865cf..9b12e917 100644 --- a/cmd/tools/cmd/cfg/cfggen.go +++ b/cmd/tools/cmd/cfg/cfggen.go @@ -26,9 +26,9 @@ import ( "github.com/BurntSushi/toml" - "juno/cmd/proxy/config" - sscfg "juno/cmd/storageserv/config" - "juno/pkg/cmd" + "github.com/paypal/junodb/cmd/proxy/config" + sscfg "github.com/paypal/junodb/cmd/storageserv/config" + "github.com/paypal/junodb/pkg/cmd" ) type cfgTypeT int diff --git a/cmd/tools/cmd/cfg/conf.go b/cmd/tools/cmd/cfg/conf.go index add9a23c..3d17fb07 100644 --- a/cmd/tools/cmd/cfg/conf.go +++ b/cmd/tools/cmd/cfg/conf.go @@ -25,8 +25,8 @@ import ( "os" "strings" - "juno/pkg/cfg" - "juno/pkg/cmd" + "github.com/paypal/junodb/pkg/cfg" + "github.com/paypal/junodb/pkg/cmd" ) type cmdConfUnify struct { diff --git a/cmd/tools/cmd/cfg/rtcfg.go b/cmd/tools/cmd/cfg/rtcfg.go index 70b30bc3..b1bdb2e0 100644 --- a/cmd/tools/cmd/cfg/rtcfg.go +++ b/cmd/tools/cmd/cfg/rtcfg.go @@ -26,11 +26,11 @@ import ( "os" "time" - "juno/cmd/proxy/config" - "juno/pkg/cfg" - "juno/pkg/client" - "juno/pkg/cmd" - "juno/pkg/etcd" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/pkg/cfg" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/etcd" ) const ( diff --git a/cmd/tools/cmd/cli/cli.go b/cmd/tools/cmd/cli/cli.go index 8e1c5fec..d90f1490 100644 --- a/cmd/tools/cmd/cli/cli.go +++ b/cmd/tools/cmd/cli/cli.go @@ -26,17 +26,17 @@ import ( "os" "time" - "juno/pkg/logging/cal" - "juno/pkg/logging/cal/config" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/cal/config" + "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" uuid "github.com/satori/go.uuid" - "juno/pkg/client" - "juno/pkg/cmd" - "juno/pkg/sec" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/sec" + "github.com/paypal/junodb/pkg/util" ) const ( diff --git a/cmd/tools/cmd/cli/sscli.go b/cmd/tools/cmd/cli/sscli.go index 05494edf..2d29078e 100644 --- a/cmd/tools/cmd/cli/sscli.go +++ b/cmd/tools/cmd/cli/sscli.go @@ -25,15 +25,15 @@ import ( "os" "time" - // "juno/third_party/forked/golang/glog" + // "github.com/paypal/junodb/third_party/forked/golang/glog" uuid "github.com/satori/go.uuid" - "juno/internal/cli" - "juno/pkg/client" - "juno/pkg/cluster" - "juno/pkg/cmd" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/internal/cli" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) const ( diff --git a/cmd/tools/cmd/insp/inspmsg.go b/cmd/tools/cmd/insp/inspmsg.go index 94f742bc..97118dba 100644 --- a/cmd/tools/cmd/insp/inspmsg.go +++ b/cmd/tools/cmd/insp/inspmsg.go @@ -25,8 +25,8 @@ import ( "fmt" "os" - "juno/pkg/cmd" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/proto" ) type cmdInspMsgT struct { diff --git a/cmd/tools/cmd/insp/insprid.go b/cmd/tools/cmd/insp/insprid.go index 95249a55..791744ad 100644 --- a/cmd/tools/cmd/insp/insprid.go +++ b/cmd/tools/cmd/insp/insprid.go @@ -26,10 +26,10 @@ import ( uuid "github.com/satori/go.uuid" - "juno/pkg/cmd" - "juno/pkg/proto" - "juno/pkg/proto/mayfly" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/proto/mayfly" + "github.com/paypal/junodb/pkg/util" ) type ( diff --git a/cmd/tools/cmd/insp/ssgrp.go b/cmd/tools/cmd/insp/ssgrp.go index 20bd047a..2d0bf48f 100644 --- a/cmd/tools/cmd/insp/ssgrp.go +++ b/cmd/tools/cmd/insp/ssgrp.go @@ -23,13 +23,13 @@ import ( "encoding/hex" "fmt" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" - "juno/pkg/cluster" - "juno/pkg/cmd" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/util" ) type ( diff --git a/cmd/tools/cmd/stats/proxystats.go b/cmd/tools/cmd/stats/proxystats.go index 4760dc82..92c13bd1 100644 --- a/cmd/tools/cmd/stats/proxystats.go +++ b/cmd/tools/cmd/stats/proxystats.go @@ -23,8 +23,8 @@ import ( "fmt" "os" - "juno/cmd/proxy/stats/shmstats" - "juno/pkg/cmd" + "github.com/paypal/junodb/cmd/proxy/stats/shmstats" + "github.com/paypal/junodb/pkg/cmd" ) var _ cmd.ICommand = (*CmdProxyStats)(nil) diff --git a/cmd/tools/cmd/stats/storagestats.go b/cmd/tools/cmd/stats/storagestats.go index 03ee76c2..5bfa84f3 100644 --- a/cmd/tools/cmd/stats/storagestats.go +++ b/cmd/tools/cmd/stats/storagestats.go @@ -23,8 +23,8 @@ import ( "fmt" "os" - "juno/cmd/storageserv/stats/shmstats" - "juno/pkg/cmd" + "github.com/paypal/junodb/cmd/storageserv/stats/shmstats" + "github.com/paypal/junodb/pkg/cmd" ) var _ cmd.ICommand = (*CmdStorageStats)(nil) diff --git a/cmd/tools/goldendata/client.go b/cmd/tools/goldendata/client.go index b66d2208..c0202423 100644 --- a/cmd/tools/goldendata/client.go +++ b/cmd/tools/goldendata/client.go @@ -25,10 +25,10 @@ import ( "strings" "time" - "juno/pkg/client" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/util" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type Duration = util.Duration diff --git a/cmd/tools/goldendata/goldenset.go b/cmd/tools/goldendata/goldenset.go index 22d74747..ef973579 100644 --- a/cmd/tools/goldendata/goldenset.go +++ b/cmd/tools/goldendata/goldenset.go @@ -21,7 +21,8 @@ package gld import ( "fmt" - "juno/pkg/util" + + "github.com/paypal/junodb/pkg/util" ) type KEY []byte diff --git a/cmd/tools/goldendata/redistset.go b/cmd/tools/goldendata/redistset.go index 6f80a52e..b81e79c5 100644 --- a/cmd/tools/goldendata/redistset.go +++ b/cmd/tools/goldendata/redistset.go @@ -21,9 +21,10 @@ package gld import ( "fmt" - "juno/pkg/util" "strconv" "strings" + + "github.com/paypal/junodb/pkg/util" ) type RedistSet struct { diff --git a/cmd/tools/goldentool/goldentool.go b/cmd/tools/goldentool/goldentool.go index ec957af0..b387f66f 100644 --- a/cmd/tools/goldentool/goldentool.go +++ b/cmd/tools/goldentool/goldentool.go @@ -25,8 +25,8 @@ import ( "os" "path/filepath" - "juno/cmd/tools/goldendata" - "juno/third_party/forked/golang/glog" + gld "github.com/paypal/junodb/cmd/tools/goldendata" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) func main() { diff --git a/cmd/tools/junocfg/junocfg.go b/cmd/tools/junocfg/junocfg.go index 2e5c440b..2fba125a 100644 --- a/cmd/tools/junocfg/junocfg.go +++ b/cmd/tools/junocfg/junocfg.go @@ -22,8 +22,8 @@ package main import ( "fmt" - "juno/cmd/tools/cmd/cfg" - "juno/pkg/cmd" + "github.com/paypal/junodb/cmd/tools/cmd/cfg" + "github.com/paypal/junodb/pkg/cmd" ) func main() { diff --git a/cmd/tools/junocli/junocli.go b/cmd/tools/junocli/junocli.go index fa972002..1d39cb50 100644 --- a/cmd/tools/junocli/junocli.go +++ b/cmd/tools/junocli/junocli.go @@ -22,11 +22,11 @@ package main import ( "fmt" - _ "juno/cmd/tools/cmd/cfg" - _ "juno/cmd/tools/cmd/cli" - _ "juno/cmd/tools/cmd/insp" - "juno/pkg/cmd" - "juno/pkg/logging/cal" + _ "github.com/paypal/junodb/cmd/tools/cmd/cfg" + _ "github.com/paypal/junodb/cmd/tools/cmd/cli" + _ "github.com/paypal/junodb/cmd/tools/cmd/insp" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/logging/cal" ) func main() { diff --git a/cmd/tools/junostats/junostats.go b/cmd/tools/junostats/junostats.go index 0e19cc66..2b54b756 100644 --- a/cmd/tools/junostats/junostats.go +++ b/cmd/tools/junostats/junostats.go @@ -22,8 +22,8 @@ package main import ( "fmt" - "juno/cmd/tools/cmd/stats" - "juno/pkg/cmd" + "github.com/paypal/junodb/cmd/tools/cmd/stats" + "github.com/paypal/junodb/pkg/cmd" ) func main() { diff --git a/pkg/client/clientimpl.go b/pkg/client/clientimpl.go index 1e02733e..7ab57804 100644 --- a/pkg/client/clientimpl.go +++ b/pkg/client/clientimpl.go @@ -24,12 +24,12 @@ import ( "fmt" "runtime" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/internal/cli" - "juno/pkg/io" - "juno/pkg/logging" - "juno/pkg/proto" + "github.com/paypal/junodb/internal/cli" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/proto" ) // clientImplT is the default implementation of the IClient interface. diff --git a/pkg/client/config.go b/pkg/client/config.go index c683579d..860cf076 100644 --- a/pkg/client/config.go +++ b/pkg/client/config.go @@ -22,8 +22,8 @@ import ( "fmt" "time" - "juno/pkg/io" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/util" ) // Duration is a type alias for util.Duration. @@ -59,7 +59,7 @@ func SetDefaultTimeToLive(ttl int) { defaultConfig.DefaultTimeToLive = ttl } -// SetDefaultTimeout sets the default timeout durations for the configuration. +// SetDefaultTimeout sets the default timeout durations for the configuration. func SetDefaultTimeout(connect, read, write, request, connRecycle time.Duration) { defaultConfig.ConnectTimeout.Duration = connect defaultConfig.ReadTimeout.Duration = read @@ -89,4 +89,3 @@ func (c *Config) validate() error { // TODO to validate others return nil } - diff --git a/pkg/client/error.go b/pkg/client/error.go index 89661b1b..5dd6991f 100644 --- a/pkg/client/error.go +++ b/pkg/client/error.go @@ -17,30 +17,30 @@ // limitations under the License. // -// client is a package that handles various error situations in the Juno application. +// client is a package that handles various error situations in the Juno application. package client import ( - "juno/internal/cli" - "juno/pkg/proto" + "github.com/paypal/junodb/internal/cli" + "github.com/paypal/junodb/pkg/proto" ) // Error variables for different scenarios in the application. var ( - ErrNoKey error // Error when no key is found. - ErrUniqueKeyViolation error // Error when there is a violation of a unique key. - ErrBadParam error // Error when a bad parameter is provided. - ErrConditionViolation error // Error when a condition violation occurs. + ErrNoKey error // Error when no key is found. + ErrUniqueKeyViolation error // Error when there is a violation of a unique key. + ErrBadParam error // Error when a bad parameter is provided. + ErrConditionViolation error // Error when a condition violation occurs. - ErrBadMsg error // Error when a bad message is encountered. - ErrNoStorage error // Error when no storage is available. - ErrRecordLocked error // Error when a record is locked. - ErrTTLExtendFailure error // Error when TTL extension fails. - ErrBusy error // Error when the server is busy. + ErrBadMsg error // Error when a bad message is encountered. + ErrNoStorage error // Error when no storage is available. + ErrRecordLocked error // Error when a record is locked. + ErrTTLExtendFailure error // Error when TTL extension fails. + ErrBusy error // Error when the server is busy. - ErrWriteFailure error // Error when a write operation fails. - ErrInternal error // Error when an internal problem occurs. - ErrOpNotSupported error // Error when the operation is not supported. + ErrWriteFailure error // Error when a write operation fails. + ErrInternal error // Error when an internal problem occurs. + ErrOpNotSupported error // Error when the operation is not supported. ) // errorMapping is a map between different operation status and their corresponding errors. @@ -48,35 +48,35 @@ var errorMapping map[proto.OpStatus]error // init function initializes the error variables and the errorMapping map. func init() { - ErrNoKey = &cli.Error{"no key"} // Error when the key does not exist. - ErrUniqueKeyViolation = &cli.Error{"unique key violation"} // Error when unique key constraint is violated. - ErrBadParam = &cli.Error{"bad parameter"} // Error when a bad parameter is passed. - ErrConditionViolation = &cli.Error{"condition violation"} // Error when there is a condition violation. - ErrTTLExtendFailure = &cli.Error{"fail to extend TTL"} // Error when TTL extension fails. + ErrNoKey = &cli.Error{"no key"} // Error when the key does not exist. + ErrUniqueKeyViolation = &cli.Error{"unique key violation"} // Error when unique key constraint is violated. + ErrBadParam = &cli.Error{"bad parameter"} // Error when a bad parameter is passed. + ErrConditionViolation = &cli.Error{"condition violation"} // Error when there is a condition violation. + ErrTTLExtendFailure = &cli.Error{"fail to extend TTL"} // Error when TTL extension fails. - ErrBadMsg = &cli.RetryableError{"bad message"} // Error when an inappropriate message is received. - ErrNoStorage = &cli.RetryableError{"no storage"} // Error when there is no storage available. - ErrRecordLocked = &cli.RetryableError{"record locked"} // Error when a record is locked. - ErrBusy = &cli.RetryableError{"server busy"} // Error when the server is busy. + ErrBadMsg = &cli.RetryableError{"bad message"} // Error when an inappropriate message is received. + ErrNoStorage = &cli.RetryableError{"no storage"} // Error when there is no storage available. + ErrRecordLocked = &cli.RetryableError{"record locked"} // Error when a record is locked. + ErrBusy = &cli.RetryableError{"server busy"} // Error when the server is busy. - ErrWriteFailure = &cli.Error{"write failure"} // Error when a write operation fails. - ErrInternal = &cli.Error{"internal error"} // Error when an internal error occurs. - ErrOpNotSupported = &cli.Error{"Op not supported"} // Error when the operation is not supported. + ErrWriteFailure = &cli.Error{"write failure"} // Error when a write operation fails. + ErrInternal = &cli.Error{"internal error"} // Error when an internal error occurs. + ErrOpNotSupported = &cli.Error{"Op not supported"} // Error when the operation is not supported. // Mapping between the operation status and the corresponding errors. errorMapping = map[proto.OpStatus]error{ - proto.OpStatusNoError: nil, // Status when there is no error. - proto.OpStatusInconsistent: nil, // Status when there is an inconsistency. - proto.OpStatusBadMsg: ErrBadMsg, // Status when a bad message is received. - proto.OpStatusNoKey: ErrNoKey, // Status when the key is not present. - proto.OpStatusDupKey: ErrUniqueKeyViolation, // Status when unique key constraint is violated. - proto.OpStatusNoStorageServer: ErrNoStorage, // Status when there is no storage server available. - proto.OpStatusBadParam: ErrBadParam, // Status when a bad parameter is passed. - proto.OpStatusRecordLocked: ErrRecordLocked, // Status when a record is locked. - proto.OpStatusVersionConflict: ErrConditionViolation, // Status when there is a version conflict. - proto.OpStatusSSReadTTLExtendErr: ErrTTLExtendFailure, // Status when TTL extension fails. - proto.OpStatusCommitFailure: ErrWriteFailure, // Status when a commit operation fails. - proto.OpStatusBusy: ErrBusy, // Status when the server is busy. - proto.OpStatusNotSupported: ErrOpNotSupported, // Status when the operation is not supported. + proto.OpStatusNoError: nil, // Status when there is no error. + proto.OpStatusInconsistent: nil, // Status when there is an inconsistency. + proto.OpStatusBadMsg: ErrBadMsg, // Status when a bad message is received. + proto.OpStatusNoKey: ErrNoKey, // Status when the key is not present. + proto.OpStatusDupKey: ErrUniqueKeyViolation, // Status when unique key constraint is violated. + proto.OpStatusNoStorageServer: ErrNoStorage, // Status when there is no storage server available. + proto.OpStatusBadParam: ErrBadParam, // Status when a bad parameter is passed. + proto.OpStatusRecordLocked: ErrRecordLocked, // Status when a record is locked. + proto.OpStatusVersionConflict: ErrConditionViolation, // Status when there is a version conflict. + proto.OpStatusSSReadTTLExtendErr: ErrTTLExtendFailure, // Status when TTL extension fails. + proto.OpStatusCommitFailure: ErrWriteFailure, // Status when a commit operation fails. + proto.OpStatusBusy: ErrBusy, // Status when the server is busy. + proto.OpStatusNotSupported: ErrOpNotSupported, // Status when the operation is not supported. } } diff --git a/pkg/client/example_test.go b/pkg/client/example_test.go index ebb7a991..2e1a8985 100644 --- a/pkg/client/example_test.go +++ b/pkg/client/example_test.go @@ -22,7 +22,7 @@ package client_test import ( "fmt" - "juno/pkg/client" + "github.com/paypal/junodb/pkg/client" ) func Example_config() { diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 3b0827d7..57087fbe 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -32,7 +32,7 @@ import ( "syscall" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" ) diff --git a/pkg/cluster/clusterstats.go b/pkg/cluster/clusterstats.go index 804c5134..c643338a 100644 --- a/pkg/cluster/clusterstats.go +++ b/pkg/cluster/clusterstats.go @@ -24,8 +24,8 @@ import ( "sync" "time" - "juno/pkg/util" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type ProcStat struct { diff --git a/pkg/cluster/lock_linux.go b/pkg/cluster/lock_linux.go index e9188f3d..2c541515 100644 --- a/pkg/cluster/lock_linux.go +++ b/pkg/cluster/lock_linux.go @@ -20,8 +20,9 @@ package cluster import ( - "juno/third_party/forked/golang/glog" "syscall" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) func lockFile(fd int, mode int) bool { diff --git a/pkg/cluster/node.go b/pkg/cluster/node.go index 5451ce86..d9ed0c6c 100644 --- a/pkg/cluster/node.go +++ b/pkg/cluster/node.go @@ -26,7 +26,7 @@ import ( "strconv" "strings" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) // Node class represent a logic node diff --git a/pkg/cluster/shardmgr.go b/pkg/cluster/shardmgr.go index 1d578316..fc9c7eb9 100644 --- a/pkg/cluster/shardmgr.go +++ b/pkg/cluster/shardmgr.go @@ -28,14 +28,14 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" - - "juno/pkg/io" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/shard" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/shard" + "github.com/paypal/junodb/pkg/util" ) var ( @@ -338,8 +338,8 @@ func (p *ShardManager) GetSSProcessor(zoneId int, nodeId int) *OutboundSSProcess return p.processors[zoneId][nodeId] } -//used by request processor -//the caller's responsibility to make sure +// used by request processor +// the caller's responsibility to make sure // cap(procs) >= numZones and cap(pos) >= numZones func (p *ShardManager) GetSSProcessors(key []byte, confNumWrites int, procs []*OutboundSSProcessor, pos []int) (shardId shard.ID, numProcs int) { diff --git a/pkg/cluster/util.go b/pkg/cluster/util.go index 50f3220d..50c9f0cb 100644 --- a/pkg/cluster/util.go +++ b/pkg/cluster/util.go @@ -22,7 +22,7 @@ package cluster import ( "fmt" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) func DisplayZones(zones []*Zone, header string) { diff --git a/pkg/cluster/zone.go b/pkg/cluster/zone.go index 0de13647..7fa97aca 100644 --- a/pkg/cluster/zone.go +++ b/pkg/cluster/zone.go @@ -23,7 +23,7 @@ import ( "fmt" "sort" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) // Node class represent a logic node diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 2859cb76..aa405968 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -30,9 +30,9 @@ import ( "strings" "text/tabwriter" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/version" + "github.com/paypal/junodb/pkg/version" ) var ( diff --git a/pkg/etcd/config.go b/pkg/etcd/config.go index a89f4c62..0aea1c02 100644 --- a/pkg/etcd/config.go +++ b/pkg/etcd/config.go @@ -24,7 +24,7 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" ) var ( diff --git a/pkg/etcd/etcd.go b/pkg/etcd/etcd.go index 8fdd8d19..65e0d1d6 100644 --- a/pkg/etcd/etcd.go +++ b/pkg/etcd/etcd.go @@ -21,8 +21,9 @@ package etcd import ( "errors" - "juno/third_party/forked/golang/glog" "sync" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var ( diff --git a/pkg/etcd/etcdclient.go b/pkg/etcd/etcdclient.go index 770c7de2..26c873b4 100644 --- a/pkg/etcd/etcdclient.go +++ b/pkg/etcd/etcdclient.go @@ -32,7 +32,7 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/client/v3/namespace" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var ( diff --git a/pkg/etcd/etcdreader.go b/pkg/etcd/etcdreader.go index 00cead95..93bb0db9 100644 --- a/pkg/etcd/etcdreader.go +++ b/pkg/etcd/etcdreader.go @@ -28,11 +28,11 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/cluster" - "juno/pkg/shard" - "juno/pkg/stats/redist" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/shard" + "github.com/paypal/junodb/pkg/stats/redist" ) // Implements cluster.IReader diff --git a/pkg/etcd/etcdwriter.go b/pkg/etcd/etcdwriter.go index 5bf10b10..d9a963c2 100644 --- a/pkg/etcd/etcdwriter.go +++ b/pkg/etcd/etcdwriter.go @@ -25,9 +25,9 @@ import ( "strconv" "strings" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/cluster" + "github.com/paypal/junodb/pkg/cluster" ) type IKVWriter interface { diff --git a/pkg/initmgr/init.go b/pkg/initmgr/init.go index 01092d9b..1818b276 100644 --- a/pkg/initmgr/init.go +++ b/pkg/initmgr/init.go @@ -32,8 +32,8 @@ import ( "syscall" "time" - "juno/pkg/logging" - "juno/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" ) var ( diff --git a/pkg/io/conn.go b/pkg/io/conn.go index 226810f8..92c0b297 100644 --- a/pkg/io/conn.go +++ b/pkg/io/conn.go @@ -24,12 +24,12 @@ import ( "net" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/sec" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/sec" ) type Conn interface { diff --git a/pkg/io/connmgr.go b/pkg/io/connmgr.go index f44f1c6a..c2250841 100644 --- a/pkg/io/connmgr.go +++ b/pkg/io/connmgr.go @@ -23,7 +23,7 @@ import ( "sync" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type InboundConnManager struct { diff --git a/pkg/io/inboundconnector.go b/pkg/io/inboundconnector.go index bfc8a862..c4a498ef 100644 --- a/pkg/io/inboundconnector.go +++ b/pkg/io/inboundconnector.go @@ -29,13 +29,13 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/debug" - "juno/pkg/io/ioutil" - "juno/pkg/logging/cal" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/debug" + "github.com/paypal/junodb/pkg/io/ioutil" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) type Connector struct { diff --git a/pkg/io/ioconfig.go b/pkg/io/ioconfig.go index 107b8253..6aae8148 100644 --- a/pkg/io/ioconfig.go +++ b/pkg/io/ioconfig.go @@ -22,7 +22,7 @@ package io import ( "time" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" ) var ( diff --git a/pkg/io/ioutil/logerr.go b/pkg/io/ioutil/logerr.go index 5af8f8f5..63568e48 100644 --- a/pkg/io/ioutil/logerr.go +++ b/pkg/io/ioutil/logerr.go @@ -25,7 +25,7 @@ import ( "os" "syscall" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) func LogError(err error) { diff --git a/pkg/io/listener.go b/pkg/io/listener.go index c93f3fba..b78f08e6 100644 --- a/pkg/io/listener.go +++ b/pkg/io/listener.go @@ -25,10 +25,10 @@ import ( "os" "time" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/util" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) const ( diff --git a/pkg/io/mayflyreqctx.go b/pkg/io/mayflyreqctx.go index 8cf0a1f7..386896f9 100644 --- a/pkg/io/mayflyreqctx.go +++ b/pkg/io/mayflyreqctx.go @@ -26,13 +26,13 @@ import ( "io" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/proto" - "juno/pkg/proto/mayfly" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/proto/mayfly" + "github.com/paypal/junodb/pkg/util" ) type ( diff --git a/pkg/io/outboundconnector.go b/pkg/io/outboundconnector.go index db737560..5449405d 100644 --- a/pkg/io/outboundconnector.go +++ b/pkg/io/outboundconnector.go @@ -31,13 +31,13 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/io/ioutil" - "juno/pkg/logging/cal" - "juno/pkg/proto" - "juno/pkg/proto/mayfly" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/io/ioutil" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/proto/mayfly" + "github.com/paypal/junodb/pkg/util" ) const ( diff --git a/pkg/io/outboundprocessor.go b/pkg/io/outboundprocessor.go index 48fcbf28..9d30b11a 100644 --- a/pkg/io/outboundprocessor.go +++ b/pkg/io/outboundprocessor.go @@ -28,14 +28,14 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" - - "juno/pkg/errors" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/logging/otel" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/pkg/errors" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/otel" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) type ( @@ -114,9 +114,7 @@ func (p *OutboundProcessor) GetRequestCh() chan IRequestContext { return p.reqCh } -// // Non-blocking send -// func (p *OutboundProcessor) sendRequest(req IRequestContext) (err *errors.Error) { // send request select { diff --git a/pkg/io/requestcontext.go b/pkg/io/requestcontext.go index 25c06f24..96cf9cd5 100644 --- a/pkg/io/requestcontext.go +++ b/pkg/io/requestcontext.go @@ -26,11 +26,11 @@ import ( "io" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/proto" - "juno/pkg/proto/mayfly" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/proto/mayfly" + "github.com/paypal/junodb/pkg/util" ) var oResponsePool = util.NewChanPool(10000, func() interface{} { diff --git a/pkg/io/ssllistener.go b/pkg/io/ssllistener.go index db623292..466f82bc 100644 --- a/pkg/io/ssllistener.go +++ b/pkg/io/ssllistener.go @@ -26,11 +26,11 @@ import ( "syscall" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/sec" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/sec" ) type SslListener struct { diff --git a/pkg/logging/cal/config/calconfig.go b/pkg/logging/cal/config/calconfig.go index 993c8471..12cf3dc0 100644 --- a/pkg/logging/cal/config/calconfig.go +++ b/pkg/logging/cal/config/calconfig.go @@ -20,7 +20,7 @@ package config import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var CalConfig *Config diff --git a/pkg/logging/cal/logger.go b/pkg/logging/cal/logger.go index d0a8b4a6..dcd6cb49 100644 --- a/pkg/logging/cal/logger.go +++ b/pkg/logging/cal/logger.go @@ -27,11 +27,11 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging/cal/config" - "juno/pkg/logging/cal/net/io" - "juno/pkg/logging/cal/net/protocol" + "github.com/paypal/junodb/pkg/logging/cal/config" + "github.com/paypal/junodb/pkg/logging/cal/net/io" + "github.com/paypal/junodb/pkg/logging/cal/net/protocol" ) // CAL message Status field values @@ -126,7 +126,7 @@ func LogAtomicTransaction(txnType, eventName, status string, duration time.Durat client.Send(msg) } -//SendEvent logs an event of eventType with a sub-classification of +// SendEvent logs an event of eventType with a sub-classification of // eventName. The event may optionally contain extra eventData. func LogEvent(eventType, eventName, status string, eventData map[string]interface{}) { // TODO guard against bad eventType/eventName (perhaps in NewMsg()) diff --git a/pkg/logging/cal/net/io/client.go b/pkg/logging/cal/net/io/client.go index a735200e..38137347 100644 --- a/pkg/logging/cal/net/io/client.go +++ b/pkg/logging/cal/net/io/client.go @@ -32,11 +32,11 @@ import ( "sync/atomic" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging/cal/config" - "juno/pkg/logging/cal/net/protocol" - "juno/pkg/logging/cal/util" + "github.com/paypal/junodb/pkg/logging/cal/config" + "github.com/paypal/junodb/pkg/logging/cal/net/protocol" + "github.com/paypal/junodb/pkg/logging/cal/util" ) var logFile *os.File @@ -259,9 +259,7 @@ func (c *client) sendFileLoop() { } } -// // Sender need to make should not send message after Shutdown() -// func (c *client) Send(m *protocol.CalMessage) { if m == nil { glog.V(2).Info("Cal: Message can not be nil.") @@ -282,7 +280,7 @@ func (c *client) Send(m *protocol.CalMessage) { } } -//If you close client connection you wouldnt be able to log anything using logger. +// If you close client connection you wouldnt be able to log anything using logger. // This should be called when parent process Shutdown. func (c *client) Shutdown() { c.closeOnce.Do(func() { @@ -307,7 +305,7 @@ func (c *client) Shutdown() { }) } -//Flush blocks till the messages are processed by the channel +// Flush blocks till the messages are processed by the channel func (c *client) Flush() { c.wg.Wait() if logFile != nil { diff --git a/pkg/logging/cal/net/io/clientapi.go b/pkg/logging/cal/net/io/clientapi.go index fd0155ef..c9105393 100644 --- a/pkg/logging/cal/net/io/clientapi.go +++ b/pkg/logging/cal/net/io/clientapi.go @@ -21,10 +21,11 @@ package io import ( "bufio" - "juno/pkg/logging/cal/net/protocol" "net" "sync" "sync/atomic" + + "github.com/paypal/junodb/pkg/logging/cal/net/protocol" ) // Client is a CAL client. It is used to configure and organize diff --git a/pkg/logging/cal/net/protocol/message.go b/pkg/logging/cal/net/protocol/message.go index f096318c..1ebb2896 100644 --- a/pkg/logging/cal/net/protocol/message.go +++ b/pkg/logging/cal/net/protocol/message.go @@ -22,11 +22,12 @@ package protocol import ( "bytes" "fmt" - "juno/third_party/forked/golang/glog" "strconv" "strings" "time" "unicode" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) // Msg is a single log message. diff --git a/pkg/logging/cal/test/main.go b/pkg/logging/cal/test/main.go index 9d107065..75b2e0fb 100644 --- a/pkg/logging/cal/test/main.go +++ b/pkg/logging/cal/test/main.go @@ -20,21 +20,21 @@ package main import ( - "juno/pkg/logging/cal" - logger "juno/pkg/logging/cal" - "juno/pkg/logging/cal/config" + "github.com/paypal/junodb/pkg/logging/cal" + logger "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/logging/cal/config" "github.com/BurntSushi/toml" - //"juno/pkg/logging/cal/config" + //"github.com/paypal/junodb/pkg/logging/cal/config" "flag" "fmt" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" //"io" // "log" - //"juno/pkg/logging/cal/net/protocol" + //"github.com/paypal/junodb/pkg/logging/cal/net/protocol" //"os" // "runtime" // "net/http" diff --git a/pkg/logging/cal/util/util.go b/pkg/logging/cal/util/util.go index c5760ea0..c2919beb 100644 --- a/pkg/logging/cal/util/util.go +++ b/pkg/logging/cal/util/util.go @@ -22,13 +22,14 @@ package util import ( "fmt" "hash/fnv" - "juno/pkg/logging/cal/net/protocol" "net" "os" "path" "runtime" "strconv" "time" + + "github.com/paypal/junodb/pkg/logging/cal/net/protocol" ) const ( diff --git a/pkg/logging/calstatus.go b/pkg/logging/calstatus.go index 8ff37d8a..4e89d1f7 100644 --- a/pkg/logging/calstatus.go +++ b/pkg/logging/calstatus.go @@ -20,8 +20,8 @@ package logging import ( - "juno/pkg/logging/cal" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" ) type Status int diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go index 061cc7bd..b219aeec 100644 --- a/pkg/logging/logging.go +++ b/pkg/logging/logging.go @@ -26,11 +26,11 @@ import ( "strconv" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/logging/cal" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) type KeyValueBuffer struct { @@ -360,7 +360,7 @@ func (b *KeyValueBuffer) AddOpRequestInfo(request *proto.OperationalMessage) *Ke return b } -//difference from AddOpRequestInfo(): namespace and key not logged +// difference from AddOpRequestInfo(): namespace and key not logged func (b *KeyValueBuffer) AddOpRequest(request *proto.OperationalMessage) *KeyValueBuffer { b.Add(logDataKeyRid, request.GetRequestIDString()) reqParam := &KeyValueBuffer{delimiter: ':', pairDelimiter: '|'} diff --git a/pkg/logging/otel/config/otelconfig.go b/pkg/logging/otel/config/otelconfig.go index 458dc90a..26774026 100644 --- a/pkg/logging/otel/config/otelconfig.go +++ b/pkg/logging/otel/config/otelconfig.go @@ -1,25 +1,23 @@ +// Copyright 2023 PayPal Inc. // -// Copyright 2023 PayPal Inc. +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at // -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// http://www.apache.org/licenses/LICENSE-2.0 // +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package config import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var OtelConfig *Config diff --git a/pkg/logging/otel/logger.go b/pkg/logging/otel/logger.go index 5559085e..98a4e4f7 100644 --- a/pkg/logging/otel/logger.go +++ b/pkg/logging/otel/logger.go @@ -1,21 +1,19 @@ +// Copyright 2023 PayPal Inc. // -// Copyright 2023 PayPal Inc. +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at // -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// http://www.apache.org/licenses/LICENSE-2.0 // +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package otel import ( @@ -30,8 +28,8 @@ import ( "sync" "time" - otelCfg "juno/pkg/logging/otel/config" - "juno/third_party/forked/golang/glog" + otelCfg "github.com/paypal/junodb/pkg/logging/otel/config" + "github.com/paypal/junodb/third_party/forked/golang/glog" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" diff --git a/pkg/logging/otel/statsLogger.go b/pkg/logging/otel/statsLogger.go index f93031f5..955c9cc0 100644 --- a/pkg/logging/otel/statsLogger.go +++ b/pkg/logging/otel/statsLogger.go @@ -1,33 +1,32 @@ +// Copyright 2023 PayPal Inc. // -// Copyright 2023 PayPal Inc. +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at // -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// http://www.apache.org/licenses/LICENSE-2.0 // +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package otel import ( "context" "errors" "fmt" - "juno/pkg/stats" "runtime" "strconv" "sync" "time" + "github.com/paypal/junodb/pkg/stats" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/metric/instrument" diff --git a/pkg/logging/otel/test/logger_test.go b/pkg/logging/otel/test/logger_test.go index f8117449..98005abe 100644 --- a/pkg/logging/otel/test/logger_test.go +++ b/pkg/logging/otel/test/logger_test.go @@ -23,11 +23,12 @@ package test import ( "fmt" - "juno/pkg/logging/otel" - config "juno/pkg/logging/otel/config" - "juno/pkg/stats" "testing" "time" + + "github.com/paypal/junodb/pkg/logging/otel" + config "github.com/paypal/junodb/pkg/logging/otel/config" + "github.com/paypal/junodb/pkg/stats" ) var exportinterval int = 10 diff --git a/pkg/net/netutil/netutil.go b/pkg/net/netutil/netutil.go index 03665f71..90c46f8c 100644 --- a/pkg/net/netutil/netutil.go +++ b/pkg/net/netutil/netutil.go @@ -22,7 +22,7 @@ package netutil import ( "net" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var ( diff --git a/pkg/proto/cryptoks.go b/pkg/proto/cryptoks.go index 71f04d14..8cf9a153 100644 --- a/pkg/proto/cryptoks.go +++ b/pkg/proto/cryptoks.go @@ -24,7 +24,7 @@ import ( "errors" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type ( diff --git a/pkg/proto/decode.go b/pkg/proto/decode.go index 2273be5d..43baccbe 100644 --- a/pkg/proto/decode.go +++ b/pkg/proto/decode.go @@ -22,9 +22,9 @@ package proto import ( "io" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" ) const ( @@ -69,7 +69,7 @@ func (dec *Decoder) Decode(op *OperationalMessage) error { return op.decode(raw, &header, true) } -//Caller's responsibility to have op zeroed +// Caller's responsibility to have op zeroed func (op *OperationalMessage) decode(raw []byte, msgHeader *messageHeaderT, copyData bool) error { offset := 0 szBuf := len(raw) diff --git a/pkg/proto/mayfly/mapping.go b/pkg/proto/mayfly/mapping.go index b9d292c8..f4d8ab99 100644 --- a/pkg/proto/mayfly/mapping.go +++ b/pkg/proto/mayfly/mapping.go @@ -23,9 +23,9 @@ import ( "encoding/binary" "net" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) func opCodeToJuno(mfop OpCode) (opcode proto.OpCode, err error) { @@ -69,7 +69,7 @@ func toMayflyOpCode(jop proto.OpCode) (opcode OpCode, err error) { return } -///TODO to be reviewed +// /TODO to be reviewed var opStatusJunoToMayflyMapping []OpStatus = []OpStatus{ OpStatusdNoError, //Ok, 0 OpStatusBadMsg, //BadMsg, 1 diff --git a/pkg/proto/mayfly/msg.go b/pkg/proto/mayfly/msg.go index 97b52762..643af4d7 100644 --- a/pkg/proto/mayfly/msg.go +++ b/pkg/proto/mayfly/msg.go @@ -25,7 +25,7 @@ import ( "fmt" "io" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" ) type Msg struct { diff --git a/pkg/proto/mayfly/msg_test.go b/pkg/proto/mayfly/msg_test.go index 46dbca45..1988eff9 100644 --- a/pkg/proto/mayfly/msg_test.go +++ b/pkg/proto/mayfly/msg_test.go @@ -23,7 +23,7 @@ import ( "bytes" "testing" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" ) func testRequestResponse(t *testing.T, rawRequest []byte, rawResponse []byte) { diff --git a/pkg/proto/mayfly/opmsg.go b/pkg/proto/mayfly/opmsg.go index a1eddce7..8a3c4b7c 100644 --- a/pkg/proto/mayfly/opmsg.go +++ b/pkg/proto/mayfly/opmsg.go @@ -30,8 +30,8 @@ import ( "sync/atomic" "time" - "juno/pkg/net/netutil" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/net/netutil" + "github.com/paypal/junodb/pkg/util" ) type ( diff --git a/pkg/proto/metaField.go b/pkg/proto/metaField.go index aace1d4c..c5f45776 100644 --- a/pkg/proto/metaField.go +++ b/pkg/proto/metaField.go @@ -18,53 +18,53 @@ // /* - ============================== - *** Predefined Field Types *** - ============================== - - Tag/ID | Field | SizeType - -------+--------------------------------------+------ - 0x01 | TimeToLive | 0x01 - 0x02 | Version | 0x01 - 0x03 | Creation Time | 0x01 - 0x04 | Expiration Time | 0x01 - 0x05 | RequestID/UUID | 0x03 - 0x06 | Source Info | 0 - 0x07 | Last Modification time (nano second) | 0x02 - 0x08 | Originator RequestID/UUID | 0x03 - 0x09 | Correlation ID | 0 - 0x0a | RequestHandlingTime | 0x01 - 0x0b | UDF Name | 0 - -------+--------------------------------------+------ - - - Tag/ID: 0x06 - +-----------------------------------------------------------------------------------------------+ - | 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| - | 0| 1| 2| 3| - +-----------+-----------+--------------------+--+-----------------------+-----------------------+ - | size (include padding)| app name length | T| Port | - +-----------------------+--------------------+--+-----------------------------------------------+ - | IPv4 address if T is 0 or IPv6 address if T is 1 | - +-----------------------------------------------------------------------------------------------+ - | application name, padding to 4-byte aligned | - +-----------------------------------------------------------------------------------------------+ - - Tag/ID: 0x09; 0x0b - +----+------------------------------------------- - | 0 | field size (including padding) - +----+------------------------------------------- - | 1 | octet sequence length - +----+------------------------------------------- - | | octet sequence, padding to 4-byte aligned - +----+------------------------------------------- + ============================== + *** Predefined Field Types *** + ============================== + + Tag/ID | Field | SizeType + -------+--------------------------------------+------ + 0x01 | TimeToLive | 0x01 + 0x02 | Version | 0x01 + 0x03 | Creation Time | 0x01 + 0x04 | Expiration Time | 0x01 + 0x05 | RequestID/UUID | 0x03 + 0x06 | Source Info | 0 + 0x07 | Last Modification time (nano second) | 0x02 + 0x08 | Originator RequestID/UUID | 0x03 + 0x09 | Correlation ID | 0 + 0x0a | RequestHandlingTime | 0x01 + 0x0b | UDF Name | 0 + -------+--------------------------------------+------ + + + Tag/ID: 0x06 + +-----------------------------------------------------------------------------------------------+ + | 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| + | 0| 1| 2| 3| + +-----------+-----------+--------------------+--+-----------------------+-----------------------+ + | size (include padding)| app name length | T| Port | + +-----------------------+--------------------+--+-----------------------------------------------+ + | IPv4 address if T is 0 or IPv6 address if T is 1 | + +-----------------------------------------------------------------------------------------------+ + | application name, padding to 4-byte aligned | + +-----------------------------------------------------------------------------------------------+ + + Tag/ID: 0x09; 0x0b + +----+------------------------------------------- + | 0 | field size (including padding) + +----+------------------------------------------- + | 1 | octet sequence length + +----+------------------------------------------- + | | octet sequence, padding to 4-byte aligned + +----+------------------------------------------- */ package proto import ( "net" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) // Meta Component Field Tag @@ -173,7 +173,7 @@ func (t requestHandlingTimeT) tagAndSizeTypeByte() uint8 { return kFieldTagRequestHandlingTime | kMetaField_4Bytes } -//uint64 meta field +// uint64 meta field func (t uint64T) isSet() bool { return t != 0 } @@ -212,7 +212,7 @@ func (t lastModificationTimeT) tagAndSizeTypeByte() uint8 { return kFieldTagLastModificationTime | kMetaField_8Bytes } -//16-byte meta field +// 16-byte meta field func (t *requestIdBaseT) value() []byte { return t.Bytes() } @@ -270,7 +270,7 @@ func (t *originatorT) tagAndSizeTypeByte() uint8 { return kFieldTagOriginatorRequestID | kMetaField_16Bytes } -//sourceinfo +// sourceinfo func (t *sourceInfoT) isSet() bool { if (len(t.ip) != 0) || (t.port != 0) || (len(t.appName) != 0) { return true @@ -379,7 +379,7 @@ func (t sourceInfoT) tagAndSizeTypeByte() uint8 { return kFieldTagSourceInfo | kMetaFieldVariableSize } -//byte sequence +// byte sequence func (t byteSequenceT) isSet() bool { return len(t) != 0 } diff --git a/pkg/proto/opMsg.go b/pkg/proto/opMsg.go index 74ddd893..f2616587 100644 --- a/pkg/proto/opMsg.go +++ b/pkg/proto/opMsg.go @@ -27,7 +27,7 @@ import ( uuid "github.com/satori/go.uuid" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" ) type OperationalMessage struct { diff --git a/pkg/proto/opMsg_test.go b/pkg/proto/opMsg_test.go index 555a03b4..fd4946a4 100644 --- a/pkg/proto/opMsg_test.go +++ b/pkg/proto/opMsg_test.go @@ -23,8 +23,9 @@ import ( "bytes" "encoding/binary" "fmt" - "juno/pkg/util" "testing" + + "github.com/paypal/junodb/pkg/util" ) func testRequestResponse(t *testing.T, rawRequest []byte, rawResponse []byte) { diff --git a/pkg/proto/payload.go b/pkg/proto/payload.go index e1bf4bdf..ae61c152 100644 --- a/pkg/proto/payload.go +++ b/pkg/proto/payload.go @@ -28,9 +28,9 @@ import ( "fmt" "io" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" "github.com/golang/snappy" ) @@ -106,7 +106,7 @@ func (p *Payload) SetWithClearValue(value []byte) { p.data = value } -///TODO +// /TODO func (p *Payload) GetClearValue() (value []byte, err error) { if p.GetLength() == 0 { return diff --git a/pkg/proto/rawmessage.go b/pkg/proto/rawmessage.go index 5fb11782..ea2e7e6e 100644 --- a/pkg/proto/rawmessage.go +++ b/pkg/proto/rawmessage.go @@ -23,10 +23,10 @@ import ( "bytes" "io" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/debug" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/debug" + "github.com/paypal/junodb/pkg/util" ) type RawMessage struct { @@ -95,9 +95,7 @@ func (m *RawMessage) allocateBuffer(size int) { m.body = m.buf.Bytes() } -// // Note: read timeout is set at conn level -// func (msg *RawMessage) Read(r io.Reader) (n int, err error) { var hBuffer [kMessageHeaderSize]byte @@ -152,9 +150,7 @@ func (msg *RawMessage) ReadWithHeader(header []byte, r io.Reader) (n int, err er return } -// // Note: this api is not thread safe -// func (m *RawMessage) Write(w io.Writer) (n int, err error) { var mheader [kMessageHeaderSize]byte raw := mheader[:] diff --git a/pkg/sec/certs.go b/pkg/sec/certs.go index cacc680e..fa0b4666 100644 --- a/pkg/sec/certs.go +++ b/pkg/sec/certs.go @@ -20,8 +20,9 @@ package sec import ( - "juno/third_party/forked/golang/glog" "os" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type localFileProtectedT struct { diff --git a/pkg/sec/certs_test.go b/pkg/sec/certs_test.go index a1a39e0b..d598b285 100644 --- a/pkg/sec/certs_test.go +++ b/pkg/sec/certs_test.go @@ -20,10 +20,11 @@ package sec import ( - "juno/third_party/forked/golang/glog" "os" "reflect" "testing" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) // Sample secrets crt, not a real crt diff --git a/pkg/sec/gotlsimpl.go b/pkg/sec/gotlsimpl.go index 47b026a5..76e7d011 100644 --- a/pkg/sec/gotlsimpl.go +++ b/pkg/sec/gotlsimpl.go @@ -27,9 +27,10 @@ import ( "time" "crypto/tls" - "juno/third_party/forked/golang/glog" - "juno/pkg/proto" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/pkg/proto" ) type ( diff --git a/pkg/sec/keystore.go b/pkg/sec/keystore.go index ba29de85..d7728d7e 100644 --- a/pkg/sec/keystore.go +++ b/pkg/sec/keystore.go @@ -23,10 +23,11 @@ import ( "encoding/hex" "errors" "fmt" - "juno/pkg/proto" - "juno/third_party/forked/golang/glog" "time" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/BurntSushi/toml" ) diff --git a/pkg/sec/keystore_test.go b/pkg/sec/keystore_test.go index 3a8c0389..3e7eb737 100644 --- a/pkg/sec/keystore_test.go +++ b/pkg/sec/keystore_test.go @@ -20,10 +20,11 @@ package sec import ( - "juno/pkg/proto" "os" "reflect" "testing" + + "github.com/paypal/junodb/pkg/proto" ) var tomlData = []byte(`# Sample Keystore diff --git a/pkg/sec/seccfg.go b/pkg/sec/seccfg.go index b57344a2..883d147a 100644 --- a/pkg/sec/seccfg.go +++ b/pkg/sec/seccfg.go @@ -23,7 +23,7 @@ import ( "fmt" "sync/atomic" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var ( diff --git a/pkg/sec/secinit.go b/pkg/sec/secinit.go index 1817dcc6..4733936c 100644 --- a/pkg/sec/secinit.go +++ b/pkg/sec/secinit.go @@ -23,12 +23,12 @@ import ( "fmt" "sync" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/initmgr" - "juno/pkg/logging" - "juno/pkg/logging/cal" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/initmgr" + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/proto" ) const ( @@ -60,10 +60,10 @@ var ( type Flag uint8 /* - Two arguments required - arg 0: *Config - arg 1: uint8 bitmask flag - arg 2: isServerManager (optional) +Two arguments required +arg 0: *Config +arg 1: uint8 bitmask flag +arg 2: isServerManager (optional) */ func Initialize(args ...interface{}) (err error) { var cfg *Config diff --git a/pkg/service/servermgr.go b/pkg/service/servermgr.go index 53f0bad6..d6182581 100644 --- a/pkg/service/servermgr.go +++ b/pkg/service/servermgr.go @@ -28,7 +28,7 @@ import ( "strings" "syscall" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type ( diff --git a/pkg/service/service.go b/pkg/service/service.go index 96629915..2dff0053 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -27,10 +27,10 @@ import ( "sync/atomic" "syscall" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/io" - "juno/pkg/logging/cal" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging/cal" ) type Service struct { diff --git a/pkg/service/svccfg.go b/pkg/service/svccfg.go index fc808cf5..b1971d01 100644 --- a/pkg/service/svccfg.go +++ b/pkg/service/svccfg.go @@ -24,8 +24,8 @@ import ( "strings" "time" - "juno/pkg/io" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/util" ) const ( diff --git a/pkg/stats/appnsstats.go b/pkg/stats/appnsstats.go index 44230817..9a2ffbe9 100644 --- a/pkg/stats/appnsstats.go +++ b/pkg/stats/appnsstats.go @@ -23,7 +23,7 @@ import ( "sync" "sync/atomic" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) const ( diff --git a/pkg/stats/redist/stats.go b/pkg/stats/redist/stats.go index 8674f88c..02a10b64 100644 --- a/pkg/stats/redist/stats.go +++ b/pkg/stats/redist/stats.go @@ -21,12 +21,13 @@ package redist import ( "fmt" - "juno/pkg/logging" - "juno/pkg/util" "strconv" "strings" "sync/atomic" "time" + + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/util" ) var ( diff --git a/pkg/stats/sharedstats.go b/pkg/stats/sharedstats.go index 9d9d7e35..2563ec89 100644 --- a/pkg/stats/sharedstats.go +++ b/pkg/stats/sharedstats.go @@ -23,9 +23,9 @@ import ( "fmt" "os" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/shm" + "github.com/paypal/junodb/pkg/shm" ) type ( diff --git a/pkg/stats/statelog.go b/pkg/stats/statelog.go index 214cacd0..0a368273 100644 --- a/pkg/stats/statelog.go +++ b/pkg/stats/statelog.go @@ -28,9 +28,9 @@ import ( "time" "unsafe" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/proto" ) const ( diff --git a/pkg/udf/udfmgr_test.go b/pkg/udf/udfmgr_test.go index d1aca400..ed35ea09 100644 --- a/pkg/udf/udfmgr_test.go +++ b/pkg/udf/udfmgr_test.go @@ -21,7 +21,8 @@ package udf import ( "encoding/binary" - "juno/third_party/forked/golang/glog" + + "github.com/paypal/junodb/third_party/forked/golang/glog" "fmt" "testing" diff --git a/pkg/udf/udfplugin.go b/pkg/udf/udfplugin.go index 80a8e5a1..fcd3dc6e 100644 --- a/pkg/udf/udfplugin.go +++ b/pkg/udf/udfplugin.go @@ -26,7 +26,7 @@ import ( "plugin" "strings" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) func loadOneUDFbyName(dir string, name string) (iudf IUDF, err error) { diff --git a/pkg/util/cmap.go b/pkg/util/cmap.go index 7d32db7e..762d5778 100644 --- a/pkg/util/cmap.go +++ b/pkg/util/cmap.go @@ -20,9 +20,10 @@ package util import ( - "github.com/spaolacci/murmur3" - "juno/third_party/forked/golang/glog" "sync" + + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/spaolacci/murmur3" ) type MapPartition struct { diff --git a/pkg/version/version.go b/pkg/version/version.go index 36f49f8c..028fb4fe 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -27,7 +27,7 @@ import ( "path/filepath" "runtime" - "juno/pkg/debug" + "github.com/paypal/junodb/pkg/debug" ) var ( diff --git a/test/drv/bulkload/bulkload.go b/test/drv/bulkload/bulkload.go index 066d6c28..f7460e3f 100644 --- a/test/drv/bulkload/bulkload.go +++ b/test/drv/bulkload/bulkload.go @@ -21,7 +21,7 @@ // // Tool to create a set of random keys, which can be accessed by // [-get|-update|-set|-delete] in a subsequent command. -//================================================================= +// ================================================================= package main import ( @@ -35,10 +35,10 @@ import ( "strings" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/client" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/util" ) type CmdLine struct { diff --git a/test/drv/junoload/dbstats.go b/test/drv/junoload/dbstats.go index ed8b5f24..8cf414b8 100644 --- a/test/drv/junoload/dbstats.go +++ b/test/drv/junoload/dbstats.go @@ -22,10 +22,10 @@ package main import ( "fmt" - "juno/third_party/forked/golang/glog" - "juno/third_party/forked/tecbot/gorocksdb" + "github.com/paypal/junodb/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/tecbot/gorocksdb" - stor "juno/cmd/storageserv/storage/db" + stor "github.com/paypal/junodb/cmd/storageserv/storage/db" ) func PrintDbStats(name string) { diff --git a/test/drv/junoload/junoload.go b/test/drv/junoload/junoload.go index 9297f651..d48b2fc3 100644 --- a/test/drv/junoload/junoload.go +++ b/test/drv/junoload/junoload.go @@ -29,15 +29,15 @@ import ( "sync" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" - "juno/pkg/client" - "juno/pkg/cmd" - "juno/pkg/logging/cal" - "juno/pkg/sec" - "juno/pkg/version" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/cmd" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/sec" + "github.com/paypal/junodb/pkg/version" ) type ( diff --git a/test/drv/junoload/tdscfg.go b/test/drv/junoload/tdscfg.go index 46fb27e6..38517adc 100644 --- a/test/drv/junoload/tdscfg.go +++ b/test/drv/junoload/tdscfg.go @@ -20,9 +20,9 @@ package main import ( - "juno/pkg/client" - "juno/pkg/logging/cal/config" - "juno/pkg/sec" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/logging/cal/config" + "github.com/paypal/junodb/pkg/sec" ) type ( diff --git a/test/drv/junoload/tsteng.go b/test/drv/junoload/tsteng.go index a9970bfc..30fd587f 100644 --- a/test/drv/junoload/tsteng.go +++ b/test/drv/junoload/tsteng.go @@ -27,9 +27,9 @@ import ( "sync" "time" - "juno/pkg/client" + "github.com/paypal/junodb/pkg/client" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" uuid "github.com/satori/go.uuid" ) diff --git a/test/fakess/config.go b/test/fakess/config.go index f6169bca..39521e64 100644 --- a/test/fakess/config.go +++ b/test/fakess/config.go @@ -1,23 +1,24 @@ -// +// // Copyright 2023 PayPal Inc. -// +// // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// - -// +build +// + +//go:build ignore +// +build ignore package main @@ -25,13 +26,13 @@ import ( "math" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" - "juno/pkg/io" - cal "juno/pkg/logging/cal/config" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/io" + cal "github.com/paypal/junodb/pkg/logging/cal/config" + "github.com/paypal/junodb/pkg/util" ) var Conf = Config{ diff --git a/test/fakess/main.go b/test/fakess/main.go index cf0c2eba..fd5ef392 100644 --- a/test/fakess/main.go +++ b/test/fakess/main.go @@ -1,34 +1,36 @@ -// +// // Copyright 2023 PayPal Inc. -// +// // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// - -// +build +// + +//go:build ignore +// +build ignore package main import ( "flag" - "juno/pkg/logging/cal" - "juno/pkg/service" "net" "strconv" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/service" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var ( diff --git a/test/fakess/requesthandler.go b/test/fakess/requesthandler.go index ebc03ab3..837949b5 100644 --- a/test/fakess/requesthandler.go +++ b/test/fakess/requesthandler.go @@ -1,34 +1,36 @@ -// +// // Copyright 2023 PayPal Inc. -// +// // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// - -// +build +// + +//go:build ignore +// +build ignore package main import ( - "juno/pkg/io" - _ "juno/pkg/logging" - "juno/pkg/proto" "math/rand" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/io" + _ "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/proto" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) //type IRequestHandler interface { diff --git a/test/functest/create_test.go b/test/functest/create_test.go index 1e4e261a..206539e0 100644 --- a/test/functest/create_test.go +++ b/test/functest/create_test.go @@ -24,11 +24,11 @@ import ( "testing" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/client" - "juno/pkg/util" - "juno/test/testutil" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/test/testutil" ) /*************************************************** diff --git a/test/functest/delete_test.go b/test/functest/delete_test.go index 0b0dfb6b..6ebb9603 100644 --- a/test/functest/delete_test.go +++ b/test/functest/delete_test.go @@ -21,10 +21,11 @@ package functest import ( // "encoding/hex" - "juno/pkg/client" - "juno/test/testutil" "testing" "time" + + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/test/testutil" ) /************************************************************************ diff --git a/test/functest/get_test.go b/test/functest/get_test.go index 1a8640fa..2013fd13 100644 --- a/test/functest/get_test.go +++ b/test/functest/get_test.go @@ -20,13 +20,14 @@ package functest import ( - "juno/pkg/client" - "juno/pkg/util" - "juno/test/testutil" "testing" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/test/testutil" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) /*********************************************************************** diff --git a/test/functest/set_test.go b/test/functest/set_test.go index fc333ef9..b7133914 100644 --- a/test/functest/set_test.go +++ b/test/functest/set_test.go @@ -20,13 +20,14 @@ package functest import ( - "juno/pkg/client" - "juno/test/testutil" "strconv" "testing" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/test/testutil" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) /************************************************* diff --git a/test/functest/setup_test.go b/test/functest/setup_test.go index e5244527..27dd384e 100644 --- a/test/functest/setup_test.go +++ b/test/functest/setup_test.go @@ -30,19 +30,19 @@ import ( "testing" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/BurntSushi/toml" - "juno/cmd/proxy/config" - "juno/pkg/client" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/io" - "juno/pkg/logging/cal" - "juno/pkg/sec" - "juno/pkg/util" - "juno/test/testutil/server" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/logging/cal" + "github.com/paypal/junodb/pkg/sec" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/test/testutil/server" ) var testConfig = server.ClusterConfig{ diff --git a/test/functest/update_test.go b/test/functest/update_test.go index 28697e15..bbc4eed4 100644 --- a/test/functest/update_test.go +++ b/test/functest/update_test.go @@ -24,10 +24,10 @@ import ( "testing" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/client" - "juno/test/testutil" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/test/testutil" ) /**************************************************** diff --git a/test/mockss/mockss.go b/test/mockss/mockss.go index e1d87588..bc22f2fb 100644 --- a/test/mockss/mockss.go +++ b/test/mockss/mockss.go @@ -22,8 +22,9 @@ package main import ( "flag" "fmt" - "juno/test/testutil/mock" - "juno/third_party/forked/golang/glog" + + "github.com/paypal/junodb/test/testutil/mock" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) func main() { diff --git a/test/testutil/log/frwk/frwk.go b/test/testutil/log/frwk/frwk.go index 63b55fae..55570efa 100644 --- a/test/testutil/log/frwk/frwk.go +++ b/test/testutil/log/frwk/frwk.go @@ -20,7 +20,7 @@ package frwk import ( - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var ( diff --git a/test/testutil/mock/client.go b/test/testutil/mock/client.go index 9cedae1e..189ab15c 100644 --- a/test/testutil/mock/client.go +++ b/test/testutil/mock/client.go @@ -26,11 +26,11 @@ import ( "net" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/client" - "juno/pkg/cluster" - "juno/pkg/proto" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/proto" ) type MockClient struct { diff --git a/test/testutil/mock/common.go b/test/testutil/mock/common.go index 1d6184ed..9d203733 100644 --- a/test/testutil/mock/common.go +++ b/test/testutil/mock/common.go @@ -26,7 +26,7 @@ import ( "testing" "time" - . "juno/pkg/proto" + . "github.com/paypal/junodb/pkg/proto" ) const ( diff --git a/test/testutil/mock/config.go b/test/testutil/mock/config.go index 93a98c75..dcd03d5c 100644 --- a/test/testutil/mock/config.go +++ b/test/testutil/mock/config.go @@ -23,9 +23,9 @@ import ( "math" "time" - "juno/pkg/io" - "juno/pkg/service" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/pkg/util" ) type SSConfig struct { diff --git a/test/testutil/mock/ssreqhandler.go b/test/testutil/mock/ssreqhandler.go index c5942b06..40d4d86a 100644 --- a/test/testutil/mock/ssreqhandler.go +++ b/test/testutil/mock/ssreqhandler.go @@ -25,13 +25,13 @@ import ( "sync" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - // "juno/cmd/proxy/handler" - "juno/pkg/io" - "juno/pkg/proto" - "juno/pkg/service" - "juno/pkg/util" + // "github.com/paypal/junodb/cmd/proxy/handler" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/pkg/util" ) var _ io.IRequestHandler = (*RequestHandler)(nil) diff --git a/test/testutil/server/cluster.go b/test/testutil/server/cluster.go index 4467ef7e..1d9d287b 100644 --- a/test/testutil/server/cluster.go +++ b/test/testutil/server/cluster.go @@ -31,16 +31,16 @@ import ( "syscall" "time" - "juno/third_party/forked/golang/glog" - - "juno/cmd/proxy/handler" - "juno/cmd/proxy/stats" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/net/netutil" - "juno/pkg/util" - "juno/test/testutil/log/frwk" - "juno/test/testutil/mock" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/proxy/handler" + "github.com/paypal/junodb/cmd/proxy/stats" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/net/netutil" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/test/testutil/log/frwk" + "github.com/paypal/junodb/test/testutil/mock" ) type ICluster interface { diff --git a/test/testutil/server/config.go b/test/testutil/server/config.go index dde5a67f..9899ac15 100644 --- a/test/testutil/server/config.go +++ b/test/testutil/server/config.go @@ -20,11 +20,11 @@ package server import ( - "juno/cmd/proxy/config" - "juno/pkg/io" - cal "juno/pkg/logging/cal/config" - "juno/pkg/sec" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/pkg/io" + cal "github.com/paypal/junodb/pkg/logging/cal/config" + "github.com/paypal/junodb/pkg/sec" + "github.com/paypal/junodb/pkg/util" ) type ServerDef struct { diff --git a/test/testutil/server/inprocsrv.go b/test/testutil/server/inprocsrv.go index 69ef735b..217c14d7 100644 --- a/test/testutil/server/inprocsrv.go +++ b/test/testutil/server/inprocsrv.go @@ -23,10 +23,10 @@ import ( "fmt" "sync" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/service" - "juno/test/testutil/log/frwk" + "github.com/paypal/junodb/pkg/service" + "github.com/paypal/junodb/test/testutil/log/frwk" ) var _ IServer = (*InProcessServer)(nil) diff --git a/test/testutil/server/server.go b/test/testutil/server/server.go index 88871815..9f1c0c10 100644 --- a/test/testutil/server/server.go +++ b/test/testutil/server/server.go @@ -22,7 +22,6 @@ package server import ( "fmt" pkgio "io" - "juno/third_party/forked/golang/glog" "net" "net/http" "os" @@ -31,11 +30,13 @@ import ( "strings" "time" - "juno/internal/cli" - "juno/pkg/io" - "juno/pkg/net/netutil" - "juno/pkg/proto" - "juno/test/testutil/log/frwk" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/internal/cli" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/net/netutil" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/test/testutil/log/frwk" ) const ( diff --git a/test/testutil/server/ssnode.go b/test/testutil/server/ssnode.go index dcd2994a..105d0469 100644 --- a/test/testutil/server/ssnode.go +++ b/test/testutil/server/ssnode.go @@ -23,10 +23,10 @@ import ( "fmt" "net" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/client" - "juno/test/testutil/ssclient" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/test/testutil/ssclient" ) type SSNode struct { diff --git a/test/testutil/ssclient/advssclient.go b/test/testutil/ssclient/advssclient.go index 876e69e7..ffe0abfe 100644 --- a/test/testutil/ssclient/advssclient.go +++ b/test/testutil/ssclient/advssclient.go @@ -24,11 +24,11 @@ import ( "net" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/etcd" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) type Config struct { diff --git a/test/testutil/ssclient/ssclient.go b/test/testutil/ssclient/ssclient.go index 273704e0..a5a4b82a 100644 --- a/test/testutil/ssclient/ssclient.go +++ b/test/testutil/ssclient/ssclient.go @@ -24,12 +24,12 @@ import ( "io" "net" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/internal/cli" - "juno/pkg/client" - "juno/pkg/proto" - "juno/pkg/util" + "github.com/paypal/junodb/internal/cli" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/pkg/util" ) type Record struct { diff --git a/test/testutil/testhelper.go b/test/testutil/testhelper.go index 537e36ee..7ac9b6cc 100644 --- a/test/testutil/testhelper.go +++ b/test/testutil/testhelper.go @@ -34,17 +34,17 @@ import ( "testing" "time" - "juno/third_party/forked/golang/glog" - - "juno/cmd/proxy/config" - "juno/internal/cli" - "juno/pkg/client" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/io" - "juno/pkg/util" - "juno/test/testutil/mock" - "juno/test/testutil/server" + "github.com/paypal/junodb/third_party/forked/golang/glog" + + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/internal/cli" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/test/testutil/mock" + "github.com/paypal/junodb/test/testutil/server" ) type KVMap map[string]string @@ -227,7 +227,7 @@ func ApproxEqual(v1 uint32, v2 uint32, epsilon uint32) bool { return (v2 - v1) <= epsilon } -//This function has to be called right after getting RecordInfo +// This function has to be called right after getting RecordInfo func VerifyRecordInfo(recInfo client.IContext, ver uint32, ttl uint32, creationTime uint32) error { if recInfo == nil { return fmt.Errorf("nil recInfo") @@ -958,7 +958,7 @@ func LoadInitConfig(hostip string) { time.Sleep(3 * time.Second) } -//temporally create redist info +// temporally create redist info func UpdateRedistConfig(t *testing.T, hostip string, connNo string, configFile string) { var cmd string var cmd1 string @@ -989,7 +989,7 @@ func UpdateRedistConfig(t *testing.T, hostip string, connNo string, configFile s time.Sleep(3 * time.Second) } -//start redist +// start redist func StartRedistConfig(t *testing.T, hostip string, markdown string) { var localIp bool = false var cmd string @@ -1009,7 +1009,7 @@ func StartRedistConfig(t *testing.T, hostip string, markdown string) { } } -//start auto redistribution +// start auto redistribution func StartAutoRedistConfig(t *testing.T, hostip string, markdown string) { var localIp bool = false var cmd string @@ -1027,7 +1027,7 @@ func StartAutoRedistConfig(t *testing.T, hostip string, markdown string) { exec.Command("bash", "-c", cmd).Output() } -//temporally check forward finish, all zones are snapshot_finish +// temporally check forward finish, all zones are snapshot_finish func FinishForwardCheck(t *testing.T, hostip string) { var cmd string if ResolveHostIp() != hostip { @@ -1044,7 +1044,7 @@ func FinishForwardCheck(t *testing.T, hostip string) { } } -//resume the aborted redistribution +// resume the aborted redistribution func ResumeAbortedReq(t *testing.T, hostip string) { var cmd string if ResolveHostIp() != hostip { @@ -1058,7 +1058,7 @@ func ResumeAbortedReq(t *testing.T, hostip string) { exec.Command("bash", "-c", cmd).Output() } -//commit the new change +// commit the new change func FinalizeConfig(t *testing.T, hostip string) { var cmd string if ResolveHostIp() != hostip { @@ -1164,7 +1164,7 @@ func ReInitializeCluster(config server.ClusterConfig) (c *server.Cluster) { return server.NewClusterWithConfig(&config) } -//This definitely will be deleted as it's a temporally workaround for shutdown issue +// This definitely will be deleted as it's a temporally workaround for shutdown issue func SSShutdown(hostip string, secondhost bool) { var cmd string if ResolveHostIp() != hostip { diff --git a/test/testutil/tmkeeper.go b/test/testutil/tmkeeper.go index ee269605..622a0171 100644 --- a/test/testutil/tmkeeper.go +++ b/test/testutil/tmkeeper.go @@ -22,7 +22,7 @@ package testutil import ( "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" ) type timeKeeper struct { diff --git a/test/unittest/create_test.go b/test/unittest/create_test.go index 7a78105f..d49974f4 100644 --- a/test/unittest/create_test.go +++ b/test/unittest/create_test.go @@ -21,14 +21,15 @@ package unittest import ( "fmt" - "juno/pkg/client" - "juno/pkg/proto" - "juno/test/testutil" - "juno/test/testutil/mock" "strconv" "testing" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/test/testutil" + "github.com/paypal/junodb/test/testutil/mock" + + "github.com/paypal/junodb/third_party/forked/golang/glog" ) var createStatusArray [7]uint8 diff --git a/test/unittest/destroy_test.go b/test/unittest/destroy_test.go index baf68e68..f43b7854 100644 --- a/test/unittest/destroy_test.go +++ b/test/unittest/destroy_test.go @@ -1,29 +1,28 @@ +// Copyright 2023 PayPal Inc. // -// Copyright 2023 PayPal Inc. +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at // -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// http://www.apache.org/licenses/LICENSE-2.0 // +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package unittest import ( - "juno/pkg/client" - "juno/pkg/proto" - "juno/test/testutil" - "juno/test/testutil/mock" "testing" + + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/test/testutil" + "github.com/paypal/junodb/test/testutil/mock" ) var prepareDeleteArr [4]uint8 diff --git a/test/unittest/get_test.go b/test/unittest/get_test.go index de355260..3235a8b9 100644 --- a/test/unittest/get_test.go +++ b/test/unittest/get_test.go @@ -20,11 +20,12 @@ package unittest import ( - "juno/pkg/client" - "juno/pkg/proto" - "juno/test/testutil" - "juno/test/testutil/mock" "testing" + + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/test/testutil" + "github.com/paypal/junodb/test/testutil/mock" ) var getStatusArray [5]uint8 diff --git a/test/unittest/set_test.go b/test/unittest/set_test.go index 2981117b..b961176e 100644 --- a/test/unittest/set_test.go +++ b/test/unittest/set_test.go @@ -20,11 +20,12 @@ package unittest import ( - "juno/pkg/client" - "juno/pkg/proto" - "juno/test/testutil" - "juno/test/testutil/mock" "testing" + + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/test/testutil" + "github.com/paypal/junodb/test/testutil/mock" ) var setPrepareArray [7]uint8 diff --git a/test/unittest/setup_test.go b/test/unittest/setup_test.go index f7541e9a..a7881275 100644 --- a/test/unittest/setup_test.go +++ b/test/unittest/setup_test.go @@ -28,17 +28,17 @@ import ( "testing" "time" - "juno/third_party/forked/golang/glog" + "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/cmd/proxy/config" - "juno/pkg/client" - "juno/pkg/cluster" - "juno/pkg/etcd" - "juno/pkg/io" - "juno/pkg/util" + "github.com/paypal/junodb/cmd/proxy/config" + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/cluster" + "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/io" + "github.com/paypal/junodb/pkg/util" - "juno/test/testutil/mock" - "juno/test/testutil/server" + "github.com/paypal/junodb/test/testutil/mock" + "github.com/paypal/junodb/test/testutil/server" ) var testConfig = server.ClusterConfig{ diff --git a/test/unittest/update_test.go b/test/unittest/update_test.go index 4f74b5e6..d8b0ef4f 100644 --- a/test/unittest/update_test.go +++ b/test/unittest/update_test.go @@ -20,12 +20,13 @@ package unittest import ( - //"juno/third_party/forked/golang/glog" - "juno/pkg/client" - "juno/pkg/proto" - "juno/test/testutil" - "juno/test/testutil/mock" + //"github.com/paypal/junodb/third_party/forked/golang/glog" "testing" + + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/proto" + "github.com/paypal/junodb/test/testutil" + "github.com/paypal/junodb/test/testutil/mock" ) var updatePrepareArray [6]uint8 From efad3068cc7ae95af02c905f1fd9dd2d109cbef1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 20:42:55 +0000 Subject: [PATCH 08/27] Bump actions/setup-go from 4.0.0 to 4.0.1 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/client_ft.yml | 2 +- .github/workflows/juno_server_bin_build.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/client_ft.yml b/.github/workflows/client_ft.yml index b854961e..882b1508 100644 --- a/.github/workflows/client_ft.yml +++ b/.github/workflows/client_ft.yml @@ -25,7 +25,7 @@ jobs: - name: Get code uses: actions/checkout@v3.5.2 - name: Install golang - uses: actions/setup-go@v4.0.0 + uses: actions/setup-go@v4.0.1 with: go-version: "${{ matrix.go_version }}" - name: Install Build dependencies diff --git a/.github/workflows/juno_server_bin_build.yml b/.github/workflows/juno_server_bin_build.yml index a22451a9..eb43160a 100644 --- a/.github/workflows/juno_server_bin_build.yml +++ b/.github/workflows/juno_server_bin_build.yml @@ -25,7 +25,7 @@ jobs: - name: Get code uses: actions/checkout@v3.5.2 - name: Install golang - uses: actions/setup-go@v4.0.0 + uses: actions/setup-go@v4.0.1 with: go-version: "${{ matrix.go_version }}" - name: Install Build dependencies From a109fb4a2a8679548c7f6fde9bd14705333f3c51 Mon Sep 17 00:00:00 2001 From: Ahmad Kashif Date: Thu, 25 May 2023 01:04:12 +0500 Subject: [PATCH 09/27] Fixing Build error --- docker/build/build.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docker/build/build.sh b/docker/build/build.sh index 97efb62c..15c894b2 100755 --- a/docker/build/build.sh +++ b/docker/build/build.sh @@ -23,17 +23,17 @@ export CGO_CFLAGS="-I/usr/local/include" export CGO_LDFLAGS="-L/usr/local/lib -lrocksdb -lstdc++ -lm -lrt -lpthread -ldl" juno_executables="\ - juno/cmd/proxy \ - juno/cmd/storageserv \ - juno/cmd/clustermgr/clusterctl \ - juno/cmd/dbscanserv \ - juno/cmd/dbscanserv/junoctl \ - juno/cmd/tools/junostats \ - juno/cmd/tools/junocfg \ - juno/cmd/tools/junocli \ - juno/test/drv/junoload \ - juno/test/drv/bulkload \ - juno/cmd/storageserv/storage/db/dbcopy \ + github.com/paypal/junodb/cmd/proxy \ + github.com/paypal/junodb/cmd/storageserv \ + github.com/paypal/junodb/cmd/clustermgr/clusterctl \ + github.com/paypal/junodb/cmd/dbscanserv \ + github.com/paypal/junodb/cmd/dbscanserv/junoctl \ + github.com/paypal/junodb/cmd/tools/junostats \ + github.com/paypal/junodb/cmd/tools/junocfg \ + github.com/paypal/junodb/cmd/tools/junocli \ + github.com/paypal/junodb/test/drv/junoload \ + github.com/paypal/junodb/test/drv/bulkload \ + github.com/paypal/junodb/cmd/storageserv/storage/db/dbcopy \ " export PATH=/usr/local/go/bin:$PATH From 106bb370ff8aff8c7b52db394ba1c14b8a2a56fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 22:59:33 +0000 Subject: [PATCH 10/27] Bump spring-boot-autoconfigure Bumps [spring-boot-autoconfigure](https://github.com/spring-projects/spring-boot) from 2.6.7 to 2.6.15. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v2.6.7...v2.6.15) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-autoconfigure dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .../client/junoReferenceApp/junoreferenceAppService/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/Java/examples/client/junoReferenceApp/junoreferenceAppService/pom.xml b/client/Java/examples/client/junoReferenceApp/junoreferenceAppService/pom.xml index 37c2f1b0..780a3aa8 100644 --- a/client/Java/examples/client/junoReferenceApp/junoreferenceAppService/pom.xml +++ b/client/Java/examples/client/junoReferenceApp/junoreferenceAppService/pom.xml @@ -136,7 +136,7 @@ limitations under the License. org.springframework.boot spring-boot-autoconfigure - 2.6.7 + 2.6.15 org.springframework.boot From 2a1aab1dfdc8c74fe9962616af54b7549c9394b8 Mon Sep 17 00:00:00 2001 From: nepathak Date: Mon, 12 Jun 2023 23:03:11 -0700 Subject: [PATCH 11/27] Docker build changes. 1. Adding error return in shell script. 2. Change ubuntu version 20.04 3. Update github workflow for build on mac-os --- .github/workflows/juno_server_docker_build.yml | 10 +++++----- docker/build.sh | 18 +++++++++++++++++- docker/build/Dockerfile | 2 +- docker/build/build.sh | 2 ++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/juno_server_docker_build.yml b/.github/workflows/juno_server_docker_build.yml index 560a654f..7d620cac 100644 --- a/.github/workflows/juno_server_docker_build.yml +++ b/.github/workflows/juno_server_docker_build.yml @@ -18,7 +18,7 @@ jobs: build-juno-docker: strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} env: PROXY_TLS_PORT: 5080 @@ -28,14 +28,14 @@ jobs: steps: - name: Get code uses: actions/checkout@v3.5.2 - - name: Install dependencies + - name: Check openssl version dependencies run: | - sudo apt install openssl -y echo "Openssl Version:" `openssl version` + - name: Set up Docker + uses: docker-practice/actions-setup-docker@master + if: ${{ matrix.os == 'macos-latest' }} - name: Docker Version Check run: docker version - # - name: Expose GitHub Runtime - # uses: crazy-max/ghaction-github-runtime@v2 - name: Build docker containers run: docker/build.sh - name: Start juno containers diff --git a/docker/build.sh b/docker/build.sh index c7c701e3..e8afd73a 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -57,7 +57,23 @@ make image_tag=${image_tag} docker_repo=${docker_repo} source_repo=${source_repo # Set the app version using image_tag in manifest/.env -sed -i "s#.*VERSION=.*#VERSION=${image_tag}#g" ${wd}/manifest/.env +# Get the kernel name +kernel=$(uname -s) + +# Set the sed command based on the operating system +if [[ $kernel == "Linux" ]]; then + sed_command="sed -i 's#.*VERSION=.*#VERSION=${image_tag}#g' ${wd}/manifest/.env" +elif [[ $kernel == "Darwin" ]]; then + sed_command="sed -i '' 's#.*VERSION=.*#VERSION=${image_tag}#g' ${wd}/manifest/.env" +elif [[ $kernel == "CYGWIN"* || $kernel == "MINGW"* ]]; then + sed_command="sed -i 's#.*VERSION=.*#VERSION=${image_tag}#g' ${wd}/manifest/.env" +else + echo "Unknown operating system." + exit 1 +fi + +# Run the sed command +eval "$sed_command" # Generate the test secrets to initialize proxy manifest/config/secrets/gensecrets.sh diff --git a/docker/build/Dockerfile b/docker/build/Dockerfile index 2aebfb69..7f3a9df9 100644 --- a/docker/build/Dockerfile +++ b/docker/build/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:16.04 AS juno-base +FROM ubuntu:20.04 AS juno-base USER root RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y \ cmake \ diff --git a/docker/build/build.sh b/docker/build/build.sh index 97efb62c..f7043473 100755 --- a/docker/build/build.sh +++ b/docker/build/build.sh @@ -18,6 +18,8 @@ # limitations under the License. # +set -eo pipefail +cd "$(dirname "$0")" export CGO_CFLAGS="-I/usr/local/include" export CGO_LDFLAGS="-L/usr/local/lib -lrocksdb -lstdc++ -lm -lrt -lpthread -ldl" From 4dbf8867b741cd5959c3c0443454f8f445e3051a Mon Sep 17 00:00:00 2001 From: nepathak Date: Tue, 13 Jun 2023 13:04:32 -0700 Subject: [PATCH 12/27] Docker build supported platforms Manual build supported platforms --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 98f10fde..caffd5e6 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,10 @@ Continue building JunoDB server with +**Note** : +Docker build supported platforms +- Linux (Ubuntu) +- OS X (macOS) ###

Install Dependencies

[Install Docker Engine version 20.10.0+](https://docs.docker.com/engine/install/ubuntu/) Check for existing docker version @@ -207,6 +211,10 @@ More about junoload [here](docs/junoload.md) ## Manual Build +**Note** : +Manual build supported platforms +- Linux (Ubuntu 20.04) + The following sections explain the process for manually building the JunoDB server without Docker. These instructions are based on an Ubuntu 20.04.5 system * [Manual](#manual-build) * [Install Dependencies](#manual_install_dependencies) From abd198887e89bd702267b497d2548f15ab60551b Mon Sep 17 00:00:00 2001 From: yapingshi Date: Fri, 7 Jul 2023 13:47:26 -0700 Subject: [PATCH 13/27] support fixed key range in junoload --- test/drv/junoload/junoload.go | 74 +++++++++++++-- test/drv/junoload/tdscfg.go | 3 + test/drv/junoload/tsteng.go | 170 ++++++++++++++++------------------ 3 files changed, 147 insertions(+), 100 deletions(-) diff --git a/test/drv/junoload/junoload.go b/test/drv/junoload/junoload.go index 9297f651..c4e7aaff 100644 --- a/test/drv/junoload/junoload.go +++ b/test/drv/junoload/junoload.go @@ -24,7 +24,9 @@ import ( "fmt" "math/rand" "net/http" + _ "net/http/pprof" "os" + "strconv" "strings" "sync" "time" @@ -37,6 +39,7 @@ import ( "juno/pkg/cmd" "juno/pkg/logging/cal" "juno/pkg/sec" + "juno/pkg/util" "juno/pkg/version" ) @@ -75,6 +78,8 @@ type ( logLevel string isVariable bool disableGetTTL bool + keys string + randomize bool } ) @@ -96,6 +101,7 @@ const ( func (d *SyncTestDriver) setDefaultConfig() { d.config.SetDefault() + d.config.Sec = sec.DefaultConfig d.config.Cal.Default() d.config.Cal.Poolname = "junoload" @@ -112,6 +118,9 @@ func (d *SyncTestDriver) setDefaultConfig() { d.config.StatOutputRate = kDefaultStatOutputRate d.config.isVariable = false d.config.disableGetTTL = false + d.config.sKey = -1 + d.config.eKey = -1 + d.config.randomize = false } func (d *SyncTestDriver) Init(name string, desc string) { @@ -141,6 +150,8 @@ func (d *SyncTestDriver) Init(name string, desc string) { d.StringOption(&d.cmdOpts.dbpath, "dbpath", "", "to display rocksdb stats") d.StringOption(&d.cmdOpts.logLevel, "log-level", "info", "specify log level") d.BoolOption(&d.cmdOpts.disableGetTTL, "disableGetTTL", false, "not use random ttl for get operation") + d.StringOption(&d.cmdOpts.keys, "keys", "", "key strange, separated with ,") + d.BoolOption(&d.cmdOpts.randomize, "r|randomize", false, "randomize, get/update/delete") t := &SyncTestDriver{} t.setDefaultConfig() @@ -160,7 +171,28 @@ func (d *SyncTestDriver) Init(name string, desc string) { d.AddExample(name+" -s 127.0.0.1:8080 -ssl", "\trun the driver with SSL") d.AddExample(name+" -c config.toml", "\trun the driver with options specified in config.toml") +} + +func parseKeys(key string) (start int, last int) { + var err error + list := strings.Split(key, ",") + start, err = strconv.Atoi(list[0]) + if err != nil { + glog.Exitf("%s", err) + } + if len(list) < 2 { + last = start + 1 + } else { + last, err = strconv.Atoi(list[1]) + if err != nil { + glog.Exitf("%s", err) + } + } + if start < 0 || last < 0 { + glog.Exitf("Negative range params are not allowed.") + } + return } func (d *SyncTestDriver) Parse(args []string) (err error) { @@ -235,6 +267,14 @@ func (d *SyncTestDriver) Parse(args []string) (err error) { d.config.HttpMonAddr = ":" + d.config.HttpMonAddr } + if d.cmdOpts.keys != "" { + start, end := parseKeys(d.cmdOpts.keys) + d.config.sKey = int64(start) + d.config.eKey = int64(end) + } + + d.config.randomize = d.cmdOpts.randomize + d.config.Cal.Default() if d.config.Cal.Enabled { @@ -303,6 +343,10 @@ func (d *SyncTestDriver) Exec() { var wg sync.WaitGroup chDone := make(chan bool) + var numRunningExecutors util.AtomicCounter + numRunningExecutors.Reset() + numRunningExecutors.Add(int32(d.config.NumExecutor)) + if d.config.NumExecutor > 0 { wg.Add(1) go func() { @@ -322,9 +366,14 @@ func (d *SyncTestDriver) Exec() { case <-ticker.C: d.movingStats.PrettyPrint(os.Stdout) d.movingStats.Reset() + if numRunningExecutors.Get() == 0 { + timer.Stop() + ticker.Stop() + close(chDone) + break loop + } } } - }() } else { glog.Errorf("number of executor specified is zero") @@ -333,15 +382,17 @@ func (d *SyncTestDriver) Exec() { d.tmStart = time.Now() d.stats.Init() d.movingStats.Init() + var start int = -1 + var end int = -1 + for i := 0; i < d.config.NumExecutor; i++ { - size := d.cmdOpts.numKeys / 2 - num := size / d.config.NumExecutor - offGet := i*num + size + if d.config.sKey >= 0 && d.config.eKey > d.config.sKey { + range_size := (d.config.eKey - d.config.sKey + int64(d.config.NumExecutor-1)) / int64(d.config.NumExecutor) - if size > MaxDeletes { - size = MaxDeletes + start = i*int(range_size) + int(d.config.sKey) + end = start + int(range_size) - 1 //pad } - offDel := i * (size / d.config.NumExecutor) + //fmt.Printf("s=%d, e=%d\n", start, end) cli, err := client.New(d.config.Config) if err != nil { glog.Error(err) @@ -350,15 +401,18 @@ func (d *SyncTestDriver) Exec() { eng := &TestEngine{ rdgen: d.randgen, recStore: RecordStore{ - numKeys: num, - offsetDel: offDel, - offsetGet: offGet}, + nextKey: int(start), + sKey: int(start), + eKey: int(end), + randomize: d.config.randomize, + }, reqSequence: d.reqSequence, // chDone: chDone, client: cli, stats: &d.stats, movingStats: &d.movingStats, numReqPerSecond: d.config.NumReqPerSecond, + numRunningExec: &numRunningExecutors, } eng.Init() wg.Add(1) diff --git a/test/drv/junoload/tdscfg.go b/test/drv/junoload/tdscfg.go index 46fb27e6..8e577852 100644 --- a/test/drv/junoload/tdscfg.go +++ b/test/drv/junoload/tdscfg.go @@ -43,5 +43,8 @@ type ( StatOutputRate int isVariable bool disableGetTTL bool + sKey int64 + eKey int64 + randomize bool } ) diff --git a/test/drv/junoload/tsteng.go b/test/drv/junoload/tsteng.go index a9970bfc..196dcafd 100644 --- a/test/drv/junoload/tsteng.go +++ b/test/drv/junoload/tsteng.go @@ -16,11 +16,11 @@ // See the License for the specific language governing permissions and // limitations under the License. // - package main import ( "encoding/binary" + "errors" "fmt" "math" "math/rand" @@ -31,6 +31,8 @@ import ( "juno/third_party/forked/golang/glog" + "juno/pkg/util" + uuid "github.com/satori/go.uuid" ) @@ -43,7 +45,7 @@ const ( kNumRequestTypes ) -const MaxDeletes = 10000 +var ErrNoMoreKeys = errors.New("no more keys") type ( RequestType uint8 @@ -53,14 +55,11 @@ type ( } RecordStore struct { - records []Record - // Used for preloaded keys - numKeys int - currGet int - nextDelete int - offsetDel int - offsetGet int - LastDelete bool + records []Record + sKey int + eKey int + nextKey int + randomize bool } TestEngine struct { @@ -73,6 +72,7 @@ type ( stats *Statistics movingStats *Statistics numReqPerSecond int + numRunningExec *util.AtomicCounter } InvokeFunc func() error ) @@ -128,27 +128,35 @@ func (r *Record) isExpired() bool { } func (s *RecordStore) Add(rec Record) { - if s.numKeys > 0 { + if s.isKeyRange() { return } + s.records = append(s.records, rec) } func (s *RecordStore) display() { - glog.Infof("numKeys=%d currGet=%d nextDelete=%d offsetDel=%d offsetGet=%d", - s.numKeys, s.currGet, s.nextDelete, s.offsetDel, s.offsetGet) + /* + glog.Infof("numKeys=%d currGet=%d nextDelete=%d offsetDel=%d offsetGet=%d", + s.numKeys, s.currGet, s.nextDelete, s.offsetDel, s.offsetGet)*/ } func (s *RecordStore) takeRecord() (rec Record, err error) { - if s.numKeys > 0 { // preloaded keys - rec = Record{ - key: NewRandomKey(s.offsetDel + s.nextDelete), + if s.isKeyRange() { + var key_id int + if s.randomize { + key_id = s.sKey + rand.Intn(s.eKey-s.sKey+1) + } else { + if s.nextKey > s.eKey { + err = ErrNoMoreKeys + return + } + key_id = s.nextKey + s.nextKey++ } - if s.endOfDelete() { - err = fmt.Errorf("no more record for destroy") - return + rec = Record{ + key: NewRandomKey(key_id), } - s.nextDelete++ return } @@ -164,17 +172,23 @@ func (s *RecordStore) takeRecord() (rec Record, err error) { } func (s *RecordStore) getRecord() (rec Record, err error) { - if s.numKeys > 0 { // preloaded keys - count := s.numKeys - if s.numKeys >= MaxDeletes { - count = s.numKeys >> 2 + if s.isKeyRange() { + var key_id int + if s.randomize { + key_id = s.sKey + rand.Intn(s.eKey-s.sKey+1) + } else { + if s.nextKey > s.eKey { + err = ErrNoMoreKeys + return + } + key_id = s.nextKey + s.nextKey++ } - k := expRand(count) - s.currGet = k rec = Record{ - key: NewRandomKey(s.offsetGet + k), + key: NewRandomKey(key_id), } + return } @@ -193,32 +207,26 @@ func (s *RecordStore) getRecord() (rec Record, err error) { } func (s *RecordStore) empty() bool { - return len(s.records) == 0 && s.numKeys == 0 -} - -func (s *RecordStore) endOfDelete() bool { - return s.numKeys > 0 && - (s.nextDelete >= s.numKeys || s.nextDelete >= MaxDeletes) + return len(s.records) == 0 && s.isKeyRange() == false } func (s *RecordStore) Get() (rec Record, err error) { for !s.empty() { rec, err = s.getRecord() - if err == nil { - return - } + return } err = fmt.Errorf("no record") return } func (s *RecordStore) Take() (rec Record, err error) { - for !s.empty() && !s.endOfDelete() { + if s.isKeyRange() { + return s.takeRecord() + } + + if !s.empty() { rec, err = s.takeRecord() - if err == nil && !rec.isExpired() { - if s.endOfDelete() { - s.LastDelete = true - } + if err == nil { return } } @@ -226,6 +234,23 @@ func (s *RecordStore) Take() (rec Record, err error) { return } +func (s *RecordStore) getNextKey() (key []byte) { + if s.sKey == -1 { + key = newTestKey() + } else { + if s.nextKey > s.eKey { + return nil + } + key = NewRandomKey(s.nextKey) + s.nextKey++ + } + return +} + +func (s *RecordStore) isKeyRange() bool { + return s.sKey > -1 +} + func (e *TestEngine) Init() { e.invokeFuncs = make([]InvokeFunc, kNumRequestTypes) e.invokeFuncs[kRequestTypeCreate] = e.invokeCreate @@ -235,70 +260,35 @@ func (e *TestEngine) Init() { e.invokeFuncs[kRequestTypeDestroy] = e.invokeDestroy } -func (e *TestEngine) restoreData() { - if e.recStore.numKeys <= 0 || e.recStore.nextDelete <= 0 { - return - } - - count := e.recStore.nextDelete - - // Add back deleted keys - glog.Infof("Add back deleted keys: count=%d", count) - for i := 0; i < count; i++ { - - now := time.Now() - - key := NewRandomKey(e.recStore.offsetDel + i) - _, err := e.client.Create(key, e.rdgen.createPayload()) - tm := time.Since(now) - - e.stats.Put(kRequestTypeCreate, tm, err) - e.movingStats.Put(kRequestTypeCreate, tm, err) - if err != nil { - glog.Errorf("%s error: %s", kRequestTypeCreate.String(), err) - e.recStore.display() - } - } -} - func (e *TestEngine) Run(wg *sync.WaitGroup, chDone <-chan bool) { defer wg.Done() + defer e.numRunningExec.Add(-1) startTime := time.Now() var numreq int = 0 errCount := 0 + for { for _, item := range e.reqSequence.items { - if e.recStore.numKeys > 0 && - item.reqType == kRequestTypeCreate { - continue - } for i := 0; i < item.numRequests; i++ { select { case <-chDone: - e.restoreData() return default: now := time.Now() err := e.invoke(item.reqType) tm := time.Since(now) - if item.reqType == kRequestTypeDestroy && - e.recStore.endOfDelete() { - if e.recStore.LastDelete { - e.recStore.LastDelete = false - } else { - continue - } + if errors.Is(err, ErrNoMoreKeys) { + return } + e.stats.Put(item.reqType, tm, err) e.movingStats.Put(item.reqType, tm, err) if err != nil { glog.Errorf("%s error: %s", item.reqType.String(), err) - if e.recStore.numKeys > 0 { - e.recStore.display() - errCount++ - if errCount > 100 { - return - } + e.recStore.display() + errCount++ + if errCount > 100 { + //return } } diff := now.Sub(startTime) @@ -355,10 +345,13 @@ func (e *TestEngine) checkSpeedForVariableTp(now time.Time, numReq int, startTim } func (e *TestEngine) invokeCreate() (err error) { - if e.recStore.numKeys > 0 { + + key := e.recStore.getNextKey() + if key == nil { + err = ErrNoMoreKeys return } - key := newTestKey() + var ctx client.IContext if ctx, err = e.client.Create(key, e.rdgen.createPayload(), client.WithTTL(e.rdgen.getTTL())); err == nil { @@ -405,9 +398,6 @@ func (e *TestEngine) invokeSet() (err error) { func (e *TestEngine) invokeDestroy() (err error) { var rec Record - if e.recStore.endOfDelete() { - return nil - } if rec, err = e.recStore.Take(); err == nil { err = e.client.Destroy(rec.key) } From 9b8470d277110a276826afc9b5b1917f0aa10ca0 Mon Sep 17 00:00:00 2001 From: Nitish Tripathi Date: Wed, 9 Aug 2023 22:49:52 -0700 Subject: [PATCH 14/27] Fix: Dependabot alerts Fixing all Dependabot alerts in https://github.com/paypal/junodb/security/dependabot --- client/Java/Juno/FunctionalTests/pom.xml | 4 ++-- client/Java/Juno/juno-client-impl/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/Java/Juno/FunctionalTests/pom.xml b/client/Java/Juno/FunctionalTests/pom.xml index 64fd11c1..15ec7336 100644 --- a/client/Java/Juno/FunctionalTests/pom.xml +++ b/client/Java/Juno/FunctionalTests/pom.xml @@ -76,7 +76,7 @@ limitations under the License. org.xerial.snappy snappy-java - 1.1.9.1 + 1.1.10.1 org.testng @@ -131,7 +131,7 @@ limitations under the License. io.netty netty-handler - 4.1.82.Final + 4.1.94.Final commons-lang diff --git a/client/Java/Juno/juno-client-impl/pom.xml b/client/Java/Juno/juno-client-impl/pom.xml index 6861c63b..d0a2c597 100644 --- a/client/Java/Juno/juno-client-impl/pom.xml +++ b/client/Java/Juno/juno-client-impl/pom.xml @@ -71,7 +71,7 @@ limitations under the License. io.netty netty-handler - 4.1.82.Final + 4.1.94.Final org.powermock @@ -124,7 +124,7 @@ limitations under the License. org.xerial.snappy snappy-java - 1.1.9.1 + 1.1.10.1 javax.inject From 3f55f7e0aa2338057058c55db60fd1f1b98fd2b9 Mon Sep 17 00:00:00 2001 From: Vaibhav Alreja Date: Fri, 11 Aug 2023 11:53:46 -0700 Subject: [PATCH 15/27] moved ruby client to opensource --- client/Ruby/README.md | 38 + client/Ruby/docs/Juno.html | 301 ++ client/Ruby/docs/Juno/Client.html | 128 + client/Ruby/docs/Juno/Client/CacheStore.html | 557 ++++ client/Ruby/docs/Juno/Client/JunoRequest.html | 905 ++++++ .../docs/Juno/Client/JunoRequest/Type.html | 157 + .../Ruby/docs/Juno/Client/JunoResponse.html | 616 ++++ .../docs/Juno/Client/OperationStatus.html | 344 +++ .../Ruby/docs/Juno/Client/OperationType.html | 199 ++ client/Ruby/docs/Juno/Client/ReactClient.html | 1347 +++++++++ .../Ruby/docs/Juno/Client/RecordContext.html | 579 ++++ client/Ruby/docs/Juno/Client/SyncClient.html | 844 ++++++ client/Ruby/docs/Juno/ClientUtils.html | 676 +++++ client/Ruby/docs/Juno/Config.html | 2381 ++++++++++++++++ client/Ruby/docs/Juno/Config/Source.html | 147 + client/Ruby/docs/Juno/ConfigProvider.html | 603 ++++ client/Ruby/docs/Juno/ConfigReader.html | 1002 +++++++ client/Ruby/docs/Juno/DefaultProperties.html | 291 ++ client/Ruby/docs/Juno/IO.html | 227 ++ client/Ruby/docs/Juno/IO/CompressionType.html | 250 ++ client/Ruby/docs/Juno/IO/JunoMessage.html | 1860 ++++++++++++ .../Juno/IO/JunoMessage/OperationType.html | 304 ++ .../Ruby/docs/Juno/IO/MetadataComponent.html | 2518 +++++++++++++++++ .../IO/MetadataComponent/MetadataField.html | 514 ++++ .../MetadataField/SizeType.html | 137 + .../Juno/IO/MetadataComponent/TagAndType.html | 212 ++ .../Juno/IO/MetadataComponentTemplate.html | 255 ++ .../CorrelationIDField.html | 195 ++ .../FixedLengthField.html | 124 + .../MetadataHeaderField.html | 124 + .../SourceInfoField.html | 319 +++ client/Ruby/docs/Juno/IO/OffsetWidth.html | 359 +++ .../Ruby/docs/Juno/IO/OperationMessage.html | 716 +++++ .../Ruby/docs/Juno/IO/PayloadComponent.html | 648 +++++ .../CompressedPayloadData.html | 193 ++ .../EncryptedPayloadData.html | 124 + .../UncompressedPayloadData.html | 142 + client/Ruby/docs/Juno/IO/PayloadType.html | 158 ++ client/Ruby/docs/Juno/IO/ProtocolHeader.html | 329 +++ .../IO/ProtocolHeader/MessageTypeFlag.html | 124 + .../Juno/IO/ProtocolHeader/MessageTypes.html | 147 + .../docs/Juno/IO/ProtocolHeader/OpCodes.html | 324 +++ .../Juno/IO/ProtocolHeader/RequestTypes.html | 147 + client/Ruby/docs/Juno/Logger.html | 307 ++ client/Ruby/docs/Juno/Net.html | 117 + client/Ruby/docs/Juno/Net/BaseProcessor.html | 404 +++ client/Ruby/docs/Juno/Net/ClientHandler.html | 1143 ++++++++ client/Ruby/docs/Juno/Net/IOProcessor.html | 1536 ++++++++++ client/Ruby/docs/Juno/Net/PingMessage.html | 864 ++++++ client/Ruby/docs/Juno/Net/QueueEntry.html | 475 ++++ client/Ruby/docs/Juno/Net/RequestQueue.html | 826 ++++++ client/Ruby/docs/Juno/Net/WorkerPool.html | 405 +++ client/Ruby/docs/Juno/Properties.html | 269 ++ client/Ruby/docs/Juno/ServerStatus.html | 351 +++ client/Ruby/docs/Juno/Utils.html | 640 +++++ client/Ruby/docs/_index.html | 620 ++++ client/Ruby/docs/class_list.html | 51 + client/Ruby/docs/css/common.css | 1 + client/Ruby/docs/css/full_list.css | 58 + client/Ruby/docs/css/style.css | 497 ++++ client/Ruby/docs/file.README.html | 115 + client/Ruby/docs/file_list.html | 56 + client/Ruby/docs/frames.html | 17 + client/Ruby/docs/index.html | 115 + client/Ruby/docs/js/app.js | 314 ++ client/Ruby/docs/js/full_list.js | 216 ++ client/Ruby/docs/js/jquery.js | 4 + client/Ruby/docs/method_list.html | 2075 ++++++++++++++ client/Ruby/docs/top-level-namespace.html | 110 + client/Ruby/juno.yml | 26 + client/Ruby/juno/.rspec | 1 + client/Ruby/juno/CODE_OF_CONDUCT.md | 74 + client/Ruby/juno/Gemfile | 10 + client/Ruby/juno/Gemfile.lock | 95 + client/Ruby/juno/LICENSE.txt | 21 + client/Ruby/juno/Rakefile | 36 + client/Ruby/juno/bin/console | 15 + client/Ruby/juno/bin/setup | 8 + client/Ruby/juno/juno.gemspec | 56 + client/Ruby/juno/lib/juno.rb | 51 + .../Ruby/juno/lib/juno/Client/cache_store.rb | 55 + .../Ruby/juno/lib/juno/Client/juno_request.rb | 53 + .../juno/lib/juno/Client/juno_response.rb | 26 + .../juno/lib/juno/Client/operation_status.rb | 41 + .../juno/lib/juno/Client/operation_type.rb | 38 + .../Ruby/juno/lib/juno/Client/react_client.rb | 233 ++ .../juno/lib/juno/Client/record_context.rb | 22 + .../Ruby/juno/lib/juno/Client/sync_client.rb | 89 + client/Ruby/juno/lib/juno/Config/config.rb | 129 + .../juno/lib/juno/Config/config_provider.rb | 66 + .../juno/lib/juno/Config/config_reader.rb | 20 + .../lib/juno/Config/default_properties.rb | 37 + .../Ruby/juno/lib/juno/Config/properties.rb | 33 + client/Ruby/juno/lib/juno/IO/JunoMessage.rb | 53 + .../juno/lib/juno/IO/MetadataComponent.rb | 354 +++ .../lib/juno/IO/MetadataComponentTemplate.rb | 72 + .../Ruby/juno/lib/juno/IO/OperationMessage.rb | 56 + .../Ruby/juno/lib/juno/IO/PayloadComponent.rb | 167 ++ .../Ruby/juno/lib/juno/IO/ProtocolHeader.rb | 181 ++ client/Ruby/juno/lib/juno/IO/constants.rb | 46 + .../Ruby/juno/lib/juno/Net/base_processor.rb | 37 + .../Ruby/juno/lib/juno/Net/client_handler.rb | 116 + client/Ruby/juno/lib/juno/Net/io_processor.rb | 211 ++ client/Ruby/juno/lib/juno/Net/ping_message.rb | 84 + .../Ruby/juno/lib/juno/Net/request_queue.rb | 85 + client/Ruby/juno/lib/juno/Net/worker_pool.rb | 32 + .../Ruby/juno/lib/juno/Utils/client_utils.rb | 163 ++ client/Ruby/juno/lib/juno/Utils/utils.rb | 124 + client/Ruby/juno/lib/juno/logger.rb | 33 + client/Ruby/juno/lib/juno/server_status.rb | 49 + client/Ruby/juno/lib/juno/version.rb | 5 + client/Ruby/juno/pkg/juno-0.1.0.gem | Bin 0 -> 4608 bytes client/Ruby/juno/pkg/juno-1.0.0.gem | Bin 0 -> 31744 bytes client/Ruby/juno/spec/Client/sync_client.rb | 46 + .../Ruby/juno/spec/IO/metadata_component.rb | 139 + .../juno/spec/IO/payload_component_spec.rb | 136 + .../Ruby/juno/spec/IO/protocol_header_spec.rb | 107 + client/Ruby/juno/spec/spec_helper.rb | 12 + client/Ruby/sample.rb | 41 + 119 files changed, 36735 insertions(+) create mode 100644 client/Ruby/README.md create mode 100644 client/Ruby/docs/Juno.html create mode 100644 client/Ruby/docs/Juno/Client.html create mode 100644 client/Ruby/docs/Juno/Client/CacheStore.html create mode 100644 client/Ruby/docs/Juno/Client/JunoRequest.html create mode 100644 client/Ruby/docs/Juno/Client/JunoRequest/Type.html create mode 100644 client/Ruby/docs/Juno/Client/JunoResponse.html create mode 100644 client/Ruby/docs/Juno/Client/OperationStatus.html create mode 100644 client/Ruby/docs/Juno/Client/OperationType.html create mode 100644 client/Ruby/docs/Juno/Client/ReactClient.html create mode 100644 client/Ruby/docs/Juno/Client/RecordContext.html create mode 100644 client/Ruby/docs/Juno/Client/SyncClient.html create mode 100644 client/Ruby/docs/Juno/ClientUtils.html create mode 100644 client/Ruby/docs/Juno/Config.html create mode 100644 client/Ruby/docs/Juno/Config/Source.html create mode 100644 client/Ruby/docs/Juno/ConfigProvider.html create mode 100644 client/Ruby/docs/Juno/ConfigReader.html create mode 100644 client/Ruby/docs/Juno/DefaultProperties.html create mode 100644 client/Ruby/docs/Juno/IO.html create mode 100644 client/Ruby/docs/Juno/IO/CompressionType.html create mode 100644 client/Ruby/docs/Juno/IO/JunoMessage.html create mode 100644 client/Ruby/docs/Juno/IO/JunoMessage/OperationType.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponent.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponent/MetadataField.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponent/MetadataField/SizeType.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponent/TagAndType.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponentTemplate.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponentTemplate/CorrelationIDField.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponentTemplate/FixedLengthField.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponentTemplate/MetadataHeaderField.html create mode 100644 client/Ruby/docs/Juno/IO/MetadataComponentTemplate/SourceInfoField.html create mode 100644 client/Ruby/docs/Juno/IO/OffsetWidth.html create mode 100644 client/Ruby/docs/Juno/IO/OperationMessage.html create mode 100644 client/Ruby/docs/Juno/IO/PayloadComponent.html create mode 100644 client/Ruby/docs/Juno/IO/PayloadComponent/CompressedPayloadData.html create mode 100644 client/Ruby/docs/Juno/IO/PayloadComponent/EncryptedPayloadData.html create mode 100644 client/Ruby/docs/Juno/IO/PayloadComponent/UncompressedPayloadData.html create mode 100644 client/Ruby/docs/Juno/IO/PayloadType.html create mode 100644 client/Ruby/docs/Juno/IO/ProtocolHeader.html create mode 100644 client/Ruby/docs/Juno/IO/ProtocolHeader/MessageTypeFlag.html create mode 100644 client/Ruby/docs/Juno/IO/ProtocolHeader/MessageTypes.html create mode 100644 client/Ruby/docs/Juno/IO/ProtocolHeader/OpCodes.html create mode 100644 client/Ruby/docs/Juno/IO/ProtocolHeader/RequestTypes.html create mode 100644 client/Ruby/docs/Juno/Logger.html create mode 100644 client/Ruby/docs/Juno/Net.html create mode 100644 client/Ruby/docs/Juno/Net/BaseProcessor.html create mode 100644 client/Ruby/docs/Juno/Net/ClientHandler.html create mode 100644 client/Ruby/docs/Juno/Net/IOProcessor.html create mode 100644 client/Ruby/docs/Juno/Net/PingMessage.html create mode 100644 client/Ruby/docs/Juno/Net/QueueEntry.html create mode 100644 client/Ruby/docs/Juno/Net/RequestQueue.html create mode 100644 client/Ruby/docs/Juno/Net/WorkerPool.html create mode 100644 client/Ruby/docs/Juno/Properties.html create mode 100644 client/Ruby/docs/Juno/ServerStatus.html create mode 100644 client/Ruby/docs/Juno/Utils.html create mode 100644 client/Ruby/docs/_index.html create mode 100644 client/Ruby/docs/class_list.html create mode 100644 client/Ruby/docs/css/common.css create mode 100644 client/Ruby/docs/css/full_list.css create mode 100644 client/Ruby/docs/css/style.css create mode 100644 client/Ruby/docs/file.README.html create mode 100644 client/Ruby/docs/file_list.html create mode 100644 client/Ruby/docs/frames.html create mode 100644 client/Ruby/docs/index.html create mode 100644 client/Ruby/docs/js/app.js create mode 100644 client/Ruby/docs/js/full_list.js create mode 100644 client/Ruby/docs/js/jquery.js create mode 100644 client/Ruby/docs/method_list.html create mode 100644 client/Ruby/docs/top-level-namespace.html create mode 100644 client/Ruby/juno.yml create mode 100644 client/Ruby/juno/.rspec create mode 100644 client/Ruby/juno/CODE_OF_CONDUCT.md create mode 100644 client/Ruby/juno/Gemfile create mode 100644 client/Ruby/juno/Gemfile.lock create mode 100644 client/Ruby/juno/LICENSE.txt create mode 100644 client/Ruby/juno/Rakefile create mode 100755 client/Ruby/juno/bin/console create mode 100755 client/Ruby/juno/bin/setup create mode 100644 client/Ruby/juno/juno.gemspec create mode 100644 client/Ruby/juno/lib/juno.rb create mode 100755 client/Ruby/juno/lib/juno/Client/cache_store.rb create mode 100644 client/Ruby/juno/lib/juno/Client/juno_request.rb create mode 100644 client/Ruby/juno/lib/juno/Client/juno_response.rb create mode 100644 client/Ruby/juno/lib/juno/Client/operation_status.rb create mode 100644 client/Ruby/juno/lib/juno/Client/operation_type.rb create mode 100644 client/Ruby/juno/lib/juno/Client/react_client.rb create mode 100644 client/Ruby/juno/lib/juno/Client/record_context.rb create mode 100644 client/Ruby/juno/lib/juno/Client/sync_client.rb create mode 100644 client/Ruby/juno/lib/juno/Config/config.rb create mode 100644 client/Ruby/juno/lib/juno/Config/config_provider.rb create mode 100644 client/Ruby/juno/lib/juno/Config/config_reader.rb create mode 100644 client/Ruby/juno/lib/juno/Config/default_properties.rb create mode 100644 client/Ruby/juno/lib/juno/Config/properties.rb create mode 100644 client/Ruby/juno/lib/juno/IO/JunoMessage.rb create mode 100644 client/Ruby/juno/lib/juno/IO/MetadataComponent.rb create mode 100644 client/Ruby/juno/lib/juno/IO/MetadataComponentTemplate.rb create mode 100644 client/Ruby/juno/lib/juno/IO/OperationMessage.rb create mode 100644 client/Ruby/juno/lib/juno/IO/PayloadComponent.rb create mode 100644 client/Ruby/juno/lib/juno/IO/ProtocolHeader.rb create mode 100644 client/Ruby/juno/lib/juno/IO/constants.rb create mode 100644 client/Ruby/juno/lib/juno/Net/base_processor.rb create mode 100644 client/Ruby/juno/lib/juno/Net/client_handler.rb create mode 100644 client/Ruby/juno/lib/juno/Net/io_processor.rb create mode 100644 client/Ruby/juno/lib/juno/Net/ping_message.rb create mode 100644 client/Ruby/juno/lib/juno/Net/request_queue.rb create mode 100644 client/Ruby/juno/lib/juno/Net/worker_pool.rb create mode 100644 client/Ruby/juno/lib/juno/Utils/client_utils.rb create mode 100644 client/Ruby/juno/lib/juno/Utils/utils.rb create mode 100644 client/Ruby/juno/lib/juno/logger.rb create mode 100644 client/Ruby/juno/lib/juno/server_status.rb create mode 100644 client/Ruby/juno/lib/juno/version.rb create mode 100644 client/Ruby/juno/pkg/juno-0.1.0.gem create mode 100644 client/Ruby/juno/pkg/juno-1.0.0.gem create mode 100644 client/Ruby/juno/spec/Client/sync_client.rb create mode 100644 client/Ruby/juno/spec/IO/metadata_component.rb create mode 100644 client/Ruby/juno/spec/IO/payload_component_spec.rb create mode 100644 client/Ruby/juno/spec/IO/protocol_header_spec.rb create mode 100644 client/Ruby/juno/spec/spec_helper.rb create mode 100644 client/Ruby/sample.rb diff --git a/client/Ruby/README.md b/client/Ruby/README.md new file mode 100644 index 00000000..272a4dca --- /dev/null +++ b/client/Ruby/README.md @@ -0,0 +1,38 @@ +# juno-ruby-client +Ruby client for JunoDB
+ +[Documentation](https://github.com/VaibhavA19/junodb/tree/dev/client/Ruby/docs)
+[SampleScript](https://github.com/VaibhavA19/junodb/blob/dev/client/Ruby/sample.rb)
+[ConfigFile](https://github.com/VaibhavA19/junodb/blob/dev/client/Ruby/juno.yml) + +### Work Directory: [juno] +# Gem Tasks +### Build gem +``` +cd juno +rake build +``` + +### Install gem +``` +cd juno +rake install +``` + +### Execute tests +``` +cd juno +rake spec +``` + +### Execute rubocop linitng +``` +cd juno +rake lint +``` + +### Update documentation +``` +cd juno +rake yard:document +``` diff --git a/client/Ruby/docs/Juno.html b/client/Ruby/docs/Juno.html new file mode 100644 index 00000000..76931cd3 --- /dev/null +++ b/client/Ruby/docs/Juno.html @@ -0,0 +1,301 @@ + + + + + + + Module: Juno + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: Juno + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/constants.rb,
+ lib/juno/logger.rb,
lib/juno/version.rb,
lib/juno/Utils/utils.rb,
lib/juno/Config/config.rb,
lib/juno/server_status.rb,
lib/juno/IO/JunoMessage.rb,
lib/juno/Net/worker_pool.rb,
lib/juno/Net/io_processor.rb,
lib/juno/Net/ping_message.rb,
lib/juno/Config/properties.rb,
lib/juno/IO/ProtocolHeader.rb,
lib/juno/Net/request_queue.rb,
lib/juno/Client/cache_store.rb,
lib/juno/Client/sync_client.rb,
lib/juno/Net/base_processor.rb,
lib/juno/Net/client_handler.rb,
lib/juno/Utils/client_utils.rb,
lib/juno/Client/juno_request.rb,
lib/juno/Client/react_client.rb,
lib/juno/IO/OperationMessage.rb,
lib/juno/IO/PayloadComponent.rb,
lib/juno/Client/juno_response.rb,
lib/juno/Config/config_reader.rb,
lib/juno/IO/MetadataComponent.rb,
lib/juno/Client/operation_type.rb,
lib/juno/Client/record_context.rb,
lib/juno/Config/config_provider.rb,
lib/juno/Client/operation_status.rb,
lib/juno/Config/default_properties.rb,
lib/juno/IO/MetadataComponentTemplate.rb
+
+
+ +
+ +

Overview

+
+ +

Top module for juno client

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + Modules: Client, ClientUtils, IO, Net + + + + Classes: Config, ConfigProvider, ConfigReader, DefaultProperties, Logger, Properties, ServerStatus, Utils + + +

+ + +

+ Constant Summary + collapse +

+ +
+ +
VERSION = + +
+
'0.1.0'
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .configureObject + + + + + +

+ + + + +
+
+
+
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+
+
# File 'lib/juno/Config/config.rb', line 105
+
+def self.configure
+  if @juno_config.nil?
+    config_reader = Juno::ConfigReader.new
+    yield config_reader
+    if !config_reader.file_path.nil?
+      @juno_config = Config.new(ConfigProvider.new(config_reader.file_path, config_reader.source_format, nil),
+                                log_file: config_reader.log_file, ssl_cert_file: config_reader.ssl_cert_file,
+                                ssl_key_file: config_reader.ssl_key_file)
+      @LOGGER = Juno::Logger.instance
+    elsif !config_reader.url.nil? # URL should URI Object
+      # @juno_config = Config.new(ConfigProvider.new(@juno_config.file_path, @juno_config.source_format, nil))
+    else
+      raise 'No file or url provided'
+    end
+  else
+    Juno::Logger.instance.warn('Juno client cannot be reconfigured')
+  end
+end
+
+
+ +
+

+ + .juno_configObject + + + + + +

+ + + + +
+
+
+
+124
+125
+126
+127
+128
+
+
# File 'lib/juno/Config/config.rb', line 124
+
+def self.juno_config
+  raise 'Please configure the properties using Juno.configure' if @juno_config.nil?
+
+  @juno_config
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client.html b/client/Ruby/docs/Juno/Client.html new file mode 100644 index 00000000..5f989283 --- /dev/null +++ b/client/Ruby/docs/Juno/Client.html @@ -0,0 +1,128 @@ + + + + + + + Module: Juno::Client + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: Juno::Client + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/cache_store.rb,
+ lib/juno/Client/sync_client.rb,
lib/juno/Client/juno_request.rb,
lib/juno/Client/react_client.rb,
lib/juno/Client/juno_response.rb,
lib/juno/Client/operation_type.rb,
lib/juno/Client/record_context.rb,
lib/juno/Client/operation_status.rb
+
+
+ +
+ +

Overview

+
+ +

Module for code exposed to the developer

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: CacheStore, JunoRequest, JunoResponse, OperationStatus, OperationType, ReactClient, RecordContext, SyncClient + + +

+ + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/CacheStore.html b/client/Ruby/docs/Juno/Client/CacheStore.html new file mode 100644 index 00000000..c1e16bfb --- /dev/null +++ b/client/Ruby/docs/Juno/Client/CacheStore.html @@ -0,0 +1,557 @@ + + + + + + + Class: Juno::Client::CacheStore + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::CacheStore + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/cache_store.rb
+
+ +
+ +

Overview

+
+ +

Cache store for ruby on rails

+ + +
+
+
+ + +
+ + + + + + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeCacheStore + + + + + +

+
+ +

Returns a new instance of CacheStore.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Client/cache_store.rb', line 9
+
+def initialize
+  @react_client = Juno::Client::ReactClient.new
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .supports_cache_versioning?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+27
+28
+29
+
+
# File 'lib/juno/Client/cache_store.rb', line 27
+
+def self.supports_cache_versioning?
+  true
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #delete(key) ⇒ Object + + + + + +

+ + + + +
+
+
+
+31
+32
+33
+34
+35
+36
+
+
# File 'lib/juno/Client/cache_store.rb', line 31
+
+def delete(key)
+  future_obj = @react_client.destroy(key).wait
+  return false if future_obj.nil? || future_obj.rejected?
+
+  future_obj.value.status[:code] == Juno::Client::OperationStatus::SUCCESS[:code]
+end
+
+
+ +
+

+ + #exist?(key) ⇒ Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+38
+39
+40
+
+
# File 'lib/juno/Client/cache_store.rb', line 38
+
+def exist?(key)
+  !read(key).nil?
+end
+
+
+ +
+

+ + #read(key, options = {}) ⇒ Object + + + + + +

+ + + + +
+
+
+
+22
+23
+24
+25
+
+
# File 'lib/juno/Client/cache_store.rb', line 22
+
+def read(key, options = {})
+  future_obj = @react_client.get(key).wait
+  read_response(future_obj, options[:version])
+end
+
+
+ +
+

+ + #write(key, value, _options = {}) ⇒ Object + + + + + +

+ + + + +
+
+
+
+13
+14
+15
+16
+17
+18
+19
+20
+
+
# File 'lib/juno/Client/cache_store.rb', line 13
+
+def write(key, value, _options = {})
+  future_obj = @react_client.set(key, value).wait
+  return false if future_obj.nil?
+
+  raise future_obj.reason if future_obj.rejected?
+
+  future_obj.value.status[:txnOk]
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/JunoRequest.html b/client/Ruby/docs/Juno/Client/JunoRequest.html new file mode 100644 index 00000000..7358430f --- /dev/null +++ b/client/Ruby/docs/Juno/Client/JunoRequest.html @@ -0,0 +1,905 @@ + + + + + + + Class: Juno::Client::JunoRequest + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::JunoRequest + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/juno_request.rb
+
+ +
+ +

Overview

+
+ +

Request Object created from application request

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: Type + + +

+ + + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #creation_time ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute creation_time.

    +
    + +
  • + + +
  • + + + #key ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute key.

    +
    + +
  • + + +
  • + + + #time_to_live_s ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute time_to_live_s.

    +
    + +
  • + + +
  • + + + #type ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute type.

    +
    + +
  • + + +
  • + + + #value ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute value.

    +
    + +
  • + + +
  • + + + #version ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute version.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(key:, version:, type:, value: nil, time_to_live_s: nil, creation_time: nil) ⇒ JunoRequest + + + + + +

+
+ +

Constructor for JunoRequest

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    key for the document (required)

    +
    + +
  • + +
  • + + version + + + (Integer) + + + + — +
    +

    value for the document (required)

    +
    + +
  • + +
  • + + version + + + (Juno::JunoRequest::Type) + + + + — +
    +

    value for the document (required)

    +
    + +
  • + +
  • + + type + + + (String) + + + + — +
    +

    value for the document (optional, default: 1 byte string: 0.chr)

    +
    + +
  • + +
  • + + type + + + (Integer) + + + + — +
    +

    Time to live for the document (optional, default: read from config file)

    +
    + +
  • + +
  • + + type + + + (Integer) + + + + — +
    +

    Time to live for the document (optional, default: initialized to Time.now in JunoMessage)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
+
# File 'lib/juno/Client/juno_request.rb', line 25
+
+def initialize(key:, version:, type:, value: nil, time_to_live_s: nil, creation_time: nil)
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @key = key
+  @version = version.to_i
+  @type = type
+  @value = value.to_s.empty? ? 0.chr : value.to_s
+  @time_to_live_s = time_to_live_s.to_i
+  @creation_time = creation_time
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #creation_timeObject + + + + + +

+
+ +

Returns the value of attribute creation_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+16
+17
+18
+
+
# File 'lib/juno/Client/juno_request.rb', line 16
+
+def creation_time
+  @creation_time
+end
+
+
+ + + +
+

+ + #keyObject + + + + + +

+
+ +

Returns the value of attribute key.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+16
+17
+18
+
+
# File 'lib/juno/Client/juno_request.rb', line 16
+
+def key
+  @key
+end
+
+
+ + + +
+

+ + #time_to_live_sObject + + + + + +

+
+ +

Returns the value of attribute time_to_live_s.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+16
+17
+18
+
+
# File 'lib/juno/Client/juno_request.rb', line 16
+
+def time_to_live_s
+  @time_to_live_s
+end
+
+
+ + + +
+

+ + #typeObject + + + + + +

+
+ +

Returns the value of attribute type.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+16
+17
+18
+
+
# File 'lib/juno/Client/juno_request.rb', line 16
+
+def type
+  @type
+end
+
+
+ + + +
+

+ + #valueObject + + + + + +

+
+ +

Returns the value of attribute value.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+16
+17
+18
+
+
# File 'lib/juno/Client/juno_request.rb', line 16
+
+def value
+  @value
+end
+
+
+ + + +
+

+ + #versionObject + + + + + +

+
+ +

Returns the value of attribute version.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+16
+17
+18
+
+
# File 'lib/juno/Client/juno_request.rb', line 16
+
+def version
+  @version
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #==(other) ⇒ Object + + + + + +

+ + + + +
+
+
+
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+
+
# File 'lib/juno/Client/juno_request.rb', line 36
+
+def ==(other)
+  return false unless other.is_a?(JunoRequest)
+
+  other.key == @key &&
+    other.version == @version &&
+    other.type == @type &&
+    other.value == @value &&
+    other.time_to_live_s == @time_to_live_s &&
+    other.creation_time == @creation_time
+end
+
+
+ +
+

+ + #to_sObject + + + + + +

+
+ +

Function to serialize JunoRequest

+ + +
+
+
+ + +
+ + + + +
+
+
+
+48
+49
+50
+
+
# File 'lib/juno/Client/juno_request.rb', line 48
+
+def to_s
+  "JunoRequest key:#{@key} version:#{@version} type:#{@type}, value: #{@value}, time_to_live: #{@time_to_live}, creation_time: #{@creation_time}"
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/JunoRequest/Type.html b/client/Ruby/docs/Juno/Client/JunoRequest/Type.html new file mode 100644 index 00000000..619822d7 --- /dev/null +++ b/client/Ruby/docs/Juno/Client/JunoRequest/Type.html @@ -0,0 +1,157 @@ + + + + + + + Class: Juno::Client::JunoRequest::Type + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::JunoRequest::Type + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/juno_request.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
CREATE = + +
+
1
+ +
GET = + +
+
2
+ +
UPDATE = + +
+
3
+ +
SET = + +
+
4
+ +
DESTROY = + +
+
5
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/JunoResponse.html b/client/Ruby/docs/Juno/Client/JunoResponse.html new file mode 100644 index 00000000..3aab10d2 --- /dev/null +++ b/client/Ruby/docs/Juno/Client/JunoResponse.html @@ -0,0 +1,616 @@ + + + + + + + Class: Juno::Client::JunoResponse + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::JunoResponse + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/juno_response.rb
+
+ +
+ +

Overview

+
+ +

Response sent to the application

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #key ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute key.

    +
    + +
  • + + +
  • + + + #record_context ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute record_context.

    +
    + +
  • + + +
  • + + + #status ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute status.

    +
    + +
  • + + +
  • + + + #value ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute value.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(key:, value:, version:, time_to_live_s:, creation_time:, operation_status:) ⇒ JunoResponse + + + + + +

+
+ +

Returns a new instance of JunoResponse.

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + status + + + (Juno::Client::OperationStatus) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + value + + + (String) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + version + + + (Integer) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + creation_time + + + (Integer) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + time_to_live_s + + + (Integer) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
+
# File 'lib/juno/Client/juno_response.rb', line 15
+
+def initialize(key:, value:, version:, time_to_live_s:, creation_time:, operation_status:)
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @key = key
+  @status = operation_status
+  @value = value
+  @record_context = Juno::Client::RecordContext.new(key: key, version: version, creation_time: creation_time,
+                                                    time_to_live_s: time_to_live_s)
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #keyObject + + + + + +

+
+ +

Returns the value of attribute key.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/Client/juno_response.rb', line 7
+
+def key
+  @key
+end
+
+
+ + + +
+

+ + #record_contextObject + + + + + +

+
+ +

Returns the value of attribute record_context.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/Client/juno_response.rb', line 7
+
+def record_context
+  @record_context
+end
+
+
+ + + +
+

+ + #statusObject + + + + + +

+
+ +

Returns the value of attribute status.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/Client/juno_response.rb', line 7
+
+def status
+  @status
+end
+
+
+ + + +
+

+ + #valueObject + + + + + +

+
+ +

Returns the value of attribute value.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/Client/juno_response.rb', line 7
+
+def value
+  @value
+end
+
+
+ +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/OperationStatus.html b/client/Ruby/docs/Juno/Client/OperationStatus.html new file mode 100644 index 00000000..37fb85f3 --- /dev/null +++ b/client/Ruby/docs/Juno/Client/OperationStatus.html @@ -0,0 +1,344 @@ + + + + + + + Class: Juno::Client::OperationStatus + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::OperationStatus + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/operation_status.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
SUCCESS = + +
+
{ code: 0, error_msg: 'No error', txnOk: true }.freeze
+ +
NO_KEY = + +
+
{ code: 1, error_msg: 'Key not found', txnOk: true }.freeze
+ +
BAD_PARAM = + +
+
{ code: 2, error_msg: 'Bad parameter', txnOk: false }.freeze
+ +
UNIQUE_KEY_VIOLATION = + +
+
{ code: 3, error_msg: 'Duplicate key', txnOk: true }.freeze
+ +
RECORD_LOCKED = + +
+
{ code: 4, error_msg: 'Record Locked', txnOk: true }.freeze
+ +
ILLEGAL_ARGUMENT = + +
+
{ code: 5, error_msg: 'Illegal argument', txnOk: false }.freeze
+ +
CONDITION_VIOLATION = + +
+
{ code: 6, error_msg: 'Condition in the request violated', txnOk: true }.freeze
+ +
INTERNAL_ERROR = + +
+
{ code: 7, error_msg: 'Internal error', txnOk: false }.freeze
+ +
QUEUE_FULL = + +
+
{ code: 8, error_msg: 'Outbound client queue full', txnOk: false }.freeze
+ +
NO_STORAGE = + +
+
{ code: 9, error_msg: 'No storage server running', txnOk: false }.freeze
+ +
TTL_EXTEND_FAILURE = + +
+
{ code: 10, error_msg: 'Failure to extend TTL on get', txnOk: true }.freeze
+ +
RESPONSE_TIMEOUT = + +
+
{ code: 11, error_msg: 'Response Timed out', txnOk: false }.freeze
+ +
CONNECTION_ERROR = + +
+
{ code: 12, error_msg: 'Connection Error', txnOk: false }.freeze
+ +
UNKNOWN_ERROR = + +
+
{ code: 13, error_msg: 'Unknown Error', txnOk: false }.freeze
+ +
@@status_code_map = + +
+
nil
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .get(status_code) ⇒ Object + + + + + +

+ + + + +
+
+
+
+33
+34
+35
+36
+37
+38
+
+
# File 'lib/juno/Client/operation_status.rb', line 33
+
+def self.get(status_code)
+  initialize_map if @@status_code_map.nil?
+  return @@status_code_map[status_code] if @@status_code_map.key?(status_code)
+
+  INTERNAL_ERROR
+end
+
+
+ +
+

+ + .initialize_mapObject + + + + + +

+ + + + +
+
+
+
+24
+25
+26
+27
+28
+29
+30
+31
+
+
# File 'lib/juno/Client/operation_status.rb', line 24
+
+def self.initialize_map
+  @@status_code_map = {}
+
+  constants.each do |const|
+    const_obj = const_get(const)
+    @@status_code_map[const_obj[:code]] = const_obj
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/OperationType.html b/client/Ruby/docs/Juno/Client/OperationType.html new file mode 100644 index 00000000..a207babb --- /dev/null +++ b/client/Ruby/docs/Juno/Client/OperationType.html @@ -0,0 +1,199 @@ + + + + + + + Class: Juno::Client::OperationType + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::OperationType + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/operation_type.rb
+
+ +
+ +

Overview

+
+ +

Constant for JunoRequest operation type

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
Nop = + +
+
{
+  code: 0,
+  str: 'NOP'
+}.freeze
+ +
Create = + +
+
{
+  code: 1,
+  str: 'CREATE'
+}.freeze
+ +
Get = + +
+
{
+  code: 2,
+  str: 'GET'
+}.freeze
+ +
Update = + +
+
{
+  code: 3,
+  str: 'UPDATE'
+}.freeze
+ +
Set = + +
+
{
+  code: 4,
+  str: 'SET'
+}.freeze
+ +
CompareAndSet = + +
+
{
+  code: 5,
+  str: 'COMPAREANDSET'
+}.freeze
+ +
Destroy = + +
+
{
+  code: 6,
+  str: 'DESTROY'
+}.freeze
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/ReactClient.html b/client/Ruby/docs/Juno/Client/ReactClient.html new file mode 100644 index 00000000..45993963 --- /dev/null +++ b/client/Ruby/docs/Juno/Client/ReactClient.html @@ -0,0 +1,1347 @@ + + + + + + + Class: Juno::Client::ReactClient + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::ReactClient + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/react_client.rb
+
+ +
+ +

Overview

+
+ +

Async Client for Juno

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
MAX_OPERATION_RETRY = +
+
+ +

Constants for operation retry

+ + +
+
+
+ + +
+
+
1
+ +
MAX_RETRY_INTERVAL = +
+
+ +

msec

+ + +
+
+
+ + +
+
+
15
+ +
MIN_RETRY_INTERVAL = +
+
+ +

msec

+ + +
+
+
+ + +
+
+
10
+ +
@@OPAQUE_GENERATOR = +
+
+ +

@ Global Opaque generator. Uses ConcurrentFixnum for thread safety

+ + +
+
+
+ + +
+
+
Concurrent::AtomicFixnum.new(-1)
+ +
@@fail_count = +
+
+ +

@ Variable to count failed requests

+ + +
+
+
+ + +
+
+
Concurrent::AtomicFixnum.new(0)
+ +
@@received = +
+
+ +

@ Variable to count responses received

+ + +
+
+
+ + +
+
+
Concurrent::AtomicFixnum.new(0)
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeReactClient + + + + + +

+
+ +

Returns a new instance of ReactClient.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+33
+34
+35
+36
+37
+38
+39
+
+
# File 'lib/juno/Client/react_client.rb', line 33
+
+def initialize
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @request_queue = Juno::Net::RequestQueue.instance
+  @executor = Concurrent::ThreadPoolExecutor.new(min_threads: 4, max_threads: 16, max_queue: 10_000)
+  @count = 0
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .failed_countObject + + + + + +

+ + + + +
+
+
+
+25
+26
+27
+
+
# File 'lib/juno/Client/react_client.rb', line 25
+
+def self.failed_count
+  @@fail_count.value
+end
+
+
+ +
+

+ + .op_countObject + + + + + +

+ + + + +
+
+
+
+21
+22
+23
+
+
# File 'lib/juno/Client/react_client.rb', line 21
+
+def self.op_count
+  @@OPAQUE_GENERATOR.value
+end
+
+
+ +
+

+ + .recvObject + + + + + +

+ + + + +
+
+
+
+29
+30
+31
+
+
# File 'lib/juno/Client/react_client.rb', line 29
+
+def self.recv
+  @@received.value
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #compare_and_set(record_context, value, ttl) ⇒ Object + + + + + +

+
+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+
+
# File 'lib/juno/Client/react_client.rb', line 86
+
+def compare_and_set(record_context, value, ttl)
+  unless record_context.is_a?(Juno::Client::RecordContext)
+    raise ArgumentError, 'recird context should be of type Juno::Client::RecordContext'
+  end
+  raise ArgumentError, 'Version cannot be less than 1' if record_context.version.to_i < 1
+
+  juno_request = Juno::Client::JunoRequest.new(key: record_context.key.to_s,
+                                               value: value,
+                                               version: record_context.version.to_i,
+                                               type: Juno::Client::JunoRequest::Type::UPDATE,
+                                               time_to_live_s: ttl,
+                                               creation_time: Time.now.to_i)
+  process_single(juno_request)
+end
+
+
+ +
+

+ + #create(key, value, ttl: nil) ⇒ Boolean + + + + + +

+
+ +

Function to create new key value pair

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    key for the document (required)

    +
    + +
  • + +
  • + + value + + + (String) + + + + — +
    +

    value for the document (required)

    +
    + +
  • + +
  • + + ttl + + + (Integer) + + + (defaults to: nil) + + + — +
    +

    Time to live for the document (optional, default: read from config file)

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    True if operation submited successfully, else false

    +
    + +
  • + +
+ +

See Also:

+ + +
+ + + + +
+
+
+
+56
+57
+58
+59
+60
+61
+62
+63
+64
+
+
# File 'lib/juno/Client/react_client.rb', line 56
+
+def create(key, value, ttl: nil)
+  juno_request = Juno::Client::JunoRequest.new(key: key,
+                                               value: value,
+                                               version: 0,
+                                               type: Juno::Client::JunoRequest::Type::CREATE,
+                                               time_to_live_s: ttl,
+                                               creation_time: Time.now.to_i)
+  process_single(juno_request)
+end
+
+
+ +
+

+ + #destroy(key, ttl: nil) ⇒ Object + + + + Also known as: + delete + + + + +

+ + + + +
+
+
+
+131
+132
+133
+134
+135
+136
+137
+138
+139
+
+
# File 'lib/juno/Client/react_client.rb', line 131
+
+def destroy(key, ttl: nil)
+  juno_request = Juno::Client::JunoRequest.new(key: key,
+                                               value: '',
+                                               version: 0,
+                                               type: Juno::Client::JunoRequest::Type::DESTROY,
+                                               time_to_live_s: ttl,
+                                               creation_time: Time.now.to_i)
+  process_single(juno_request)
+end
+
+
+ +
+

+ + #exist?(key) ⇒ Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+144
+
+
# File 'lib/juno/Client/react_client.rb', line 144
+
+def exist?(key); end
+
+
+ +
+

+ + #get(key, ttl: nil) ⇒ Juno::Client::JunoResponse + + + + Also known as: + read + + + + +

+
+ +

Function to get existing key value pair

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    key for the document (required)

    +
    + +
  • + +
  • + + value + + + (String) + + + + — +
    +

    value for the document (required)

    +
    + +
  • + +
  • + + ttl + + + (Integer) + + + (defaults to: nil) + + + — +
    +

    Time to live for the document (optional, default: read from config file)

    +
    + +
  • + +
+ +

Returns:

+ + +

See Also:

+ + +
+ + + + +
+
+
+
+108
+109
+110
+111
+112
+113
+114
+115
+116
+
+
# File 'lib/juno/Client/react_client.rb', line 108
+
+def get(key, ttl: nil)
+  juno_request = Juno::Client::JunoRequest.new(key: key,
+                                               value: '',
+                                               version: 0,
+                                               type: Juno::Client::JunoRequest::Type::GET,
+                                               time_to_live_s: ttl,
+                                               creation_time: Time.now.to_i)
+  process_single(juno_request)
+end
+
+
+ +
+

+ + #set(key, value, ttl: nil) ⇒ Boolean + + + + Also known as: + write + + + + +

+
+ +

Function to set value for given key

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    key for the document (required)

    +
    + +
  • + +
  • + + value + + + (String) + + + + — +
    +

    value for the document (required)

    +
    + +
  • + +
  • + + ttl + + + (Integer) + + + (defaults to: nil) + + + — +
    +

    Time to live for the document (optional, default: read from config file)

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    +

    True if operation submited successfully, else false

    +
    + +
  • + +
+ +

See Also:

+ + +
+ + + + +
+
+
+
+73
+74
+75
+76
+77
+78
+79
+80
+81
+
+
# File 'lib/juno/Client/react_client.rb', line 73
+
+def set(key, value, ttl: nil)
+  juno_request = Juno::Client::JunoRequest.new(key: key,
+                                               value: value,
+                                               version: 0,
+                                               type: Juno::Client::JunoRequest::Type::SET,
+                                               time_to_live_s: ttl,
+                                               creation_time: Time.now.to_i)
+  process_single(juno_request)
+end
+
+
+ +
+

+ + #stopObject + + + + + +

+ + + + +
+
+
+
+41
+42
+43
+44
+45
+46
+47
+
+
# File 'lib/juno/Client/react_client.rb', line 41
+
+def stop
+  @LOGGER.info(@PROG_NAME) { 'stop initiated by client' }
+  @request_queue.stop
+  @executor.shutdown
+  @executor.wait_for_termination
+  @executor.kill
+end
+
+
+ +
+

+ + #update(key, value, ttl: nil) ⇒ Object + + + + + +

+ + + + +
+
+
+
+121
+122
+123
+124
+125
+126
+127
+128
+129
+
+
# File 'lib/juno/Client/react_client.rb', line 121
+
+def update(key, value, ttl: nil)
+  juno_request = Juno::Client::JunoRequest.new(key: key,
+                                               value: value,
+                                               version: 0,
+                                               type: Juno::Client::JunoRequest::Type::UPDATE,
+                                               time_to_live_s: ttl,
+                                               creation_time: Time.now.to_i)
+  process_single(juno_request)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/RecordContext.html b/client/Ruby/docs/Juno/Client/RecordContext.html new file mode 100644 index 00000000..db0d8e5b --- /dev/null +++ b/client/Ruby/docs/Juno/Client/RecordContext.html @@ -0,0 +1,579 @@ + + + + + + + Class: Juno::Client::RecordContext + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::RecordContext + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/record_context.rb
+
+ +
+ + + + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #creation_time ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute creation_time.

    +
    + +
  • + + +
  • + + + #key ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute key.

    +
    + +
  • + + +
  • + + + #time_to_live_s ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute time_to_live_s.

    +
    + +
  • + + +
  • + + + #version ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute version.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(key:, version:, creation_time:, time_to_live_s:) ⇒ RecordContext + + + + + +

+
+ +

Returns a new instance of RecordContext.

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + version + + + (Integer) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + creation_time + + + (Integer) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + time_to_live_s + + + (Integer) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+12
+13
+14
+15
+16
+17
+18
+19
+
+
# File 'lib/juno/Client/record_context.rb', line 12
+
+def initialize(key:, version:, creation_time:, time_to_live_s:)
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @key = key
+  @version = version
+  @creation_time = creation_time
+  @time_to_live_s = time_to_live_s
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #creation_timeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute creation_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+6
+7
+8
+
+
# File 'lib/juno/Client/record_context.rb', line 6
+
+def creation_time
+  @creation_time
+end
+
+
+ + + +
+

+ + #keyObject (readonly) + + + + + +

+
+ +

Returns the value of attribute key.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+6
+7
+8
+
+
# File 'lib/juno/Client/record_context.rb', line 6
+
+def key
+  @key
+end
+
+
+ + + +
+

+ + #time_to_live_sObject (readonly) + + + + + +

+
+ +

Returns the value of attribute time_to_live_s.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+6
+7
+8
+
+
# File 'lib/juno/Client/record_context.rb', line 6
+
+def time_to_live_s
+  @time_to_live_s
+end
+
+
+ + + +
+

+ + #versionObject (readonly) + + + + + +

+
+ +

Returns the value of attribute version.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+6
+7
+8
+
+
# File 'lib/juno/Client/record_context.rb', line 6
+
+def version
+  @version
+end
+
+
+ +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Client/SyncClient.html b/client/Ruby/docs/Juno/Client/SyncClient.html new file mode 100644 index 00000000..1e2e052f --- /dev/null +++ b/client/Ruby/docs/Juno/Client/SyncClient.html @@ -0,0 +1,844 @@ + + + + + + + Class: Juno::Client::SyncClient + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Client::SyncClient + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Client/sync_client.rb
+
+ +
+ +

Overview

+
+ +

Async Client for Juno

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeSyncClient + + + + + +

+
+ +

Returns a new instance of SyncClient.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Client/sync_client.rb', line 9
+
+def initialize
+  @react_client = Juno::Client::ReactClient.new
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #compare_and_set(record_context, value, ttl: nil) ⇒ Object + + + + + +

+ + + + +
+
+
+
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+
+
# File 'lib/juno/Client/sync_client.rb', line 64
+
+def compare_and_set(record_context, value, ttl: nil)
+  juno_resp = nil
+  begin
+    juno_resp = @react_client.compare_and_set(record_context, value, ttl)
+  rescue ArgumentError => e
+    raise e.message
+  end
+
+  return nil if juno_resp.nil?
+
+  raise juno_resp.reason if juno_resp.rejected?
+
+  juno_resp.value # JunoResponse
+end
+
+
+ +
+

+ + #create(key, value, ttl: nil) ⇒ Juno::Client::JunoResponse + + + + + +

+
+ +

Function to create new key value pair

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    key for the document (required)

    +
    + +
  • + +
  • + + value + + + (String) + + + + — +
    +

    value for the document (required)

    +
    + +
  • + +
  • + + ttl + + + (Integer) + + + (defaults to: nil) + + + — +
    +

    Time to live for the document (optional, default: read from config file)

    +
    + +
  • + +
+ +

Returns:

+ + +
+ + + + +
+
+
+
+18
+19
+20
+21
+22
+23
+24
+25
+
+
# File 'lib/juno/Client/sync_client.rb', line 18
+
+def create(key, value, ttl: nil)
+  juno_resp = @react_client.create(key, value, ttl: ttl).wait
+  return nil if juno_resp.nil?
+
+  raise juno_resp.reason if juno_resp.rejected?
+
+  juno_resp.value # JunoResponse
+end
+
+
+ +
+

+ + #destroy(key, ttl: nil) ⇒ Object + + + + + +

+ + + + +
+
+
+
+79
+80
+81
+82
+83
+84
+85
+86
+
+
# File 'lib/juno/Client/sync_client.rb', line 79
+
+def destroy(key, ttl: nil)
+  juno_resp = @react_client.destroy(key.to_s, ttl: ttl).wait
+  return nil if juno_resp.nil?
+
+  raise juno_resp.reason if juno_resp.rejected?
+
+  juno_resp.value # JunoResponse
+end
+
+
+ +
+

+ + #get(key, ttl: nil) ⇒ Juno::Client::JunoResponse + + + + + +

+
+ +

Function to get existing key value pair

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    key for the document (required)

    +
    + +
  • + +
  • + + value + + + (String) + + + + — +
    +

    value for the document (required)

    +
    + +
  • + +
  • + + ttl + + + (Integer) + + + (defaults to: nil) + + + — +
    +

    Time to live for the document (optional, default: read from config file)

    +
    + +
  • + +
+ +

Returns:

+ + +
+ + + + +
+
+
+
+46
+47
+48
+49
+50
+51
+52
+53
+
+
# File 'lib/juno/Client/sync_client.rb', line 46
+
+def get(key, ttl: nil)
+  juno_resp = @react_client.get(key.to_s, ttl: ttl).wait
+  return nil if juno_resp.nil?
+
+  raise juno_resp.reason if juno_resp.rejected?
+
+  juno_resp.value # JunoResponse
+end
+
+
+ +
+

+ + #set(key, value, ttl: nil) ⇒ Juno::Client::JunoResponse + + + + + +

+
+ +

Function to set value for given key

+ + +
+
+
+

Parameters:

+
    + +
  • + + key + + + (String) + + + + — +
    +

    key for the document (required)

    +
    + +
  • + +
  • + + value + + + (String) + + + + — +
    +

    value for the document (required)

    +
    + +
  • + +
  • + + ttl + + + (Integer) + + + (defaults to: nil) + + + — +
    +

    Time to live for the document (optional, default: read from config file)

    +
    + +
  • + +
+ +

Returns:

+ + +
+ + + + +
+
+
+
+32
+33
+34
+35
+36
+37
+38
+39
+
+
# File 'lib/juno/Client/sync_client.rb', line 32
+
+def set(key, value, ttl: nil)
+  juno_resp = @react_client.set(key.to_s, value.to_s, ttl: ttl).wait
+  return nil if juno_resp.nil?
+
+  raise juno_resp.reason if juno_resp.rejected?
+
+  juno_resp.value # JunoResponse
+end
+
+
+ +
+

+ + #update(key, value, ttl: nil) ⇒ Object + + + + + +

+ + + + +
+
+
+
+55
+56
+57
+58
+59
+60
+61
+62
+
+
# File 'lib/juno/Client/sync_client.rb', line 55
+
+def update(key, value, ttl: nil)
+  juno_resp = @react_client.update(key.to_s, value.to_s, ttl: ttl).wait
+  return nil if juno_resp.nil?
+
+  raise juno_resp.reason if juno_resp.rejected?
+
+  juno_resp.value # JunoResponse
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/ClientUtils.html b/client/Ruby/docs/Juno/ClientUtils.html new file mode 100644 index 00000000..da69bfb2 --- /dev/null +++ b/client/Ruby/docs/Juno/ClientUtils.html @@ -0,0 +1,676 @@ + + + + + + + Module: Juno::ClientUtils + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: Juno::ClientUtils + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Utils/client_utils.rb
+
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .compressed_value(value) ⇒ Object + + + + + +

+ + + + +
+
+
+
+42
+43
+44
+45
+46
+47
+48
+
+
# File 'lib/juno/Utils/client_utils.rb', line 42
+
+def self.compressed_value(value)
+  compressed_value = Snappy.deflate(value)
+  compression_achieved = 100 - (compressed_value.length * 100) / value.length
+  [compressed_value, compression_achieved]
+rescue Exception
+  [value, false]
+end
+
+
+ +
+

+ + .create_operation_message(juno_message, opaque) ⇒ Object + + + + + +

+ + + + +
+
+
+
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+
+
# File 'lib/juno/Utils/client_utils.rb', line 5
+
+def self.create_operation_message(juno_message, opaque)
+  protocol_header = Juno::IO::ProtocolHeader.new
+  protocol_header.version = juno_message.version
+  protocol_header.opcode = juno_message.operation_type
+  protocol_header.opaque = opaque
+
+   = Juno::IO::MetadataComponent.new
+  if juno_message.time_to_live_s.to_i.positive?
+    .set_time_to_live(juno_message.time_to_live_s.to_i)
+  end
+  .set_version(juno_message.version)
+
+  if [Juno::IO::JunoMessage::OperationType::CREATE,
+      Juno::IO::JunoMessage::OperationType::SET].include?(juno_message.operation_type)
+    .set_creation_time(Time.now.to_i)
+  end
+
+  .set_expiration_time((Time.now + juno_message.time_to_live_s).to_i) # what ?
+  .set_request_uuid(juno_message.request_uuid)
+  .set_source_info(app_name: juno_message.app_name, ip: juno_message.ip, port: juno_message.port)
+  .set_originator_request_id # what
+
+  payload_component = Juno::IO::PayloadComponent.new
+  payload_component.namespace = juno_message.namespace
+  payload_component.payload_key = juno_message.key
+  payload_component.set_value(juno_message.value, juno_message.compression_type)
+
+  operation_message = Juno::IO::OperationMessage.new
+  operation_message.protocol_header = protocol_header
+  operation_message. = 
+  operation_message.payload_component = payload_component
+
+  juno_message.message_size = operation_message.size
+
+  operation_message
+end
+
+
+ +
+

+ + .decode_operation_message(operation_message) ⇒ Object + + + + + +

+ + + + +
+
+
+
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+
+
# File 'lib/juno/Utils/client_utils.rb', line 111
+
+def self.decode_operation_message(operation_message)
+  return nil unless operation_message.is_a?(Juno::IO::OperationMessage)
+
+  juno_message = Juno::IO::JunoMessage.new
+  opcode = operation_message.protocol_header.opcode.to_i
+  juno_message.operation_type = Juno::IO::JunoMessage::OperationType.get(opcode)
+
+  server_status = operation_message.protocol_header.status.to_i
+  juno_message.server_status = Juno::ServerStatus.get(server_status)
+
+  juno_message.message_size = operation_message.protocol_header.message_size.to_i
+
+  unless operation_message..nil?
+     = operation_message.
+    juno_message.time_to_live_s = .time_to_live.to_i
+    juno_message.ip = .ip
+    juno_message.port = .port
+    juno_message.version = .version.to_i
+    juno_message.creation_time = .creation_time.to_i
+    juno_message.expiration_time = .expiration_time.to_i
+    juno_message.request_uuid = .request_uuid
+    juno_message.app_name = .app_name
+    juno_message.last_modification = .last_modification.to_i
+    juno_message.originator_request_id = .originator_request_id.to_s
+    juno_message.correlation_id = .correlation_id
+    juno_message.request_handling_time = .request_handling_time.to_i
+    # juno_message.request_start_time = metadata_component.
+    # expiry
+  end
+
+  unless operation_message.payload_component.nil?
+    juno_message.namespace = operation_message.payload_component.namespace.to_s
+    juno_message.key = operation_message.payload_component.payload_key.to_s
+    juno_message.is_compressed = operation_message.payload_component.compressed?
+    juno_message.compression_type = operation_message.payload_component.compression_type.to_i
+    juno_message.value = if operation_message.payload_component.payload_length.to_i.zero?
+                           juno_message.compression_achieved = 0
+                           nil
+                         elsif juno_message.is_compressed
+                           compressed_value = operation_message.payload_component.value.to_s
+                           decompressed_value = decompress_value(compressed_value)
+                           juno_message.compression_achieved = 100 - (compressed_value.length / decompressed_value.length.to_f) * 100.0
+                           decompressed_value
+                         else
+                           juno_message.compression_achieved = 0
+                           operation_message.payload_component.value.to_s
+                         end
+  end
+
+  juno_message
+end
+
+
+ +
+

+ + .decompress_value(value) ⇒ Object + + + + + +

+ + + + +
+
+
+
+50
+51
+52
+53
+54
+55
+
+
# File 'lib/juno/Utils/client_utils.rb', line 50
+
+def self.decompress_value(value)
+  Snappy.inflate(value)
+rescue Exception
+  # Log failure
+  value
+end
+
+
+ +
+

+ + .validate!(juno_request) ⇒ Object + + + + + +

+
+ + +
+
+
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+
+
# File 'lib/juno/Utils/client_utils.rb', line 57
+
+def self.validate!(juno_request)
+  return false unless juno_request.is_a?(Juno::Client::JunoRequest)
+
+  raise ArgumentError, 'Juno request key cannot be empty' if juno_request.key.to_s.nil?
+
+  juno_request.time_to_live_s = Juno.juno_config.default_lifetime unless juno_request.time_to_live_s.to_i.positive?
+
+  if juno_request.time_to_live_s > Juno.juno_config.max_lifetime || juno_request.time_to_live_s.negative?
+    raise ArgumentError,
+          "Record time_to_live_s (#{juno_request.time_to_live_s}s) cannot be greater than  #{Juno.juno_config.max_lifetime}s or negative ."
+  end
+
+  if juno_request.key.to_s.size > Juno.juno_config.max_key_size
+    raise ArgumentError,
+          "Key size cannot be greater than #{Juno.juno_config.max_key_size}"
+  end
+
+  if juno_request.key.to_s.size > Juno.juno_config.max_key_size
+    raise ArgumentError,
+          "Key size cannot be greater than #{Juno.juno_config.max_key_size}"
+  end
+
+  juno_message = Juno::IO::JunoMessage.new
+  juno_message.key = juno_request.key
+  juno_message.version = juno_request.version
+  juno_message.operation_type = juno_request.type
+  juno_message.time_to_live_s = juno_request.time_to_live_s
+  juno_message.creation_time = juno_request.creation_time
+  juno_message.namespace = Juno.juno_config.record_namespace
+  juno_message.app_name = Juno.juno_config.app_name
+  juno_message.request_uuid = UUIDTools::UUID.random_create.to_s
+  juno_message.ip = IPAddr.new(Juno::Utils.local_ips[0])
+  juno_message.port = 0
+
+  unless [Juno::Client::JunoRequest::Type::GET,
+          Juno::Client::JunoRequest::Type::DESTROY].include?(juno_request.type)
+    payload_value = juno_request.value
+    is_compressed = false
+    compression_achieved = 0
+    if Juno.juno_config.use_payload_compression && value.length > 1024
+      payload_value, compression_achieved = compressed_value(value)
+      is_compressed = true if compression_achieved.positive?
+    end
+    juno_message.is_compressed = is_compressed
+    juno_message.value = payload_value
+    juno_message.compression_achieved = compression_achieved
+    juno_message.compression_type = is_compressed ? Juno::IO::CompressionType::Snappy : Juno::IO::CompressionType::None
+  end
+
+  juno_message
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Config.html b/client/Ruby/docs/Juno/Config.html new file mode 100644 index 00000000..45920a60 --- /dev/null +++ b/client/Ruby/docs/Juno/Config.html @@ -0,0 +1,2381 @@ + + + + + + + Class: Juno::Config + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Config + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Config/config.rb
+
+ +
+ +

Overview

+
+ +

Juno.configure do |config|

+ +
config.record_namespace = "kk"
+config.host = '10.138.38.83'
+config.port = 5080
+config.app_name = "TestApp"
+config.file_path = ""
+config.url = ""
+
+ +

end

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: Source + + +

+ + +

+ Constant Summary + collapse +

+ +
+ +
REQUIRED_PROPERTIES = + +
+
%i[host port app_name record_namespace log_file].freeze
+ +
+ + + + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #app_name ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute app_name.

    +
    + +
  • + + +
  • + + + #bypass_ltm ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute bypass_ltm.

    +
    + +
  • + + +
  • + + + #config_prefix ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute config_prefix.

    +
    + +
  • + + +
  • + + + #connection_lifetime ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute connection_lifetime.

    +
    + +
  • + + +
  • + + + #connection_pool_size ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute connection_pool_size.

    +
    + +
  • + + +
  • + + + #connection_timeout ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute connection_timeout.

    +
    + +
  • + + +
  • + + + #default_lifetime ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute default_lifetime.

    +
    + +
  • + + +
  • + + + #host ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute host.

    +
    + +
  • + + +
  • + + + #log_file ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute log_file.

    +
    + +
  • + + +
  • + + + #max_connection_lifetime ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute max_connection_lifetime.

    +
    + +
  • + + +
  • + + + #max_connection_pool_size ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute max_connection_pool_size.

    +
    + +
  • + + +
  • + + + #max_connection_timeout ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute max_connection_timeout.

    +
    + +
  • + + +
  • + + + #max_key_size ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute max_key_size.

    +
    + +
  • + + +
  • + + + #max_lifetime ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute max_lifetime.

    +
    + +
  • + + +
  • + + + #max_namespace_length ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute max_namespace_length.

    +
    + +
  • + + +
  • + + + #max_response_timeout ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute max_response_timeout.

    +
    + +
  • + + +
  • + + + #max_value_size ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute max_value_size.

    +
    + +
  • + + +
  • + + + #operation_retry ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute operation_retry.

    +
    + +
  • + + +
  • + + + #port ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute port.

    +
    + +
  • + + +
  • + + + #reconnect_on_fail ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute reconnect_on_fail.

    +
    + +
  • + + +
  • + + + #record_namespace ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute record_namespace.

    +
    + +
  • + + +
  • + + + #response_timeout ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute response_timeout.

    +
    + +
  • + + +
  • + + + #ssl_cert_file ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute ssl_cert_file.

    +
    + +
  • + + +
  • + + + #ssl_key_file ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute ssl_key_file.

    +
    + +
  • + + +
  • + + + #use_payload_compression ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute use_payload_compression.

    +
    + +
  • + + +
  • + + + #use_ssl ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute use_ssl.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(config_provider, log_file:, ssl_cert_file: nil, ssl_key_file: nil) ⇒ Config + + + + + +

+
+ +

Returns a new instance of Config.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
+
# File 'lib/juno/Config/config.rb', line 22
+
+def initialize(config_provider, log_file:, ssl_cert_file: nil, ssl_key_file: nil)
+  @PROG_NAME = self.class.name
+  @log_file = log_file
+  @ssl_cert_file = ssl_cert_file
+  @ssl_key_file = ssl_key_file
+  @config = config_provider
+  read_all
+  validate!
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #app_nameObject (readonly) + + + + + +

+
+ +

Returns the value of attribute app_name.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def app_name
+  @app_name
+end
+
+
+ + + +
+

+ + #bypass_ltmObject (readonly) + + + + + +

+
+ +

Returns the value of attribute bypass_ltm.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def bypass_ltm
+  @bypass_ltm
+end
+
+
+ + + +
+

+ + #config_prefixObject (readonly) + + + + + +

+
+ +

Returns the value of attribute config_prefix.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def config_prefix
+  @config_prefix
+end
+
+
+ + + +
+

+ + #connection_lifetimeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute connection_lifetime.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def connection_lifetime
+  @connection_lifetime
+end
+
+
+ + + +
+

+ + #connection_pool_sizeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute connection_pool_size.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def connection_pool_size
+  @connection_pool_size
+end
+
+
+ + + +
+

+ + #connection_timeoutObject (readonly) + + + + + +

+
+ +

Returns the value of attribute connection_timeout.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def connection_timeout
+  @connection_timeout
+end
+
+
+ + + +
+

+ + #default_lifetimeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute default_lifetime.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def default_lifetime
+  @default_lifetime
+end
+
+
+ + + +
+

+ + #hostObject (readonly) + + + + + +

+
+ +

Returns the value of attribute host.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def host
+  @host
+end
+
+
+ + + +
+

+ + #log_fileObject (readonly) + + + + + +

+
+ +

Returns the value of attribute log_file.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def log_file
+  @log_file
+end
+
+
+ + + +
+

+ + #max_connection_lifetimeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute max_connection_lifetime.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def max_connection_lifetime
+  @max_connection_lifetime
+end
+
+
+ + + +
+

+ + #max_connection_pool_sizeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute max_connection_pool_size.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def max_connection_pool_size
+  @max_connection_pool_size
+end
+
+
+ + + +
+

+ + #max_connection_timeoutObject (readonly) + + + + + +

+
+ +

Returns the value of attribute max_connection_timeout.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def max_connection_timeout
+  @max_connection_timeout
+end
+
+
+ + + +
+

+ + #max_key_sizeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute max_key_size.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def max_key_size
+  @max_key_size
+end
+
+
+ + + +
+

+ + #max_lifetimeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute max_lifetime.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def max_lifetime
+  @max_lifetime
+end
+
+
+ + + +
+

+ + #max_namespace_lengthObject (readonly) + + + + + +

+
+ +

Returns the value of attribute max_namespace_length.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def max_namespace_length
+  @max_namespace_length
+end
+
+
+ + + +
+

+ + #max_response_timeoutObject (readonly) + + + + + +

+
+ +

Returns the value of attribute max_response_timeout.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def max_response_timeout
+  @max_response_timeout
+end
+
+
+ + + +
+

+ + #max_value_sizeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute max_value_size.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def max_value_size
+  @max_value_size
+end
+
+
+ + + +
+

+ + #operation_retryObject (readonly) + + + + + +

+
+ +

Returns the value of attribute operation_retry.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def operation_retry
+  @operation_retry
+end
+
+
+ + + +
+

+ + #portObject (readonly) + + + + + +

+
+ +

Returns the value of attribute port.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def port
+  @port
+end
+
+
+ + + +
+

+ + #reconnect_on_failObject (readonly) + + + + + +

+
+ +

Returns the value of attribute reconnect_on_fail.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def reconnect_on_fail
+  @reconnect_on_fail
+end
+
+
+ + + +
+

+ + #record_namespaceObject (readonly) + + + + + +

+
+ +

Returns the value of attribute record_namespace.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def record_namespace
+  @record_namespace
+end
+
+
+ + + +
+

+ + #response_timeoutObject (readonly) + + + + + +

+
+ +

Returns the value of attribute response_timeout.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def response_timeout
+  @response_timeout
+end
+
+
+ + + +
+

+ + #ssl_cert_fileObject (readonly) + + + + + +

+
+ +

Returns the value of attribute ssl_cert_file.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def ssl_cert_file
+  @ssl_cert_file
+end
+
+
+ + + +
+

+ + #ssl_key_fileObject (readonly) + + + + + +

+
+ +

Returns the value of attribute ssl_key_file.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def ssl_key_file
+  @ssl_key_file
+end
+
+
+ + + +
+

+ + #use_payload_compressionObject (readonly) + + + + + +

+
+ +

Returns the value of attribute use_payload_compression.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def use_payload_compression
+  @use_payload_compression
+end
+
+
+ + + +
+

+ + #use_sslObject (readonly) + + + + + +

+
+ +

Returns the value of attribute use_ssl.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Config/config.rb', line 14
+
+def use_ssl
+  @use_ssl
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #read_allObject + + + + + +

+
+ +

Function to map all properties read from config file to variables

+ + +
+
+
+ + +
+ + + + +
+
+
+
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+
+
# File 'lib/juno/Config/config.rb', line 33
+
+def read_all
+  @response_timeout = @config.get_property(Juno::Properties::RESPONSE_TIMEOUT,
+                                           Juno::DefaultProperties::RESPONSE_TIMEOUT_MS)
+  @connection_timeout = @config.get_property(Juno::Properties::CONNECTION_TIMEOUT,
+                                             Juno::DefaultProperties::CONNECTION_TIMEOUT_MS)
+  @connection_pool_size = @config.get_property(Juno::Properties::CONNECTION_POOLSIZE,
+                                               Juno::DefaultProperties::CONNECTION_POOLSIZE)
+  @connection_lifetime = @config.get_property(Juno::Properties::CONNECTION_LIFETIME,
+                                              Juno::DefaultProperties::CONNECTION_LIFETIME_MS)
+  @default_lifetime = @config.get_property(Juno::Properties::DEFAULT_LIFETIME,
+                                           Juno::DefaultProperties::DEFAULT_LIFETIME_S)
+  @max_response_timeout = @config.get_property(Juno::Properties::MAX_RESPONSE_TIMEOUT,
+                                               Juno::DefaultProperties::MAX_RESPONSE_TIMEOUT_MS)
+  @max_connection_timeout = @config.get_property(Juno::Properties::MAX_CONNECTION_TIMEOUT,
+                                                 Juno::DefaultProperties::MAX_CONNECTION_TIMEOUT_MS)
+  @max_connection_pool_size = @config.get_property(Juno::Properties::MAX_CONNECTION_POOL_SIZE,
+                                                   Juno::DefaultProperties::MAX_CONNECTION_POOL_SIZE)
+  @max_connection_lifetime = @config.get_property(Juno::Properties::MAX_CONNECTION_LIFETIME,
+                                                  Juno::DefaultProperties::MAX_CONNECTION_LIFETIME_MS)
+  @max_lifetime = @config.get_property(Juno::Properties::MAX_LIFETIME, Juno::DefaultProperties::MAX_LIFETIME_S)
+  @max_key_size = @config.get_property(Juno::Properties::MAX_KEY_SIZE, Juno::DefaultProperties::MAX_KEY_SIZE_B)
+  @max_value_size = @config.get_property(Juno::Properties::MAX_VALUE_SIZE,
+                                         Juno::DefaultProperties::MAX_VALUE_SIZE_B)
+  @max_namespace_length = @config.get_property(Juno::Properties::MAX_NAMESPACE_LENGTH,
+                                               Juno::DefaultProperties::MAX_NAMESPACE_LENGTH)
+  @host = @config.get_property(Juno::Properties::HOST, Juno::DefaultProperties::HOST)
+  @port = @config.get_property(Juno::Properties::PORT, Juno::DefaultProperties::PORT)
+  @app_name = @config.get_property(Juno::Properties::APP_NAME, Juno::DefaultProperties::APP_NAME)
+  @record_namespace = @config.get_property(Juno::Properties::RECORD_NAMESPACE,
+                                           Juno::DefaultProperties::RECORD_NAMESPACE)
+  @use_ssl = @config.get_property(Juno::Properties::USE_SSL, Juno::DefaultProperties::USE_SSL)
+  @use_payload_compression = @config.get_property(Juno::Properties::USE_PAYLOAD_COMPRESSION,
+                                                  Juno::DefaultProperties::USE_PAYLOAD_COMPRESSION)
+  @operation_retry = @config.get_property(Juno::Properties::ENABLE_RETRY, Juno::DefaultProperties::OPERATION_RETRY)
+  @bypass_ltm = @config.get_property(Juno::Properties::BYPASS_LTM, Juno::DefaultProperties::BYPASS_LTM)
+  @reconnect_on_fail = @config.get_property(Juno::Properties::RECONNECT_ON_FAIL,
+                                            Juno::DefaultProperties::RECONNECT_ON_FAIL)
+  @config_prefix = @config.get_property(Juno::Properties::CONFIG_PREFIX, Juno::DefaultProperties::CONFIG_PREFIX)
+  nil
+end
+
+
+ +
+

+ + #validate!Object + + + + + +

+ + + + +
+
+
+
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+
+
# File 'lib/juno/Config/config.rb', line 74
+
+def validate!
+  missing_properties = []
+  REQUIRED_PROPERTIES.each do |property|
+    missing_properties.push(property) if send(property).nil?
+  end
+
+  if @use_ssl
+    %i[ssl_cert_file ssl_key_file].each do |property|
+      missing_properties.push(property) if send(property).nil?
+    end
+  end
+
+  if missing_properties.length.positive?
+    raise "Please provide a value for the required property(s) #{missing_properties.join(', ')}."
+  end
+
+  if @use_ssl
+    raise 'SSL Certificate file not found' unless File.exist?(@ssl_cert_file)
+    raise 'SSL Key file not found' unless File.exist?(@ssl_key_file)
+  end
+
+  nil
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Config/Source.html b/client/Ruby/docs/Juno/Config/Source.html new file mode 100644 index 00000000..a8cc3831 --- /dev/null +++ b/client/Ruby/docs/Juno/Config/Source.html @@ -0,0 +1,147 @@ + + + + + + + Class: Juno::Config::Source + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Config::Source + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Config/config.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
YAML_FILE = + +
+
0
+ +
JSON_FILE = + +
+
1
+ +
URL = + +
+
2
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/ConfigProvider.html b/client/Ruby/docs/Juno/ConfigProvider.html new file mode 100644 index 00000000..9115e800 --- /dev/null +++ b/client/Ruby/docs/Juno/ConfigProvider.html @@ -0,0 +1,603 @@ + + + + + + + Class: Juno::ConfigProvider + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::ConfigProvider + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Config/config_provider.rb
+
+ +
+ +

Overview

+
+ +

Class to read config from file or url

+ + +
+
+
+ + +
+ + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(source_uri, source_format = nil, _http_handler = nil) ⇒ Object + + + + + +

+
+ +

Constructor

+ + +
+
+
+

Parameters:

+
    + +
  • + + source_uri + + + (URI) + + + + — +
    +

    Ruby URI Object for file or URL (required)

    +
    + +
  • + +
  • + + source_format + + + (String) + + + (defaults to: nil) + + + — +
    +

    source_format required only for url. Inferred from file extension when using file (optional)

    +
    + +
  • + +
+ + +

See Also:

+ + +
+ + + + +
+
+
+
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
+
# File 'lib/juno/Config/config_provider.rb', line 12
+
+def initialize(source_uri, source_format = nil, _http_handler = nil)
+  begin
+    source_scheme = source_uri&.send(:scheme)
+  rescue StandardError => e
+    raise "Invalid source_uri object.\n #{e.message}"
+  end
+  if source_scheme == 'file'
+    read_from_file(source_uri)
+  elsif source_scheme =~ /^http(s)?$/
+    read_from_url(source_uri, source_format, http_handler)
+  else
+    raise 'Only local file and URL supported'
+  end
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #get_property(property_key, default_value = nil) ⇒ Object + + + + + +

+
+ +

Function to read propertied in the heirarchy define in Juno::Properties

+ + +
+
+
+

Parameters:

+
    + +
  • + + property_key + + + (String) + + + + — +
    +

    String key (required)

    +
    + +
  • + +
  • + + default_value + + + (optional) + + + (defaults to: nil) + + + — +
    +

    default value if property not found

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + + + + + +
    +

    Propert value. Returns default_value if property not found

    +
    + +
  • + +
+ +

See Also:

+ + +
+ + + + +
+
+
+
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+
+
# File 'lib/juno/Config/config_provider.rb', line 52
+
+def get_property(property_key, default_value = nil)
+  return default_value if property_key.to_s.empty?
+
+  value = configatron.to_h
+  property_key.to_s.split('.').each do |k|
+    return default_value unless value.is_a?(Hash) && value.key?(k.to_sym)
+
+    value = value[k.to_sym]
+    # puts "#{k} --- #{value}"
+  end
+
+  value.nil? || value.is_a?(Hash) ? default_value : value
+end
+
+
+ +
+

+ + #read_from_file(source_uri) ⇒ nil + + + + + +

+
+ +

Function to intialize configatron object from config file/URL

+ + +
+
+
+

Parameters:

+
    + +
  • + + source_uri + + + (URI) + + + + — +
    +

    Ruby URI Object for file or URL (required)

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (nil) + + + +
  • + +
+ +
+ + + + +
+
+
+
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
+
# File 'lib/juno/Config/config_provider.rb', line 30
+
+def read_from_file(source_uri)
+  raise 'Config file not found' unless File.exist?(source_uri.path)
+
+  hsh = if ['.yml', '.yaml'].include?(File.extname(source_uri.path))
+          YAML.load_file(source_uri.path)
+        elsif ['.json'].inlcude?(File.extname(source_uri.path))
+          json_text = File.read(source_uri.path)
+          JSON.parse(json_text)
+        else
+          raise 'Unknown file format'
+        end
+  configatron.configure_from_hash(hsh)
+  nil
+end
+
+
+ +
+

+ + #read_from_url(source_uri, source_format, http_handler) ⇒ Object + + + + + +

+ + + + +
+
+
+
+45
+
+
# File 'lib/juno/Config/config_provider.rb', line 45
+
+def read_from_url(source_uri, source_format, http_handler); end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/ConfigReader.html b/client/Ruby/docs/Juno/ConfigReader.html new file mode 100644 index 00000000..df1a4d19 --- /dev/null +++ b/client/Ruby/docs/Juno/ConfigReader.html @@ -0,0 +1,1002 @@ + + + + + + + Class: Juno::ConfigReader + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::ConfigReader + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Config/config_reader.rb
+
+ +
+ +

Overview

+
+ +

Properties Reader - Properties to be read from the developer using Juno.configure Either file_path or url is required log device can be a filename or IO Object

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #file_path ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute file_path.

    +
    + +
  • + + +
  • + + + #http_handler ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute http_handler.

    +
    + +
  • + + +
  • + + + #log_device ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute log_device.

    +
    + +
  • + + +
  • + + + #log_file ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute log_file.

    +
    + +
  • + + +
  • + + + #log_level ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute log_level.

    +
    + +
  • + + +
  • + + + #log_rotation ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute log_rotation.

    +
    + +
  • + + +
  • + + + #max_log_file_bytes ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute max_log_file_bytes.

    +
    + +
  • + + +
  • + + + #source_format ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute source_format.

    +
    + +
  • + + +
  • + + + #ssl_cert_file ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute ssl_cert_file.

    +
    + +
  • + + +
  • + + + #ssl_key_file ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute ssl_key_file.

    +
    + +
  • + + +
  • + + + #url ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute url.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeConfigReader + + + + + +

+
+ +

Returns a new instance of ConfigReader.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+12
+13
+14
+15
+16
+17
+18
+
+
# File 'lib/juno/Config/config_reader.rb', line 12
+
+def initialize
+  # default values
+  @log_level = ::Logger::Severity::INFO
+  @max_log_file_bytes = 1_048_576 # default for inbuilt logger class
+  @log_device = $stdout
+  @log_rotation = 'daily' # daily, weekly, monthly
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #file_pathObject + + + + + +

+
+ +

Returns the value of attribute file_path.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def file_path
+  @file_path
+end
+
+
+ + + +
+

+ + #http_handlerObject + + + + + +

+
+ +

Returns the value of attribute http_handler.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def http_handler
+  @http_handler
+end
+
+
+ + + +
+

+ + #log_deviceObject + + + + + +

+
+ +

Returns the value of attribute log_device.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def log_device
+  @log_device
+end
+
+
+ + + +
+

+ + #log_fileObject + + + + + +

+
+ +

Returns the value of attribute log_file.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def log_file
+  @log_file
+end
+
+
+ + + +
+

+ + #log_levelObject + + + + + +

+
+ +

Returns the value of attribute log_level.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def log_level
+  @log_level
+end
+
+
+ + + +
+

+ + #log_rotationObject + + + + + +

+
+ +

Returns the value of attribute log_rotation.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def log_rotation
+  @log_rotation
+end
+
+
+ + + +
+

+ + #max_log_file_bytesObject + + + + + +

+
+ +

Returns the value of attribute max_log_file_bytes.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def max_log_file_bytes
+  @max_log_file_bytes
+end
+
+
+ + + +
+

+ + #source_formatObject + + + + + +

+
+ +

Returns the value of attribute source_format.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def source_format
+  @source_format
+end
+
+
+ + + +
+

+ + #ssl_cert_fileObject + + + + + +

+
+ +

Returns the value of attribute ssl_cert_file.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def ssl_cert_file
+  @ssl_cert_file
+end
+
+
+ + + +
+

+ + #ssl_key_fileObject + + + + + +

+
+ +

Returns the value of attribute ssl_key_file.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def ssl_key_file
+  @ssl_key_file
+end
+
+
+ + + +
+

+ + #urlObject + + + + + +

+
+ +

Returns the value of attribute url.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/Config/config_reader.rb', line 9
+
+def url
+  @url
+end
+
+
+ +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/DefaultProperties.html b/client/Ruby/docs/Juno/DefaultProperties.html new file mode 100644 index 00000000..be59348d --- /dev/null +++ b/client/Ruby/docs/Juno/DefaultProperties.html @@ -0,0 +1,291 @@ + + + + + + + Class: Juno::DefaultProperties + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::DefaultProperties + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Config/default_properties.rb
+
+ +
+ +

Overview

+
+ +

Module containing constant default values for Properties

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
RESPONSE_TIMEOUT_MS = + +
+
200
+ +
CONNECTION_TIMEOUT_MS = + +
+
200
+ +
CONNECTION_POOLSIZE = + +
+
1
+ +
CONNECTION_LIFETIME_MS = + +
+
30_000
+ +
DEFAULT_LIFETIME_S = + +
+
259_200
+ +
MAX_RESPONSE_TIMEOUT_MS = +
+
+ +

Max for all above property

+ + +
+
+
+ + +
+
+
5000
+ +
MAX_CONNECTION_LIFETIME_MS = + +
+
30_000
+ +
MAX_CONNECTION_TIMEOUT_MS = + +
+
5000
+ +
MAX_KEY_SIZE_B = + +
+
128
+ +
MAX_VALUE_SIZE_B = + +
+
204_800
+ +
MAX_NAMESPACE_LENGTH = + +
+
64
+ +
MAX_CONNECTION_POOL_SIZE = + +
+
3
+ +
MAX_LIFETIME_S = + +
+
259_200
+ +
HOST = +
+
+ +

Required Properties

+ + +
+
+
+ + +
+
+
''
+ +
PORT = + +
+
0
+ +
APP_NAME = + +
+
''
+ +
RECORD_NAMESPACE = + +
+
''
+ +
CONFIG_PREFIX = +
+
+ +

optional Properties

+ + +
+
+
+ + +
+
+
''
+ +
USE_SSL = + +
+
true
+ +
RECONNECT_ON_FAIL = + +
+
false
+ +
USE_PAYLOAD_COMPRESSION = + +
+
false
+ +
OPERATION_RETRY = + +
+
false
+ +
BYPASS_LTM = + +
+
true
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO.html b/client/Ruby/docs/Juno/IO.html new file mode 100644 index 00000000..fbc7867d --- /dev/null +++ b/client/Ruby/docs/Juno/IO.html @@ -0,0 +1,227 @@ + + + + + + + Module: Juno::IO + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: Juno::IO + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/constants.rb,
+ lib/juno/IO/JunoMessage.rb,
lib/juno/IO/ProtocolHeader.rb,
lib/juno/IO/OperationMessage.rb,
lib/juno/IO/PayloadComponent.rb,
lib/juno/IO/MetadataComponent.rb,
lib/juno/IO/MetadataComponentTemplate.rb
+
+
+ +
+ +

Overview

+
+ +

Juno wire protocol consists of a 12-byte header. Depending on the type, the appropriate message payload follows the fixed header section. Following is the header protocol:

+ +
      | 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7|
+ byte |                      0|                      1|                      2|                      3|
+------+-----------------------+-----------------------+-----------------------+-----------------------+
+    0 | magic                                         | version               | message type flag     |
+      |                                               |                       +-----------------+-----+
+      |                                               |                       | type            | RQ  |
+------+-----------------------------------------------+-----------------------+-----------------+-----+
+    4 | message size                                                                                  |
+------+-----------------------------------------------------------------------------------------------+
+    8 | opaque                                                                                        |
+------+-----------------------------------------------------------------------------------------------+
+
+ +

Following is the detailed description of each field in the header:

+ +

offset name size (bytes) meaning 0 Magic 2 Magic number, used to identify Juno message.

+ +

'0x5050'

+ +

2 Version 1 Protocol version, current version is 1. 3 Message Type flag

+ +
1 	bit 0-5
+
+ +

Message Type

+ +

0: Operational Message

+ +

1: Admin Message

+ +

2: Cluster Control Message

+ +

bit 6-7 RQ flag

+ +

0: response

+ +

1: two way request

+ +

3: one way request

+ +

4 Message size 4 Specifies the length of the message 8 Opaque 4 The Opaque data set in the request will be copied back in the response Operational Message Client Info (ip, port, type, application name) Request Type: request or response Operation Type: Create, Get, Update, Delete Request Id Request Info (key, ttl, version, namespace) Payload data size Payload Response Info (status/error code, error string) Flag Before defining the details of the protocol for operational message, we need to review, and finalize somethings at page.

+ +

Operational Message Header

+ +
operational request header
+      |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
+ byte |              0|              1|              2|              3|
+------+---------------+---------------+---------------+---------------+
+    0 | opcode        |flag           | shard Id                      |
+      |               +-+-------------+                               |
+      |               |R|             |                               |
+------+---------------+-+-------------+-------------------------------+
+
+operational response header
+      |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
+ byte |              0|              1|              2|              3|
+------+---------------+---------------+---------------+---------------+
+    0 | opcode        |flag           | reserved      | status        |
+      |               +-+-------------+               |               |
+      |               |R|             |               |               |
+------+---------------+-+-------------+---------------+---------------+
+
+opcode:
+  0x00    Nop
+  0x01    Create
+  0x02    Get
+  0x03    Update
+  0x04    Set
+  0x05    Destroy
+  0x81    PrepareCreate
+  0x82    Read
+  0x83    PrepareUpdate
+  0x84    PrepareSet
+  0x85    PrepareDelete
+  0x86    Delete
+  0xC1    Commit
+  0xC2    Abort (Rollback)
+  0xC3    Repair
+  0xC4    MarkDelete
+  0xE1    Clone
+  0xFE    MockSetParam
+  oxFF    MockReSet
+
+ +

R:

+ +
1 if it is for replication
+
+ +

shard Id:

+ +
only meaning for request to SS
+
+ +

status:

+ +
1 byte, only meaningful for response
+
+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: CompressionType, JunoMessage, MetadataComponent, MetadataComponentTemplate, OffsetWidth, OperationMessage, PayloadComponent, PayloadType, ProtocolHeader + + +

+ + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/CompressionType.html b/client/Ruby/docs/Juno/IO/CompressionType.html new file mode 100644 index 00000000..7ccc89ec --- /dev/null +++ b/client/Ruby/docs/Juno/IO/CompressionType.html @@ -0,0 +1,250 @@ + + + + + + + Class: Juno::IO::CompressionType + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::CompressionType + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/constants.rb
+
+ +
+ +

Overview

+
+ +

Class containing constants for CompressionType

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
None = + +
+
'None'
+ +
Snappy = + +
+
'Snappy'
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .valid?(compression_type) ⇒ Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+31
+32
+33
+34
+35
+36
+
+
# File 'lib/juno/IO/constants.rb', line 31
+
+def self.valid?(compression_type)
+  constants.each do |constant|
+    return true if const_get(constant) == compression_type
+  end
+  false
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/JunoMessage.html b/client/Ruby/docs/Juno/IO/JunoMessage.html new file mode 100644 index 00000000..7994ce10 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/JunoMessage.html @@ -0,0 +1,1860 @@ + + + + + + + Class: Juno::IO::JunoMessage + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::JunoMessage + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/JunoMessage.rb
+
+ +
+ +

Overview

+
+ +

JunoMessage containing all configuration required to create an operation message

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: OperationType + + +

+ + + + +

Instance Attribute Summary collapse

+ + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeJunoMessage + + + + + +

+
+ +

Returns a new instance of JunoMessage.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 13
+
+def initialize
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @operation_type = Juno::IO::JunoMessage::OperationType::NOP
+  @server_status = Juno::ServerStatus::SUCCESS
+  @compression_type = Juno::IO::CompressionType::None
+  @is_compressed = false
+  @compression_achieved = 0
+  @message_size = 0
+  @value = ''
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #app_nameObject + + + + + +

+
+ +

Returns the value of attribute app_name.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def app_name
+  @app_name
+end
+
+
+ + + +
+

+ + #compression_achievedObject + + + + + +

+
+ +

Returns the value of attribute compression_achieved.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def compression_achieved
+  @compression_achieved
+end
+
+
+ + + +
+

+ + #compression_typeObject + + + + + +

+
+ +

Returns the value of attribute compression_type.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def compression_type
+  @compression_type
+end
+
+
+ + + +
+

+ + #correlation_idObject + + + + + +

+
+ +

Returns the value of attribute correlation_id.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def correlation_id
+  @correlation_id
+end
+
+
+ + + +
+

+ + #creation_timeObject + + + + + +

+
+ +

Returns the value of attribute creation_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def creation_time
+  @creation_time
+end
+
+
+ + + +
+

+ + #expiration_timeObject + + + + + +

+
+ +

Returns the value of attribute expiration_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def expiration_time
+  @expiration_time
+end
+
+
+ + + +
+

+ + #expiryObject + + + + + +

+
+ +

Returns the value of attribute expiry.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def expiry
+  @expiry
+end
+
+
+ + + +
+

+ + #ipObject + + + + + +

+
+ +

Returns the value of attribute ip.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def ip
+  @ip
+end
+
+
+ + + +
+

+ + #is_compressedObject + + + + + +

+
+ +

Returns the value of attribute is_compressed.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def is_compressed
+  @is_compressed
+end
+
+
+ + + +
+

+ + #keyObject + + + + + +

+
+ +

Returns the value of attribute key.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def key
+  @key
+end
+
+
+ + + +
+

+ + #last_modificationObject + + + + + +

+
+ +

Returns the value of attribute last_modification.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def last_modification
+  @last_modification
+end
+
+
+ + + +
+

+ + #message_sizeObject + + + + + +

+
+ +

Returns the value of attribute message_size.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def message_size
+  @message_size
+end
+
+
+ + + +
+

+ + #namespaceObject + + + + + +

+
+ +

Returns the value of attribute namespace.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def namespace
+  @namespace
+end
+
+
+ + + +
+

+ + #operation_typeObject + + + + + +

+
+ +

Returns the value of attribute operation_type.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def operation_type
+  @operation_type
+end
+
+
+ + + +
+

+ + #originator_request_idObject + + + + + +

+
+ +

Returns the value of attribute originator_request_id.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def originator_request_id
+  @originator_request_id
+end
+
+
+ + + +
+

+ + #portObject + + + + + +

+
+ +

Returns the value of attribute port.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def port
+  @port
+end
+
+
+ + + +
+

+ + #request_handling_timeObject + + + + + +

+
+ +

Returns the value of attribute request_handling_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def request_handling_time
+  @request_handling_time
+end
+
+
+ + + +
+

+ + #request_start_timeObject + + + + + +

+
+ +

Returns the value of attribute request_start_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def request_start_time
+  @request_start_time
+end
+
+
+ + + +
+

+ + #request_uuidObject + + + + + +

+
+ +

Returns the value of attribute request_uuid.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def request_uuid
+  @request_uuid
+end
+
+
+ + + +
+

+ + #server_statusObject + + + + + +

+
+ +

Returns the value of attribute server_status.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def server_status
+  @server_status
+end
+
+
+ + + +
+

+ + #time_to_live_sObject + + + + + +

+
+ +

Returns the value of attribute time_to_live_s.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def time_to_live_s
+  @time_to_live_s
+end
+
+
+ + + +
+

+ + #valueObject + + + + + +

+
+ +

Returns the value of attribute value.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def value
+  @value
+end
+
+
+ + + +
+

+ + #versionObject + + + + + +

+
+ +

Returns the value of attribute version.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 7
+
+def version
+  @version
+end
+
+
+ +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/JunoMessage/OperationType.html b/client/Ruby/docs/Juno/IO/JunoMessage/OperationType.html new file mode 100644 index 00000000..2b425508 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/JunoMessage/OperationType.html @@ -0,0 +1,304 @@ + + + + + + + Class: Juno::IO::JunoMessage::OperationType + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::JunoMessage::OperationType + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/JunoMessage.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
NOP = + +
+
0
+ +
CREATE = + +
+
1
+ +
GET = + +
+
2
+ +
UPDATE = + +
+
3
+ +
SET = + +
+
4
+ +
DESTROY = + +
+
5
+ +
@@status_code_map = + +
+
nil
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .get(status_code) ⇒ Object + + + + + +

+ + + + +
+
+
+
+44
+45
+46
+47
+48
+49
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 44
+
+def self.get(status_code)
+  initialize_map if @@status_code_map.nil?
+  return @@status_code_map[status_code.to_i] if @@status_code_map.key?(status_code)
+
+  INTERNAL_ERROR
+end
+
+
+ +
+

+ + .initialize_mapObject + + + + + +

+ + + + +
+
+
+
+35
+36
+37
+38
+39
+40
+41
+42
+
+
# File 'lib/juno/IO/JunoMessage.rb', line 35
+
+def self.initialize_map
+  @@status_code_map = {}
+
+  constants.each do |const|
+    const_obj = const_get(const)
+    @@status_code_map[const_obj.to_i] = const_obj
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponent.html b/client/Ruby/docs/Juno/IO/MetadataComponent.html new file mode 100644 index 00000000..affccc6a --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponent.html @@ -0,0 +1,2518 @@ + + + + + + + Class: Juno::IO::MetadataComponent + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponent + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponent.rb
+
+ +
+ +

Overview

+
+ +

Wrapper class for MetadataComponentTemplate

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: MetadataField, TagAndType + + +

+ + + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #app_name ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute app_name.

    +
    + +
  • + + +
  • + + + #correlation_id ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute correlation_id.

    +
    + +
  • + + +
  • + + + #creation_time ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute creation_time.

    +
    + +
  • + + +
  • + + + #expiration_time ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute expiration_time.

    +
    + +
  • + + +
  • + + + #ip ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute ip.

    +
    + +
  • + + +
  • + + + #last_modification ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute last_modification.

    +
    + +
  • + + +
  • + + + #metadata_field_list ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute metadata_field_list.

    +
    + +
  • + + +
  • + + + #originator_request_id ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute originator_request_id.

    +
    + +
  • + + +
  • + + + #port ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute port.

    +
    + +
  • + + +
  • + + + #request_handling_time ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute request_handling_time.

    +
    + +
  • + + +
  • + + + #request_uuid ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute request_uuid.

    +
    + +
  • + + +
  • + + + #time_to_live ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute time_to_live.

    +
    + +
  • + + +
  • + + + #version ⇒ Object + + + + + + + + + readonly + + + + + + + + + +
    +

    Returns the value of attribute version.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeMetadataComponent + + + + + +

+
+ +

Returns a new instance of MetadataComponent.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+94
+95
+96
+97
+98
+99
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 94
+
+def initialize
+  @PROG_NAME = self.class.name
+  # @LOGGER = Juno::Logger.instance
+  # @metadata_field_list [Array<MetadataField>]
+  @metadata_field_list = []
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #app_nameObject (readonly) + + + + + +

+
+ +

Returns the value of attribute app_name.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def app_name
+  @app_name
+end
+
+
+ + + +
+

+ + #correlation_idObject (readonly) + + + + + +

+
+ +

Returns the value of attribute correlation_id.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def correlation_id
+  @correlation_id
+end
+
+
+ + + +
+

+ + #creation_timeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute creation_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def creation_time
+  @creation_time
+end
+
+
+ + + +
+

+ + #expiration_timeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute expiration_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def expiration_time
+  @expiration_time
+end
+
+
+ + + +
+

+ + #ipObject (readonly) + + + + + +

+
+ +

Returns the value of attribute ip.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def ip
+  @ip
+end
+
+
+ + + +
+

+ + #last_modificationObject (readonly) + + + + + +

+
+ +

Returns the value of attribute last_modification.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def last_modification
+  @last_modification
+end
+
+
+ + + +
+

+ + #metadata_field_listObject (readonly) + + + + + +

+
+ +

Returns the value of attribute metadata_field_list.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def 
+  @metadata_field_list
+end
+
+
+ + + +
+

+ + #originator_request_idObject (readonly) + + + + + +

+
+ +

Returns the value of attribute originator_request_id.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def originator_request_id
+  @originator_request_id
+end
+
+
+ + + +
+

+ + #portObject (readonly) + + + + + +

+
+ +

Returns the value of attribute port.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def port
+  @port
+end
+
+
+ + + +
+

+ + #request_handling_timeObject (readonly) + + + + + +

+
+ +

Returns the value of attribute request_handling_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def request_handling_time
+  @request_handling_time
+end
+
+
+ + + +
+

+ + #request_uuidObject (readonly) + + + + + +

+
+ +

Returns the value of attribute request_uuid.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def request_uuid
+  @request_uuid
+end
+
+
+ + + +
+

+ + #time_to_liveObject (readonly) + + + + + +

+
+ +

Returns the value of attribute time_to_live.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def time_to_live
+  @time_to_live
+end
+
+
+ + + +
+

+ + #versionObject (readonly) + + + + + +

+
+ +

Returns the value of attribute version.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+91
+92
+93
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 91
+
+def version
+  @version
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #add_field(field) ⇒ Object + + + + + +

+
+ +

Function to add feild to the list

+ + +
+
+
+

Parameters:

+
    + +
  • + + field + + + (MetadataField) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+202
+203
+204
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 202
+
+def add_field(field)
+  .push(field)
+end
+
+
+ +
+

+ + #num_bytesObject + + + + + +

+
+ +

function to calculate size of metadata component

+ + +
+
+
+ + +
+ + + + +
+
+
+
+207
+208
+209
+210
+211
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 207
+
+def num_bytes
+  io = StringIO.new
+  write(io)
+  io.size
+end
+
+
+ +
+

+ + #read(io) ⇒ Object + + + + + +

+
+ +

Function to de-serialize Component to buffer

+ + +
+
+
+

Parameters:

+
    + +
  • + + io + + + (StringIO) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 238
+
+def read(io)
+   = MetadataComponentTemplate.new
+  .read(io)
+
+  body_buffer = StringIO.new(.body)
+
+  ..each do |field|
+    case field.field_tag
+    when TagAndType::TimeToLive[:tag]
+      ttl_byte_string = body_buffer.read(1 << (1 + TagAndType::TimeToLive[:size_type]))
+      set_time_to_live(ttl_byte_string.unpack1(OffsetWidth.UINT32))
+
+    when TagAndType::Version[:tag]
+      version_byte_string = body_buffer.read(1 << (1 + TagAndType::Version[:size_type]))
+      set_version(version_byte_string.unpack1(OffsetWidth.UINT32))
+
+    when TagAndType::CreationTime[:tag]
+      creation_time_byte_string = body_buffer.read(1 << (1 + TagAndType::CreationTime[:size_type]))
+      set_creation_time(creation_time_byte_string.unpack1(OffsetWidth.UINT32))
+
+    when TagAndType::RequestUUID[:tag]
+      request_uuid_byte_string = body_buffer.read(1 << (1 + TagAndType::RequestUUID[:size_type]))
+      set_request_uuid(request_uuid_byte_string)
+
+    when TagAndType::SourceInfo[:tag]
+      source_info = MetadataComponentTemplate::SourceInfoField.new
+      source_info.read(body_buffer)
+      set_source_info(app_name: source_info.app_name, ip: IPAddr.new_ntoh(source_info.ip), port: source_info.port)
+
+    when TagAndType::LastModification[:tag]
+      last_modification_byte_string = body_buffer.read(1 << (1 + TagAndType::LastModification[:size_type]))
+      set_last_modification(last_modification_byte_string.unpack1(OffsetWidth.UINT64))
+
+    when TagAndType::ExpirationTime[:tag]
+      expiration_time_byte_string = body_buffer.read(1 << (1 + TagAndType::ExpirationTime[:size_type]))
+      set_expiration_time(expiration_time_byte_string.unpack1(OffsetWidth.UINT32))
+
+    when TagAndType::OriginatorRequestID[:tag]
+      originator_request_id_byte_string = body_buffer.read(1 << (1 + TagAndType::OriginatorRequestID[:size_type]))
+      set_originator_request_id(originator_request_id_byte_string)
+    # when TagAndType::CorrelationID[:tag]
+
+    when TagAndType::RequestHandlingTime[:tag]
+      request_handling_time_byte_string = body_buffer.read(1 << (1 + TagAndType::RequestHandlingTime[:size_type]))
+      set_request_handling_time(request_handling_time_byte_string.unpack1(OffsetWidth.UINT32))
+    end
+  end
+end
+
+
+ +
+

+ + #set_correlation_id(input_uuid_byte_string = nil) ⇒ Object + + + + + +

+
+ +

if not provided, creates a uuid itself

+ + +
+
+
+

Parameters:

+
    + +
  • + + input_uuid_byte_string + + + (String) + + + (defaults to: nil) + + + — +
    +

    (optional)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 180
+
+def set_correlation_id(input_uuid_byte_string = nil)
+  @correlation_id = if input_uuid_byte_string.nil?
+                      UUIDTools::UUID.random_create
+                    else
+                      UUIDTools::UUID.parse_raw(input_uuid_byte_string)
+                    end
+  field = MetadataComponentTemplate::CorrelationIDField.new
+  field.correlation_id = @correlation_id.raw
+  str_io = StringIO.new
+  field.write(str_io)
+  # puts field
+  add_field(MetadataField.new(0x09, 0x0, str_io.string))
+end
+
+
+ +
+

+ + #set_creation_time(data) ⇒ Object + + + + + +

+
+ + +
+
+
+

Parameters:

+
    + +
  • + + creation_time + + + (Integer) + + + + — +
    • +

      Unix timestamp (required)

      +
    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+119
+120
+121
+122
+123
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 119
+
+def set_creation_time(data)
+  @creation_time = data
+  creation_time_bytes_string = [data].pack(OffsetWidth.UINT32)
+  add_field(MetadataField.new(0x03, 0x01, creation_time_bytes_string))
+end
+
+
+ +
+

+ + #set_expiration_time(data) ⇒ Object + + + + + +

+ + + + +
+
+
+
+125
+126
+127
+128
+129
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 125
+
+def set_expiration_time(data)
+  @expiration_time = data
+  expiration_time_bytes_string = [data].pack(OffsetWidth.UINT32)
+  add_field(MetadataField.new(0x04, 0x01, expiration_time_bytes_string))
+end
+
+
+ +
+

+ + #set_last_modification(data) ⇒ Object + + + + + +

+ + + + +
+
+
+
+160
+161
+162
+163
+164
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 160
+
+def set_last_modification(data)
+  @last_modification = data
+  last_modification_bytes_string = [data].pack(OffsetWidth.UINT64)
+  add_field(MetadataField.new(0x07, 0x02, last_modification_bytes_string))
+end
+
+
+ +
+

+ + #set_originator_request_id(input_uuid_byte_string = nil) ⇒ Object + + + + + +

+
+ +

if not provided, creates a uuid itself

+ + +
+
+
+

Parameters:

+
    + +
  • + + input_uuid_byte_string + + + (String) + + + (defaults to: nil) + + + — +
    +

    (optional)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+168
+169
+170
+171
+172
+173
+174
+175
+176
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 168
+
+def set_originator_request_id(input_uuid_byte_string = nil)
+  @originator_request_id = if input_uuid_byte_string.nil?
+                             UUIDTools::UUID.random_create
+                           else
+                             UUIDTools::UUID.parse_raw(input_uuid_byte_string)
+                           end
+  add_field(MetadataField.new(0x08, 0x03, @originator_request_id.raw))
+  @originator_request_id
+end
+
+
+ +
+

+ + #set_request_handling_time(data) ⇒ Object + + + + + +

+ + + + +
+
+
+
+194
+195
+196
+197
+198
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 194
+
+def set_request_handling_time(data)
+  @request_handling_time = data
+  request_handling_time_bytes_string = [data].pack(OffsetWidth.UINT32)
+  add_field(MetadataField.new(0x0A, 0x01, request_handling_time_bytes_string))
+end
+
+
+ +
+

+ + #set_request_uuid(input_uuid_byte_string = nil) ⇒ Object + + + + + +

+
+ +

if not provided, creates a uuid itself

+ + +
+
+
+

Parameters:

+
    + +
  • + + input_uuid_byte_string + + + (String) + + + (defaults to: nil) + + + — +
    • +

      Record Time to live (optional)

      +
    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+133
+134
+135
+136
+137
+138
+139
+140
+141
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 133
+
+def set_request_uuid(input_uuid_byte_string = nil)
+  @request_uuid = if input_uuid_byte_string.nil?
+                    UUIDTools::UUID.random_create
+                  else
+                    UUIDTools::UUID.parse_raw(input_uuid_byte_string)
+                  end
+  add_field(MetadataField.new(0x05, 0x03, @request_uuid.raw))
+  @request_uuid
+end
+
+
+ +
+

+ + #set_source_info(app_name:, ip:, port:) ⇒ Object + + + + + +

+
+ +

SourceInfoField

+ + +
+
+
+

Parameters:

+
    + +
  • + + app_name + + + (String) + + + + — +
    • +

      Record Time to live (required)

      +
    +
    + +
  • + +
  • + + ip + + + (IPAddr) + + + + — +
    • +

      ip address for component (required)

      +
    +
    + +
  • + +
  • + + port + + + (Integer) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 147
+
+def set_source_info(app_name:, ip:, port:)
+  @ip = ip
+  @port = port
+  @app_name = app_name
+  data = MetadataComponentTemplate::SourceInfoField.new
+  data.app_name = app_name
+  data.ip = ip.hton
+  data.port = port
+  str_io = StringIO.new
+  data.write(str_io)
+  add_field(MetadataField.new(0x06, 0x00, str_io.string))
+end
+
+
+ +
+

+ + #set_time_to_live(ttl) ⇒ Object + + + + + +

+
+ + +
+
+
+

Parameters:

+
    + +
  • + + ttl + + + (Integer) + + + + — +
    • +

      Record Time to live

      +
    +
    + +
  • + +
+ +

Raises:

+
    + +
  • + + + (ArgumentError) + + + +
  • + +
+ +
+ + + + +
+
+
+
+102
+103
+104
+105
+106
+107
+108
+109
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 102
+
+def set_time_to_live(ttl)
+  ttl = ttl.to_i
+  raise ArgumentError, 'TTL should be > 0' unless ttl.positive?
+
+  @time_to_live = ttl
+  ttl = [ttl].pack(OffsetWidth.UINT32)
+  add_field(MetadataField.new(0x01, 0x01, ttl))
+end
+
+
+ +
+

+ + #set_version(data) ⇒ Object + + + + + +

+
+ + +
+
+
+

Parameters:

+
    + +
  • + + version + + + (Integer) + + + + — +
    • +

      Record version

      +
    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+112
+113
+114
+115
+116
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 112
+
+def set_version(data)
+  @version = data
+  version_bytes_string = [data].pack(OffsetWidth.UINT32)
+  add_field(MetadataField.new(0x02, 0x01, version_bytes_string))
+end
+
+
+ +
+

+ + #write(io) ⇒ Object + + + + + +

+
+ +

Function to serialize Component to buffer

+ + +
+
+
+

Parameters:

+
    + +
  • + + io + + + (StringIO) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 215
+
+def write(io)
+  buffer = MetadataComponentTemplate.new
+  buffer.number_of_fields = .length
+  .each do |field|
+    f = MetadataComponentTemplate::MetadataHeaderField.new
+    f.size_type = field.size_type
+    f.field_tag = field.tag
+    buffer..push(f)
+  end
+
+  body = StringIO.new
+  .each do |field|
+    body.write(field.data)
+  end
+  padding_size = (8 - body.size % 8) % 8
+  body.write(Array.new(0, padding_size).pack(OffsetWidth.UINT8('*'))) if padding_size.positive?
+  buffer.body = body.string
+
+  buffer.write(io)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponent/MetadataField.html b/client/Ruby/docs/Juno/IO/MetadataComponent/MetadataField.html new file mode 100644 index 00000000..7853e2ae --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponent/MetadataField.html @@ -0,0 +1,514 @@ + + + + + + + Class: Juno::IO::MetadataComponent::MetadataField + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponent::MetadataField + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponent.rb
+
+ +
+ +

Overview

+
+ +

DataType for @metadata_field_list

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: SizeType + + +

+ + + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #data ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute data.

    +
    + +
  • + + +
  • + + + #size_type ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute size_type.

    +
    + +
  • + + +
  • + + + #tag ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute tag.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(tag, size_type, data) ⇒ MetadataField + + + + + +

+
+ +

Returns a new instance of MetadataField.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+334
+335
+336
+337
+338
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 334
+
+def initialize(tag, size_type, data)
+  @tag = tag
+  @size_type = size_type
+  @data = data
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #dataObject + + + + + +

+
+ +

Returns the value of attribute data.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+332
+333
+334
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 332
+
+def data
+  @data
+end
+
+
+ + + +
+

+ + #size_typeObject + + + + + +

+
+ +

Returns the value of attribute size_type.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+332
+333
+334
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 332
+
+def size_type
+  @size_type
+end
+
+
+ + + +
+

+ + #tagObject + + + + + +

+
+ +

Returns the value of attribute tag.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+332
+333
+334
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 332
+
+def tag
+  @tag
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #sizeObject + + + + + +

+ + + + +
+
+
+
+340
+341
+342
+343
+344
+345
+346
+
+
# File 'lib/juno/IO/MetadataComponent.rb', line 340
+
+def size
+  if size_type == SizeType::Variable
+    data.length
+  else
+    1 << (size_type + 1)
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponent/MetadataField/SizeType.html b/client/Ruby/docs/Juno/IO/MetadataComponent/MetadataField/SizeType.html new file mode 100644 index 00000000..a866f481 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponent/MetadataField/SizeType.html @@ -0,0 +1,137 @@ + + + + + + + Class: Juno::IO::MetadataComponent::MetadataField::SizeType + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponent::MetadataField::SizeType + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponent.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
Variable = + +
+
0
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponent/TagAndType.html b/client/Ruby/docs/Juno/IO/MetadataComponent/TagAndType.html new file mode 100644 index 00000000..bfa69384 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponent/TagAndType.html @@ -0,0 +1,212 @@ + + + + + + + Class: Juno::IO::MetadataComponent::TagAndType + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponent::TagAndType + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponent.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
TimeToLive = + +
+
{
+  tag: 0x01,
+  size_type: 0x01
+}.freeze
+ +
Version = + +
+
{
+  tag: 0x02,
+  size_type: 0x01
+}.freeze
+ +
CreationTime = + +
+
{
+  tag: 0x03,
+  size_type: 0x01
+}.freeze
+ +
RequestUUID = + +
+
{
+  tag: 0x05,
+  size_type: 0x03
+}.freeze
+ +
SourceInfo = + +
+
{
+  tag: 0x06,
+  size_type: 0x00
+}.freeze
+ +
ExpirationTime = + +
+
{
+  tag: 0x04,
+  size_type: 0x01
+}.freeze
+ +
LastModification = + +
+
{
+  tag: 0x07,
+  size_type: 0x02
+}.freeze
+ +
OriginatorRequestID = + +
+
{
+  tag: 0x08,
+  size_type: 0x03
+}.freeze
+ +
CorrelationID = + +
+
{
+  tag: 0x09,
+  size_type: 0x00
+}.freeze
+ +
RequestHandlingTime = + +
+
{
+  tag: 0x0A,
+  size_type: 0x01
+}.freeze
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponentTemplate.html b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate.html new file mode 100644 index 00000000..d1fb34ee --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate.html @@ -0,0 +1,255 @@ + + + + + + + Class: Juno::IO::MetadataComponentTemplate + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponentTemplate + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponentTemplate.rb
+
+ +
+ +

Defined Under Namespace

+

+ + + + + Classes: CorrelationIDField, FixedLengthField, MetadataHeaderField, SourceInfoField + + +

+ + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + + +
+

Instance Method Details

+ + +
+

+ + #header_num_bytesObject + + + + + +

+ + + + +
+
+
+
+52
+53
+54
+
+
# File 'lib/juno/IO/MetadataComponentTemplate.rb', line 52
+
+def header_num_bytes
+  component_size.num_bytes + tag_id.num_bytes + number_of_fields.num_bytes + .num_bytes
+end
+
+
+ +
+

+ + #header_padding_lengthObject + + + + + +

+ + + + +
+
+
+
+56
+57
+58
+
+
# File 'lib/juno/IO/MetadataComponentTemplate.rb', line 56
+
+def header_padding_length
+  (4 - header_num_bytes % 4) % 4
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/CorrelationIDField.html b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/CorrelationIDField.html new file mode 100644 index 00000000..ea32bd9b --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/CorrelationIDField.html @@ -0,0 +1,195 @@ + + + + + + + Class: Juno::IO::MetadataComponentTemplate::CorrelationIDField + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponentTemplate::CorrelationIDField + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponentTemplate.rb
+
+ +
+ + + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + + +
+

Instance Method Details

+ + +
+

+ + #padding_sizeObject + + + + + +

+ + + + +
+
+
+
+40
+41
+42
+43
+
+
# File 'lib/juno/IO/MetadataComponentTemplate.rb', line 40
+
+def padding_size
+  size = component_size.num_bytes + correlation_id_length.num_bytes + correlation_id.num_bytes
+  (4 - size % 4) % 4
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/FixedLengthField.html b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/FixedLengthField.html new file mode 100644 index 00000000..0afbca92 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/FixedLengthField.html @@ -0,0 +1,124 @@ + + + + + + + Class: Juno::IO::MetadataComponentTemplate::FixedLengthField + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponentTemplate::FixedLengthField + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponentTemplate.rb
+
+ +
+ + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/MetadataHeaderField.html b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/MetadataHeaderField.html new file mode 100644 index 00000000..b173ae2c --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/MetadataHeaderField.html @@ -0,0 +1,124 @@ + + + + + + + Class: Juno::IO::MetadataComponentTemplate::MetadataHeaderField + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponentTemplate::MetadataHeaderField + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponentTemplate.rb
+
+ +
+ + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/SourceInfoField.html b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/SourceInfoField.html new file mode 100644 index 00000000..f1d8d8f0 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/MetadataComponentTemplate/SourceInfoField.html @@ -0,0 +1,319 @@ + + + + + + + Class: Juno::IO::MetadataComponentTemplate::SourceInfoField + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::MetadataComponentTemplate::SourceInfoField + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/MetadataComponentTemplate.rb
+
+ +
+ + + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + + +
+

Instance Method Details

+ + +
+

+ + #field_bytesObject + + + + + +

+ + + + +
+
+
+
+18
+19
+20
+
+
# File 'lib/juno/IO/MetadataComponentTemplate.rb', line 18
+
+def field_bytes
+  field_length.num_bytes + app_name_length.num_bytes + port.num_bytes + ip.num_bytes + app_name.num_bytes
+end
+
+
+ +
+

+ + #ipv6?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+26
+27
+28
+
+
# File 'lib/juno/IO/MetadataComponentTemplate.rb', line 26
+
+def ipv6?
+  IPAddr.new_ntoh(ip).ipv6?
+end
+
+
+ +
+

+ + #padding_sizeObject + + + + + +

+ + + + +
+
+
+
+22
+23
+24
+
+
# File 'lib/juno/IO/MetadataComponentTemplate.rb', line 22
+
+def padding_size
+  (4 - field_bytes % 4) % 4
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/OffsetWidth.html b/client/Ruby/docs/Juno/IO/OffsetWidth.html new file mode 100644 index 00000000..9ac361ec --- /dev/null +++ b/client/Ruby/docs/Juno/IO/OffsetWidth.html @@ -0,0 +1,359 @@ + + + + + + + Class: Juno::IO::OffsetWidth + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::OffsetWidth + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/constants.rb
+
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .UINT16(count = '') ⇒ Object + + + + + +

+ + + + +
+
+
+
+17
+18
+19
+
+
# File 'lib/juno/IO/constants.rb', line 17
+
+def self.UINT16(count = '')
+  "n#{count}"
+end
+
+
+ +
+

+ + .UINT32(count = '') ⇒ Object + + + + + +

+ + + + +
+
+
+
+13
+14
+15
+
+
# File 'lib/juno/IO/constants.rb', line 13
+
+def self.UINT32(count = '')
+  "N#{count}"
+end
+
+
+ +
+

+ + .UINT64(count = '') ⇒ Object + + + + + +

+
+ +

Count can be integer or '*'

+ + +
+
+
+ + +
+ + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/juno/IO/constants.rb', line 9
+
+def self.UINT64(count = '')
+  "Q#{count}"
+end
+
+
+ +
+

+ + .UINT8(count = '') ⇒ Object + + + + + +

+ + + + +
+
+
+
+21
+22
+23
+
+
# File 'lib/juno/IO/constants.rb', line 21
+
+def self.UINT8(count = '')
+  "C#{count}"
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/OperationMessage.html b/client/Ruby/docs/Juno/IO/OperationMessage.html new file mode 100644 index 00000000..8d770e3e --- /dev/null +++ b/client/Ruby/docs/Juno/IO/OperationMessage.html @@ -0,0 +1,716 @@ + + + + + + + Class: Juno::IO::OperationMessage + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::OperationMessage + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/OperationMessage.rb
+
+ +
+ + + + + +

Instance Attribute Summary collapse

+ + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeOperationMessage + + + + + +

+
+ +

Returns a new instance of OperationMessage.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+8
+9
+10
+11
+12
+
+
# File 'lib/juno/IO/OperationMessage.rb', line 8
+
+def initialize
+  @protocol_header = ProtocolHeader.new
+  @metadata_component = nil
+  @payload_component = nil
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #metadata_componentObject + + + + + +

+
+ +

Returns the value of attribute metadata_component.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+6
+7
+8
+
+
# File 'lib/juno/IO/OperationMessage.rb', line 6
+
+def 
+  @metadata_component
+end
+
+
+ + + +
+

+ + #payload_componentObject + + + + + +

+
+ +

Returns the value of attribute payload_component.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+6
+7
+8
+
+
# File 'lib/juno/IO/OperationMessage.rb', line 6
+
+def payload_component
+  @payload_component
+end
+
+
+ + + +
+

+ + #protocol_headerObject + + + + + +

+
+ +

Returns the value of attribute protocol_header.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+6
+7
+8
+
+
# File 'lib/juno/IO/OperationMessage.rb', line 6
+
+def protocol_header
+  @protocol_header
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #read(io) ⇒ Object + + + + + +

+
+ +

Function to de-serialize message to buffer

+ + +
+
+
+

Parameters:

+
    + +
  • + + io + + + (StringIO) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+
+
# File 'lib/juno/IO/OperationMessage.rb', line 35
+
+def read(io)
+  return if io.eof? || (io.size - io.pos) < 16
+
+  @protocol_header = ProtocolHeader.new
+  @metadata_component = MetadataComponent.new
+  @payload_component = PayloadComponent.new
+
+  @protocol_header.read(io)
+
+  remaining_size = protocol_header.message_size - 16
+  prev_position = io.pos
+
+  @metadata_component.read(io) if !io.eof? && (io.size - io.pos) >= remaining_size
+
+  remaining_size -= (io.pos - prev_position)
+
+  @payload_component.read(io) if !io.eof? && (io.size - io.pos) >= remaining_size
+  nil
+end
+
+
+ +
+

+ + #sizeObject + + + + + +

+
+ +

Calculates size of message

+ + +
+
+
+ + +
+ + + + +
+
+
+
+15
+16
+17
+18
+19
+20
+
+
# File 'lib/juno/IO/OperationMessage.rb', line 15
+
+def size
+  total_size = protocol_header.num_bytes
+  total_size += payload_component.num_bytes unless payload_component.nil?
+  total_size += .num_bytes unless .nil?
+  total_size
+end
+
+
+ +
+

+ + #write(io) ⇒ Object + + + + + +

+
+ +

Function to serialize message to buffer

+ + +
+
+
+

Parameters:

+
    + +
  • + + io + + + (StringIO) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+24
+25
+26
+27
+28
+29
+30
+31
+
+
# File 'lib/juno/IO/OperationMessage.rb', line 24
+
+def write(io)
+  protocol_header.message_size = size
+
+  protocol_header.write(io)
+  &.write(io)
+  payload_component&.write(io)
+  nil
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/PayloadComponent.html b/client/Ruby/docs/Juno/IO/PayloadComponent.html new file mode 100644 index 00000000..65b84664 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/PayloadComponent.html @@ -0,0 +1,648 @@ + + + + + + + Class: Juno::IO::PayloadComponent + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::PayloadComponent + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/PayloadComponent.rb
+
+ +
+ +

Overview

+
+ +

** Payload (or KeyValue) Component **

+ +

A 12-byte header followed by name, key and value

+ +
Tag/ID: 0x01
+
+
  • +

    Header *

    + +
    |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
    +|              0|              1|              2|              3|
    +
    +
+ +

——---------------—————---------------—————+

+ +
0 | Size                                                          |
+
+ +

——---------------—————-------------------------------

+ +
4 | Tag/ID (0x01) | namespace len | key length                    |
+
+ +

——---------------—————-------------------------------

+ +
8 | payload length                                                |
+
+ +

——---------------------------------------------------------------

+ +
(
+ The max namespace length: 255
+ payload length = 0 if len(payload data) = 0, otherwise,
+ payload length = 1 + len(payload data) = len(payload field)
+)
+
+
  • +

    Body *

    +
+ +

---------—–---------------————————-+ |namespace| key | payload field | Padding to align 8-byte | ---------—–---------------————————-+

+
  • +

    Payload field*

    +
+ +

---------------------————–+ | 1 byte payload type | Payload data | ---------------------————–+

+
  • +

    Payload Type

    +
+ +

0: payload data is the actual value passed from client user 1: payload data is encrypted by Juno client library, details not specified 2: payload data is encrypted by Juno proxy with AES-GCM. encryption key length is 256 bits 3: Payload data is compressed by Juno Client library.

+
  • +

    Payload data

    +
+ +

for payload type 2 --------------------------------—————----------------- | 4 bytes encryption key version | 12 bytes nonce | encrypted data | --------------------------------—————-----------------

+ +

for payload type 3 ---------------------------------——————---------------- | 1 byte size of compression type | compression type | compressed data| ---------------------------------——————----------------

+
  • +

    compression type

    +
+ +

1) snappy (default algorithm) 2) TBD

+ + +
+
+
+ + +

Defined Under Namespace

+

+ + + + + Classes: CompressedPayloadData, EncryptedPayloadData, UncompressedPayloadData + + +

+ + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + + +
+

Instance Method Details

+ + +
+

+ + #compressed?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+154
+155
+156
+157
+158
+
+
# File 'lib/juno/IO/PayloadComponent.rb', line 154
+
+def compressed?
+  return true if payload_type == PayloadType::Compressed
+
+  false
+end
+
+
+ +
+

+ + #compression_typeObject + + + + + +

+ + + + +
+
+
+
+160
+161
+162
+163
+164
+
+
# File 'lib/juno/IO/PayloadComponent.rb', line 160
+
+def compression_type
+  return payload_data.compression_type if compressed?
+
+  CompressionType::None
+end
+
+
+ +
+

+ + #custom_num_bytesObject + + + + + +

+
+ +

to prevent stack overflow

+ + +
+
+
+ + +
+ + + + +
+
+
+
+103
+104
+105
+106
+107
+
+
# File 'lib/juno/IO/PayloadComponent.rb', line 103
+
+def custom_num_bytes
+  size = component_size.num_bytes + tag_id.num_bytes + namespace_length.num_bytes + key_length.num_bytes + payload_length.num_bytes + namespace.num_bytes + payload_key.num_bytes
+  size += payload_type.num_bytes + payload_data.num_bytes if payload_length.positive?
+  size
+end
+
+
+ +
+

+ + #get_payload_data_lengthObject + + + + + +

+ + + + +
+
+
+
+98
+99
+100
+
+
# File 'lib/juno/IO/PayloadComponent.rb', line 98
+
+def get_payload_data_length
+  (payload_length.positive? ? payload_length - 1 : 0)
+end
+
+
+ +
+

+ + #padding_lengthObject + + + + + +

+ + + + +
+
+
+
+109
+110
+111
+
+
# File 'lib/juno/IO/PayloadComponent.rb', line 109
+
+def padding_length
+  (8 - custom_num_bytes % 8) % 8
+end
+
+
+ +
+

+ + #set_value(input_value, compression_type = CompressionType::None) ⇒ Object + + + + + +

+ + + + +
+
+
+
+140
+141
+142
+143
+144
+145
+146
+147
+148
+
+
# File 'lib/juno/IO/PayloadComponent.rb', line 140
+
+def set_value(input_value, compression_type = CompressionType::None)
+  if compression_type != CompressionType::None
+    self.payload_type = PayloadType::Compressed
+    payload_data.compression_type = compression_type
+  else
+    self.payload_type = PayloadType::UnCompressed
+  end
+  payload_data.data = input_value
+end
+
+
+ +
+

+ + #valueObject + + + + + +

+ + + + +
+
+
+
+150
+151
+152
+
+
# File 'lib/juno/IO/PayloadComponent.rb', line 150
+
+def value
+  payload_data.data
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/PayloadComponent/CompressedPayloadData.html b/client/Ruby/docs/Juno/IO/PayloadComponent/CompressedPayloadData.html new file mode 100644 index 00000000..8f58a9d8 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/PayloadComponent/CompressedPayloadData.html @@ -0,0 +1,193 @@ + + + + + + + Class: Juno::IO::PayloadComponent::CompressedPayloadData + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::PayloadComponent::CompressedPayloadData + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/PayloadComponent.rb
+
+ +
+ + + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + + +
+

Instance Method Details

+ + +
+

+ + #data_lengthObject + + + + + +

+ + + + +
+
+
+
+65
+66
+67
+
+
# File 'lib/juno/IO/PayloadComponent.rb', line 65
+
+def data_length
+  eval_parameter(:payload_data_length) - 1 - compression_type
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/PayloadComponent/EncryptedPayloadData.html b/client/Ruby/docs/Juno/IO/PayloadComponent/EncryptedPayloadData.html new file mode 100644 index 00000000..f2bb7e08 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/PayloadComponent/EncryptedPayloadData.html @@ -0,0 +1,124 @@ + + + + + + + Class: Juno::IO::PayloadComponent::EncryptedPayloadData + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::PayloadComponent::EncryptedPayloadData + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/PayloadComponent.rb
+
+ +
+ + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/PayloadComponent/UncompressedPayloadData.html b/client/Ruby/docs/Juno/IO/PayloadComponent/UncompressedPayloadData.html new file mode 100644 index 00000000..68f9f00c --- /dev/null +++ b/client/Ruby/docs/Juno/IO/PayloadComponent/UncompressedPayloadData.html @@ -0,0 +1,142 @@ + + + + + + + Class: Juno::IO::PayloadComponent::UncompressedPayloadData + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::PayloadComponent::UncompressedPayloadData + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/PayloadComponent.rb
+
+ +
+ +

Overview

+
+ +

encrypted_payload_data PayloadType::Encrypted, payload_data_length: lambda {

+ +
                                                                        get_payload_data_length
+                                                                      }
+end
+
+ +

end

+ + +
+
+
+ + +
+ + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/PayloadType.html b/client/Ruby/docs/Juno/IO/PayloadType.html new file mode 100644 index 00000000..e620dfe5 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/PayloadType.html @@ -0,0 +1,158 @@ + + + + + + + Class: Juno::IO::PayloadType + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::PayloadType + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/constants.rb
+
+ +
+ +

Overview

+
+ +

Class containing constants for PayloadType

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
UnCompressed = + +
+
0x00
+ +
Encrypted = + +
+
0x02
+ +
Compressed = + +
+
0x03
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/ProtocolHeader.html b/client/Ruby/docs/Juno/IO/ProtocolHeader.html new file mode 100644 index 00000000..5a973178 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/ProtocolHeader.html @@ -0,0 +1,329 @@ + + + + + + + Class: Juno::IO::ProtocolHeader + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::ProtocolHeader + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/ProtocolHeader.rb
+
+ +
+ +

Defined Under Namespace

+

+ + + + + Classes: MessageTypeFlag, MessageTypes, OpCodes, RequestTypes + + +

+ + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + + + + +
+

Instance Method Details

+ + +
+

+ + #message_typeObject + + + + + +

+ + + + +
+
+
+
+176
+177
+178
+
+
# File 'lib/juno/IO/ProtocolHeader.rb', line 176
+
+def message_type
+  message_type_flag.message_type
+end
+
+
+ +
+

+ + #request?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+155
+156
+157
+
+
# File 'lib/juno/IO/ProtocolHeader.rb', line 155
+
+def request?
+  message_type_flag.message_request_type != RequestTypes::Response
+end
+
+
+ +
+

+ + #request_typeObject + + + + + +

+ + + + +
+
+
+
+172
+173
+174
+
+
# File 'lib/juno/IO/ProtocolHeader.rb', line 172
+
+def request_type
+  message_type_flag.message_request_type
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/ProtocolHeader/MessageTypeFlag.html b/client/Ruby/docs/Juno/IO/ProtocolHeader/MessageTypeFlag.html new file mode 100644 index 00000000..0063eb4e --- /dev/null +++ b/client/Ruby/docs/Juno/IO/ProtocolHeader/MessageTypeFlag.html @@ -0,0 +1,124 @@ + + + + + + + Class: Juno::IO::ProtocolHeader::MessageTypeFlag + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::ProtocolHeader::MessageTypeFlag + + + +

+
+ +
+
Inherits:
+
+ BinData::Record + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/ProtocolHeader.rb
+
+ +
+ + + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/ProtocolHeader/MessageTypes.html b/client/Ruby/docs/Juno/IO/ProtocolHeader/MessageTypes.html new file mode 100644 index 00000000..8c1bf0b9 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/ProtocolHeader/MessageTypes.html @@ -0,0 +1,147 @@ + + + + + + + Class: Juno::IO::ProtocolHeader::MessageTypes + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::ProtocolHeader::MessageTypes + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/ProtocolHeader.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
OperationalMessage = + +
+
0
+ +
AdminMessage = + +
+
1
+ +
ClusterControlMessage = + +
+
2
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/ProtocolHeader/OpCodes.html b/client/Ruby/docs/Juno/IO/ProtocolHeader/OpCodes.html new file mode 100644 index 00000000..59fe3e80 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/ProtocolHeader/OpCodes.html @@ -0,0 +1,324 @@ + + + + + + + Class: Juno::IO::ProtocolHeader::OpCodes + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::ProtocolHeader::OpCodes + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/ProtocolHeader.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
Nop = + +
+
0x00
+ +
Create = + +
+
0x01
+ +
Get = + +
+
0x02
+ +
Update = + +
+
0x03
+ +
Set = + +
+
0x04
+ +
Destroy = + +
+
0x05
+ +
PrepareCreate = + +
+
0x81
+ +
Read = + +
+
0x82
+ +
PrepareUpdate = + +
+
0x83
+ +
PrepareSet = + +
+
0x84
+ +
PrepareDelete = + +
+
0x85
+ +
Delete = + +
+
0x86
+ +
Commit = + +
+
0xC1
+ +
Abort = + +
+
0xC2
+ +
Repair = + +
+
0xC3
+ +
MarkDelete = + +
+
0xC4
+ +
Clone = + +
+
0xE1
+ +
MockSetParam = + +
+
0xFE
+ +
MockReSet = + +
+
0xFF
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .valid?(opcode) ⇒ Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+142
+143
+144
+145
+146
+147
+
+
# File 'lib/juno/IO/ProtocolHeader.rb', line 142
+
+def self.valid?(opcode)
+  constants.each do |constant|
+    return true if const_get(constant) == opcode
+  end
+  false
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/IO/ProtocolHeader/RequestTypes.html b/client/Ruby/docs/Juno/IO/ProtocolHeader/RequestTypes.html new file mode 100644 index 00000000..d414bbf7 --- /dev/null +++ b/client/Ruby/docs/Juno/IO/ProtocolHeader/RequestTypes.html @@ -0,0 +1,147 @@ + + + + + + + Class: Juno::IO::ProtocolHeader::RequestTypes + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::IO::ProtocolHeader::RequestTypes + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/IO/ProtocolHeader.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
Response = + +
+
0
+ +
TwoWayRequest = + +
+
1
+ +
OneWayRequest = + +
+
2
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Logger.html b/client/Ruby/docs/Juno/Logger.html new file mode 100644 index 00000000..fee07851 --- /dev/null +++ b/client/Ruby/docs/Juno/Logger.html @@ -0,0 +1,307 @@ + + + + + + + Class: Juno::Logger + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Logger + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/logger.rb
+
+ +
+ +

Overview

+
+ +

DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
@@mutex = + +
+
Mutex.new
+ +
@@instance = +
+
+ +

Singleton instance

+ + +
+
+
+ + +
+
+
nil
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .instanceObject + + + + + +

+ + + + +
+
+
+
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
+
# File 'lib/juno/logger.rb', line 12
+
+def self.instance
+  return @@instance unless @@instance.nil?
+
+  @@mutex.synchronize do
+    if @@instance.nil?
+      raise 'log file not configured' if Juno.juno_config.log_file.to_s.empty?
+
+      @@instance = ::Logger.new(Juno.juno_config.log_file, 'daily', progname: 'JunoRubyClient')
+
+      @@instance.level = ::Logger::INFO
+    end
+  end
+  @@instance
+end
+
+
+ +
+

+ + .level=(log_level) ⇒ Object + + + + + +

+ + + + +
+
+
+
+27
+28
+29
+
+
# File 'lib/juno/logger.rb', line 27
+
+def self.level=(log_level)
+  @@instance.level = log_level unless @@instance.nil?
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Net.html b/client/Ruby/docs/Juno/Net.html new file mode 100644 index 00000000..d9786d61 --- /dev/null +++ b/client/Ruby/docs/Juno/Net.html @@ -0,0 +1,117 @@ + + + + + + + Module: Juno::Net + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Module: Juno::Net + + + +

+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Net/worker_pool.rb,
+ lib/juno/Net/io_processor.rb,
lib/juno/Net/ping_message.rb,
lib/juno/Net/request_queue.rb,
lib/juno/Net/base_processor.rb,
lib/juno/Net/client_handler.rb
+
+
+ +
+ +

Defined Under Namespace

+

+ + + + + Classes: BaseProcessor, ClientHandler, IOProcessor, PingMessage, QueueEntry, RequestQueue, WorkerPool + + +

+ + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Net/BaseProcessor.html b/client/Ruby/docs/Juno/Net/BaseProcessor.html new file mode 100644 index 00000000..f7a608b8 --- /dev/null +++ b/client/Ruby/docs/Juno/Net/BaseProcessor.html @@ -0,0 +1,404 @@ + + + + + + + Class: Juno::Net::BaseProcessor + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Net::BaseProcessor + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Net/base_processor.rb
+
+ +
+ +

Overview

+
+ +

BaseProcessor - base class for IOProcessor Handles logic for reading and writing ping_ip for bypass ltm

+ + +
+
+
+ + +
+

Direct Known Subclasses

+

IOProcessor

+
+ + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(_ = nil) ⇒ BaseProcessor + + + + + +

+
+ +

Returns a new instance of BaseProcessor.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+8
+9
+10
+
+
# File 'lib/juno/Net/base_processor.rb', line 8
+
+def initialize(_ = nil)
+  @ping_queue = SizedQueue.new(1)
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #clear_ping_queueObject + + + + + +

+ + + + +
+
+
+
+32
+33
+34
+
+
# File 'lib/juno/Net/base_processor.rb', line 32
+
+def clear_ping_queue
+  @ping_queue.clear
+end
+
+
+ +
+

+ + #ping_ipObject + + + + + +

+ + + + +
+
+
+
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
+
# File 'lib/juno/Net/base_processor.rb', line 16
+
+def ping_ip
+  begin
+    ip = @ping_queue.pop(true)
+  rescue ThreadError
+    return nil
+  end
+
+  return nil if ip.to_s.empty?
+
+  begin
+    IPAddr.new(ip)
+  rescue StandardError
+    nil
+  end
+end
+
+
+ +
+

+ + #ping_ip=(ip) ⇒ Object + + + + + +

+ + + + +
+
+
+
+12
+13
+14
+
+
# File 'lib/juno/Net/base_processor.rb', line 12
+
+def ping_ip=(ip)
+  @ping_queue.push(ip)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Net/ClientHandler.html b/client/Ruby/docs/Juno/Net/ClientHandler.html new file mode 100644 index 00000000..bebd3697 --- /dev/null +++ b/client/Ruby/docs/Juno/Net/ClientHandler.html @@ -0,0 +1,1143 @@ + + + + + + + Class: Juno::Net::ClientHandler + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Net::ClientHandler + + + +

+
+ +
+
Inherits:
+
+ EventMachine::Connection + +
    +
  • Object
  • + + + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Net/client_handler.rb
+
+ +
+ +

Overview

+
+ +

Hanldes connection creation and receiving data asynchronously Managed by EventMachine::Connection

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
@@recv_count = +
+
+ +

Constant to count messages received

+ + +
+
+
+ + +
+
+
Concurrent::AtomicFixnum.new(0)
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + + +
+

Constructor Details

+ +
+

+ + #initialize(io_processor) ⇒ ClientHandler + + + + + +

+
+ +

Constructor

+ + +
+
+
+

Parameters:

+ + + +
+ + + + +
+
+
+
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
+
# File 'lib/juno/Net/client_handler.rb', line 17
+
+def initialize(io_processor)
+  super
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @io_processor = io_processor
+  @channel = self
+  @connected = Concurrent::AtomicBoolean.new(false)
+  @ssl_connected = Concurrent::AtomicBoolean.new(false)
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .received_messagesObject + + + + + +

+ + + + +
+
+
+
+11
+12
+13
+
+
# File 'lib/juno/Net/client_handler.rb', line 11
+
+def self.received_messages
+  @@recv_count.value
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #connection_completedObject + + + + + +

+
+ +

Called by EventMachine after TCP Connection estabilished

+ + +
+
+
+ + +
+ + + + +
+
+
+
+92
+93
+94
+95
+
+
# File 'lib/juno/Net/client_handler.rb', line 92
+
+def connection_completed
+  @connected.value = true
+  on_connection_completed unless use_ssl?
+end
+
+
+ +
+

+ + #is_connected?Boolean + + + + + +

+
+ +

method to check if channel is connected

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+
+
# File 'lib/juno/Net/client_handler.rb', line 55
+
+def is_connected?
+  if use_ssl?
+    return false if @ssl_connected.false?
+  elsif @connected.false?
+    return false
+  end
+
+  # get ip and port of server
+  # Socket.unpack_sockaddr_in(@channel.get_peername)
+  true
+rescue Exception => e
+  @LOGGER.error(@PROG_NAME) { e.message }
+  false
+end
+
+
+ +
+

+ + #on_connection_completedObject + + + + + +

+
+ +

Method called when TCP connection estabilished. If useSSL is true, it is called after a successfull ssl handshake

+ + +
+
+
+ + +
+ + + + +
+
+
+
+50
+51
+52
+
+
# File 'lib/juno/Net/client_handler.rb', line 50
+
+def on_connection_completed
+  # puts "completed #{Time.now}"
+end
+
+
+ +
+

+ + #post_initObject + + + + + +

+
+ +

Method called once for each instance of Juno::Net::ClientHandler at initialization

+ + +
+
+
+ + +
+ + + + +
+
+
+
+28
+29
+30
+
+
# File 'lib/juno/Net/client_handler.rb', line 28
+
+def post_init
+  start_tls_connection if use_ssl?
+end
+
+
+ +
+

+ + #receive_data(data) ⇒ Object + + + + + +

+
+ +

method called by EventMachine when data is received from server

+ + +
+
+
+

Parameters:

+
    + +
  • + + data + + + (String) + + + + — +
    • +

      byte data received from server

      +
    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+
+
# File 'lib/juno/Net/client_handler.rb', line 80
+
+def receive_data(data)
+  @@recv_count.increment
+  # puts @@recv_count
+
+  EventMachine.defer do
+    operation_message = Juno::IO::OperationMessage.new
+    operation_message.read(StringIO.new(data))
+    @io_processor.put_response(operation_message)
+  end
+end
+
+
+ +
+

+ + #ssl_handshake_completedObject + + + + + +

+
+ +

Called by EventMachine after ssl handshake

+ + +
+
+
+ + +
+ + + + +
+
+
+
+106
+107
+108
+109
+110
+111
+112
+113
+
+
# File 'lib/juno/Net/client_handler.rb', line 106
+
+def ssl_handshake_completed
+  @ssl_connected.value = true
+  on_connection_completed if use_ssl?
+
+  # puts get_cipher_name
+  # puts get_cipher_protocol
+  @server_handshake_completed = true
+end
+
+
+ +
+

+ + #start_tls_connectionObject + + + + + +

+
+ +

starts tls connection once TCP connection is estabilished

+ + +
+
+
+ + +
+ + + + +
+
+
+
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+
+
# File 'lib/juno/Net/client_handler.rb', line 33
+
+def start_tls_connection
+  raise 'SSL Cert file not found' unless File.exist?(Juno.juno_config.ssl_cert_file)
+  raise 'SSL Key file not found' unless File.exist?(Juno.juno_config.ssl_key_file)
+
+  @channel.start_tls(
+    private_key_file: Juno.juno_config.ssl_key_file, cert: File.read(Juno.juno_config.ssl_cert_file)
+  )
+  # Timer to check if SSL Handshake was successful
+  EventMachine::Timer.new(Juno.juno_config.max_connection_timeout.to_f / 1000) do
+    if @ssl_connected.false?
+      puts 'SLL Handshake timeout'
+      close_connection
+    end
+  end
+end
+
+
+ +
+

+ + #unbind(error) ⇒ Object + + + + + +

+
+ +

Called by EventMachine after connection disconnected

+ + +
+
+
+

Parameters:

+
    + +
  • + + m + + + + + + + — +
    • +

      Error if disconnected due to an error

      +
    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+99
+100
+101
+102
+103
+
+
# File 'lib/juno/Net/client_handler.rb', line 99
+
+def unbind(error)
+  @connected.value = false
+  @ssl_connected.value = false
+  puts error unless error.nil?
+end
+
+
+ +
+

+ + #use_ltm?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+74
+75
+76
+
+
# File 'lib/juno/Net/client_handler.rb', line 74
+
+def use_ltm?
+  Juno.juno_config.bypass_ltm
+end
+
+
+ +
+

+ + #use_ssl?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+70
+71
+72
+
+
# File 'lib/juno/Net/client_handler.rb', line 70
+
+def use_ssl?
+  Juno.juno_config.use_ssl
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Net/IOProcessor.html b/client/Ruby/docs/Juno/Net/IOProcessor.html new file mode 100644 index 00000000..8201fc68 --- /dev/null +++ b/client/Ruby/docs/Juno/Net/IOProcessor.html @@ -0,0 +1,1536 @@ + + + + + + + Class: Juno::Net::IOProcessor + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Net::IOProcessor + + + +

+
+ +
+
Inherits:
+
+ BaseProcessor + + + show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Net/io_processor.rb
+
+ +
+ +

Overview

+
+ +

Module to handle connections to server, reading/writing requests from request queue

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
INITIAL_BYPASSLTM_RETRY_INTERVAL = + +
+
337_500
+ +
MAX_BYPASSLTM_RETRY_INTERVAL = + +
+
86_400_000
+ +
INIT_WAIT_TIME = + +
+
200
+ +
MAX_WAIT_TIME = + +
+
60_000
+ +
@@counter = +
+
+ +

class variable to count messages sent using send_data

+ + +
+
+
+ + +
+
+
Concurrent::AtomicFixnum.new(0)
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + + + + + + + + + + +

Methods inherited from BaseProcessor

+

#clear_ping_queue, #ping_ip, #ping_ip=

+
+

Constructor Details

+ +
+

+ + #initialize(request_queue, opaque_resp_queue_map) ⇒ IOProcessor + + + + + +

+
+ +

Returns a new instance of IOProcessor.

+ + +
+
+
+

Parameters:

+
    + +
  • + + request_queue + + + (Juno::Net::RequestQueue) + + + +
  • + +
  • + + opaque_resp_queue_map + + + (Concurrent::Map) + + + + — +
    • +

      map containing opaque as key and value as Response queue corresponding to opaque

      +
    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+
+
# File 'lib/juno/Net/io_processor.rb', line 20
+
+def initialize(request_queue, opaque_resp_queue_map)
+  super()
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @stop = false
+  @request_queue = request_queue
+  @opaque_resp_queue_map = opaque_resp_queue_map
+  @ctx = nil ## changed
+  @handshake_failed_attempts = 0
+  @reconnect_wait_time = INIT_WAIT_TIME
+  @shift = 5 # seconds
+  @next_bypass_ltm_check_time = Time.now
+  @bypass_ltm_retry_interval = INITIAL_BYPASSLTM_RETRY_INTERVAL
+  # @config = config
+  @channel = nil
+  @next_reconnect_due = Float::INFINITY
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .send_countObject + + + + + +

+ + + + +
+
+
+
+14
+15
+16
+
+
# File 'lib/juno/Net/io_processor.rb', line 14
+
+def self.send_count
+  @@counter.value
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #bypass_ltm_disabled?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+202
+203
+204
+205
+206
+207
+208
+
+
# File 'lib/juno/Net/io_processor.rb', line 202
+
+def bypass_ltm_disabled?
+  if Time.now > @next_bypass_ltm_check_time && @bypass_ltm_retry_interval < MAX_BYPASSLTM_RETRY_INTERVAL
+    return false
+  end
+
+  true
+end
+
+
+ +
+

+ + #connection_lifetimeObject + + + + + +

+ + + + +
+
+
+
+38
+39
+40
+
+
# File 'lib/juno/Net/io_processor.rb', line 38
+
+def connection_lifetime
+  Juno.juno_config.connection_lifetime
+end
+
+
+ +
+

+ + #disconnect_channel(channel) ⇒ Object + + + + + +

+ + + + +
+
+
+
+63
+64
+65
+66
+67
+
+
# File 'lib/juno/Net/io_processor.rb', line 63
+
+def disconnect_channel(channel)
+  EventMachine::Timer.new(2 * Juno.juno_config.max_response_timeout.to_f / 1000) do
+    channel&.close_connection_after_writing if !channel.nil? && channel.is_connected?
+  end
+end
+
+
+ +
+

+ + #hostObject + + + + + +

+ + + + +
+
+
+
+190
+191
+192
+
+
# File 'lib/juno/Net/io_processor.rb', line 190
+
+def host
+  Juno.juno_config.host
+end
+
+
+ +
+

+ + #initiate_bypass_ltmObject + + + + + +

+ + + + +
+
+
+
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+
+
# File 'lib/juno/Net/io_processor.rb', line 75
+
+def initiate_bypass_ltm
+  send_ping_message
+  EventMachine::Timer.new(Juno.juno_config.response_timeout.to_f / 1000) do
+    ip = ping_ip
+    unless ip.nil?
+      new_channel = EventMachine.connect(ip.to_s, Juno.juno_config.port, ClientHandler, self)
+      EventMachine::Timer.new(Juno.juno_config.connection_timeout.to_f / 1000) do
+        if new_channel.is_connected?
+          @LOGGER.info(@PROG_NAME) { "conncected to Proxy #{ip}:#{Juno.juno_config.port} " }
+          old_channel = @channel
+          @channel = new_channel
+          disconnect_channel(old_channel)
+        else
+          @LOGGER.info(@PROG_NAME) { "could not conncect to Proxy #{ip}:#{Juno.juno_config.port} " }
+        end
+      end
+    end
+  end
+end
+
+
+ +
+

+ + #juno_connect(recycle = false) ⇒ Object + + + + + +

+
+ +

Method to handle connections creation, re-attempts on failure, initiates connection refresh and connection to Proxy

+ + +
+
+
+

Parameters:

+
    + +
  • + + recycle + + + (Boolean) + + + (defaults to: false) + + + — +
    • +

      True if connection refresh request (optional, default: false)

      +
    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+
+
# File 'lib/juno/Net/io_processor.rb', line 107
+
+def juno_connect(recycle = false)
+  return if !recycle && !@channel.nil? && @channel.is_connected?
+
+  new_channel = EventMachine.connect(Juno.juno_config.host, Juno.juno_config.port, ClientHandler, self)
+  new_channel.pending_connect_timeout = Juno.juno_config.connection_lifetime
+  EventMachine::Timer.new(Juno.juno_config.connection_timeout.to_f / 1000) do
+    if new_channel.is_connected?
+      @LOGGER.info(@PROG_NAME) { "conncected to #{Juno.juno_config.host}:#{Juno.juno_config.port} " }
+      if recycle
+        old_channel = @channel
+        @channel = new_channel
+        disconnect_channel(old_channel)
+      else
+        @channel = new_channel
+      end
+      initiate_bypass_ltm if use_ltm?
+      set_recycle_timer
+    else
+      @recycle_timer&.cancel
+      new_channel&.close_connection if !new_channel.nil? && new_channel.is_connected?
+      @LOGGER.info(@PROG_NAME) do
+        "Could not conncect to #{Juno.juno_config.host}:#{Juno.juno_config.port}\n Retrying in #{@reconnect_wait_time.to_f / 1000}ms "
+      end
+      EventMachine::Timer.new(@reconnect_wait_time.to_f / 1000) do
+        @reconnect_wait_time *= 2
+        @reconnect_wait_time = MAX_WAIT_TIME if @reconnect_wait_time > MAX_WAIT_TIME
+        @reconnect_wait_time *= (1 + 0.3 * rand)
+        juno_connect(recycle)
+      end
+    end
+  end
+end
+
+
+ +
+

+ + #portObject + + + + + +

+ + + + +
+
+
+
+194
+195
+196
+
+
# File 'lib/juno/Net/io_processor.rb', line 194
+
+def port
+  Juno.juno_config.port
+end
+
+
+ +
+

+ + #put_response(operation_message) ⇒ Object + + + + + +

+ + + + +
+
+
+
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+
+
# File 'lib/juno/Net/io_processor.rb', line 42
+
+def put_response(operation_message)
+  return if operation_message.nil?
+
+  unless Juno::Net::PingMessage.ping_response?(operation_message, self)
+    opaque = operation_message&.protocol_header&.opaque
+    return if opaque.nil?
+
+    resp_queue = @opaque_resp_queue_map.get_and_set(opaque.to_i, nil)
+    if !resp_queue.nil?
+      begin
+        resp_queue.push(operation_message)
+      rescue ThreadError
+        @LOGGER.debug(@PROG_NAME) { "response queue for #{opaque.to_i} is full" }
+      end
+    else
+      @LOGGER.debug(@PROG_NAME) { "resp_queue nil for #{opaque.to_i}" }
+    end
+  end
+  nil
+end
+
+
+ +
+

+ + #reset_connectionsObject + + + + + +

+ + + + +
+
+
+
+181
+182
+183
+184
+
+
# File 'lib/juno/Net/io_processor.rb', line 181
+
+def reset_connections
+  @recycle_timer&.cancel
+  disconnect_channel(@channel)
+end
+
+
+ +
+

+ + #runObject + + + + + +

+
+ +

Event loop to continously check for requests in @request_queue

+ + +
+
+
+ + +
+ + + + +
+
+
+
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+
+
# File 'lib/juno/Net/io_processor.rb', line 145
+
+def run
+  EventMachine.run do
+    juno_connect
+    EventMachine.tick_loop do
+      if !@channel.nil? && @channel.is_connected?
+        # key = "19key#{rand(100) + rand(1_000_000)}"
+        item = @request_queue.pop
+        unless item.nil?
+          @@counter.increment
+          @channel.send_data(item.msg_buffer)
+        end
+      end
+      :stop if @stop == true
+    rescue Exception => e
+      @LOGGER.error(@PROG_NAME) do
+        "Error in tick_loop: #{e.message}. Stopping tick_loop"
+      end
+      :stop
+    end.on_stop do
+      @LOGGER.debug(@PROG_NAME) do
+        "tick loop stopped. Stop initiated by client #{@stop}"
+      end
+      reset_connections
+      EventMachine::Timer.new(2 * Juno.juno_config.connection_timeout.to_f / 1000) do
+        EventMachine.stop
+      end
+    end
+  rescue Exception => e
+    @LOGGER.debug(@PROG_NAME) do
+      "EventMachine Fatal Exception #{e.message}"
+    end
+    reset_connections
+    EventMachine.stop
+  end
+end
+
+
+ +
+

+ + #send_ping_messageObject + + + + + +

+
+ +

Sends ping message to LoadBalancer to get ProxyIP

+ + +
+
+
+ + +

See Also:

+ + +
+ + + + +
+
+
+
+97
+98
+99
+100
+101
+102
+103
+
+
# File 'lib/juno/Net/io_processor.rb', line 97
+
+def send_ping_message
+  ping_message = Juno::Net::PingMessage.new
+  buff = StringIO.new
+  ping_message.write(buff)
+  request_uuid = ping_message&.operation_message&.&.request_uuid.to_s
+  @request_queue.push(buff.string, request_uuid)
+end
+
+
+ +
+

+ + #set_recycle_timerObject + + + + + +

+ + + + +
+
+
+
+69
+70
+71
+72
+73
+
+
# File 'lib/juno/Net/io_processor.rb', line 69
+
+def set_recycle_timer
+  @recycle_timer = EventMachine::Timer.new(Juno.juno_config.connection_lifetime.to_f / 1000) do
+    juno_connect(true)
+  end
+end
+
+
+ +
+

+ + #stopObject + + + + + +

+ + + + +
+
+
+
+140
+141
+142
+
+
# File 'lib/juno/Net/io_processor.rb', line 140
+
+def stop
+  @stop = true
+end
+
+
+ +
+

+ + #use_ltm?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+198
+199
+200
+
+
# File 'lib/juno/Net/io_processor.rb', line 198
+
+def use_ltm?
+  host != '127.0.0.1' && Juno.juno_config.bypass_ltm # boolean
+end
+
+
+ +
+

+ + #use_ssl?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+186
+187
+188
+
+
# File 'lib/juno/Net/io_processor.rb', line 186
+
+def use_ssl?
+  Juno.juno_config.use_ssl
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Net/PingMessage.html b/client/Ruby/docs/Juno/Net/PingMessage.html new file mode 100644 index 00000000..13ea3377 --- /dev/null +++ b/client/Ruby/docs/Juno/Net/PingMessage.html @@ -0,0 +1,864 @@ + + + + + + + Class: Juno::Net::PingMessage + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Net::PingMessage + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Net/ping_message.rb
+
+ +
+ +

Overview

+
+ +

Module to Create/Decode Ping messages

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
JUNO_INTERNAL_APPNAME = +
+
+ +

Constant Internal app name to check for ping messages

+ + +
+
+
+ + +
+
+
'JunoInternal'
+ +
+ + + + + +

Instance Attribute Summary collapse

+ + + + + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(app_name = nil, opaque = 0) ⇒ PingMessage + + + + + +

+
+ +

Returns a new instance of PingMessage.

+ + +
+
+
+

Parameters:

+
    + +
  • + + operation_message + + + (Juno::IO::OperationMessage) + + + + — +
    +

    (optional, default: Juno::Net::PingMessage::JUNO_INTERNAL_APPNAME)

    +
    + +
  • + +
  • + + opaque + + + (Integer) + + + (defaults to: 0) + + + — +
    +

    (optional, default: 0)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
+
# File 'lib/juno/Net/ping_message.rb', line 15
+
+def initialize(app_name = nil, opaque = 0)
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  app_name = JUNO_INTERNAL_APPNAME if app_name.to_s.empty?
+
+   = Juno::IO::MetadataComponent.new
+  .set_request_uuid
+  .set_source_info(app_name: app_name, ip: IPAddr.new(Juno::Utils.local_ips[0]), port: 0)
+
+  protocol_header = Juno::IO::ProtocolHeader.new
+  protocol_header.opcode = Juno::IO::ProtocolHeader::OpCodes::Nop
+  protocol_header.opaque = opaque
+
+  @operation_message = Juno::IO::OperationMessage.new
+  @operation_message. = 
+  @operation_message.protocol_header = protocol_header
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #operation_messageObject (readonly) + + + + + +

+
+ +

method to read the operation message

+ + +
+
+
+ + +
+ + + + +
+
+
+
+11
+12
+13
+
+
# File 'lib/juno/Net/ping_message.rb', line 11
+
+def operation_message
+  @operation_message
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .ping_response?(operation_message, processor) ⇒ Boolean + + + + + +

+
+ +

method to check if given operation message is a Ping response Updates ping_ip in processor if it is a ping response

+ + +
+
+
+

Parameters:

+ + +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+
+
# File 'lib/juno/Net/ping_message.rb', line 37
+
+def self.ping_response?(operation_message, processor)
+  return false unless processor.use_ltm?
+
+  opcode = operation_message&.protocol_header&.opcode
+  raise 'missing protocol header' if opcode.nil?
+  return false if opcode != Juno::IO::ProtocolHeader::OpCodes::Nop
+  return false if operation_message&..nil?
+  return false if operation_message&.&.ip.to_s.empty?
+  return false if operation_message&.&.app_name != JUNO_INTERNAL_APPNAME
+
+  ping_ip = operation_message..ip.to_s
+  if ping_ip.split('.')[0] == '127'
+    processor.ping_ip = ''
+    return true
+  end
+  if Juno::Utils.local_ips.include?(ping_ip)
+    processor.ping_ip = ''
+    return true
+  end
+  processor.ping_ip = ping_ip
+  true
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #ipObject + + + + + +

+
+ +

Function to read the ping ip

+ + +
+
+
+ + +
+ + + + +
+
+
+
+74
+75
+76
+
+
# File 'lib/juno/Net/ping_message.rb', line 74
+
+def ip
+  @operation_message&.&.ip
+end
+
+
+ +
+

+ + #portObject + + + + + +

+
+ +

Function to read the port from metadata component

+ + +
+
+
+ + +
+ + + + +
+
+
+
+79
+80
+81
+
+
# File 'lib/juno/Net/ping_message.rb', line 79
+
+def port
+  @operation_message&.&.port
+end
+
+
+ +
+

+ + #read(buf) ⇒ Object + + + + + +

+
+ +

Function to de-serialize Component from buffer

+ + +
+
+
+

Parameters:

+
    + +
  • + + buff + + + (StringIO) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+68
+69
+70
+71
+
+
# File 'lib/juno/Net/ping_message.rb', line 68
+
+def read(buf)
+  @operation_message = Juno::IO::OperationMessage.new
+  @operation_message.read(buf)
+end
+
+
+ +
+

+ + #write(buf) ⇒ Object + + + + + +

+
+ +

Function to serialize Component to buffer

+ + +
+
+
+

Parameters:

+
    + +
  • + + buff + + + (StringIO) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+62
+63
+64
+
+
# File 'lib/juno/Net/ping_message.rb', line 62
+
+def write(buf)
+  @operation_message.write(buf)
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Net/QueueEntry.html b/client/Ruby/docs/Juno/Net/QueueEntry.html new file mode 100644 index 00000000..849f02e9 --- /dev/null +++ b/client/Ruby/docs/Juno/Net/QueueEntry.html @@ -0,0 +1,475 @@ + + + + + + + Class: Juno::Net::QueueEntry + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Net::QueueEntry + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Net/request_queue.rb
+
+ +
+ +

Overview

+
+ +

DataType of each item in RequestQueue

+ + +
+
+
+ + +
+ + + +

Instance Attribute Summary collapse

+
    + +
  • + + + #enqueue_time ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute enqueue_time.

    +
    + +
  • + + +
  • + + + #msg_buffer ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute msg_buffer.

    +
    + +
  • + + +
  • + + + #req_id ⇒ Object + + + + + + + + + + + + + + + + +
    +

    Returns the value of attribute req_id.

    +
    + +
  • + + +
+ + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(msg_buffer, id) ⇒ QueueEntry + + + + + +

+
+ +

Returns a new instance of QueueEntry.

+ + +
+
+
+

Parameters:

+
    + +
  • + + msg_bugger + + + (StringIO) + + + + — +
    +

    (required)

    +
    + +
  • + +
  • + + id + + + (String) + + + + — +
    • +

      request UUID (required)

      +
    +
    + +
  • + +
+ + +
+ + + + +
+
+
+
+11
+12
+13
+14
+15
+
+
# File 'lib/juno/Net/request_queue.rb', line 11
+
+def initialize(msg_buffer, id)
+  @msg_buffer = msg_buffer
+  @req_id = id
+  @enqueue_time = Time.now
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #enqueue_timeObject + + + + + +

+
+ +

Returns the value of attribute enqueue_time.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/Net/request_queue.rb', line 7
+
+def enqueue_time
+  @enqueue_time
+end
+
+
+ + + +
+

+ + #msg_bufferObject + + + + + +

+
+ +

Returns the value of attribute msg_buffer.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/Net/request_queue.rb', line 7
+
+def msg_buffer
+  @msg_buffer
+end
+
+
+ + + +
+

+ + #req_idObject + + + + + +

+
+ +

Returns the value of attribute req_id.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+7
+8
+9
+
+
# File 'lib/juno/Net/request_queue.rb', line 7
+
+def req_id
+  @req_id
+end
+
+
+ +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Net/RequestQueue.html b/client/Ruby/docs/Juno/Net/RequestQueue.html new file mode 100644 index 00000000..8bec3653 --- /dev/null +++ b/client/Ruby/docs/Juno/Net/RequestQueue.html @@ -0,0 +1,826 @@ + + + + + + + Class: Juno::Net::RequestQueue + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Net::RequestQueue + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Net/request_queue.rb
+
+ +
+ +

Overview

+
+ +

Request queue - Singleton

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
@@mutex = +
+
+ +

mutex to synchronize creation of RequestQueue instance

+ + +
+
+
+ + +
+
+
Mutex.new
+ +
@@instance = +
+
+ +

Singleton instance

+ + +
+
+
+ + +
+
+
nil
+ +
+ + + + + +

Instance Attribute Summary collapse

+ + + + + + +

+ Class Method Summary + collapse +

+ + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initializeRequestQueue + + + + + +

+
+ +

Returns a new instance of RequestQueue.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+38
+39
+40
+41
+42
+43
+44
+45
+
+
# File 'lib/juno/Net/request_queue.rb', line 38
+
+def initialize
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @size = 13_000 # Default request queue size
+  @request_queue = SizedQueue.new(@size)
+  @opaque_resp_queue_map = Concurrent::Map.new
+  @worker_pool = Juno::Net::WorkerPool.new(self)
+end
+
+
+ +
+ +
+

Instance Attribute Details

+ + + +
+

+ + #opaque_resp_queue_mapObject (readonly) + + + + + +

+
+ +

Returns the value of attribute opaque_resp_queue_map.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+36
+37
+38
+
+
# File 'lib/juno/Net/request_queue.rb', line 36
+
+def opaque_resp_queue_map
+  @opaque_resp_queue_map
+end
+
+
+ +
+ + +
+

Class Method Details

+ + +
+

+ + .instanceObject + + + + + +

+ + + + +
+
+
+
+26
+27
+28
+29
+30
+31
+32
+
+
# File 'lib/juno/Net/request_queue.rb', line 26
+
+def self.instance
+  return @@instance unless @@instance.nil?
+
+  @@mutex.synchronize do
+    @@instance ||= new
+  end
+end
+
+
+ +
+ +
+

Instance Method Details

+ + +
+

+ + #full?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+47
+48
+49
+
+
# File 'lib/juno/Net/request_queue.rb', line 47
+
+def full?
+  @request_queue.size == @size
+end
+
+
+ +
+

+ + #popQueueEntry + + + + + +

+
+ +

Returns - nil if queue empty.

+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (QueueEntry) + + + + — +
    • +

      nil if queue empty

      +
    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+77
+78
+79
+80
+81
+82
+
+
# File 'lib/juno/Net/request_queue.rb', line 77
+
+def pop
+  @request_queue.pop(true)
+rescue ThreadError
+  # queue empty
+  nil
+end
+
+
+ +
+

+ + #push(msg_buffer, request_uuid) ⇒ Boolean + + + + + +

+
+ +

Returns - true if successfully pushed item to queue. Else false.

+ + +
+
+
+

Parameters:

+
    + +
  • + + operation_message + + + (Juno::IO::OperatioinMessage) + + + + — +
    +

    (required)

    +
    + +
  • + +
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + + — +
    • +

      true if successfully pushed item to queue. Else false

      +
    +
    + +
  • + +
+ +
+ + + + +
+
+
+
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+
+
# File 'lib/juno/Net/request_queue.rb', line 62
+
+def push(msg_buffer, request_uuid)
+  # buffer = StringIO.new
+  # operation_message.write(buffer)
+  # request_uuid = operation_message&.metadata_component&.request_uuid.to_s
+  request_uuid = 'not_set' if request_uuid.to_s.empty?
+
+  begin
+    @request_queue.push(QueueEntry.new(msg_buffer, request_uuid), true)
+  rescue ThreadError
+    return false
+  end
+  true
+end
+
+
+ +
+

+ + #sizeObject + + + + + +

+ + + + +
+
+
+
+51
+52
+53
+
+
# File 'lib/juno/Net/request_queue.rb', line 51
+
+def size
+  @request_queue.size
+end
+
+
+ +
+

+ + #stopObject + + + + + +

+ + + + +
+
+
+
+55
+56
+57
+58
+
+
# File 'lib/juno/Net/request_queue.rb', line 55
+
+def stop
+  @worker_pool.stop
+  @request_queue.clear
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Net/WorkerPool.html b/client/Ruby/docs/Juno/Net/WorkerPool.html new file mode 100644 index 00000000..7c85a5ae --- /dev/null +++ b/client/Ruby/docs/Juno/Net/WorkerPool.html @@ -0,0 +1,405 @@ + + + + + + + Class: Juno::Net::WorkerPool + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Net::WorkerPool + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Net/worker_pool.rb
+
+ +
+ + + + + + + + + +

+ Instance Method Summary + collapse +

+ + + + +
+

Constructor Details

+ +
+

+ + #initialize(request_queue) ⇒ WorkerPool + + + + + +

+
+ +

Returns a new instance of WorkerPool.

+ + +
+
+
+ + +
+ + + + +
+
+
+
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
+
# File 'lib/juno/Net/worker_pool.rb', line 6
+
+def initialize(request_queue)
+  raise 'Request queue cannot be nil' if request_queue.nil?
+
+  @PROG_NAME = self.class.name
+  @LOGGER = Juno::Logger.instance
+  @request_queue = request_queue
+  EventMachine.threadpool_size = 200
+  @io_processor = Juno::Net::IOProcessor.new(@request_queue, @request_queue.opaque_resp_queue_map)
+  init
+end
+
+
+ +
+ + +
+

Instance Method Details

+ + +
+

+ + #active?Boolean + + + + + +

+
+ + +
+
+
+ +

Returns:

+
    + +
  • + + + (Boolean) + + + +
  • + +
+ +
+ + + + +
+
+
+
+23
+24
+25
+
+
# File 'lib/juno/Net/worker_pool.rb', line 23
+
+def active?
+  @worker.alive?
+end
+
+
+ +
+

+ + #initObject + + + + + +

+ + + + +
+
+
+
+17
+18
+19
+20
+21
+
+
# File 'lib/juno/Net/worker_pool.rb', line 17
+
+def init
+  @worker = Thread.new do
+    @io_processor.run
+  end
+end
+
+
+ +
+

+ + #stopObject + + + + + +

+ + + + +
+
+
+
+27
+28
+29
+
+
# File 'lib/juno/Net/worker_pool.rb', line 27
+
+def stop
+  @io_processor.stop
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Properties.html b/client/Ruby/docs/Juno/Properties.html new file mode 100644 index 00000000..96f382db --- /dev/null +++ b/client/Ruby/docs/Juno/Properties.html @@ -0,0 +1,269 @@ + + + + + + + Class: Juno::Properties + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Properties + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Config/properties.rb
+
+ +
+ +

Overview

+
+ +

Module for properties key (config file)

+ + +
+
+
+ + +
+ +

+ Constant Summary + collapse +

+ +
+ +
RESPONSE_TIMEOUT = + +
+
'juno.response.timeout_msec'
+ +
CONNECTION_TIMEOUT = + +
+
'juno.connection.timeout_msec'
+ +
DEFAULT_LIFETIME = + +
+
'juno.default_record_lifetime_sec'
+ +
CONNECTION_LIFETIME = + +
+
'juno.connection.recycle_duration_msec'
+ +
CONNECTION_POOLSIZE = + +
+
'juno.connection.pool_size'
+ +
RECONNECT_ON_FAIL = + +
+
'juno.connection.reconnect_on_fail'
+ +
HOST = + +
+
'juno.server.host'
+ +
PORT = + +
+
'juno.server.port'
+ +
APP_NAME = + +
+
'juno.application_name'
+ +
RECORD_NAMESPACE = + +
+
'juno.record_namespace'
+ +
USE_SSL = + +
+
'juno.useSSL'
+ +
USE_PAYLOAD_COMPRESSION = + +
+
'juno.usePayloadCompression'
+ +
ENABLE_RETRY = + +
+
'juno.operation.retry'
+ +
BYPASS_LTM = + +
+
'juno.connection.byPassLTM'
+ +
CONFIG_PREFIX = + +
+
'prefix'
+ +
MAX_LIFETIME = +
+
+ +

Max for each property

+ + +
+
+
+ + +
+
+
'juno.max_record_lifetime_sec'
+ +
MAX_KEY_SIZE = + +
+
'juno.max_key_size'
+ +
MAX_VALUE_SIZE = + +
+
'juno.max_value_size'
+ +
MAX_RESPONSE_TIMEOUT = + +
+
'juno.response.max_timeout_msec'
+ +
MAX_CONNECTION_TIMEOUT = + +
+
'juno.connection.max_timeout_msec'
+ +
MAX_CONNECTION_LIFETIME = + +
+
'juno.connection.max_recycle_duration_msec'
+ +
MAX_CONNECTION_POOL_SIZE = + +
+
'juno.connection.max_pool_size'
+ +
MAX_NAMESPACE_LENGTH = + +
+
'juno.max_record_namespace_length'
+ +
+ + + + + + + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/ServerStatus.html b/client/Ruby/docs/Juno/ServerStatus.html new file mode 100644 index 00000000..d5da9619 --- /dev/null +++ b/client/Ruby/docs/Juno/ServerStatus.html @@ -0,0 +1,351 @@ + + + + + + + Class: Juno::ServerStatus + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::ServerStatus + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/server_status.rb
+
+ +
+ + + +

+ Constant Summary + collapse +

+ +
+ +
SUCCESS = + +
+
{ code: 0, error_msg: 'no error', client_operation_status: Juno::Client::OperationStatus::SUCCESS }.freeze
+ +
BAD_MSG = + +
+
{ code: 1, error_msg: 'bad message',
+client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze
+ +
NO_KEY = + +
+
{ code: 3, error_msg: 'key not found',
+client_operation_status: Juno::Client::OperationStatus::NO_KEY }.freeze
+ +
DUP_KEY = + +
+
{ code: 4, error_msg: 'dup key',
+client_operation_status: Juno::Client::OperationStatus::UNIQUE_KEY_VIOLATION }.freeze
+ +
BAD_PARAM = + +
+
{ code: 7,  error_msg: 'bad parameter',
+client_operation_status: Juno::Client::OperationStatus::BAD_PARAM }.freeze
+ +
RECORD_LOCKED = + +
+
{ code: 8, error_msg: 'record locked',
+client_operation_status: Juno::Client::OperationStatus::RECORD_LOCKED }.freeze
+ +
NO_STORAGE_SERVER = + +
+
{ code: 12, error_msg: 'no active storage server',
+client_operation_status: Juno::Client::OperationStatus::NO_STORAGE }.freeze
+ +
SERVER_BUSY = + +
+
{ code: 14, error_msg: 'Server busy',
+client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze
+ +
VERSION_CONFLICT = + +
+
{ code: 19, error_msg:  'version conflict',
+client_operation_status: Juno::Client::OperationStatus::CONDITION_VIOLATION }.freeze
+ +
OP_STATUS_SS_READ_TTL_EXTENDERR = + +
+
{ code: 23, error_msg: 'Error extending TTL by SS',
+client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze
+ +
COMMIT_FAILURE = + +
+
{ code: 25, error_msg: 'Commit Failure',
+client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze
+ +
INCONSISTENT_STATE = + +
+
{ code: 26, error_msg: 'Inconsistent State',
+client_operation_status: Juno::Client::OperationStatus::SUCCESS }.freeze
+ +
INTERNAL = + +
+
{ code: 255, error_msg: 'Internal error',
+client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze
+ +
@@status_code_map = + +
+
nil
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .get(status_code) ⇒ Object + + + + + +

+ + + + +
+
+
+
+42
+43
+44
+45
+46
+47
+
+
# File 'lib/juno/server_status.rb', line 42
+
+def self.get(status_code)
+  initialize_map if @@status_code_map.nil?
+  return @@status_code_map[status_code] if @@status_code_map.key?(status_code.to_i)
+
+  INTERNAL
+end
+
+
+ +
+

+ + .initialize_mapObject + + + + + +

+ + + + +
+
+
+
+33
+34
+35
+36
+37
+38
+39
+40
+
+
# File 'lib/juno/server_status.rb', line 33
+
+def self.initialize_map
+  @@status_code_map = {}
+
+  constants.each do |const|
+    const_obj = const_get(const)
+    @@status_code_map[const_obj[:code].to_i] = const_obj
+  end
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/Juno/Utils.html b/client/Ruby/docs/Juno/Utils.html new file mode 100644 index 00000000..35fced58 --- /dev/null +++ b/client/Ruby/docs/Juno/Utils.html @@ -0,0 +1,640 @@ + + + + + + + Class: Juno::Utils + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Class: Juno::Utils + + + +

+
+ +
+
Inherits:
+
+ Object + +
    +
  • Object
  • + + + +
+ show all + +
+
+ + + + + + + + + + + +
+
Defined in:
+
lib/juno/Utils/utils.rb
+
+ +
+ + + + + + + + + +

+ Class Method Summary + collapse +

+ + + + + + +
+

Class Method Details

+ + +
+

+ + .create_and_send_ping_messageObject + + + + + +

+ + + + +
+
+
+
+110
+111
+112
+113
+114
+115
+116
+117
+118
+
+
# File 'lib/juno/Utils/utils.rb', line 110
+
+def self.create_and_send_ping_message
+  ping_message = Juno::Net::PingMessage.new
+  ping_buffer = StringIO.new
+  ping_message.write(ping_buffer)
+  response = ssl_request(ping_buffer)
+  ping_message = Juno::Net::PingMessage.new
+  ping_message.read(response)
+  ping_message
+end
+
+
+ +
+

+ + .create_message(key, op_type, value = '') ⇒ Object + + + + + +

+ + + + +
+
+
+
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+
+
# File 'lib/juno/Utils/utils.rb', line 15
+
+def self.create_message(key, op_type, value = '')
+   = Juno::IO::MetadataComponent.new
+  # ip = IPAddr.new(Socket.ip_address_list.detect(&:ipv4_private?).ip_address)
+  .set_time_to_live(Juno.juno_config.default_lifetime)
+  .set_version(1)
+  .set_creation_time(1000)
+  .set_request_uuid
+  .set_correlation_id
+  .set_originator_request_id
+  .set_source_info(app_name: Juno.juno_config.app_name, ip: IPAddr.new(Juno::Utils.local_ips[0]),
+                                      port: Juno.juno_config.port)
+
+  payload_component = Juno::IO::PayloadComponent.new
+  payload_component.namespace = Juno.juno_config.record_namespace
+  payload_component.payload_key = key
+  if op_type == Juno::IO::ProtocolHeader::OpCodes::Create && !value.to_s.empty?
+    is_compressed = false
+    if Juno.juno_config.use_payload_compression && value.length > 1024
+      value, compression_achieved = compressed_value(value)
+      is_compressed = true if compression_achieved.positive?
+    end
+    if is_compressed
+      puts 'using compression'
+      payload_component.set_value(value, Juno::IO::CompressionType::Snappy)
+    else
+      payload_component.set_value(value)
+    end
+  end
+
+  protocol_header = Juno::IO::ProtocolHeader.new
+  protocol_header.opcode = op_type
+
+  operation_message = Juno::IO::OperationMessage.new
+  operation_message. = 
+  operation_message.protocol_header = protocol_header
+  operation_message.payload_component = payload_component
+  buffer = StringIO.new
+  operation_message.write(buffer)
+  buffer
+end
+
+
+ +
+

+ + .local_ipsObject + + + + + +

+ + + + +
+
+
+
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
+
# File 'lib/juno/Utils/utils.rb', line 5
+
+def self.local_ips
+  ip_addresses = Socket.ip_address_list.select do |addr|
+    addr.ipv4? && !addr.ipv4_loopback? # && !addr.ipv4_private?
+  end
+  ip_addresses.map!(&:ip_address)
+rescue StandardError => e
+  puts e.message
+  ['127.0.0.1']
+end
+
+
+ +
+

+ + .ssl_contextObject + + + + + +

+ + + + +
+
+
+
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+
+
# File 'lib/juno/Utils/utils.rb', line 56
+
+def self.ssl_context
+  ssl_context = OpenSSL::SSL::SSLContext.new
+  ssl_context.ssl_version = :TLSv1_1
+  cert = OpenSSL::X509::Certificate.new(File.open(File.expand_path(File.join(
+                                                                     __dir__, '..', '..', 'server.crt'
+                                                                   ))))
+  key = OpenSSL::PKey::RSA.new(File.open(File.expand_path(File.join(
+                                                            __dir__, '..', '..', 'server.pem'
+                                                          ))))
+  ca_file = OpenSSL::X509::Certificate.new(File.open(File.expand_path(File.join(
+                                                                        __dir__, '..', '..', 'myca.crt'
+                                                                      ))))
+  ssl_context.add_certificate(cert, key, [ca_file])
+  # ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
+  ssl_context.ssl_timeout = 10
+  ssl_context.timeout = 10
+  ssl_context
+end
+
+
+ +
+

+ + .ssl_request(buffer) ⇒ Object + + + + + +

+ + + + +
+
+
+
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+
+
# File 'lib/juno/Utils/utils.rb', line 75
+
+def self.ssl_request(buffer)
+  socket = TCPSocket.open(Juno.juno_config.host, Juno.juno_config.port)
+  if Juno.juno_config.use_ssl
+    ctx = ssl_context
+    socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
+    socket.sync_close = true
+    # socket.post_connection_check(Juno.juno_config.host)
+    socket.connect
+  end
+
+  # puts socket.peer_finished_message.bytes.join(', ')
+  # puts socket.verify_result
+
+  socket.write(buffer.string)
+  res_buffer = StringIO.new
+  header = true
+
+  size = 0
+  while (line = socket.sysread(1024 * 16)) # buffer size of OpenSSL library
+    if header
+      p = Juno::IO::ProtocolHeader.new
+      p.read(StringIO.new(line))
+      header = false
+      size = p.message_size
+    end
+    res_buffer.write(line)
+    break if res_buffer.length == size
+  end
+  socket.close
+
+  res_buffer.rewind
+
+  res_buffer
+end
+
+
+ +
+

+ + .time_diff_ms(a, b) ⇒ Object + + + + + +

+ + + + +
+
+
+
+120
+121
+122
+
+
# File 'lib/juno/Utils/utils.rb', line 120
+
+def self.time_diff_ms(a, b)
+  (b - a).abs * 1000
+end
+
+
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/_index.html b/client/Ruby/docs/_index.html new file mode 100644 index 00000000..2c8e152e --- /dev/null +++ b/client/Ruby/docs/_index.html @@ -0,0 +1,620 @@ + + + + + + + Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
+ + +

Documentation by YARD 0.9.34

+
+

Alphabetic Index

+ +

File Listing

+ + +
+

Namespace Listing A-Z

+ + + + + + + + +
+ + + + + + + + + + + + + + +
    +
  • F
  • + +
+ + +
    +
  • I
  • +
      + +
    • + IO + + (Juno) + +
    • + +
    • + IOProcessor + + (Juno::Net) + +
    • + +
    +
+ + + + + +
+ + +
    +
  • L
  • +
      + +
    • + Logger + + (Juno) + +
    • + +
    +
+ + + + + +
    +
  • N
  • +
      + +
    • + Net + + (Juno) + +
    • + +
    +
+ + + + + + + + +
    +
  • Q
  • + +
+ + + + + +
    +
  • S
  • + +
+ + +
+ + +
    +
  • T
  • +
      + +
    • + TagAndType + + (Juno::IO::MetadataComponent) + +
    • + +
    • + Type + + (Juno::Client::JunoRequest) + +
    • + +
    +
+ + + + + +
    +
  • W
  • + +
+ +
+ +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/client/Ruby/docs/class_list.html b/client/Ruby/docs/class_list.html new file mode 100644 index 00000000..aac651f8 --- /dev/null +++ b/client/Ruby/docs/class_list.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + Class List + + + +
+
+

Class List

+ + + +
+ + +
+ + diff --git a/client/Ruby/docs/css/common.css b/client/Ruby/docs/css/common.css new file mode 100644 index 00000000..cf25c452 --- /dev/null +++ b/client/Ruby/docs/css/common.css @@ -0,0 +1 @@ +/* Override this file with custom rules */ \ No newline at end of file diff --git a/client/Ruby/docs/css/full_list.css b/client/Ruby/docs/css/full_list.css new file mode 100644 index 00000000..fa359824 --- /dev/null +++ b/client/Ruby/docs/css/full_list.css @@ -0,0 +1,58 @@ +body { + margin: 0; + font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; + font-size: 13px; + height: 101%; + overflow-x: hidden; + background: #fafafa; +} + +h1 { padding: 12px 10px; padding-bottom: 0; margin: 0; font-size: 1.4em; } +.clear { clear: both; } +.fixed_header { position: fixed; background: #fff; width: 100%; padding-bottom: 10px; margin-top: 0; top: 0; z-index: 9999; height: 70px; } +#search { position: absolute; right: 5px; top: 9px; padding-left: 24px; } +#content.insearch #search, #content.insearch #noresults { background: url(data:image/gif;base64,R0lGODlhEAAQAPYAAP///wAAAPr6+pKSkoiIiO7u7sjIyNjY2J6engAAAI6OjsbGxjIyMlJSUuzs7KamppSUlPLy8oKCghwcHLKysqSkpJqamvT09Pj4+KioqM7OzkRERAwMDGBgYN7e3ujo6Ly8vCoqKjY2NkZGRtTU1MTExDw8PE5OTj4+PkhISNDQ0MrKylpaWrS0tOrq6nBwcKysrLi4uLq6ul5eXlxcXGJiYoaGhuDg4H5+fvz8/KKiohgYGCwsLFZWVgQEBFBQUMzMzDg4OFhYWBoaGvDw8NbW1pycnOLi4ubm5kBAQKqqqiQkJCAgIK6urnJyckpKSjQ0NGpqatLS0sDAwCYmJnx8fEJCQlRUVAoKCggICLCwsOTk5ExMTPb29ra2tmZmZmhoaNzc3KCgoBISEiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCAAAACwAAAAAEAAQAAAHaIAAgoMgIiYlg4kACxIaACEJCSiKggYMCRselwkpghGJBJEcFgsjJyoAGBmfggcNEx0flBiKDhQFlIoCCA+5lAORFb4AJIihCRbDxQAFChAXw9HSqb60iREZ1omqrIPdJCTe0SWI09GBACH5BAkIAAAALAAAAAAQABAAAAdrgACCgwc0NTeDiYozCQkvOTo9GTmDKy8aFy+NOBA7CTswgywJDTIuEjYFIY0JNYMtKTEFiRU8Pjwygy4ws4owPyCKwsMAJSTEgiQlgsbIAMrO0dKDGMTViREZ14kYGRGK38nHguHEJcvTyIEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDAggPg4iJAAMJCRUAJRIqiRGCBI0WQEEJJkWDERkYAAUKEBc4Po1GiKKJHkJDNEeKig4URLS0ICImJZAkuQAhjSi/wQyNKcGDCyMnk8u5rYrTgqDVghgZlYjcACTA1sslvtHRgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCQARAtOUoQRGRiFD0kJUYWZhUhKT1OLhR8wBaaFBzQ1NwAlkIszCQkvsbOHL7Y4q4IuEjaqq0ZQD5+GEEsJTDCMmIUhtgk1lo6QFUwJVDKLiYJNUd6/hoEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4uen4ICCA+IkIsDCQkVACWmhwSpFqAABQoQF6ALTkWFnYMrVlhWvIKTlSAiJiVVPqlGhJkhqShHV1lCW4cMqSkAR1ofiwsjJyqGgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCSMhREZGIYYGY2ElYebi56fhyWQniSKAKKfpaCLFlAPhl0gXYNGEwkhGYREUywag1wJwSkHNDU3D0kJYIMZQwk8MjPBLx9eXwuETVEyAC/BOKsuEjYFhoEAIfkECQgAAAAsAAAAABAAEAAAB2eAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4ueICImip6CIQkJKJ4kigynKaqKCyMnKqSEK05StgAGQRxPYZaENqccFgIID4KXmQBhXFkzDgOnFYLNgltaSAAEpxa7BQoQF4aBACH5BAkIAAAALAAAAAAQABAAAAdogACCg4SFggJiPUqCJSWGgkZjCUwZACQkgxGEXAmdT4UYGZqCGWQ+IjKGGIUwPzGPhAc0NTewhDOdL7Ykji+dOLuOLhI2BbaFETICx4MlQitdqoUsCQ2vhKGjglNfU0SWmILaj43M5oEAOwAAAAAAAAAAAA==) no-repeat center left; } +#full_list { padding: 0; list-style: none; margin-left: 0; margin-top: 80px; font-size: 1.1em; } +#full_list ul { padding: 0; } +#full_list li { padding: 0; margin: 0; list-style: none; } +#full_list li .item { padding: 5px 5px 5px 12px; } +#noresults { padding: 7px 12px; background: #fff; } +#content.insearch #noresults { margin-left: 7px; } +li.collapsed ul { display: none; } +li a.toggle { cursor: default; position: relative; left: -5px; top: 4px; text-indent: -999px; width: 10px; height: 9px; margin-left: -10px; display: block; float: left; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK8AAACvABQqw0mAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMy8xNC8wOeNZPpQAAAE2SURBVDiNrZTBccIwEEXfelIAHUA6CZ24BGaWO+FuzZAK4k6gg5QAdGAq+Bxs2Yqx7BzyL7Llp/VfzZeQhCTc/ezuGzKKnKSzpCxXJM8fwNXda3df5RZETlIt6YUzSQDs93sl8w3wBZxCCE10GM1OcWbWjB2mWgEH4Mfdyxm3PSepBHibgQE2wLe7r4HjEidpnXMYdQPKEMJcsZ4zs2POYQOcaPfwMVOo58zsAdMt18BuoVDPxUJRacELbXv3hUIX2vYmOUvi8C8ydz/ThjXrqKqqLbDIAdsCKBd+Wo7GWa7o9qzOQHVVVXeAbs+yHHCH4aTsaCOQqunmUy1yBUAXkdMIfMlgF5EXLo2OpV/c/Up7jG4hhHcYLgWzAZXUc2b2ixsfvc/RmNNfOXD3Q/oeL9axJE1yT9IOoUu6MGUkAAAAAElFTkSuQmCC) no-repeat bottom left; } +li.collapsed a.toggle { opacity: 0.5; cursor: default; background-position: top left; } +li { color: #888; cursor: pointer; } +li.deprecated { text-decoration: line-through; font-style: italic; } +li.odd { background: #f0f0f0; } +li.even { background: #fafafa; } +.item:hover { background: #ddd; } +li small:before { content: "("; } +li small:after { content: ")"; } +li small.search_info { display: none; } +a, a:visited { text-decoration: none; color: #05a; } +li.clicked > .item { background: #05a; color: #ccc; } +li.clicked > .item a, li.clicked > .item a:visited { color: #eee; } +li.clicked > .item a.toggle { opacity: 0.5; background-position: bottom right; } +li.collapsed.clicked a.toggle { background-position: top right; } +#search input { border: 1px solid #bbb; border-radius: 3px; } +#full_list_nav { margin-left: 10px; font-size: 0.9em; display: block; color: #aaa; } +#full_list_nav a, #nav a:visited { color: #358; } +#full_list_nav a:hover { background: transparent; color: #5af; } +#full_list_nav span:after { content: ' | '; } +#full_list_nav span:last-child:after { content: ''; } + +#content h1 { margin-top: 0; } +li { white-space: nowrap; cursor: normal; } +li small { display: block; font-size: 0.8em; } +li small:before { content: ""; } +li small:after { content: ""; } +li small.search_info { display: none; } +#search { width: 170px; position: static; margin: 3px; margin-left: 10px; font-size: 0.9em; color: #888; padding-left: 0; padding-right: 24px; } +#content.insearch #search { background-position: center right; } +#search input { width: 110px; } + +#full_list.insearch ul { display: block; } +#full_list.insearch .item { display: none; } +#full_list.insearch .found { display: block; padding-left: 11px !important; } +#full_list.insearch li a.toggle { display: none; } +#full_list.insearch li small.search_info { display: block; } diff --git a/client/Ruby/docs/css/style.css b/client/Ruby/docs/css/style.css new file mode 100644 index 00000000..eb0dbc86 --- /dev/null +++ b/client/Ruby/docs/css/style.css @@ -0,0 +1,497 @@ +html { + width: 100%; + height: 100%; +} +body { + font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; + font-size: 13px; + width: 100%; + margin: 0; + padding: 0; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; +} + +#nav { + position: relative; + width: 100%; + height: 100%; + border: 0; + border-right: 1px dotted #eee; + overflow: auto; +} +.nav_wrap { + margin: 0; + padding: 0; + width: 20%; + height: 100%; + position: relative; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + flex-shrink: 0; + -webkit-flex-shrink: 0; + -ms-flex: 1 0; +} +#resizer { + position: absolute; + right: -5px; + top: 0; + width: 10px; + height: 100%; + cursor: col-resize; + z-index: 9999; +} +#main { + flex: 5 1; + -webkit-flex: 5 1; + -ms-flex: 5 1; + outline: none; + position: relative; + background: #fff; + padding: 1.2em; + padding-top: 0.2em; + box-sizing: border-box; +} + +@media (max-width: 920px) { + .nav_wrap { width: 100%; top: 0; right: 0; overflow: visible; position: absolute; } + #resizer { display: none; } + #nav { + z-index: 9999; + background: #fff; + display: none; + position: absolute; + top: 40px; + right: 12px; + width: 500px; + max-width: 80%; + height: 80%; + overflow-y: scroll; + border: 1px solid #999; + border-collapse: collapse; + box-shadow: -7px 5px 25px #aaa; + border-radius: 2px; + } +} + +@media (min-width: 920px) { + body { height: 100%; overflow: hidden; } + #main { height: 100%; overflow: auto; } + #search { display: none; } +} + +#main img { max-width: 100%; } +h1 { font-size: 25px; margin: 1em 0 0.5em; padding-top: 4px; border-top: 1px dotted #d5d5d5; } +h1.noborder { border-top: 0px; margin-top: 0; padding-top: 4px; } +h1.title { margin-bottom: 10px; } +h1.alphaindex { margin-top: 0; font-size: 22px; } +h2 { + padding: 0; + padding-bottom: 3px; + border-bottom: 1px #aaa solid; + font-size: 1.4em; + margin: 1.8em 0 0.5em; + position: relative; +} +h2 small { font-weight: normal; font-size: 0.7em; display: inline; position: absolute; right: 0; } +h2 small a { + display: block; + height: 20px; + border: 1px solid #aaa; + border-bottom: 0; + border-top-left-radius: 5px; + background: #f8f8f8; + position: relative; + padding: 2px 7px; +} +.clear { clear: both; } +.inline { display: inline; } +.inline p:first-child { display: inline; } +.docstring, .tags, #filecontents { font-size: 15px; line-height: 1.5145em; } +.docstring p > code, .docstring p > tt, .tags p > code, .tags p > tt { + color: #c7254e; background: #f9f2f4; padding: 2px 4px; font-size: 1em; + border-radius: 4px; +} +.docstring h1, .docstring h2, .docstring h3, .docstring h4 { padding: 0; border: 0; border-bottom: 1px dotted #bbb; } +.docstring h1 { font-size: 1.2em; } +.docstring h2 { font-size: 1.1em; } +.docstring h3, .docstring h4 { font-size: 1em; border-bottom: 0; padding-top: 10px; } +.summary_desc .object_link a, .docstring .object_link a { + font-family: monospace; font-size: 1.05em; + color: #05a; background: #EDF4FA; padding: 2px 4px; font-size: 1em; + border-radius: 4px; +} +.rdoc-term { padding-right: 25px; font-weight: bold; } +.rdoc-list p { margin: 0; padding: 0; margin-bottom: 4px; } +.summary_desc pre.code .object_link a, .docstring pre.code .object_link a { + padding: 0px; background: inherit; color: inherit; border-radius: inherit; +} + +/* style for */ +#filecontents table, .docstring table { border-collapse: collapse; } +#filecontents table th, #filecontents table td, +.docstring table th, .docstring table td { border: 1px solid #ccc; padding: 8px; padding-right: 17px; } +#filecontents table tr:nth-child(odd), +.docstring table tr:nth-child(odd) { background: #eee; } +#filecontents table tr:nth-child(even), +.docstring table tr:nth-child(even) { background: #fff; } +#filecontents table th, .docstring table th { background: #fff; } + +/* style for
    */ +#filecontents li > p, .docstring li > p { margin: 0px; } +#filecontents ul, .docstring ul { padding-left: 20px; } +/* style for
    */ +#filecontents dl, .docstring dl { border: 1px solid #ccc; } +#filecontents dt, .docstring dt { background: #ddd; font-weight: bold; padding: 3px 5px; } +#filecontents dd, .docstring dd { padding: 5px 0px; margin-left: 18px; } +#filecontents dd > p, .docstring dd > p { margin: 0px; } + +.note { + color: #222; + margin: 20px 0; + padding: 10px; + border: 1px solid #eee; + border-radius: 3px; + display: block; +} +.docstring .note { + border-left-color: #ccc; + border-left-width: 5px; +} +.note.todo { background: #ffffc5; border-color: #ececaa; } +.note.returns_void { background: #efefef; } +.note.deprecated { background: #ffe5e5; border-color: #e9dada; } +.note.title.deprecated { background: #ffe5e5; border-color: #e9dada; } +.note.private { background: #ffffc5; border-color: #ececaa; } +.note.title { padding: 3px 6px; font-size: 0.9em; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; display: inline; } +.summary_signature + .note.title { margin-left: 7px; } +h1 .note.title { font-size: 0.5em; font-weight: normal; padding: 3px 5px; position: relative; top: -3px; text-transform: capitalize; } +.note.title { background: #efefef; } +.note.title.constructor { color: #fff; background: #6a98d6; border-color: #6689d6; } +.note.title.writeonly { color: #fff; background: #45a638; border-color: #2da31d; } +.note.title.readonly { color: #fff; background: #6a98d6; border-color: #6689d6; } +.note.title.private { background: #d5d5d5; border-color: #c5c5c5; } +.note.title.not_defined_here { background: transparent; border: none; font-style: italic; } +.discussion .note { margin-top: 6px; } +.discussion .note:first-child { margin-top: 0; } + +h3.inherited { + font-style: italic; + font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; + font-weight: normal; + padding: 0; + margin: 0; + margin-top: 12px; + margin-bottom: 3px; + font-size: 13px; +} +p.inherited { + padding: 0; + margin: 0; + margin-left: 25px; +} + +.box_info dl { + margin: 0; + border: 0; + width: 100%; + font-size: 1em; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; +} +.box_info dl dt { + flex-shrink: 0; + -webkit-flex-shrink: 1; + -ms-flex-shrink: 1; + width: 100px; + text-align: right; + font-weight: bold; + border: 1px solid #aaa; + border-width: 1px 0px 0px 1px; + padding: 6px 0; + padding-right: 10px; +} +.box_info dl dd { + flex-grow: 1; + -webkit-flex-grow: 1; + -ms-flex: 1; + max-width: 420px; + padding: 6px 0; + padding-right: 20px; + border: 1px solid #aaa; + border-width: 1px 1px 0 0; + overflow: hidden; + position: relative; +} +.box_info dl:last-child > * { + border-bottom: 1px solid #aaa; +} +.box_info dl:nth-child(odd) > * { background: #eee; } +.box_info dl:nth-child(even) > * { background: #fff; } +.box_info dl > * { margin: 0; } + +ul.toplevel { list-style: none; padding-left: 0; font-size: 1.1em; } +.index_inline_list { padding-left: 0; font-size: 1.1em; } + +.index_inline_list li { + list-style: none; + display: inline-block; + padding: 0 12px; + line-height: 30px; + margin-bottom: 5px; +} + +dl.constants { margin-left: 10px; } +dl.constants dt { font-weight: bold; font-size: 1.1em; margin-bottom: 5px; } +dl.constants.compact dt { display: inline-block; font-weight: normal } +dl.constants dd { width: 75%; white-space: pre; font-family: monospace; margin-bottom: 18px; } +dl.constants .docstring .note:first-child { margin-top: 5px; } + +.summary_desc { + margin-left: 32px; + display: block; + font-family: sans-serif; + font-size: 1.1em; + margin-top: 8px; + line-height: 1.5145em; + margin-bottom: 0.8em; +} +.summary_desc tt { font-size: 0.9em; } +dl.constants .note { padding: 2px 6px; padding-right: 12px; margin-top: 6px; } +dl.constants .docstring { margin-left: 32px; font-size: 0.9em; font-weight: normal; } +dl.constants .tags { padding-left: 32px; font-size: 0.9em; line-height: 0.8em; } +dl.constants .discussion *:first-child { margin-top: 0; } +dl.constants .discussion *:last-child { margin-bottom: 0; } + +.method_details { border-top: 1px dotted #ccc; margin-top: 25px; padding-top: 0; } +.method_details.first { border: 0; margin-top: 5px; } +.method_details.first h3.signature { margin-top: 1em; } +p.signature, h3.signature { + font-size: 1.1em; font-weight: normal; font-family: Monaco, Consolas, Courier, monospace; + padding: 6px 10px; margin-top: 1em; + background: #E8F4FF; border: 1px solid #d8d8e5; border-radius: 5px; +} +p.signature tt, +h3.signature tt { font-family: Monaco, Consolas, Courier, monospace; } +p.signature .overload, +h3.signature .overload { display: block; } +p.signature .extras, +h3.signature .extras { font-weight: normal; font-family: sans-serif; color: #444; font-size: 1em; } +p.signature .not_defined_here, +h3.signature .not_defined_here, +p.signature .aliases, +h3.signature .aliases { display: block; font-weight: normal; font-size: 0.9em; font-family: sans-serif; margin-top: 0px; color: #555; } +p.signature .aliases .names, +h3.signature .aliases .names { font-family: Monaco, Consolas, Courier, monospace; font-weight: bold; color: #000; font-size: 1.2em; } + +.tags .tag_title { font-size: 1.05em; margin-bottom: 0; font-weight: bold; } +.tags .tag_title tt { color: initial; padding: initial; background: initial; } +.tags ul { margin-top: 5px; padding-left: 30px; list-style: square; } +.tags ul li { margin-bottom: 3px; } +.tags ul .name { font-family: monospace; font-weight: bold; } +.tags ul .note { padding: 3px 6px; } +.tags { margin-bottom: 12px; } + +.tags .examples .tag_title { margin-bottom: 10px; font-weight: bold; } +.tags .examples .inline p { padding: 0; margin: 0; font-weight: bold; font-size: 1em; } +.tags .examples .inline p:before { content: "â–¸"; font-size: 1em; margin-right: 5px; } + +.tags .overload .overload_item { list-style: none; margin-bottom: 25px; } +.tags .overload .overload_item .signature { + padding: 2px 8px; + background: #F1F8FF; border: 1px solid #d8d8e5; border-radius: 3px; +} +.tags .overload .signature { margin-left: -15px; font-family: monospace; display: block; font-size: 1.1em; } +.tags .overload .docstring { margin-top: 15px; } + +.defines { display: none; } + +#method_missing_details .notice.this { position: relative; top: -8px; color: #888; padding: 0; margin: 0; } + +.showSource { font-size: 0.9em; } +.showSource a, .showSource a:visited { text-decoration: none; color: #666; } + +#content a, #content a:visited { text-decoration: none; color: #05a; } +#content a:hover { background: #ffffa5; } + +ul.summary { + list-style: none; + font-family: monospace; + font-size: 1em; + line-height: 1.5em; + padding-left: 0px; +} +ul.summary a, ul.summary a:visited { + text-decoration: none; font-size: 1.1em; +} +ul.summary li { margin-bottom: 5px; } +.summary_signature { padding: 4px 8px; background: #f8f8f8; border: 1px solid #f0f0f0; border-radius: 5px; } +.summary_signature:hover { background: #CFEBFF; border-color: #A4CCDA; cursor: pointer; } +.summary_signature.deprecated { background: #ffe5e5; border-color: #e9dada; } +ul.summary.compact li { display: inline-block; margin: 0px 5px 0px 0px; line-height: 2.6em;} +ul.summary.compact .summary_signature { padding: 5px 7px; padding-right: 4px; } +#content .summary_signature:hover a, +#content .summary_signature:hover a:visited { + background: transparent; + color: #049; +} + +p.inherited a { font-family: monospace; font-size: 0.9em; } +p.inherited { word-spacing: 5px; font-size: 1.2em; } + +p.children { font-size: 1.2em; } +p.children a { font-size: 0.9em; } +p.children strong { font-size: 0.8em; } +p.children strong.modules { padding-left: 5px; } + +ul.fullTree { display: none; padding-left: 0; list-style: none; margin-left: 0; margin-bottom: 10px; } +ul.fullTree ul { margin-left: 0; padding-left: 0; list-style: none; } +ul.fullTree li { text-align: center; padding-top: 18px; padding-bottom: 12px; background: url(data:image/gif;base64,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) no-repeat top center; } +ul.fullTree li:first-child { padding-top: 0; background: transparent; } +ul.fullTree li:last-child { padding-bottom: 0; } +.showAll ul.fullTree { display: block; } +.showAll .inheritName { display: none; } + +#search { position: absolute; right: 12px; top: 0px; z-index: 9000; } +#search a { + display: block; float: left; + padding: 4px 8px; text-decoration: none; color: #05a; fill: #05a; + border: 1px solid #d8d8e5; + border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; + background: #F1F8FF; + box-shadow: -1px 1px 3px #ddd; +} +#search a:hover { background: #f5faff; color: #06b; fill: #06b; } +#search a.active { + background: #568; padding-bottom: 20px; color: #fff; fill: #fff; + border: 1px solid #457; + border-top-left-radius: 5px; border-top-right-radius: 5px; +} +#search a.inactive { color: #999; fill: #999; } +.inheritanceTree, .toggleDefines { + float: right; + border-left: 1px solid #aaa; + position: absolute; top: 0; right: 0; + height: 100%; + background: #f6f6f6; + padding: 5px; + min-width: 55px; + text-align: center; +} + +#menu { font-size: 1.3em; color: #bbb; } +#menu .title, #menu a { font-size: 0.7em; } +#menu .title a { font-size: 1em; } +#menu .title { color: #555; } +#menu a, #menu a:visited { color: #333; text-decoration: none; border-bottom: 1px dotted #bbd; } +#menu a:hover { color: #05a; } + +#footer { margin-top: 15px; border-top: 1px solid #ccc; text-align: center; padding: 7px 0; color: #999; } +#footer a, #footer a:visited { color: #444; text-decoration: none; border-bottom: 1px dotted #bbd; } +#footer a:hover { color: #05a; } + +#listing ul.alpha { font-size: 1.1em; } +#listing ul.alpha { margin: 0; padding: 0; padding-bottom: 10px; list-style: none; } +#listing ul.alpha li.letter { font-size: 1.4em; padding-bottom: 10px; } +#listing ul.alpha ul { margin: 0; padding-left: 15px; } +#listing ul small { color: #666; font-size: 0.7em; } + +li.r1 { background: #f0f0f0; } +li.r2 { background: #fafafa; } + +#content ul.summary li.deprecated .summary_signature a, +#content ul.summary li.deprecated .summary_signature a:visited { text-decoration: line-through; font-style: italic; } + +#toc { + position: relative; + float: right; + overflow-x: auto; + right: -3px; + margin-left: 20px; + margin-bottom: 20px; + padding: 20px; padding-right: 30px; + max-width: 300px; + z-index: 5000; + background: #fefefe; + border: 1px solid #ddd; + box-shadow: -2px 2px 6px #bbb; +} +#toc .title { margin: 0; } +#toc ol { padding-left: 1.8em; } +#toc li { font-size: 1.1em; line-height: 1.7em; } +#toc > ol > li { font-size: 1.1em; font-weight: bold; } +#toc ol > li > ol { font-size: 0.9em; } +#toc ol ol > li > ol { padding-left: 2.3em; } +#toc ol + li { margin-top: 0.3em; } +#toc.hidden { padding: 10px; background: #fefefe; box-shadow: none; } +#toc.hidden:hover { background: #fafafa; } +#filecontents h1 + #toc.nofloat { margin-top: 0; } +@media (max-width: 560px) { + #toc { + margin-left: 0; + margin-top: 16px; + float: none; + max-width: none; + } +} + +/* syntax highlighting */ +.source_code { display: none; padding: 3px 8px; border-left: 8px solid #ddd; margin-top: 5px; } +#filecontents pre.code, .docstring pre.code, .source_code pre { font-family: monospace; } +#filecontents pre.code, .docstring pre.code { display: block; } +.source_code .lines { padding-right: 12px; color: #555; text-align: right; } +#filecontents pre.code, .docstring pre.code, +.tags pre.example { + padding: 9px 14px; + margin-top: 4px; + border: 1px solid #e1e1e8; + background: #f7f7f9; + border-radius: 4px; + font-size: 1em; + overflow-x: auto; + line-height: 1.2em; +} +pre.code { color: #000; tab-size: 2; } +pre.code .info.file { color: #555; } +pre.code .val { color: #036A07; } +pre.code .tstring_content, +pre.code .heredoc_beg, pre.code .heredoc_end, +pre.code .qwords_beg, pre.code .qwords_end, pre.code .qwords_sep, +pre.code .words_beg, pre.code .words_end, pre.code .words_sep, +pre.code .qsymbols_beg, pre.code .qsymbols_end, pre.code .qsymbols_sep, +pre.code .symbols_beg, pre.code .symbols_end, pre.code .symbols_sep, +pre.code .tstring, pre.code .dstring { color: #036A07; } +pre.code .fid, pre.code .rubyid_new, pre.code .rubyid_to_s, +pre.code .rubyid_to_sym, pre.code .rubyid_to_f, +pre.code .dot + pre.code .id, +pre.code .rubyid_to_i pre.code .rubyid_each { color: #0085FF; } +pre.code .comment { color: #0066FF; } +pre.code .const, pre.code .constant { color: #585CF6; } +pre.code .label, +pre.code .symbol { color: #C5060B; } +pre.code .kw, +pre.code .rubyid_require, +pre.code .rubyid_extend, +pre.code .rubyid_include { color: #0000FF; } +pre.code .ivar { color: #318495; } +pre.code .gvar, +pre.code .rubyid_backref, +pre.code .rubyid_nth_ref { color: #6D79DE; } +pre.code .regexp, .dregexp { color: #036A07; } +pre.code a { border-bottom: 1px dotted #bbf; } +/* inline code */ +*:not(pre) > code { + padding: 1px 3px 1px 3px; + border: 1px solid #E1E1E8; + background: #F7F7F9; + border-radius: 4px; +} + +/* Color fix for links */ +#content .summary_desc pre.code .id > .object_link a, /* identifier */ +#content .docstring pre.code .id > .object_link a { color: #0085FF; } +#content .summary_desc pre.code .const > .object_link a, /* constant */ +#content .docstring pre.code .const > .object_link a { color: #585CF6; } diff --git a/client/Ruby/docs/file.README.html b/client/Ruby/docs/file.README.html new file mode 100644 index 00000000..cba860a9 --- /dev/null +++ b/client/Ruby/docs/file.README.html @@ -0,0 +1,115 @@ + + + + + + + File: README + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Juno

    + +

    Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file lib/juno. To experiment with that code, run bin/console for an interactive prompt.

    + +

    TODO: Delete this and the text above, and describe your gem

    + +

    Installation

    + +

    Add this line to your application's Gemfile:

    + +
    gem 'juno'
    +
    + +

    And then execute:

    + +
    $ bundle
    +
    + +

    Or install it yourself as:

    + +
    $ gem install juno
    +
    + +

    Usage

    + +

    TODO: Write usage instructions here

    + +

    Development

    + +

    After checking out the repo, run bin/setup to install dependencies. You can also run bin/console for an interactive prompt that will allow you to experiment.

    + +

    To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

    + +

    Contributing

    + +

    Bug reports and pull requests are welcome on GitHub at github.com/[USERNAME]/juno. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

    + +

    License

    + +

    The gem is available as open source under the terms of the MIT License.

    + +

    Code of Conduct

    + +

    Everyone interacting in the Juno project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the of conduct[https://github.com/[USERNAME]/juno/blob/master/CODE_OF_CONDUCT.md].

    +
    + + + +
    + + \ No newline at end of file diff --git a/client/Ruby/docs/file_list.html b/client/Ruby/docs/file_list.html new file mode 100644 index 00000000..2b6df404 --- /dev/null +++ b/client/Ruby/docs/file_list.html @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + File List + + + +
    +
    +

    File List

    + + + +
    + + +
    + + diff --git a/client/Ruby/docs/frames.html b/client/Ruby/docs/frames.html new file mode 100644 index 00000000..4f918f57 --- /dev/null +++ b/client/Ruby/docs/frames.html @@ -0,0 +1,17 @@ + + + + + Documentation by YARD 0.9.34 + + + + diff --git a/client/Ruby/docs/index.html b/client/Ruby/docs/index.html new file mode 100644 index 00000000..fe4b31b4 --- /dev/null +++ b/client/Ruby/docs/index.html @@ -0,0 +1,115 @@ + + + + + + + File: README + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Juno

    + +

    Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file lib/juno. To experiment with that code, run bin/console for an interactive prompt.

    + +

    TODO: Delete this and the text above, and describe your gem

    + +

    Installation

    + +

    Add this line to your application's Gemfile:

    + +
    gem 'juno'
    +
    + +

    And then execute:

    + +
    $ bundle
    +
    + +

    Or install it yourself as:

    + +
    $ gem install juno
    +
    + +

    Usage

    + +

    TODO: Write usage instructions here

    + +

    Development

    + +

    After checking out the repo, run bin/setup to install dependencies. You can also run bin/console for an interactive prompt that will allow you to experiment.

    + +

    To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

    + +

    Contributing

    + +

    Bug reports and pull requests are welcome on GitHub at github.com/[USERNAME]/juno. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

    + +

    License

    + +

    The gem is available as open source under the terms of the MIT License.

    + +

    Code of Conduct

    + +

    Everyone interacting in the Juno project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the of conduct[https://github.com/[USERNAME]/juno/blob/master/CODE_OF_CONDUCT.md].

    +
    + + + +
    + + \ No newline at end of file diff --git a/client/Ruby/docs/js/app.js b/client/Ruby/docs/js/app.js new file mode 100644 index 00000000..8d067fe3 --- /dev/null +++ b/client/Ruby/docs/js/app.js @@ -0,0 +1,314 @@ +(function() { + +var localStorage = {}, sessionStorage = {}; +try { localStorage = window.localStorage; } catch (e) { } +try { sessionStorage = window.sessionStorage; } catch (e) { } + +function createSourceLinks() { + $('.method_details_list .source_code'). + before("[View source]"); + $('.toggleSource').toggle(function() { + $(this).parent().nextAll('.source_code').slideDown(100); + $(this).text("Hide source"); + }, + function() { + $(this).parent().nextAll('.source_code').slideUp(100); + $(this).text("View source"); + }); +} + +function createDefineLinks() { + var tHeight = 0; + $('.defines').after(" more..."); + $('.toggleDefines').toggle(function() { + tHeight = $(this).parent().prev().height(); + $(this).prev().css('display', 'inline'); + $(this).parent().prev().height($(this).parent().height()); + $(this).text("(less)"); + }, + function() { + $(this).prev().hide(); + $(this).parent().prev().height(tHeight); + $(this).text("more..."); + }); +} + +function createFullTreeLinks() { + var tHeight = 0; + $('.inheritanceTree').toggle(function() { + tHeight = $(this).parent().prev().height(); + $(this).parent().toggleClass('showAll'); + $(this).text("(hide)"); + $(this).parent().prev().height($(this).parent().height()); + }, + function() { + $(this).parent().toggleClass('showAll'); + $(this).parent().prev().height(tHeight); + $(this).text("show all"); + }); +} + +function searchFrameButtons() { + $('.full_list_link').click(function() { + toggleSearchFrame(this, $(this).attr('href')); + return false; + }); + window.addEventListener('message', function(e) { + if (e.data === 'navEscape') { + $('#nav').slideUp(100); + $('#search a').removeClass('active inactive'); + $(window).focus(); + } + }); + + $(window).resize(function() { + if ($('#search:visible').length === 0) { + $('#nav').removeAttr('style'); + $('#search a').removeClass('active inactive'); + $(window).focus(); + } + }); +} + +function toggleSearchFrame(id, link) { + var frame = $('#nav'); + $('#search a').removeClass('active').addClass('inactive'); + if (frame.attr('src') === link && frame.css('display') !== "none") { + frame.slideUp(100); + $('#search a').removeClass('active inactive'); + } + else { + $(id).addClass('active').removeClass('inactive'); + if (frame.attr('src') !== link) frame.attr('src', link); + frame.slideDown(100); + } +} + +function linkSummaries() { + $('.summary_signature').click(function() { + document.location = $(this).find('a').attr('href'); + }); +} + +function summaryToggle() { + $('.summary_toggle').click(function(e) { + e.preventDefault(); + localStorage.summaryCollapsed = $(this).text(); + $('.summary_toggle').each(function() { + $(this).text($(this).text() == "collapse" ? "expand" : "collapse"); + var next = $(this).parent().parent().nextAll('ul.summary').first(); + if (next.hasClass('compact')) { + next.toggle(); + next.nextAll('ul.summary').first().toggle(); + } + else if (next.hasClass('summary')) { + var list = $('
      '); + list.html(next.html()); + list.find('.summary_desc, .note').remove(); + list.find('a').each(function() { + $(this).html($(this).find('strong').html()); + $(this).parent().html($(this)[0].outerHTML); + }); + next.before(list); + next.toggle(); + } + }); + return false; + }); + if (localStorage.summaryCollapsed == "collapse") { + $('.summary_toggle').first().click(); + } else { localStorage.summaryCollapsed = "expand"; } +} + +function constantSummaryToggle() { + $('.constants_summary_toggle').click(function(e) { + e.preventDefault(); + localStorage.summaryCollapsed = $(this).text(); + $('.constants_summary_toggle').each(function() { + $(this).text($(this).text() == "collapse" ? "expand" : "collapse"); + var next = $(this).parent().parent().nextAll('dl.constants').first(); + if (next.hasClass('compact')) { + next.toggle(); + next.nextAll('dl.constants').first().toggle(); + } + else if (next.hasClass('constants')) { + var list = $('
      '); + list.html(next.html()); + list.find('dt').each(function() { + $(this).addClass('summary_signature'); + $(this).text( $(this).text().split('=')[0]); + if ($(this).has(".deprecated").length) { + $(this).addClass('deprecated'); + }; + }); + // Add the value of the constant as "Tooltip" to the summary object + list.find('pre.code').each(function() { + console.log($(this).parent()); + var dt_element = $(this).parent().prev(); + var tooltip = $(this).text(); + if (dt_element.hasClass("deprecated")) { + tooltip = 'Deprecated. ' + tooltip; + }; + dt_element.attr('title', tooltip); + }); + list.find('.docstring, .tags, dd').remove(); + next.before(list); + next.toggle(); + } + }); + return false; + }); + if (localStorage.summaryCollapsed == "collapse") { + $('.constants_summary_toggle').first().click(); + } else { localStorage.summaryCollapsed = "expand"; } +} + +function generateTOC() { + if ($('#filecontents').length === 0) return; + var _toc = $('
        '); + var show = false; + var toc = _toc; + var counter = 0; + var tags = ['h2', 'h3', 'h4', 'h5', 'h6']; + var i; + var curli; + if ($('#filecontents h1').length > 1) tags.unshift('h1'); + for (i = 0; i < tags.length; i++) { tags[i] = '#filecontents ' + tags[i]; } + var lastTag = parseInt(tags[0][1], 10); + $(tags.join(', ')).each(function() { + if ($(this).parents('.method_details .docstring').length != 0) return; + if (this.id == "filecontents") return; + show = true; + var thisTag = parseInt(this.tagName[1], 10); + if (this.id.length === 0) { + var proposedId = $(this).attr('toc-id'); + if (typeof(proposedId) != "undefined") this.id = proposedId; + else { + var proposedId = $(this).text().replace(/[^a-z0-9-]/ig, '_'); + if ($('#' + proposedId).length > 0) { proposedId += counter; counter++; } + this.id = proposedId; + } + } + if (thisTag > lastTag) { + for (i = 0; i < thisTag - lastTag; i++) { + if ( typeof(curli) == "undefined" ) { + curli = $('
      1. '); + toc.append(curli); + } + toc = $('
          '); + curli.append(toc); + curli = undefined; + } + } + if (thisTag < lastTag) { + for (i = 0; i < lastTag - thisTag; i++) { + toc = toc.parent(); + toc = toc.parent(); + } + } + var title = $(this).attr('toc-title'); + if (typeof(title) == "undefined") title = $(this).text(); + curli =$('
        1. ' + title + '
        2. '); + toc.append(curli); + lastTag = thisTag; + }); + if (!show) return; + html = ''; + $('#content').prepend(html); + $('#toc').append(_toc); + $('#toc .hide_toc').toggle(function() { + $('#toc .top').slideUp('fast'); + $('#toc').toggleClass('hidden'); + $('#toc .title small').toggle(); + }, function() { + $('#toc .top').slideDown('fast'); + $('#toc').toggleClass('hidden'); + $('#toc .title small').toggle(); + }); +} + +function navResizeFn(e) { + if (e.which !== 1) { + navResizeFnStop(); + return; + } + + sessionStorage.navWidth = e.pageX.toString(); + $('.nav_wrap').css('width', e.pageX); + $('.nav_wrap').css('-ms-flex', 'inherit'); +} + +function navResizeFnStop() { + $(window).unbind('mousemove', navResizeFn); + window.removeEventListener('message', navMessageFn, false); +} + +function navMessageFn(e) { + if (e.data.action === 'mousemove') navResizeFn(e.data.event); + if (e.data.action === 'mouseup') navResizeFnStop(); +} + +function navResizer() { + $('#resizer').mousedown(function(e) { + e.preventDefault(); + $(window).mousemove(navResizeFn); + window.addEventListener('message', navMessageFn, false); + }); + $(window).mouseup(navResizeFnStop); + + if (sessionStorage.navWidth) { + navResizeFn({which: 1, pageX: parseInt(sessionStorage.navWidth, 10)}); + } +} + +function navExpander() { + var done = false, timer = setTimeout(postMessage, 500); + function postMessage() { + if (done) return; + clearTimeout(timer); + var opts = { action: 'expand', path: pathId }; + document.getElementById('nav').contentWindow.postMessage(opts, '*'); + done = true; + } + + window.addEventListener('message', function(event) { + if (event.data === 'navReady') postMessage(); + return false; + }, false); +} + +function mainFocus() { + var hash = window.location.hash; + if (hash !== '' && $(hash)[0]) { + $(hash)[0].scrollIntoView(); + } + + setTimeout(function() { $('#main').focus(); }, 10); +} + +function navigationChange() { + // This works around the broken anchor navigation with the YARD template. + window.onpopstate = function() { + var hash = window.location.hash; + if (hash !== '' && $(hash)[0]) { + $(hash)[0].scrollIntoView(); + } + }; +} + +$(document).ready(function() { + navResizer(); + navExpander(); + createSourceLinks(); + createDefineLinks(); + createFullTreeLinks(); + searchFrameButtons(); + linkSummaries(); + summaryToggle(); + constantSummaryToggle(); + generateTOC(); + mainFocus(); + navigationChange(); +}); + +})(); diff --git a/client/Ruby/docs/js/full_list.js b/client/Ruby/docs/js/full_list.js new file mode 100644 index 00000000..59069c5e --- /dev/null +++ b/client/Ruby/docs/js/full_list.js @@ -0,0 +1,216 @@ +(function() { + +var $clicked = $(null); +var searchTimeout = null; +var searchCache = []; +var caseSensitiveMatch = false; +var ignoreKeyCodeMin = 8; +var ignoreKeyCodeMax = 46; +var commandKey = 91; + +RegExp.escape = function(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +} + +function escapeShortcut() { + $(document).keydown(function(evt) { + if (evt.which == 27) { + window.parent.postMessage('navEscape', '*'); + } + }); +} + +function navResizer() { + $(window).mousemove(function(e) { + window.parent.postMessage({ + action: 'mousemove', event: {pageX: e.pageX, which: e.which} + }, '*'); + }).mouseup(function(e) { + window.parent.postMessage({action: 'mouseup'}, '*'); + }); + window.parent.postMessage("navReady", "*"); +} + +function clearSearchTimeout() { + clearTimeout(searchTimeout); + searchTimeout = null; +} + +function enableLinks() { + // load the target page in the parent window + $('#full_list li').on('click', function(evt) { + $('#full_list li').removeClass('clicked'); + $clicked = $(this); + $clicked.addClass('clicked'); + evt.stopPropagation(); + + if (evt.target.tagName === 'A') return true; + + var elem = $clicked.find('> .item .object_link a')[0]; + var e = evt.originalEvent; + var newEvent = new MouseEvent(evt.originalEvent.type); + newEvent.initMouseEvent(e.type, e.canBubble, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget); + elem.dispatchEvent(newEvent); + evt.preventDefault(); + return false; + }); +} + +function enableToggles() { + // show/hide nested classes on toggle click + $('#full_list a.toggle').on('click', function(evt) { + evt.stopPropagation(); + evt.preventDefault(); + $(this).parent().parent().toggleClass('collapsed'); + highlight(); + }); +} + +function populateSearchCache() { + $('#full_list li .item').each(function() { + var $node = $(this); + var $link = $node.find('.object_link a'); + if ($link.length > 0) { + searchCache.push({ + node: $node, + link: $link, + name: $link.text(), + fullName: $link.attr('title').split(' ')[0] + }); + } + }); +} + +function enableSearch() { + $('#search input').keyup(function(event) { + if (ignoredKeyPress(event)) return; + if (this.value === "") { + clearSearch(); + } else { + performSearch(this.value); + } + }); + + $('#full_list').after(""); +} + +function ignoredKeyPress(event) { + if ( + (event.keyCode > ignoreKeyCodeMin && event.keyCode < ignoreKeyCodeMax) || + (event.keyCode == commandKey) + ) { + return true; + } else { + return false; + } +} + +function clearSearch() { + clearSearchTimeout(); + $('#full_list .found').removeClass('found').each(function() { + var $link = $(this).find('.object_link a'); + $link.text($link.text()); + }); + $('#full_list, #content').removeClass('insearch'); + $clicked.parents().removeClass('collapsed'); + highlight(); +} + +function performSearch(searchString) { + clearSearchTimeout(); + $('#full_list, #content').addClass('insearch'); + $('#noresults').text('').hide(); + partialSearch(searchString, 0); +} + +function partialSearch(searchString, offset) { + var lastRowClass = ''; + var i = null; + for (i = offset; i < Math.min(offset + 50, searchCache.length); i++) { + var item = searchCache[i]; + var searchName = (searchString.indexOf('::') != -1 ? item.fullName : item.name); + var matchString = buildMatchString(searchString); + var matchRegexp = new RegExp(matchString, caseSensitiveMatch ? "" : "i"); + if (searchName.match(matchRegexp) == null) { + item.node.removeClass('found'); + item.link.text(item.link.text()); + } + else { + item.node.addClass('found'); + item.node.removeClass(lastRowClass).addClass(lastRowClass == 'r1' ? 'r2' : 'r1'); + lastRowClass = item.node.hasClass('r1') ? 'r1' : 'r2'; + item.link.html(item.name.replace(matchRegexp, "$&")); + } + } + if(i == searchCache.length) { + searchDone(); + } else { + searchTimeout = setTimeout(function() { + partialSearch(searchString, i); + }, 0); + } +} + +function searchDone() { + searchTimeout = null; + highlight(); + if ($('#full_list li:visible').size() === 0) { + $('#noresults').text('No results were found.').hide().fadeIn(); + } else { + $('#noresults').text('').hide(); + } + $('#content').removeClass('insearch'); +} + +function buildMatchString(searchString, event) { + caseSensitiveMatch = searchString.match(/[A-Z]/) != null; + var regexSearchString = RegExp.escape(searchString); + if (caseSensitiveMatch) { + regexSearchString += "|" + + $.map(searchString.split(''), function(e) { return RegExp.escape(e); }). + join('.+?'); + } + return regexSearchString; +} + +function highlight() { + $('#full_list li:visible').each(function(n) { + $(this).removeClass('even odd').addClass(n % 2 == 0 ? 'odd' : 'even'); + }); +} + +/** + * Expands the tree to the target element and its immediate + * children. + */ +function expandTo(path) { + var $target = $(document.getElementById('object_' + path)); + $target.addClass('clicked'); + $target.removeClass('collapsed'); + $target.parentsUntil('#full_list', 'li').removeClass('collapsed'); + if($target[0]) { + window.scrollTo(window.scrollX, $target.offset().top - 250); + highlight(); + } +} + +function windowEvents(event) { + var msg = event.data; + if (msg.action === "expand") { + expandTo(msg.path); + } + return false; +} + +window.addEventListener("message", windowEvents, false); + +$(document).ready(function() { + escapeShortcut(); + navResizer(); + enableLinks(); + enableToggles(); + populateSearchCache(); + enableSearch(); +}); + +})(); diff --git a/client/Ruby/docs/js/jquery.js b/client/Ruby/docs/js/jquery.js new file mode 100644 index 00000000..198b3ff0 --- /dev/null +++ b/client/Ruby/docs/js/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.1 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
    a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
    "+""+"
    ",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
    t
    ",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
    ",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; +f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

    ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
    ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
    ","
    "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() +{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
    ").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/client/Ruby/docs/method_list.html b/client/Ruby/docs/method_list.html new file mode 100644 index 00000000..4332e85c --- /dev/null +++ b/client/Ruby/docs/method_list.html @@ -0,0 +1,2075 @@ + + + + + + + + + + + + + + + + + + Method List + + + +
    +
    +

    Method List

    + + + +
    + + +
    + + diff --git a/client/Ruby/docs/top-level-namespace.html b/client/Ruby/docs/top-level-namespace.html new file mode 100644 index 00000000..469ccbb5 --- /dev/null +++ b/client/Ruby/docs/top-level-namespace.html @@ -0,0 +1,110 @@ + + + + + + + Top Level Namespace + + — Documentation by YARD 0.9.34 + + + + + + + + + + + + + + + + + + + +
    + + +

    Top Level Namespace + + + +

    +
    + + + + + + + + + + + +
    + +

    Defined Under Namespace

    +

    + + + Modules: Juno + + + + +

    + + + + + + + + + +
    + + + +
    + + \ No newline at end of file diff --git a/client/Ruby/juno.yml b/client/Ruby/juno.yml new file mode 100644 index 00000000..d2fdf125 --- /dev/null +++ b/client/Ruby/juno.yml @@ -0,0 +1,26 @@ +juno: + useSSL: true + default_record_lifetime_sec: 1800 + max_record_lifetime_sec: 259200 + max_record_namespace_length: 256 + max_key_size: 128 + application_name: TestApp + record_namespace: TestNamespace + usePayloadCompression: false + operation: + retry: true + response: + timeout_msec: 2000 + max_timeout_msec: 5000 + connection: + timeout_msec: 300 + recycle_duration_msec: 30000 + pool_size: 10 + reconnect_on_fail: false + byPassLTM: true + max_timeout_msec: 3000 + max_recycle_duration_msec: 50000 + max_pool_size: 20 + server: + host: "127.0.0.1" + port: 8080 diff --git a/client/Ruby/juno/.rspec b/client/Ruby/juno/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/client/Ruby/juno/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/client/Ruby/juno/CODE_OF_CONDUCT.md b/client/Ruby/juno/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..7fed206e --- /dev/null +++ b/client/Ruby/juno/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at TODO: Write your email address. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/client/Ruby/juno/Gemfile b/client/Ruby/juno/Gemfile new file mode 100644 index 00000000..30277c73 --- /dev/null +++ b/client/Ruby/juno/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Gemfile + +source 'http://rubygems.org' + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +# Specify your gem's dependencies in juno.gemspec +gemspec diff --git a/client/Ruby/juno/Gemfile.lock b/client/Ruby/juno/Gemfile.lock new file mode 100644 index 00000000..9bf1e713 --- /dev/null +++ b/client/Ruby/juno/Gemfile.lock @@ -0,0 +1,95 @@ +PATH + remote: . + specs: + juno (1.0.0) + bindata (~> 2.4.15) + concurrent-ruby (~> 1.2.2) + configatron (~> 4.5.1) + eventmachine (~> 1.2.7) + json (~> 2.5.1) + logger (~> 1.2.7) + openssl (~> 2.2.0) + snappy (~> 0.3.0) + uri (~> 0.12.2) + uuidtools (~> 2.2.0) + yaml (~> 0.1.1) + +GEM + remote: http://rubygems.org/ + specs: + ast (2.4.2) + bindata (2.4.15) + concurrent-ruby (1.2.2) + configatron (4.5.1) + diff-lcs (1.5.0) + eventmachine (1.2.7) + ffi (1.15.5) + get_process_mem (0.2.7) + ffi (~> 1.0) + ipaddr (1.2.5) + json (2.5.1) + language_server-protocol (3.17.0.3) + logger (1.2.8.1) + openssl (2.2.3) + ipaddr + parallel (1.23.0) + parser (3.2.2.3) + ast (~> 2.4.1) + racc + racc (1.7.1) + rainbow (3.1.1) + rake (13.0.6) + regexp_parser (2.8.1) + rexml (3.2.6) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-html-formatter (0.0.1) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.1) + rubocop (1.55.0) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.2.2.3) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) + ruby-prof (1.6.3) + ruby-progressbar (1.13.0) + snappy (0.3.0) + unicode-display_width (2.4.2) + uri (0.12.2) + uuidtools (2.2.0) + yaml (0.1.1) + yard (0.9.34) + +PLATFORMS + arm64-darwin-21 + +DEPENDENCIES + bundler + get_process_mem + juno! + rake + rspec + rspec-html-formatter + rubocop + ruby-prof + yard + +BUNDLED WITH + 2.2.3 diff --git a/client/Ruby/juno/LICENSE.txt b/client/Ruby/juno/LICENSE.txt new file mode 100644 index 00000000..1c7e4b6b --- /dev/null +++ b/client/Ruby/juno/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 TODO: Write your name + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/client/Ruby/juno/Rakefile b/client/Ruby/juno/Rakefile new file mode 100644 index 00000000..bcab8af9 --- /dev/null +++ b/client/Ruby/juno/Rakefile @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' +require 'rubocop/rake_task' +require 'yard' + +task default: :spec + +RSpec::Core::RakeTask.new(:spec) do |t| + t.pattern = Dir.glob('spec/**/*_spec.rb') + t.rspec_opts = ['--format html', '--out juno_spec_results.html'] +end + +RuboCop::RakeTask.new(:lint) do |task| + task.patterns = ['lib/**/*.rb'] + task.options = ['--autocorrect', '--fail-level', 'error'] +end + +namespace :yard do + task :before_yard do + FileUtils.rm_rf('../docs') if Dir.exist?('../docs') + FileUtils.rm_rf('doc') if Dir.exist?('doc') + end + + task :after_yard do + FileUtils.mv('doc', '../docs') + FileUtils.rm_rf('.yardoc') + end + + YARD::Rake::YardocTask.new(:document) do |task| + task.files = ['lib/**/*.rb'] + task.before = -> { Rake::Task['yard:before_yard'].invoke } + task.after = -> { Rake::Task['yard:after_yard'].invoke } + end +end diff --git a/client/Ruby/juno/bin/console b/client/Ruby/juno/bin/console new file mode 100755 index 00000000..1224dcae --- /dev/null +++ b/client/Ruby/juno/bin/console @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' +require 'juno' + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require 'irb' +IRB.start(__FILE__) diff --git a/client/Ruby/juno/bin/setup b/client/Ruby/juno/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/client/Ruby/juno/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/client/Ruby/juno/juno.gemspec b/client/Ruby/juno/juno.gemspec new file mode 100644 index 00000000..46425d76 --- /dev/null +++ b/client/Ruby/juno/juno.gemspec @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'juno/version' + +Gem::Specification.new do |spec| + spec.name = 'juno' + spec.version = Juno::VERSION + spec.authors = ['PayPal Inc'] + spec.email = ['paypal.com'] + + spec.summary = 'Ruby gem for Juno client' + spec.description = 'Ruby gem for Juno client' + spec.homepage = 'https://github.com/paypal/junodb' + spec.license = 'MIT' + + if spec.respond_to?(:metadata) + spec.metadata['allowed_push_host'] = 'https://github.com/paypal/junodb' + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/paypal/junodb' + spec.metadata['changelog_uri'] = 'https://github.com/paypal/junodb' + else + raise 'RubyGems 2.0 or newer is required to protect against ' \ + 'public gem pushes.' + end + + spec.files = Dir.chdir(File.expand_path(__dir__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + spec.add_development_dependency 'bundler' + spec.add_development_dependency 'get_process_mem' + spec.add_development_dependency 'rake' + spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rspec-html-formatter' + spec.add_development_dependency 'rubocop' + spec.add_development_dependency 'ruby-prof' + spec.add_development_dependency 'yard' + + spec.add_runtime_dependency 'bindata', '~> 2.4.15' + spec.add_runtime_dependency 'concurrent-ruby', '~> 1.2.2' + spec.add_runtime_dependency 'configatron', '~> 4.5.1' + spec.add_runtime_dependency 'eventmachine', '~> 1.2.7' + spec.add_runtime_dependency 'json', '~> 2.5.1' + spec.add_runtime_dependency 'logger', '~> 1.2.7' + spec.add_runtime_dependency 'openssl', '~> 2.2.0' + spec.add_runtime_dependency 'snappy', '~> 0.3.0' + spec.add_runtime_dependency 'uri', '~> 0.12.2' + spec.add_runtime_dependency 'uuidtools', '~> 2.2.0' + spec.add_runtime_dependency 'yaml', '~> 0.1.1' +end diff --git a/client/Ruby/juno/lib/juno.rb b/client/Ruby/juno/lib/juno.rb new file mode 100644 index 00000000..22739d33 --- /dev/null +++ b/client/Ruby/juno/lib/juno.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'timeout' +require 'uuidtools' +require 'bindata' +require 'uri' +require 'json' +require 'yaml' +require 'logger' +require 'snappy' +require 'eventmachine' +require 'concurrent' +require 'configatron' + +require_relative 'juno/version' + +require_relative 'juno/Utils/utils' +require_relative 'juno/Utils/client_utils' + +require_relative 'juno/IO/constants' +require_relative 'juno/IO/JunoMessage' +require_relative 'juno/IO/MetadataComponentTemplate' +require_relative 'juno/IO/MetadataComponent' +require_relative 'juno/IO/PayloadComponent' +require_relative 'juno/IO/ProtocolHeader' +require_relative 'juno/IO/OperationMessage' + +require_relative 'juno/Net/base_processor' +require_relative 'juno/Net/ping_message' +require_relative 'juno/Net/request_queue' +require_relative 'juno/Net/client_handler' +require_relative 'juno/Net/io_processor' +require_relative 'juno/Net/worker_pool' + +require_relative 'juno/Config/config_reader' +require_relative 'juno/Config/config_provider' +require_relative 'juno/Config/properties' +require_relative 'juno/Config/default_properties' +require_relative 'juno/Config/config' + +require_relative 'juno/Client/operation_status' +require_relative 'juno/Client/operation_type' +require_relative 'juno/Client/juno_request' +require_relative 'juno/Client/record_context' +require_relative 'juno/Client/juno_response' +require_relative 'juno/Client/react_client' +require_relative 'juno/Client/cache_store' +require_relative 'juno/Client/sync_client' + +require_relative 'juno/server_status' +require_relative 'juno/logger' diff --git a/client/Ruby/juno/lib/juno/Client/cache_store.rb b/client/Ruby/juno/lib/juno/Client/cache_store.rb new file mode 100755 index 00000000..dadfbefb --- /dev/null +++ b/client/Ruby/juno/lib/juno/Client/cache_store.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Module for code exposed to the developer + module Client + # Cache store for ruby on rails + class CacheStore + def initialize + @react_client = Juno::Client::ReactClient.new + end + + def write(key, value, _options = {}) + future_obj = @react_client.set(key, value).wait + return false if future_obj.nil? + + raise future_obj.reason if future_obj.rejected? + + future_obj.value.status[:txnOk] + end + + def read(key, options = {}) + future_obj = @react_client.get(key).wait + read_response(future_obj, options[:version]) + end + + def self.supports_cache_versioning? + true + end + + def delete(key) + future_obj = @react_client.destroy(key).wait + return false if future_obj.nil? || future_obj.rejected? + + future_obj.value.status[:code] == Juno::Client::OperationStatus::SUCCESS[:code] + end + + def exist?(key) + !read(key).nil? + end + + private + + def read_response(future_obj, version) + return nil if future_obj.nil? || future_obj.rejected? + + return future_obj.value.value if version.nil? + + return future_obj.value.value if future_obj.record_context.version == version + + nil + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Client/juno_request.rb b/client/Ruby/juno/lib/juno/Client/juno_request.rb new file mode 100644 index 00000000..c2f2cf63 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Client/juno_request.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + module Client + # Request Object created from application request + class JunoRequest + class Type + CREATE = 1 + GET = 2 + UPDATE = 3 + SET = 4 + DESTROY = 5 + end + + attr_accessor :key, :version, :type, :value, :time_to_live_s, :creation_time + + # Constructor for JunoRequest + # @param key [String] key for the document (required) + # @param version [Integer] value for the document (required) + # @param version [Juno::JunoRequest::Type] value for the document (required) + # @param type [String] value for the document (optional, default: 1 byte string: 0.chr) + # @param type [Integer] Time to live for the document (optional, default: read from config file) + # @param type [Integer] Time to live for the document (optional, default: initialized to Time.now in JunoMessage) + def initialize(key:, version:, type:, value: nil, time_to_live_s: nil, creation_time: nil) + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @key = key + @version = version.to_i + @type = type + @value = value.to_s.empty? ? 0.chr : value.to_s + @time_to_live_s = time_to_live_s.to_i + @creation_time = creation_time + end + + def ==(other) + return false unless other.is_a?(JunoRequest) + + other.key == @key && + other.version == @version && + other.type == @type && + other.value == @value && + other.time_to_live_s == @time_to_live_s && + other.creation_time == @creation_time + end + + # Function to serialize JunoRequest + def to_s + "JunoRequest key:#{@key} version:#{@version} type:#{@type}, value: #{@value}, time_to_live: #{@time_to_live}, creation_time: #{@creation_time}" + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Client/juno_response.rb b/client/Ruby/juno/lib/juno/Client/juno_response.rb new file mode 100644 index 00000000..8c56732e --- /dev/null +++ b/client/Ruby/juno/lib/juno/Client/juno_response.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Juno + module Client + # Response sent to the application + class JunoResponse + attr_accessor :key, :status, :value, :record_context + + # @param key [String] (required) + # @param status [Juno::Client::OperationStatus] (required) + # @param value [String] (required) + # @param version [Integer] (required) + # @param creation_time [Integer] (required) + # @param time_to_live_s [Integer] (required) + def initialize(key:, value:, version:, time_to_live_s:, creation_time:, operation_status:) + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @key = key + @status = operation_status + @value = value + @record_context = Juno::Client::RecordContext.new(key: key, version: version, creation_time: creation_time, + time_to_live_s: time_to_live_s) + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Client/operation_status.rb b/client/Ruby/juno/lib/juno/Client/operation_status.rb new file mode 100644 index 00000000..538cf5ac --- /dev/null +++ b/client/Ruby/juno/lib/juno/Client/operation_status.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# Constant for Operation Status in JunoResponse +module Juno + module Client + class OperationStatus + SUCCESS = { code: 0, error_msg: 'No error', txnOk: true }.freeze + NO_KEY = { code: 1, error_msg: 'Key not found', txnOk: true }.freeze + BAD_PARAM = { code: 2, error_msg: 'Bad parameter', txnOk: false }.freeze + UNIQUE_KEY_VIOLATION = { code: 3, error_msg: 'Duplicate key', txnOk: true }.freeze + RECORD_LOCKED = { code: 4, error_msg: 'Record Locked', txnOk: true }.freeze + ILLEGAL_ARGUMENT = { code: 5, error_msg: 'Illegal argument', txnOk: false }.freeze + CONDITION_VIOLATION = { code: 6, error_msg: 'Condition in the request violated', txnOk: true }.freeze + INTERNAL_ERROR = { code: 7, error_msg: 'Internal error', txnOk: false }.freeze + QUEUE_FULL = { code: 8, error_msg: 'Outbound client queue full', txnOk: false }.freeze + NO_STORAGE = { code: 9, error_msg: 'No storage server running', txnOk: false }.freeze + TTL_EXTEND_FAILURE = { code: 10, error_msg: 'Failure to extend TTL on get', txnOk: true }.freeze + RESPONSE_TIMEOUT = { code: 11, error_msg: 'Response Timed out', txnOk: false }.freeze + CONNECTION_ERROR = { code: 12, error_msg: 'Connection Error', txnOk: false }.freeze + UNKNOWN_ERROR = { code: 13, error_msg: 'Unknown Error', txnOk: false }.freeze + + @@status_code_map = nil + + def self.initialize_map + @@status_code_map = {} + + constants.each do |const| + const_obj = const_get(const) + @@status_code_map[const_obj[:code]] = const_obj + end + end + + def self.get(status_code) + initialize_map if @@status_code_map.nil? + return @@status_code_map[status_code] if @@status_code_map.key?(status_code) + + INTERNAL_ERROR + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Client/operation_type.rb b/client/Ruby/juno/lib/juno/Client/operation_type.rb new file mode 100644 index 00000000..3f14f860 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Client/operation_type.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + module Client + # Constant for JunoRequest operation type + class OperationType + Nop = { + code: 0, + str: 'NOP' + }.freeze + Create = { + code: 1, + str: 'CREATE' + }.freeze + Get = { + code: 2, + str: 'GET' + }.freeze + Update = { + code: 3, + str: 'UPDATE' + }.freeze + Set = { + code: 4, + str: 'SET' + }.freeze + CompareAndSet = { + code: 5, + str: 'COMPAREANDSET' + }.freeze + Destroy = { + code: 6, + str: 'DESTROY' + }.freeze + end + end +end diff --git a/client/Ruby/juno/lib/juno/Client/react_client.rb b/client/Ruby/juno/lib/juno/Client/react_client.rb new file mode 100644 index 00000000..b7e1a0bc --- /dev/null +++ b/client/Ruby/juno/lib/juno/Client/react_client.rb @@ -0,0 +1,233 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Module for code exposed to the developer + module Client + # Async Client for Juno + class ReactClient + # @ Global Opaque generator. Uses ConcurrentFixnum for thread safety + @@OPAQUE_GENERATOR = Concurrent::AtomicFixnum.new(-1) + # @ Variable to count failed requests + @@fail_count = Concurrent::AtomicFixnum.new(0) + # @ Variable to count responses received + @@received = Concurrent::AtomicFixnum.new(0) + + # Constants for operation retry + MAX_OPERATION_RETRY = 1 + MAX_RETRY_INTERVAL = 15 # msec + MIN_RETRY_INTERVAL = 10 # msec + + def self.op_count + @@OPAQUE_GENERATOR.value + end + + def self.failed_count + @@fail_count.value + end + + def self.recv + @@received.value + end + + def initialize + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @request_queue = Juno::Net::RequestQueue.instance + @executor = Concurrent::ThreadPoolExecutor.new(min_threads: 4, max_threads: 16, max_queue: 10_000) + @count = 0 + end + + def stop + @LOGGER.info(@PROG_NAME) { 'stop initiated by client' } + @request_queue.stop + @executor.shutdown + @executor.wait_for_termination + @executor.kill + end + + # Function to create new key value pair + # @param key [String] key for the document (required) + # @param value [String] value for the document (required) + # @param ttl [Integer] Time to live for the document (optional, default: read from config file) + # @return [Boolean] True if operation submited successfully, else false + # @see #process_single + # @see Juno::DefaultProperties::DEFAULT_LIFETIME_S + def create(key, value, ttl: nil) + juno_request = Juno::Client::JunoRequest.new(key: key, + value: value, + version: 0, + type: Juno::Client::JunoRequest::Type::CREATE, + time_to_live_s: ttl, + creation_time: Time.now.to_i) + process_single(juno_request) + end + + # Function to set value for given key + # @param key [String] key for the document (required) + # @param value [String] value for the document (required) + # @param ttl [Integer] Time to live for the document (optional, default: read from config file) + # @return [Boolean] True if operation submited successfully, else false + # @see #process_single + # @see Juno::DefaultProperties::DEFAULT_LIFETIME_S + def set(key, value, ttl: nil) + juno_request = Juno::Client::JunoRequest.new(key: key, + value: value, + version: 0, + type: Juno::Client::JunoRequest::Type::SET, + time_to_live_s: ttl, + creation_time: Time.now.to_i) + process_single(juno_request) + end + # @!method write + # @see #create + alias write set + + def compare_and_set(record_context, value, ttl) + unless record_context.is_a?(Juno::Client::RecordContext) + raise ArgumentError, 'recird context should be of type Juno::Client::RecordContext' + end + raise ArgumentError, 'Version cannot be less than 1' if record_context.version.to_i < 1 + + juno_request = Juno::Client::JunoRequest.new(key: record_context.key.to_s, + value: value, + version: record_context.version.to_i, + type: Juno::Client::JunoRequest::Type::UPDATE, + time_to_live_s: ttl, + creation_time: Time.now.to_i) + process_single(juno_request) + end + + # Function to get existing key value pair + # @param key [String] key for the document (required) + # @param value [String] value for the document (required) + # @param ttl [Integer] Time to live for the document (optional, default: read from config file) + # @return [Juno::Client::JunoResponse] + # @see Juno::Client#process_single + # @see Juno::DefaultProperties::DEFAULT_LIFETIME_S + def get(key, ttl: nil) + juno_request = Juno::Client::JunoRequest.new(key: key, + value: '', + version: 0, + type: Juno::Client::JunoRequest::Type::GET, + time_to_live_s: ttl, + creation_time: Time.now.to_i) + process_single(juno_request) + end + # @!method read + # @see #get + alias read get + + def update(key, value, ttl: nil) + juno_request = Juno::Client::JunoRequest.new(key: key, + value: value, + version: 0, + type: Juno::Client::JunoRequest::Type::UPDATE, + time_to_live_s: ttl, + creation_time: Time.now.to_i) + process_single(juno_request) + end + + def destroy(key, ttl: nil) + juno_request = Juno::Client::JunoRequest.new(key: key, + value: '', + version: 0, + type: Juno::Client::JunoRequest::Type::DESTROY, + time_to_live_s: ttl, + creation_time: Time.now.to_i) + process_single(juno_request) + end + # @!method delete + # @see #destroy + alias delete destroy + + def exist?(key); end + + private + + # Function to process a request. Set operation retry true in config file + # @param ttl [Juno::Client::JunoRequest] + # @return [Boolean] True if operation submited successfully, else false + # @see Juno::ClientUtils#validate + # @see Juno::DefaultProperties::DEFAULT_LIFETIME_S + # @see Juno::Net::RequestQueue#opaque_resp_queue_map + def process_single(juno_request) + # rubocop:disable Style/SignalException + # Concurrent::Future object to execute asynchronously + Concurrent::Future.execute(executor: @executor) do + begin + begin + juno_message = Juno::ClientUtils.validate!(juno_request) + rescue ArgumentError => e + fail(e.message) + end + operation_retry = Juno.juno_config.operation_retry + juno_resp = nil + opaque = nil + opaque_resp_queue_map = @request_queue.opaque_resp_queue_map + (MAX_OPERATION_RETRY + 1).times do + opaque = @@OPAQUE_GENERATOR.increment + operation_message = Juno::ClientUtils.create_operation_message(juno_message, opaque) + + resp_queue = SizedQueue.new(1) + opaque_resp_queue_map[opaque] = resp_queue + + # map to compute response times for requests + msg_buffer = StringIO.new + operation_message.write(msg_buffer) + + fail('Queue full') unless @request_queue.push(msg_buffer.string, + operation_message&.metadata_component&.request_uuid.to_s) + + resp = nil + begin + Timeout.timeout(Juno.juno_config.response_timeout.to_f / 1000) do + resp = resp_queue.pop + end + rescue Timeout::Error + resp = nil + end + + if resp.nil? + fail('Request Timeout') unless operation_retry + + @LOGGER.debug(@PROG_NAME) { "Retrying #{opaque} " } + operation_retry = false + # Backoff time for request retry + sec = rand(MAX_RETRY_INTERVAL - MIN_RETRY_INTERVAL) + MIN_RETRY_INTERVAL + @LOGGER.debug(@PROG_NAME) { "Backoff time #{sec.to_f / 1000}ms " } + sleep(sec.to_f / 1000) + next + end + juno_resp_msg = Juno::ClientUtils.decode_operation_message(resp) + fail('Could not fetch valid response') if juno_resp_msg.nil? + + juno_resp = Juno::Client::JunoResponse.new(key: juno_resp_msg.key, value: juno_resp_msg.value, + version: juno_resp_msg.version, time_to_live_s: juno_resp_msg.time_to_live_s, + creation_time: juno_resp_msg.creation_time, + operation_status: juno_resp_msg.server_status[:client_operation_status]) + + fail(juno_resp.status[:error_msg]) unless [Juno::Client::OperationStatus::SUCCESS[:code], Juno::Client::OperationStatus::CONDITION_VIOLATION[:code], + Juno::Client::OperationStatus::NO_KEY[:code], Juno::Client::OperationStatus::RECORD_LOCKED[:code], + Juno::Client::OperationStatus::UNIQUE_KEY_VIOLATION[:code], Juno::Client::OperationStatus::TTL_EXTEND_FAILURE[:code]].include?(juno_resp.status[:code]) + + break + end + rescue StandardError => e + puts e.backtrace + fail(e) + ensure + opaque_resp_queue_map.delete(opaque) + @@fail_count.increment if juno_resp.nil? + @@received.increment unless juno_resp.nil? + # rubocop:enable Style/SignalException + end + juno_resp + end + rescue Concurrent::RejectedExecutionError + @LOGGER.error(@PROG_NAME) { 'Too many requests Concurrent::Future' } + nil + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Client/record_context.rb b/client/Ruby/juno/lib/juno/Client/record_context.rb new file mode 100644 index 00000000..1f2c50a6 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Client/record_context.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Juno + module Client + class RecordContext + attr_reader :key, :version, :creation_time, :time_to_live_s + + # @param key [String] (required) + # @param version [Integer] (required) + # @param creation_time [Integer] (required) + # @param time_to_live_s [Integer] (required) + def initialize(key:, version:, creation_time:, time_to_live_s:) + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @key = key + @version = version + @creation_time = creation_time + @time_to_live_s = time_to_live_s + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Client/sync_client.rb b/client/Ruby/juno/lib/juno/Client/sync_client.rb new file mode 100644 index 00000000..f7afc7cd --- /dev/null +++ b/client/Ruby/juno/lib/juno/Client/sync_client.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Module for code exposed to the developer + module Client + # Async Client for Juno + class SyncClient + def initialize + @react_client = Juno::Client::ReactClient.new + end + + # Function to create new key value pair + # @param key [String] key for the document (required) + # @param value [String] value for the document (required) + # @param ttl [Integer] Time to live for the document (optional, default: read from config file) + # @return [Juno::Client::JunoResponse] + def create(key, value, ttl: nil) + juno_resp = @react_client.create(key, value, ttl: ttl).wait + return nil if juno_resp.nil? + + raise juno_resp.reason if juno_resp.rejected? + + juno_resp.value # JunoResponse + end + + # Function to set value for given key + # @param key [String] key for the document (required) + # @param value [String] value for the document (required) + # @param ttl [Integer] Time to live for the document (optional, default: read from config file) + # @return [Juno::Client::JunoResponse] + def set(key, value, ttl: nil) + juno_resp = @react_client.set(key.to_s, value.to_s, ttl: ttl).wait + return nil if juno_resp.nil? + + raise juno_resp.reason if juno_resp.rejected? + + juno_resp.value # JunoResponse + end + + # Function to get existing key value pair + # @param key [String] key for the document (required) + # @param value [String] value for the document (required) + # @param ttl [Integer] Time to live for the document (optional, default: read from config file) + # @return [Juno::Client::JunoResponse] + def get(key, ttl: nil) + juno_resp = @react_client.get(key.to_s, ttl: ttl).wait + return nil if juno_resp.nil? + + raise juno_resp.reason if juno_resp.rejected? + + juno_resp.value # JunoResponse + end + + def update(key, value, ttl: nil) + juno_resp = @react_client.update(key.to_s, value.to_s, ttl: ttl).wait + return nil if juno_resp.nil? + + raise juno_resp.reason if juno_resp.rejected? + + juno_resp.value # JunoResponse + end + + def compare_and_set(record_context, value, ttl: nil) + juno_resp = nil + begin + juno_resp = @react_client.compare_and_set(record_context, value, ttl) + rescue ArgumentError => e + raise e.message + end + + return nil if juno_resp.nil? + + raise juno_resp.reason if juno_resp.rejected? + + juno_resp.value # JunoResponse + end + + def destroy(key, ttl: nil) + juno_resp = @react_client.destroy(key.to_s, ttl: ttl).wait + return nil if juno_resp.nil? + + raise juno_resp.reason if juno_resp.rejected? + + juno_resp.value # JunoResponse + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Config/config.rb b/client/Ruby/juno/lib/juno/Config/config.rb new file mode 100644 index 00000000..d0254cb5 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Config/config.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Juno.configure do |config| + # config.record_namespace = "kk" + # config.host = '10.138.38.83' + # config.port = 5080 + # config.app_name = "TestApp" + # config.file_path = "" + # config.url = "" + # end + class Config + attr_reader :response_timeout, :connection_timeout, :connection_pool_size, :connection_lifetime, :default_lifetime, + :max_response_timeout, :max_connection_timeout, :max_connection_pool_size, :max_connection_lifetime, :max_lifetime, :max_key_size, :max_value_size, :max_namespace_length, + :host, :port, :app_name, :record_namespace, + :use_ssl, :use_payload_compression, :operation_retry, :bypass_ltm, :reconnect_on_fail, :config_prefix, + :log_file, :ssl_cert_file, :ssl_key_file + + REQUIRED_PROPERTIES = %i[host port app_name record_namespace log_file].freeze + + def initialize(config_provider, log_file:, ssl_cert_file: nil, ssl_key_file: nil) + @PROG_NAME = self.class.name + @log_file = log_file + @ssl_cert_file = ssl_cert_file + @ssl_key_file = ssl_key_file + @config = config_provider + read_all + validate! + end + + # Function to map all properties read from config file to variables + def read_all + @response_timeout = @config.get_property(Juno::Properties::RESPONSE_TIMEOUT, + Juno::DefaultProperties::RESPONSE_TIMEOUT_MS) + @connection_timeout = @config.get_property(Juno::Properties::CONNECTION_TIMEOUT, + Juno::DefaultProperties::CONNECTION_TIMEOUT_MS) + @connection_pool_size = @config.get_property(Juno::Properties::CONNECTION_POOLSIZE, + Juno::DefaultProperties::CONNECTION_POOLSIZE) + @connection_lifetime = @config.get_property(Juno::Properties::CONNECTION_LIFETIME, + Juno::DefaultProperties::CONNECTION_LIFETIME_MS) + @default_lifetime = @config.get_property(Juno::Properties::DEFAULT_LIFETIME, + Juno::DefaultProperties::DEFAULT_LIFETIME_S) + @max_response_timeout = @config.get_property(Juno::Properties::MAX_RESPONSE_TIMEOUT, + Juno::DefaultProperties::MAX_RESPONSE_TIMEOUT_MS) + @max_connection_timeout = @config.get_property(Juno::Properties::MAX_CONNECTION_TIMEOUT, + Juno::DefaultProperties::MAX_CONNECTION_TIMEOUT_MS) + @max_connection_pool_size = @config.get_property(Juno::Properties::MAX_CONNECTION_POOL_SIZE, + Juno::DefaultProperties::MAX_CONNECTION_POOL_SIZE) + @max_connection_lifetime = @config.get_property(Juno::Properties::MAX_CONNECTION_LIFETIME, + Juno::DefaultProperties::MAX_CONNECTION_LIFETIME_MS) + @max_lifetime = @config.get_property(Juno::Properties::MAX_LIFETIME, Juno::DefaultProperties::MAX_LIFETIME_S) + @max_key_size = @config.get_property(Juno::Properties::MAX_KEY_SIZE, Juno::DefaultProperties::MAX_KEY_SIZE_B) + @max_value_size = @config.get_property(Juno::Properties::MAX_VALUE_SIZE, + Juno::DefaultProperties::MAX_VALUE_SIZE_B) + @max_namespace_length = @config.get_property(Juno::Properties::MAX_NAMESPACE_LENGTH, + Juno::DefaultProperties::MAX_NAMESPACE_LENGTH) + @host = @config.get_property(Juno::Properties::HOST, Juno::DefaultProperties::HOST) + @port = @config.get_property(Juno::Properties::PORT, Juno::DefaultProperties::PORT) + @app_name = @config.get_property(Juno::Properties::APP_NAME, Juno::DefaultProperties::APP_NAME) + @record_namespace = @config.get_property(Juno::Properties::RECORD_NAMESPACE, + Juno::DefaultProperties::RECORD_NAMESPACE) + @use_ssl = @config.get_property(Juno::Properties::USE_SSL, Juno::DefaultProperties::USE_SSL) + @use_payload_compression = @config.get_property(Juno::Properties::USE_PAYLOAD_COMPRESSION, + Juno::DefaultProperties::USE_PAYLOAD_COMPRESSION) + @operation_retry = @config.get_property(Juno::Properties::ENABLE_RETRY, Juno::DefaultProperties::OPERATION_RETRY) + @bypass_ltm = @config.get_property(Juno::Properties::BYPASS_LTM, Juno::DefaultProperties::BYPASS_LTM) + @reconnect_on_fail = @config.get_property(Juno::Properties::RECONNECT_ON_FAIL, + Juno::DefaultProperties::RECONNECT_ON_FAIL) + @config_prefix = @config.get_property(Juno::Properties::CONFIG_PREFIX, Juno::DefaultProperties::CONFIG_PREFIX) + nil + end + + def validate! + missing_properties = [] + REQUIRED_PROPERTIES.each do |property| + missing_properties.push(property) if send(property).nil? + end + + if @use_ssl + %i[ssl_cert_file ssl_key_file].each do |property| + missing_properties.push(property) if send(property).nil? + end + end + + if missing_properties.length.positive? + raise "Please provide a value for the required property(s) #{missing_properties.join(', ')}." + end + + if @use_ssl + raise 'SSL Certificate file not found' unless File.exist?(@ssl_cert_file) + raise 'SSL Key file not found' unless File.exist?(@ssl_key_file) + end + + nil + end + + class Source + YAML_FILE = 0 + JSON_FILE = 1 + URL = 2 + end + end + + def self.configure + if @juno_config.nil? + config_reader = Juno::ConfigReader.new + yield config_reader + if !config_reader.file_path.nil? + @juno_config = Config.new(ConfigProvider.new(config_reader.file_path, config_reader.source_format, nil), + log_file: config_reader.log_file, ssl_cert_file: config_reader.ssl_cert_file, + ssl_key_file: config_reader.ssl_key_file) + @LOGGER = Juno::Logger.instance + elsif !config_reader.url.nil? # URL should URI Object + # @juno_config = Config.new(ConfigProvider.new(@juno_config.file_path, @juno_config.source_format, nil)) + else + raise 'No file or url provided' + end + else + Juno::Logger.instance.warn('Juno client cannot be reconfigured') + end + end + + def self.juno_config + raise 'Please configure the properties using Juno.configure' if @juno_config.nil? + + @juno_config + end +end diff --git a/client/Ruby/juno/lib/juno/Config/config_provider.rb b/client/Ruby/juno/lib/juno/Config/config_provider.rb new file mode 100644 index 00000000..3f5d9379 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Config/config_provider.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Class to read config from file or url + class ConfigProvider + # Constructor + # @param source_uri [URI] Ruby URI Object for file or URL (required) + # @param source_format [String] source_format required only for url. Inferred from file extension when using file (optional) + # @return Propert value. Return default_value if property not found + # @see Juno::Properties + def initialize(source_uri, source_format = nil, _http_handler = nil) + begin + source_scheme = source_uri&.send(:scheme) + rescue StandardError => e + raise "Invalid source_uri object.\n #{e.message}" + end + if source_scheme == 'file' + read_from_file(source_uri) + elsif source_scheme =~ /^http(s)?$/ + read_from_url(source_uri, source_format, http_handler) + else + raise 'Only local file and URL supported' + end + end + + # Function to intialize configatron object from config file/URL + # @param source_uri [URI] Ruby URI Object for file or URL (required) + # @return [nil] + def read_from_file(source_uri) + raise 'Config file not found' unless File.exist?(source_uri.path) + + hsh = if ['.yml', '.yaml'].include?(File.extname(source_uri.path)) + YAML.load_file(source_uri.path) + elsif ['.json'].inlcude?(File.extname(source_uri.path)) + json_text = File.read(source_uri.path) + JSON.parse(json_text) + else + raise 'Unknown file format' + end + configatron.configure_from_hash(hsh) + nil + end + + def read_from_url(source_uri, source_format, http_handler); end + + # Function to read propertied in the heirarchy define in Juno::Properties + # @param property_key [String] String key (required) + # @param default_value (optional) default value if property not found + # @return Propert value. Returns default_value if property not found + # @see Juno::Properties + def get_property(property_key, default_value = nil) + return default_value if property_key.to_s.empty? + + value = configatron.to_h + property_key.to_s.split('.').each do |k| + return default_value unless value.is_a?(Hash) && value.key?(k.to_sym) + + value = value[k.to_sym] + # puts "#{k} --- #{value}" + end + + value.nil? || value.is_a?(Hash) ? default_value : value + end + end +end diff --git a/client/Ruby/juno/lib/juno/Config/config_reader.rb b/client/Ruby/juno/lib/juno/Config/config_reader.rb new file mode 100644 index 00000000..4f8d3e68 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Config/config_reader.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Properties Reader - Properties to be read from the developer using Juno.configure + # Either file_path or url is required + # log device can be a filename or IO Object + class ConfigReader + attr_accessor :file_path, :url, :source_format, :http_handler, :log_level, :log_device, + :max_log_file_bytes, :log_rotation, :log_file, :ssl_cert_file, :ssl_key_file + + def initialize + # default values + @log_level = ::Logger::Severity::INFO + @max_log_file_bytes = 1_048_576 # default for inbuilt logger class + @log_device = $stdout + @log_rotation = 'daily' # daily, weekly, monthly + end + end +end diff --git a/client/Ruby/juno/lib/juno/Config/default_properties.rb b/client/Ruby/juno/lib/juno/Config/default_properties.rb new file mode 100644 index 00000000..fee42326 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Config/default_properties.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Module containing constant default values for Properties + class DefaultProperties + RESPONSE_TIMEOUT_MS = 200 + CONNECTION_TIMEOUT_MS = 200 + CONNECTION_POOLSIZE = 1 + CONNECTION_LIFETIME_MS = 30_000 + DEFAULT_LIFETIME_S = 259_200 + + # Max for all above property + MAX_RESPONSE_TIMEOUT_MS = 5000 + MAX_CONNECTION_LIFETIME_MS = 30_000 + MAX_CONNECTION_TIMEOUT_MS = 5000 + MAX_KEY_SIZE_B = 128 + MAX_VALUE_SIZE_B = 204_800 + MAX_NAMESPACE_LENGTH = 64 + MAX_CONNECTION_POOL_SIZE = 3 + MAX_LIFETIME_S = 259_200 + + # Required Properties + HOST = '' + PORT = 0 + APP_NAME = '' + RECORD_NAMESPACE = '' + + # optional Properties + CONFIG_PREFIX = '' + USE_SSL = true + RECONNECT_ON_FAIL = false + USE_PAYLOAD_COMPRESSION = false + OPERATION_RETRY = false + BYPASS_LTM = true + end +end diff --git a/client/Ruby/juno/lib/juno/Config/properties.rb b/client/Ruby/juno/lib/juno/Config/properties.rb new file mode 100644 index 00000000..488d12ac --- /dev/null +++ b/client/Ruby/juno/lib/juno/Config/properties.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Module for properties key (config file) + class Properties + RESPONSE_TIMEOUT = 'juno.response.timeout_msec' + CONNECTION_TIMEOUT = 'juno.connection.timeout_msec' + DEFAULT_LIFETIME = 'juno.default_record_lifetime_sec' + CONNECTION_LIFETIME = 'juno.connection.recycle_duration_msec' + CONNECTION_POOLSIZE = 'juno.connection.pool_size' + RECONNECT_ON_FAIL = 'juno.connection.reconnect_on_fail' + HOST = 'juno.server.host' + PORT = 'juno.server.port' + APP_NAME = 'juno.application_name' + RECORD_NAMESPACE = 'juno.record_namespace' + USE_SSL = 'juno.useSSL' + USE_PAYLOAD_COMPRESSION = 'juno.usePayloadCompression' + ENABLE_RETRY = 'juno.operation.retry' + BYPASS_LTM = 'juno.connection.byPassLTM' + CONFIG_PREFIX = 'prefix' + + # Max for each property + MAX_LIFETIME = 'juno.max_record_lifetime_sec' + MAX_KEY_SIZE = 'juno.max_key_size' + MAX_VALUE_SIZE = 'juno.max_value_size' + MAX_RESPONSE_TIMEOUT = 'juno.response.max_timeout_msec' + MAX_CONNECTION_TIMEOUT = 'juno.connection.max_timeout_msec' + MAX_CONNECTION_LIFETIME = 'juno.connection.max_recycle_duration_msec' + MAX_CONNECTION_POOL_SIZE = 'juno.connection.max_pool_size' + MAX_NAMESPACE_LENGTH = 'juno.max_record_namespace_length' + end +end diff --git a/client/Ruby/juno/lib/juno/IO/JunoMessage.rb b/client/Ruby/juno/lib/juno/IO/JunoMessage.rb new file mode 100644 index 00000000..7678fcf0 --- /dev/null +++ b/client/Ruby/juno/lib/juno/IO/JunoMessage.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Juno + module IO + # JunoMessage containing all configuration required to create an operation message + class JunoMessage + attr_accessor :operation_type, :server_status, + :namespace, :key, :value, :is_compressed, :compression_type, + :time_to_live_s, :version, :creation_time, :expiration_time, :request_uuid, + :ip, :app_name, :port, :last_modification, :originator_request_id, :correlation_id, + :request_handling_time, :request_start_time, :message_size, :compression_achieved, :expiry + + def initialize + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @operation_type = Juno::IO::JunoMessage::OperationType::NOP + @server_status = Juno::ServerStatus::SUCCESS + @compression_type = Juno::IO::CompressionType::None + @is_compressed = false + @compression_achieved = 0 + @message_size = 0 + @value = '' + end + + class OperationType + NOP = 0 + CREATE = 1 + GET = 2 + UPDATE = 3 + SET = 4 + DESTROY = 5 + + @@status_code_map = nil + + def self.initialize_map + @@status_code_map = {} + + constants.each do |const| + const_obj = const_get(const) + @@status_code_map[const_obj.to_i] = const_obj + end + end + + def self.get(status_code) + initialize_map if @@status_code_map.nil? + return @@status_code_map[status_code.to_i] if @@status_code_map.key?(status_code) + + INTERNAL_ERROR + end + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/IO/MetadataComponent.rb b/client/Ruby/juno/lib/juno/IO/MetadataComponent.rb new file mode 100644 index 00000000..9b8c3c9a --- /dev/null +++ b/client/Ruby/juno/lib/juno/IO/MetadataComponent.rb @@ -0,0 +1,354 @@ +# frozen_string_literal: true + +module Juno + module IO + # + # ** MetaData Component ** + # A variable length header followed by a set of meta data fields + # Tag/ID: 0x02 + # * Header * + # + # | 0| 1| 2| 3| 4| 5| 6| 7| + # 0 | size | 4 bytes + # ----+-----------------------+--------- + # 4 | Tag/ID (0x02) | 1 byte + # ----+-----------------------+--------- + # 5 | Number of fields | 1 byte + # ----+--------------+--------+--------- + # 6 | Field tag |SizeType| 1 byte + # ----+--------------+--------+--------- + # | ... | + # ----+-----------------------+--------- + # | padding to 4 | + # ----+-----------------------+--------- + # (Don't think we need a header size. ) + # + # SizeType: + # 0 variable length field, for that case, + # the first 1 byte of the field MUST be + # the size of the field(padding to 4 byte). + # The max is 255. + # n Fixed length: 2 ^ (n+1) bytes + # + # + # + # * Body * + # ----+-----------------------+--------- + # | Field data | defined by Field tag + # ----+-----------------------+--------- + # | ... | + # ----+-----------------------+--------- + # | padding to 8 | + # ----+-----------------------+--------- + # + # * Predefined Field Types * + # + # TimeToLive Field + # Tag : 0x01 + # SizeType : 0x01 + # Version Field + # Tag : 0x02 + # SizeType : 0x01 + # Creation Time Field + # Tag : 0x03 + # SizeType : 0x01 + # Expiration Time Field + # Tag : 0x04 + # SizeType : 0x01 + # RequestID/UUID Field + # Tag : 0x05 + # SizeType : 0x03 + # Source Info Field + # Tag : 0x06 + # SizeType : 0 + # Last Modification time (nano second) + # Tag : 0x07 + # SizeType : 0x02 + # Originator RequestID Field + # Tag : 0x08 + # SizeType : 0x03 + # Correlation ID field + # Tag : 0x09 + # SizeType : 0x0 + # Request Handling Time Field + # Tag : 0x0a + # SizeType : 0x01 + # + # Tag: 0x06 + # + # | 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| + # | 0| 1| 2| 3| + # +-----------+-----------+--------------------+--+-----------------------+-----------------------+ + # | size (include padding)| app name length | T| Port | + # +-----------------------+--------------------+--+-----------------------------------------------+ + # | IPv4 address if T is 0 or IPv6 address if T is 1 | + # +-----------------------------------------------------------------------------------------------+ + # | application name, padding to 4-bytes aligned | + # +-----------------------------------------------------------------------------------------------+ + + # Wrapper class for MetadataComponentTemplate + class MetadataComponent + attr_reader :metadata_field_list, :time_to_live, :version, :creation_time, :expiration_time, :request_uuid, + :ip, :app_name, :port, :last_modification, :originator_request_id, :correlation_id, :request_handling_time + + def initialize + @PROG_NAME = self.class.name + # @LOGGER = Juno::Logger.instance + # @metadata_field_list [Array] + @metadata_field_list = [] + end + + # @param ttl [Integer] - Record Time to live + def set_time_to_live(ttl) + ttl = ttl.to_i + raise ArgumentError, 'TTL should be > 0' unless ttl.positive? + + @time_to_live = ttl + ttl = [ttl].pack(OffsetWidth.UINT32) + add_field(MetadataField.new(0x01, 0x01, ttl)) + end + + # @param version [Integer] - Record version + def set_version(data) + @version = data + version_bytes_string = [data].pack(OffsetWidth.UINT32) + add_field(MetadataField.new(0x02, 0x01, version_bytes_string)) + end + + # @param creation_time [Integer] - Unix timestamp (required) + def set_creation_time(data) + @creation_time = data + creation_time_bytes_string = [data].pack(OffsetWidth.UINT32) + add_field(MetadataField.new(0x03, 0x01, creation_time_bytes_string)) + end + + def set_expiration_time(data) + @expiration_time = data + expiration_time_bytes_string = [data].pack(OffsetWidth.UINT32) + add_field(MetadataField.new(0x04, 0x01, expiration_time_bytes_string)) + end + + # @param input_uuid_byte_string [String] - Record Time to live (optional) + # if not provided, creates a uuid itself + def set_request_uuid(input_uuid_byte_string = nil) + @request_uuid = if input_uuid_byte_string.nil? + UUIDTools::UUID.random_create + else + UUIDTools::UUID.parse_raw(input_uuid_byte_string) + end + add_field(MetadataField.new(0x05, 0x03, @request_uuid.raw)) + @request_uuid + end + + # SourceInfoField + # @param app_name [String] - Record Time to live (required) + # @param ip [IPAddr] - ip address for component (required) + # @param port [Integer] (required) + def set_source_info(app_name:, ip:, port:) + @ip = ip + @port = port + @app_name = app_name + data = MetadataComponentTemplate::SourceInfoField.new + data.app_name = app_name + data.ip = ip.hton + data.port = port + str_io = StringIO.new + data.write(str_io) + add_field(MetadataField.new(0x06, 0x00, str_io.string)) + end + + def set_last_modification(data) + @last_modification = data + last_modification_bytes_string = [data].pack(OffsetWidth.UINT64) + add_field(MetadataField.new(0x07, 0x02, last_modification_bytes_string)) + end + + # @param input_uuid_byte_string [String] (optional) + # if not provided, creates a uuid itself + def set_originator_request_id(input_uuid_byte_string = nil) + @originator_request_id = if input_uuid_byte_string.nil? + UUIDTools::UUID.random_create + else + UUIDTools::UUID.parse_raw(input_uuid_byte_string) + end + add_field(MetadataField.new(0x08, 0x03, @originator_request_id.raw)) + @originator_request_id + end + + # @param input_uuid_byte_string [String] (optional) + # if not provided, creates a uuid itself + def set_correlation_id(input_uuid_byte_string = nil) + @correlation_id = if input_uuid_byte_string.nil? + UUIDTools::UUID.random_create + else + UUIDTools::UUID.parse_raw(input_uuid_byte_string) + end + field = MetadataComponentTemplate::CorrelationIDField.new + field.correlation_id = @correlation_id.raw + str_io = StringIO.new + field.write(str_io) + # puts field + add_field(MetadataField.new(0x09, 0x0, str_io.string)) + end + + def set_request_handling_time(data) + @request_handling_time = data + request_handling_time_bytes_string = [data].pack(OffsetWidth.UINT32) + add_field(MetadataField.new(0x0A, 0x01, request_handling_time_bytes_string)) + end + + # Function to add feild to the list + # @param field [MetadataField] (required) + def add_field(field) + metadata_field_list.push(field) + end + + # function to calculate size of metadata component + def num_bytes + io = StringIO.new + write(io) + io.size + end + + # Function to serialize Component to buffer + # @param io [StringIO] (required) + def write(io) + buffer = MetadataComponentTemplate.new + buffer.number_of_fields = metadata_field_list.length + metadata_field_list.each do |field| + f = MetadataComponentTemplate::MetadataHeaderField.new + f.size_type = field.size_type + f.field_tag = field.tag + buffer.metadata_fields.push(f) + end + + body = StringIO.new + metadata_field_list.each do |field| + body.write(field.data) + end + padding_size = (8 - body.size % 8) % 8 + body.write(Array.new(0, padding_size).pack(OffsetWidth.UINT8('*'))) if padding_size.positive? + buffer.body = body.string + + buffer.write(io) + end + + # Function to de-serialize Component to buffer + # @param io [StringIO] (required) + def read(io) + metadata_component = MetadataComponentTemplate.new + metadata_component.read(io) + + body_buffer = StringIO.new(metadata_component.body) + + metadata_component.metadata_fields.each do |field| + case field.field_tag + when TagAndType::TimeToLive[:tag] + ttl_byte_string = body_buffer.read(1 << (1 + TagAndType::TimeToLive[:size_type])) + set_time_to_live(ttl_byte_string.unpack1(OffsetWidth.UINT32)) + + when TagAndType::Version[:tag] + version_byte_string = body_buffer.read(1 << (1 + TagAndType::Version[:size_type])) + set_version(version_byte_string.unpack1(OffsetWidth.UINT32)) + + when TagAndType::CreationTime[:tag] + creation_time_byte_string = body_buffer.read(1 << (1 + TagAndType::CreationTime[:size_type])) + set_creation_time(creation_time_byte_string.unpack1(OffsetWidth.UINT32)) + + when TagAndType::RequestUUID[:tag] + request_uuid_byte_string = body_buffer.read(1 << (1 + TagAndType::RequestUUID[:size_type])) + set_request_uuid(request_uuid_byte_string) + + when TagAndType::SourceInfo[:tag] + source_info = MetadataComponentTemplate::SourceInfoField.new + source_info.read(body_buffer) + set_source_info(app_name: source_info.app_name, ip: IPAddr.new_ntoh(source_info.ip), port: source_info.port) + + when TagAndType::LastModification[:tag] + last_modification_byte_string = body_buffer.read(1 << (1 + TagAndType::LastModification[:size_type])) + set_last_modification(last_modification_byte_string.unpack1(OffsetWidth.UINT64)) + + when TagAndType::ExpirationTime[:tag] + expiration_time_byte_string = body_buffer.read(1 << (1 + TagAndType::ExpirationTime[:size_type])) + set_expiration_time(expiration_time_byte_string.unpack1(OffsetWidth.UINT32)) + + when TagAndType::OriginatorRequestID[:tag] + originator_request_id_byte_string = body_buffer.read(1 << (1 + TagAndType::OriginatorRequestID[:size_type])) + set_originator_request_id(originator_request_id_byte_string) + # when TagAndType::CorrelationID[:tag] + + when TagAndType::RequestHandlingTime[:tag] + request_handling_time_byte_string = body_buffer.read(1 << (1 + TagAndType::RequestHandlingTime[:size_type])) + set_request_handling_time(request_handling_time_byte_string.unpack1(OffsetWidth.UINT32)) + end + end + end + + class TagAndType + TimeToLive = { + tag: 0x01, + size_type: 0x01 + }.freeze + Version = { + tag: 0x02, + size_type: 0x01 + }.freeze + CreationTime = { + tag: 0x03, + size_type: 0x01 + }.freeze + RequestUUID = { + tag: 0x05, + size_type: 0x03 + }.freeze + SourceInfo = { + tag: 0x06, + size_type: 0x00 + }.freeze + ExpirationTime = { + tag: 0x04, + size_type: 0x01 + }.freeze + LastModification = { + tag: 0x07, + size_type: 0x02 + }.freeze + OriginatorRequestID = { + tag: 0x08, + size_type: 0x03 + }.freeze + CorrelationID = { + tag: 0x09, + size_type: 0x00 + }.freeze + RequestHandlingTime = { + tag: 0x0A, + size_type: 0x01 + }.freeze + end + + # DataType for @metadata_field_list + class MetadataField + attr_accessor :tag, :size_type, :data + + def initialize(tag, size_type, data) + @tag = tag + @size_type = size_type + @data = data + end + + def size + if size_type == SizeType::Variable + data.length + else + 1 << (size_type + 1) + end + end + + class SizeType + Variable = 0 + end + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/IO/MetadataComponentTemplate.rb b/client/Ruby/juno/lib/juno/IO/MetadataComponentTemplate.rb new file mode 100644 index 00000000..95d19486 --- /dev/null +++ b/client/Ruby/juno/lib/juno/IO/MetadataComponentTemplate.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module Juno + module IO + class MetadataComponentTemplate < BinData::Record + class MetadataHeaderField < BinData::Record + bit3 :size_type + bit5 :field_tag + end + + class FixedLengthField < BinData::Record + mandatory_parameter :field_length + + string :data, read_length: :field_length + end + + class SourceInfoField < BinData::Record + def field_bytes + field_length.num_bytes + app_name_length.num_bytes + port.num_bytes + ip.num_bytes + app_name.num_bytes + end + + def padding_size + (4 - field_bytes % 4) % 4 + end + + def ipv6? + IPAddr.new_ntoh(ip).ipv6? + end + + endian :big + uint8 :field_length, value: -> { field_bytes + padding.num_bytes } + uint8 :app_name_length, value: -> { ipv6? ? app_name.length | 128 : app_name.length } + uint16 :port + string :ip, read_length: -> { app_name_length & 128 == 1 ? 16 : 4 } + string :app_name, read_length: :app_name_length + string :padding, length: :padding_size + end + + class CorrelationIDField < BinData::Record + def padding_size + size = component_size.num_bytes + correlation_id_length.num_bytes + correlation_id.num_bytes + (4 - size % 4) % 4 + end + + endian :big + uint8 :component_size, value: -> { num_bytes } + uint8 :correlation_id_length, value: -> { correlation_id.length } + string :correlation_id + string :padding, length: :padding_size + end + + def header_num_bytes + component_size.num_bytes + tag_id.num_bytes + number_of_fields.num_bytes + metadata_fields.num_bytes + end + + def header_padding_length + (4 - header_num_bytes % 4) % 4 + end + + endian :big + uint32 :component_size, value: -> { num_bytes } + uint8 :tag_id, value: 0x02 + uint8 :number_of_fields, value: -> { metadata_fields.length } + array :metadata_fields, initial_length: :number_of_fields, type: :metadata_header_field + string :header_padding, length: :header_padding_length # implement padding length + + string :body, read_length: lambda { + component_size - 4 - 1 - 1 - number_of_fields - header_padding.num_bytes + } + end + end +end diff --git a/client/Ruby/juno/lib/juno/IO/OperationMessage.rb b/client/Ruby/juno/lib/juno/IO/OperationMessage.rb new file mode 100644 index 00000000..4eba9f08 --- /dev/null +++ b/client/Ruby/juno/lib/juno/IO/OperationMessage.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Juno + module IO + class OperationMessage + attr_accessor :protocol_header, :metadata_component, :payload_component + + def initialize + @protocol_header = ProtocolHeader.new + @metadata_component = nil + @payload_component = nil + end + + # Calculates size of message + def size + total_size = protocol_header.num_bytes + total_size += payload_component.num_bytes unless payload_component.nil? + total_size += metadata_component.num_bytes unless metadata_component.nil? + total_size + end + + # Function to serialize message to buffer + # @param io [StringIO] (required) + def write(io) + protocol_header.message_size = size + + protocol_header.write(io) + metadata_component&.write(io) + payload_component&.write(io) + nil + end + + # Function to de-serialize message to buffer + # @param io [StringIO] (required) + def read(io) + return if io.eof? || (io.size - io.pos) < 16 + + @protocol_header = ProtocolHeader.new + @metadata_component = MetadataComponent.new + @payload_component = PayloadComponent.new + + @protocol_header.read(io) + + remaining_size = protocol_header.message_size - 16 + prev_position = io.pos + + @metadata_component.read(io) if !io.eof? && (io.size - io.pos) >= remaining_size + + remaining_size -= (io.pos - prev_position) + + @payload_component.read(io) if !io.eof? && (io.size - io.pos) >= remaining_size + nil + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/IO/PayloadComponent.rb b/client/Ruby/juno/lib/juno/IO/PayloadComponent.rb new file mode 100644 index 00000000..4c2e426f --- /dev/null +++ b/client/Ruby/juno/lib/juno/IO/PayloadComponent.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +module Juno + module IO + # ** Payload (or KeyValue) Component ** + # + # A 12-byte header followed by name, key and value + # Tag/ID: 0x01 + # * Header * + # + # |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| + # | 0| 1| 2| 3| + # ------+---------------+---------------+---------------+---------------+ + # 0 | Size | + # ------+---------------+---------------+-------------------------------+ + # 4 | Tag/ID (0x01) | namespace len | key length | + # ------+---------------+---------------+-------------------------------+ + # 8 | payload length | + # ------+---------------------------------------------------------------+ + # + # ( + # The max namespace length: 255 + # payload length = 0 if len(payload data) = 0, otherwise, + # payload length = 1 + len(payload data) = len(payload field) + # ) + # + # + # * Body * + # +---------+-----+---------------+-------------------------+ + # |namespace| key | payload field | Padding to align 8-byte | + # +---------+-----+---------------+-------------------------+ + # + # * Payload field* + # +---------------------+--------------+ + # | 1 byte payload type | Payload data | + # +---------------------+--------------+ + # + # * Payload Type + # 0: payload data is the actual value passed from client user + # 1: payload data is encrypted by Juno client library, details not specified + # 2: payload data is encrypted by Juno proxy with AES-GCM. encryption key length is 256 bits + # 3: Payload data is compressed by Juno Client library. + # + # * Payload data + # for payload type 2 + # +--------------------------------+----------------+----------------+ + # | 4 bytes encryption key version | 12 bytes nonce | encrypted data | + # +--------------------------------+----------------+----------------+ + # + # for payload type 3 + # +---------------------------------+------------------+----------------+ + # | 1 byte size of compression type | compression type | compressed data| + # +---------------------------------+------------------+----------------+ + # + # * compression type + # 1) snappy (default algorithm) + # 2) TBD + class PayloadComponent < BinData::Record + class EncryptedPayloadData < BinData::Record + mandatory_parameter :payload_data_length + end + + class CompressedPayloadData < BinData::Record + mandatory_parameter :payload_data_length + def data_length + eval_parameter(:payload_data_length) - 1 - compression_type + end + + uint8 :compression_type_size, value: -> { :compression_type.length } + string :compression_type, read_length: :compression_type_size, initial_value: CompressionType::None + string :data, read_length: :data_length + end + + # class PayloadBody < BinData::Record + # mandatory_parameter :payload_length + # uint8 :payload_type, initial_value: PayloadType::UnCompressed, only_if: -> { :payload_length.positive? } # optional + + # choice :payload_data, selection: :payload_type, only_if: -> { :payload_length.positive? } do + # compressed_payload_data PayloadType::Compressed, payload_data_length: lambda { + # get_payload_data_length + # } + + # uncompressed_payload_data PayloadType::UnCompressed, payload_data_length: lambda { + # get_payload_data_length + # } + + # encrypted_payload_data PayloadType::Encrypted, payload_data_length: lambda { + # get_payload_data_length + # } + # end + # end + + class UncompressedPayloadData < BinData::Record + mandatory_parameter :payload_data_length + string :data, read_length: :payload_data_length + end + + def get_payload_data_length + (payload_length.positive? ? payload_length - 1 : 0) + end + + # to prevent stack overflow + def custom_num_bytes + size = component_size.num_bytes + tag_id.num_bytes + namespace_length.num_bytes + key_length.num_bytes + payload_length.num_bytes + namespace.num_bytes + payload_key.num_bytes + size += payload_type.num_bytes + payload_data.num_bytes if payload_length.positive? + size + end + + def padding_length + (8 - custom_num_bytes % 8) % 8 + end + + endian :big + uint32 :component_size, value: -> { num_bytes } + uint8 :tag_id, value: 0x01 + uint8 :namespace_length, value: -> { namespace.length } + uint16 :key_length, value: -> { payload_key.length } + uint32 :payload_length, value: -> { payload_data.num_bytes.zero? ? 0 : payload_data.num_bytes + 1 } + string :namespace, read_length: :namespace_length # required + string :payload_key, read_length: :key_length # required + uint8 :payload_type, onlyif: lambda { + payload_length.positive? + }, initial_value: PayloadType::UnCompressed # optional + + choice :payload_data, selection: :payload_type, onlyif: -> { payload_length.positive? } do + compressed_payload_data PayloadType::Compressed, payload_data_length: lambda { + get_payload_data_length + } + + uncompressed_payload_data PayloadType::UnCompressed, payload_data_length: lambda { + get_payload_data_length + } + + encrypted_payload_data PayloadType::Encrypted, payload_data_length: lambda { + get_payload_data_length + } + end + string :padding, length: :padding_length + + def set_value(input_value, compression_type = CompressionType::None) + if compression_type != CompressionType::None + self.payload_type = PayloadType::Compressed + payload_data.compression_type = compression_type + else + self.payload_type = PayloadType::UnCompressed + end + payload_data.data = input_value + end + + def value + payload_data.data + end + + def compressed? + return true if payload_type == PayloadType::Compressed + + false + end + + def compression_type + return payload_data.compression_type if compressed? + + CompressionType::None + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/IO/ProtocolHeader.rb b/client/Ruby/juno/lib/juno/IO/ProtocolHeader.rb new file mode 100644 index 00000000..8229797d --- /dev/null +++ b/client/Ruby/juno/lib/juno/IO/ProtocolHeader.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +module Juno + # Juno wire protocol consists of a 12-byte header. Depending on the type, the appropriate message payload follows the fixed header section. Following is the header protocol: + # + # | 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| 0| 1| 2| 3| 4| 5| 6| 7| + # byte | 0| 1| 2| 3| + # ------+-----------------------+-----------------------+-----------------------+-----------------------+ + # 0 | magic | version | message type flag | + # | | +-----------------+-----+ + # | | | type | RQ | + # ------+-----------------------------------------------+-----------------------+-----------------+-----+ + # 4 | message size | + # ------+-----------------------------------------------------------------------------------------------+ + # 8 | opaque | + # ------+-----------------------------------------------------------------------------------------------+ + # + # Following is the detailed description of each field in the header: + # + # offset name size (bytes) meaning + # 0 Magic 2 + # Magic number, used to identify Juno message. + # + # '0x5050' + # + # 2 Version 1 Protocol version, current version is 1. + # 3 Message Type flag + # 1 bit 0-5 + # Message Type + # + # 0: Operational Message + # + # 1: Admin Message + # + # 2: Cluster Control Message + # + # bit 6-7 + # RQ flag + # + # 0: response + # + # 1: two way request + # + # 3: one way request + # + # 4 Message size 4 Specifies the length of the message + # 8 Opaque 4 The Opaque data set in the request will be copied back in the response + # Operational Message + # Client Info (ip, port, type, application name) + # Request Type: request or response + # Operation Type: Create, Get, Update, Delete + # Request Id + # Request Info (key, ttl, version, namespace) + # Payload data size + # Payload + # Response Info (status/error code, error string) + # Flag + # Before defining the details of the protocol for operational message, we need to review, and finalize somethings at page. + # + # Operational Message Header + # + # operational request header + # |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| + # byte | 0| 1| 2| 3| + # ------+---------------+---------------+---------------+---------------+ + # 0 | opcode |flag | shard Id | + # | +-+-------------+ | + # | |R| | | + # ------+---------------+-+-------------+-------------------------------+ + # + # operational response header + # |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| + # byte | 0| 1| 2| 3| + # ------+---------------+---------------+---------------+---------------+ + # 0 | opcode |flag | reserved | status | + # | +-+-------------+ | | + # | |R| | | | + # ------+---------------+-+-------------+---------------+---------------+ + # + # opcode: + # 0x00 Nop + # 0x01 Create + # 0x02 Get + # 0x03 Update + # 0x04 Set + # 0x05 Destroy + # 0x81 PrepareCreate + # 0x82 Read + # 0x83 PrepareUpdate + # 0x84 PrepareSet + # 0x85 PrepareDelete + # 0x86 Delete + # 0xC1 Commit + # 0xC2 Abort (Rollback) + # 0xC3 Repair + # 0xC4 MarkDelete + # 0xE1 Clone + # 0xFE MockSetParam + # oxFF MockReSet + # R: + # 1 if it is for replication + # shard Id: + # only meaning for request to SS + # status: + # 1 byte, only meaningful for response + # + module IO + class ProtocolHeader < BinData::Record + class MessageTypes + OperationalMessage = 0 + AdminMessage = 1 + ClusterControlMessage = 2 + end + + class RequestTypes + Response = 0 + TwoWayRequest = 1 + OneWayRequest = 2 + end + + class OpCodes + Nop = 0x00 + Create = 0x01 + Get = 0x02 + Update = 0x03 + Set = 0x04 + Destroy = 0x05 + PrepareCreate = 0x81 + Read = 0x82 + PrepareUpdate = 0x83 + PrepareSet = 0x84 + PrepareDelete = 0x85 + Delete = 0x86 + Commit = 0xC1 + Abort = 0xC2 + Repair = 0xC3 + MarkDelete = 0xC4 + Clone = 0xE1 + MockSetParam = 0xFE + MockReSet = 0xFF + + def self.valid?(opcode) + constants.each do |constant| + return true if const_get(constant) == opcode + end + false + end + end + + class MessageTypeFlag < BinData::Record + bit2 :message_request_type, initial_value: ProtocolHeader::RequestTypes::TwoWayRequest + bit6 :message_type, initial_value: ProtocolHeader::MessageTypes::OperationalMessage + end + + def request? + message_type_flag.message_request_type != RequestTypes::Response + end + + endian :big + uint16 :magic, value: 0x5050 + uint8 :version, value: 1 + message_type_flag :message_type_flag + uint32 :message_size + uint32 :opaque, initial_value: 0 + uint8 :opcode, initial_value: OpCodes::Nop + uint8 :flag, value: 0 + + uint16 :shard_id, value: 0, onlyif: -> { request? } + uint8 :reserved, onlyif: -> { !request? } + uint8 :status, onlyif: -> { !request? } + + def request_type + message_type_flag.message_request_type + end + + def message_type + message_type_flag.message_type + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/IO/constants.rb b/client/Ruby/juno/lib/juno/IO/constants.rb new file mode 100644 index 00000000..543f24ef --- /dev/null +++ b/client/Ruby/juno/lib/juno/IO/constants.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# Top module for juno client +module Juno + # Submodule containing modules related to WireProtocol + module IO + class OffsetWidth + # Count can be integer or '*' + def self.UINT64(count = '') + "Q#{count}" + end + + def self.UINT32(count = '') + "N#{count}" + end + + def self.UINT16(count = '') + "n#{count}" + end + + def self.UINT8(count = '') + "C#{count}" + end + end + + # Class containing constants for CompressionType + class CompressionType + None = 'None' + Snappy = 'Snappy' + + def self.valid?(compression_type) + constants.each do |constant| + return true if const_get(constant) == compression_type + end + false + end + end + + # Class containing constants for PayloadType + class PayloadType + UnCompressed = 0x00 + Encrypted = 0x02 + Compressed = 0x03 + end + end +end diff --git a/client/Ruby/juno/lib/juno/Net/base_processor.rb b/client/Ruby/juno/lib/juno/Net/base_processor.rb new file mode 100644 index 00000000..79c71712 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Net/base_processor.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Juno + module Net + # BaseProcessor - base class for IOProcessor + # Handles logic for reading and writing ping_ip for bypass ltm + class BaseProcessor + def initialize(_ = nil) + @ping_queue = SizedQueue.new(1) + end + + def ping_ip=(ip) + @ping_queue.push(ip) + end + + def ping_ip + begin + ip = @ping_queue.pop(true) + rescue ThreadError + return nil + end + + return nil if ip.to_s.empty? + + begin + IPAddr.new(ip) + rescue StandardError + nil + end + end + + def clear_ping_queue + @ping_queue.clear + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Net/client_handler.rb b/client/Ruby/juno/lib/juno/Net/client_handler.rb new file mode 100644 index 00000000..22ecc323 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Net/client_handler.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module Juno + module Net + # Hanldes connection creation and receiving data asynchronously + # Managed by EventMachine::Connection + class ClientHandler < EventMachine::Connection + # Constant to count messages received + @@recv_count = Concurrent::AtomicFixnum.new(0) + + def self.received_messages + @@recv_count.value + end + + # Constructor + # @param io_processor (Juno::Net::IOProcessor) + def initialize(io_processor) + super + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @io_processor = io_processor + @channel = self + @connected = Concurrent::AtomicBoolean.new(false) + @ssl_connected = Concurrent::AtomicBoolean.new(false) + end + + # Method called once for each instance of Juno::Net::ClientHandler at initialization + def post_init + start_tls_connection if use_ssl? + end + + # starts tls connection once TCP connection is estabilished + def start_tls_connection + raise 'SSL Cert file not found' unless File.exist?(Juno.juno_config.ssl_cert_file) + raise 'SSL Key file not found' unless File.exist?(Juno.juno_config.ssl_key_file) + + @channel.start_tls( + private_key_file: Juno.juno_config.ssl_key_file, cert: File.read(Juno.juno_config.ssl_cert_file) + ) + # Timer to check if SSL Handshake was successful + EventMachine::Timer.new(Juno.juno_config.max_connection_timeout.to_f / 1000) do + if @ssl_connected.false? + puts 'SLL Handshake timeout' + close_connection + end + end + end + + # Method called when TCP connection estabilished. If useSSL is true, it is called after a successfull ssl handshake + def on_connection_completed + # puts "completed #{Time.now}" + end + + # method to check if channel is connected + def is_connected? + if use_ssl? + return false if @ssl_connected.false? + elsif @connected.false? + return false + end + + # get ip and port of server + # Socket.unpack_sockaddr_in(@channel.get_peername) + true + rescue Exception => e + @LOGGER.error(@PROG_NAME) { e.message } + false + end + + def use_ssl? + Juno.juno_config.use_ssl + end + + def use_ltm? + Juno.juno_config.bypass_ltm + end + + # method called by EventMachine when data is received from server + # @param data [String] - byte data received from server + def receive_data(data) + @@recv_count.increment + # puts @@recv_count + + EventMachine.defer do + operation_message = Juno::IO::OperationMessage.new + operation_message.read(StringIO.new(data)) + @io_processor.put_response(operation_message) + end + end + + # Called by EventMachine after TCP Connection estabilished + def connection_completed + @connected.value = true + on_connection_completed unless use_ssl? + end + + # Called by EventMachine after connection disconnected + # @param m - Error if disconnected due to an error + def unbind(error) + @connected.value = false + @ssl_connected.value = false + puts error unless error.nil? + end + + # Called by EventMachine after ssl handshake + def ssl_handshake_completed + @ssl_connected.value = true + on_connection_completed if use_ssl? + + # puts get_cipher_name + # puts get_cipher_protocol + @server_handshake_completed = true + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Net/io_processor.rb b/client/Ruby/juno/lib/juno/Net/io_processor.rb new file mode 100644 index 00000000..436fc9b4 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Net/io_processor.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require 'concurrent' +module Juno + module Net + # Module to handle connections to server, reading/writing requests from request queue + class IOProcessor < BaseProcessor + INITIAL_BYPASSLTM_RETRY_INTERVAL = 337_500 + MAX_BYPASSLTM_RETRY_INTERVAL = 86_400_000 + INIT_WAIT_TIME = 200 + MAX_WAIT_TIME = 60_000 + # class variable to count messages sent using send_data + @@counter = Concurrent::AtomicFixnum.new(0) + def self.send_count + @@counter.value + end + + # @param request_queue [Juno::Net::RequestQueue] + # @param opaque_resp_queue_map [Concurrent::Map] - map containing opaque as key and value as Response queue corresponding to opaque + def initialize(request_queue, opaque_resp_queue_map) + super() + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @stop = false + @request_queue = request_queue + @opaque_resp_queue_map = opaque_resp_queue_map + @ctx = nil ## changed + @handshake_failed_attempts = 0 + @reconnect_wait_time = INIT_WAIT_TIME + @shift = 5 # seconds + @next_bypass_ltm_check_time = Time.now + @bypass_ltm_retry_interval = INITIAL_BYPASSLTM_RETRY_INTERVAL + # @config = config + @channel = nil + @next_reconnect_due = Float::INFINITY + end + + def connection_lifetime + Juno.juno_config.connection_lifetime + end + + def put_response(operation_message) + return if operation_message.nil? + + unless Juno::Net::PingMessage.ping_response?(operation_message, self) + opaque = operation_message&.protocol_header&.opaque + return if opaque.nil? + + resp_queue = @opaque_resp_queue_map.get_and_set(opaque.to_i, nil) + if !resp_queue.nil? + begin + resp_queue.push(operation_message) + rescue ThreadError + @LOGGER.debug(@PROG_NAME) { "response queue for #{opaque.to_i} is full" } + end + else + @LOGGER.debug(@PROG_NAME) { "resp_queue nil for #{opaque.to_i}" } + end + end + nil + end + + def disconnect_channel(channel) + EventMachine::Timer.new(2 * Juno.juno_config.max_response_timeout.to_f / 1000) do + channel&.close_connection_after_writing if !channel.nil? && channel.is_connected? + end + end + + def set_recycle_timer + @recycle_timer = EventMachine::Timer.new(Juno.juno_config.connection_lifetime.to_f / 1000) do + juno_connect(true) + end + end + + def initiate_bypass_ltm + send_ping_message + EventMachine::Timer.new(Juno.juno_config.response_timeout.to_f / 1000) do + ip = ping_ip + unless ip.nil? + new_channel = EventMachine.connect(ip.to_s, Juno.juno_config.port, ClientHandler, self) + EventMachine::Timer.new(Juno.juno_config.connection_timeout.to_f / 1000) do + if new_channel.is_connected? + @LOGGER.info(@PROG_NAME) { "conncected to Proxy #{ip}:#{Juno.juno_config.port} " } + old_channel = @channel + @channel = new_channel + disconnect_channel(old_channel) + else + @LOGGER.info(@PROG_NAME) { "could not conncect to Proxy #{ip}:#{Juno.juno_config.port} " } + end + end + end + end + end + + # Sends ping message to LoadBalancer to get ProxyIP + # @see Juno::Net::PingMessage + def send_ping_message + ping_message = Juno::Net::PingMessage.new + buff = StringIO.new + ping_message.write(buff) + request_uuid = ping_message&.operation_message&.metadata_component&.request_uuid.to_s + @request_queue.push(buff.string, request_uuid) + end + + # Method to handle connections creation, re-attempts on failure, initiates connection refresh and connection to Proxy + # @param recycle [Boolean] - True if connection refresh request (optional, default: false) + def juno_connect(recycle = false) + return if !recycle && !@channel.nil? && @channel.is_connected? + + new_channel = EventMachine.connect(Juno.juno_config.host, Juno.juno_config.port, ClientHandler, self) + new_channel.pending_connect_timeout = Juno.juno_config.connection_lifetime + EventMachine::Timer.new(Juno.juno_config.connection_timeout.to_f / 1000) do + if new_channel.is_connected? + @LOGGER.info(@PROG_NAME) { "conncected to #{Juno.juno_config.host}:#{Juno.juno_config.port} " } + if recycle + old_channel = @channel + @channel = new_channel + disconnect_channel(old_channel) + else + @channel = new_channel + end + initiate_bypass_ltm if use_ltm? + set_recycle_timer + else + @recycle_timer&.cancel + new_channel&.close_connection if !new_channel.nil? && new_channel.is_connected? + @LOGGER.info(@PROG_NAME) do + "Could not conncect to #{Juno.juno_config.host}:#{Juno.juno_config.port}\n Retrying in #{@reconnect_wait_time.to_f / 1000}ms " + end + EventMachine::Timer.new(@reconnect_wait_time.to_f / 1000) do + @reconnect_wait_time *= 2 + @reconnect_wait_time = MAX_WAIT_TIME if @reconnect_wait_time > MAX_WAIT_TIME + @reconnect_wait_time *= (1 + 0.3 * rand) + juno_connect(recycle) + end + end + end + end + + def stop + @stop = true + end + + # Event loop to continously check for requests in @request_queue + def run + EventMachine.run do + juno_connect + EventMachine.tick_loop do + if !@channel.nil? && @channel.is_connected? + # key = "19key#{rand(100) + rand(1_000_000)}" + item = @request_queue.pop + unless item.nil? + @@counter.increment + @channel.send_data(item.msg_buffer) + end + end + :stop if @stop == true + rescue Exception => e + @LOGGER.error(@PROG_NAME) do + "Error in tick_loop: #{e.message}. Stopping tick_loop" + end + :stop + end.on_stop do + @LOGGER.debug(@PROG_NAME) do + "tick loop stopped. Stop initiated by client #{@stop}" + end + reset_connections + EventMachine::Timer.new(2 * Juno.juno_config.connection_timeout.to_f / 1000) do + EventMachine.stop + end + end + rescue Exception => e + @LOGGER.debug(@PROG_NAME) do + "EventMachine Fatal Exception #{e.message}" + end + reset_connections + EventMachine.stop + end + end + + def reset_connections + @recycle_timer&.cancel + disconnect_channel(@channel) + end + + def use_ssl? + Juno.juno_config.use_ssl + end + + def host + Juno.juno_config.host + end + + def port + Juno.juno_config.port + end + + def use_ltm? + host != '127.0.0.1' && Juno.juno_config.bypass_ltm # boolean + end + + def bypass_ltm_disabled? + if Time.now > @next_bypass_ltm_check_time && @bypass_ltm_retry_interval < MAX_BYPASSLTM_RETRY_INTERVAL + return false + end + + true + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Net/ping_message.rb b/client/Ruby/juno/lib/juno/Net/ping_message.rb new file mode 100644 index 00000000..6d060602 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Net/ping_message.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Juno + module Net + # Module to Create/Decode Ping messages + class PingMessage + # Constant Internal app name to check for ping messages + JUNO_INTERNAL_APPNAME = 'JunoInternal' + + # method to read the operation message + attr_reader :operation_message + + # @param operation_message [Juno::IO::OperationMessage] (optional, default: Juno::Net::PingMessage::JUNO_INTERNAL_APPNAME) + # @param opaque [Integer] (optional, default: 0) + def initialize(app_name = nil, opaque = 0) + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + app_name = JUNO_INTERNAL_APPNAME if app_name.to_s.empty? + + meta_data_component = Juno::IO::MetadataComponent.new + meta_data_component.set_request_uuid + meta_data_component.set_source_info(app_name: app_name, ip: IPAddr.new(Juno::Utils.local_ips[0]), port: 0) + + protocol_header = Juno::IO::ProtocolHeader.new + protocol_header.opcode = Juno::IO::ProtocolHeader::OpCodes::Nop + protocol_header.opaque = opaque + + @operation_message = Juno::IO::OperationMessage.new + @operation_message.metadata_component = meta_data_component + @operation_message.protocol_header = protocol_header + end + + # method to check if given operation message is a Ping response + # Updates ping_ip in processor if it is a ping response + # @param operation_message [Juno::IO::OperationMessage] (required) + # @param operation_message [Juno::Net::IOProcessor] (required) + def self.ping_response?(operation_message, processor) + return false unless processor.use_ltm? + + opcode = operation_message&.protocol_header&.opcode + raise 'missing protocol header' if opcode.nil? + return false if opcode != Juno::IO::ProtocolHeader::OpCodes::Nop + return false if operation_message&.metadata_component.nil? + return false if operation_message&.metadata_component&.ip.to_s.empty? + return false if operation_message&.metadata_component&.app_name != JUNO_INTERNAL_APPNAME + + ping_ip = operation_message.metadata_component.ip.to_s + if ping_ip.split('.')[0] == '127' + processor.ping_ip = '' + return true + end + if Juno::Utils.local_ips.include?(ping_ip) + processor.ping_ip = '' + return true + end + processor.ping_ip = ping_ip + true + end + + # Function to serialize Component to buffer + # @param buff [StringIO] (required) + def write(buf) + @operation_message.write(buf) + end + + # Function to de-serialize Component from buffer + # @param buff [StringIO] (required) + def read(buf) + @operation_message = Juno::IO::OperationMessage.new + @operation_message.read(buf) + end + + # Function to read the ping ip + def ip + @operation_message&.metadata_component&.ip + end + + # Function to read the port from metadata component + def port + @operation_message&.metadata_component&.port + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Net/request_queue.rb b/client/Ruby/juno/lib/juno/Net/request_queue.rb new file mode 100644 index 00000000..4b62093c --- /dev/null +++ b/client/Ruby/juno/lib/juno/Net/request_queue.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module Juno + module Net + # DataType of each item in RequestQueue + class QueueEntry + attr_accessor :msg_buffer, :req_id, :enqueue_time + + # @param msg_bugger [StringIO] (required) + # @param id [String] - request UUID (required) + def initialize(msg_buffer, id) + @msg_buffer = msg_buffer + @req_id = id + @enqueue_time = Time.now + end + end + + # Request queue - Singleton + class RequestQueue + # mutex to synchronize creation of RequestQueue instance + @@mutex = Mutex.new + + # Singleton instance + @@instance = nil + + def self.instance + return @@instance unless @@instance.nil? + + @@mutex.synchronize do + @@instance ||= new + end + end + + private_class_method :new + + attr_reader :opaque_resp_queue_map + + def initialize + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @size = 13_000 # Default request queue size + @request_queue = SizedQueue.new(@size) + @opaque_resp_queue_map = Concurrent::Map.new + @worker_pool = Juno::Net::WorkerPool.new(self) + end + + def full? + @request_queue.size == @size + end + + def size + @request_queue.size + end + + def stop + @worker_pool.stop + @request_queue.clear + end + + # @param operation_message [Juno::IO::OperatioinMessage] (required) + # @return [Boolean] - true if successfully pushed item to queue. Else false + def push(msg_buffer, request_uuid) + # buffer = StringIO.new + # operation_message.write(buffer) + # request_uuid = operation_message&.metadata_component&.request_uuid.to_s + request_uuid = 'not_set' if request_uuid.to_s.empty? + + begin + @request_queue.push(QueueEntry.new(msg_buffer, request_uuid), true) + rescue ThreadError + return false + end + true + end + + # @return [QueueEntry] - nil if queue empty + def pop + @request_queue.pop(true) + rescue ThreadError + # queue empty + nil + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Net/worker_pool.rb b/client/Ruby/juno/lib/juno/Net/worker_pool.rb new file mode 100644 index 00000000..a94c6923 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Net/worker_pool.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Juno + module Net + class WorkerPool + def initialize(request_queue) + raise 'Request queue cannot be nil' if request_queue.nil? + + @PROG_NAME = self.class.name + @LOGGER = Juno::Logger.instance + @request_queue = request_queue + EventMachine.threadpool_size = 200 + @io_processor = Juno::Net::IOProcessor.new(@request_queue, @request_queue.opaque_resp_queue_map) + init + end + + def init + @worker = Thread.new do + @io_processor.run + end + end + + def active? + @worker.alive? + end + + def stop + @io_processor.stop + end + end + end +end diff --git a/client/Ruby/juno/lib/juno/Utils/client_utils.rb b/client/Ruby/juno/lib/juno/Utils/client_utils.rb new file mode 100644 index 00000000..ae10f05a --- /dev/null +++ b/client/Ruby/juno/lib/juno/Utils/client_utils.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +module Juno + module ClientUtils + def self.create_operation_message(juno_message, opaque) + protocol_header = Juno::IO::ProtocolHeader.new + protocol_header.version = juno_message.version + protocol_header.opcode = juno_message.operation_type + protocol_header.opaque = opaque + + metadata_component = Juno::IO::MetadataComponent.new + if juno_message.time_to_live_s.to_i.positive? + metadata_component.set_time_to_live(juno_message.time_to_live_s.to_i) + end + metadata_component.set_version(juno_message.version) + + if [Juno::IO::JunoMessage::OperationType::CREATE, + Juno::IO::JunoMessage::OperationType::SET].include?(juno_message.operation_type) + metadata_component.set_creation_time(Time.now.to_i) + end + + metadata_component.set_expiration_time((Time.now + juno_message.time_to_live_s).to_i) # what ? + metadata_component.set_request_uuid(juno_message.request_uuid) + metadata_component.set_source_info(app_name: juno_message.app_name, ip: juno_message.ip, port: juno_message.port) + metadata_component.set_originator_request_id # what + + payload_component = Juno::IO::PayloadComponent.new + payload_component.namespace = juno_message.namespace + payload_component.payload_key = juno_message.key + payload_component.set_value(juno_message.value, juno_message.compression_type) + + operation_message = Juno::IO::OperationMessage.new + operation_message.protocol_header = protocol_header + operation_message.metadata_component = metadata_component + operation_message.payload_component = payload_component + + juno_message.message_size = operation_message.size + + operation_message + end + + def self.compressed_value(value) + compressed_value = Snappy.deflate(value) + compression_achieved = 100 - (compressed_value.length * 100) / value.length + [compressed_value, compression_achieved] + rescue Exception + [value, false] + end + + def self.decompress_value(value) + Snappy.inflate(value) + rescue Exception + # Log failure + value + end + + def self.validate!(juno_request) + return false unless juno_request.is_a?(Juno::Client::JunoRequest) + + raise ArgumentError, 'Juno request key cannot be empty' if juno_request.key.to_s.nil? + + juno_request.time_to_live_s = Juno.juno_config.default_lifetime unless juno_request.time_to_live_s.to_i.positive? + + if juno_request.time_to_live_s > Juno.juno_config.max_lifetime || juno_request.time_to_live_s.negative? + raise ArgumentError, + "Record time_to_live_s (#{juno_request.time_to_live_s}s) cannot be greater than #{Juno.juno_config.max_lifetime}s or negative ." + end + + if juno_request.key.to_s.size > Juno.juno_config.max_key_size + raise ArgumentError, + "Key size cannot be greater than #{Juno.juno_config.max_key_size}" + end + + if juno_request.key.to_s.size > Juno.juno_config.max_key_size + raise ArgumentError, + "Key size cannot be greater than #{Juno.juno_config.max_key_size}" + end + + juno_message = Juno::IO::JunoMessage.new + juno_message.key = juno_request.key + juno_message.version = juno_request.version + juno_message.operation_type = juno_request.type + juno_message.time_to_live_s = juno_request.time_to_live_s + juno_message.creation_time = juno_request.creation_time + juno_message.namespace = Juno.juno_config.record_namespace + juno_message.app_name = Juno.juno_config.app_name + juno_message.request_uuid = UUIDTools::UUID.random_create.to_s + juno_message.ip = IPAddr.new(Juno::Utils.local_ips[0]) + juno_message.port = 0 + + unless [Juno::Client::JunoRequest::Type::GET, + Juno::Client::JunoRequest::Type::DESTROY].include?(juno_request.type) + payload_value = juno_request.value + is_compressed = false + compression_achieved = 0 + if Juno.juno_config.use_payload_compression && value.length > 1024 + payload_value, compression_achieved = compressed_value(value) + is_compressed = true if compression_achieved.positive? + end + juno_message.is_compressed = is_compressed + juno_message.value = payload_value + juno_message.compression_achieved = compression_achieved + juno_message.compression_type = is_compressed ? Juno::IO::CompressionType::Snappy : Juno::IO::CompressionType::None + end + + juno_message + end + + # @params operation_message [Juno::IO::OperationMessage] (required) + # @returns [Juno::IO::JunoMessage] + def self.decode_operation_message(operation_message) + return nil unless operation_message.is_a?(Juno::IO::OperationMessage) + + juno_message = Juno::IO::JunoMessage.new + opcode = operation_message.protocol_header.opcode.to_i + juno_message.operation_type = Juno::IO::JunoMessage::OperationType.get(opcode) + + server_status = operation_message.protocol_header.status.to_i + juno_message.server_status = Juno::ServerStatus.get(server_status) + + juno_message.message_size = operation_message.protocol_header.message_size.to_i + + unless operation_message.metadata_component.nil? + metadata_component = operation_message.metadata_component + juno_message.time_to_live_s = metadata_component.time_to_live.to_i + juno_message.ip = metadata_component.ip + juno_message.port = metadata_component.port + juno_message.version = metadata_component.version.to_i + juno_message.creation_time = metadata_component.creation_time.to_i + juno_message.expiration_time = metadata_component.expiration_time.to_i + juno_message.request_uuid = metadata_component.request_uuid + juno_message.app_name = metadata_component.app_name + juno_message.last_modification = metadata_component.last_modification.to_i + juno_message.originator_request_id = metadata_component.originator_request_id.to_s + juno_message.correlation_id = metadata_component.correlation_id + juno_message.request_handling_time = metadata_component.request_handling_time.to_i + # juno_message.request_start_time = metadata_component. + # expiry + end + + unless operation_message.payload_component.nil? + juno_message.namespace = operation_message.payload_component.namespace.to_s + juno_message.key = operation_message.payload_component.payload_key.to_s + juno_message.is_compressed = operation_message.payload_component.compressed? + juno_message.compression_type = operation_message.payload_component.compression_type.to_i + juno_message.value = if operation_message.payload_component.payload_length.to_i.zero? + juno_message.compression_achieved = 0 + nil + elsif juno_message.is_compressed + compressed_value = operation_message.payload_component.value.to_s + decompressed_value = decompress_value(compressed_value) + juno_message.compression_achieved = 100 - (compressed_value.length / decompressed_value.length.to_f) * 100.0 + decompressed_value + else + juno_message.compression_achieved = 0 + operation_message.payload_component.value.to_s + end + end + + juno_message + end + end +end diff --git a/client/Ruby/juno/lib/juno/Utils/utils.rb b/client/Ruby/juno/lib/juno/Utils/utils.rb new file mode 100644 index 00000000..f9da34d0 --- /dev/null +++ b/client/Ruby/juno/lib/juno/Utils/utils.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +module Juno + class Utils + def self.local_ips + ip_addresses = Socket.ip_address_list.select do |addr| + addr.ipv4? && !addr.ipv4_loopback? # && !addr.ipv4_private? + end + ip_addresses.map!(&:ip_address) + rescue StandardError => e + puts e.message + ['127.0.0.1'] + end + + def self.create_message(key, op_type, value = '') + meta_data_component = Juno::IO::MetadataComponent.new + # ip = IPAddr.new(Socket.ip_address_list.detect(&:ipv4_private?).ip_address) + meta_data_component.set_time_to_live(Juno.juno_config.default_lifetime) + meta_data_component.set_version(1) + meta_data_component.set_creation_time(1000) + meta_data_component.set_request_uuid + meta_data_component.set_correlation_id + meta_data_component.set_originator_request_id + meta_data_component.set_source_info(app_name: Juno.juno_config.app_name, ip: IPAddr.new(Juno::Utils.local_ips[0]), + port: Juno.juno_config.port) + + payload_component = Juno::IO::PayloadComponent.new + payload_component.namespace = Juno.juno_config.record_namespace + payload_component.payload_key = key + if op_type == Juno::IO::ProtocolHeader::OpCodes::Create && !value.to_s.empty? + is_compressed = false + if Juno.juno_config.use_payload_compression && value.length > 1024 + value, compression_achieved = compressed_value(value) + is_compressed = true if compression_achieved.positive? + end + if is_compressed + puts 'using compression' + payload_component.set_value(value, Juno::IO::CompressionType::Snappy) + else + payload_component.set_value(value) + end + end + + protocol_header = Juno::IO::ProtocolHeader.new + protocol_header.opcode = op_type + + operation_message = Juno::IO::OperationMessage.new + operation_message.metadata_component = meta_data_component + operation_message.protocol_header = protocol_header + operation_message.payload_component = payload_component + buffer = StringIO.new + operation_message.write(buffer) + buffer + end + + def self.ssl_context + ssl_context = OpenSSL::SSL::SSLContext.new + ssl_context.ssl_version = :TLSv1_1 + cert = OpenSSL::X509::Certificate.new(File.open(File.expand_path(File.join( + __dir__, '..', '..', 'server.crt' + )))) + key = OpenSSL::PKey::RSA.new(File.open(File.expand_path(File.join( + __dir__, '..', '..', 'server.pem' + )))) + ca_file = OpenSSL::X509::Certificate.new(File.open(File.expand_path(File.join( + __dir__, '..', '..', 'myca.crt' + )))) + ssl_context.add_certificate(cert, key, [ca_file]) + # ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER + ssl_context.ssl_timeout = 10 + ssl_context.timeout = 10 + ssl_context + end + + def self.ssl_request(buffer) + socket = TCPSocket.open(Juno.juno_config.host, Juno.juno_config.port) + if Juno.juno_config.use_ssl + ctx = ssl_context + socket = OpenSSL::SSL::SSLSocket.new(socket, ctx) + socket.sync_close = true + # socket.post_connection_check(Juno.juno_config.host) + socket.connect + end + + # puts socket.peer_finished_message.bytes.join(', ') + # puts socket.verify_result + + socket.write(buffer.string) + res_buffer = StringIO.new + header = true + + size = 0 + while (line = socket.sysread(1024 * 16)) # buffer size of OpenSSL library + if header + p = Juno::IO::ProtocolHeader.new + p.read(StringIO.new(line)) + header = false + size = p.message_size + end + res_buffer.write(line) + break if res_buffer.length == size + end + socket.close + + res_buffer.rewind + + res_buffer + end + + def self.create_and_send_ping_message + ping_message = Juno::Net::PingMessage.new + ping_buffer = StringIO.new + ping_message.write(ping_buffer) + response = ssl_request(ping_buffer) + ping_message = Juno::Net::PingMessage.new + ping_message.read(response) + ping_message + end + + def self.time_diff_ms(a, b) + (b - a).abs * 1000 + end + end +end diff --git a/client/Ruby/juno/lib/juno/logger.rb b/client/Ruby/juno/lib/juno/logger.rb new file mode 100644 index 00000000..6e0ef7e7 --- /dev/null +++ b/client/Ruby/juno/lib/juno/logger.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Juno + # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN + + class Logger + @@mutex = Mutex.new + + # Singleton instance + @@instance = nil + + def self.instance + return @@instance unless @@instance.nil? + + @@mutex.synchronize do + if @@instance.nil? + raise 'log file not configured' if Juno.juno_config.log_file.to_s.empty? + + @@instance = ::Logger.new(Juno.juno_config.log_file, 'daily', progname: 'JunoRubyClient') + + @@instance.level = ::Logger::INFO + end + end + @@instance + end + + def self.level=(log_level) + @@instance.level = log_level unless @@instance.nil? + end + + private_class_method :new + end +end diff --git a/client/Ruby/juno/lib/juno/server_status.rb b/client/Ruby/juno/lib/juno/server_status.rb new file mode 100644 index 00000000..a5af05d2 --- /dev/null +++ b/client/Ruby/juno/lib/juno/server_status.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Juno + class ServerStatus + SUCCESS = { code: 0, error_msg: 'no error', client_operation_status: Juno::Client::OperationStatus::SUCCESS }.freeze + BAD_MSG = { code: 1, error_msg: 'bad message', + client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze + NO_KEY = { code: 3, error_msg: 'key not found', + client_operation_status: Juno::Client::OperationStatus::NO_KEY }.freeze + DUP_KEY = { code: 4, error_msg: 'dup key', + client_operation_status: Juno::Client::OperationStatus::UNIQUE_KEY_VIOLATION }.freeze + BAD_PARAM = { code: 7, error_msg: 'bad parameter', + client_operation_status: Juno::Client::OperationStatus::BAD_PARAM }.freeze + RECORD_LOCKED = { code: 8, error_msg: 'record locked', + client_operation_status: Juno::Client::OperationStatus::RECORD_LOCKED }.freeze + NO_STORAGE_SERVER = { code: 12, error_msg: 'no active storage server', + client_operation_status: Juno::Client::OperationStatus::NO_STORAGE }.freeze + SERVER_BUSY = { code: 14, error_msg: 'Server busy', + client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze + VERSION_CONFLICT = { code: 19, error_msg: 'version conflict', + client_operation_status: Juno::Client::OperationStatus::CONDITION_VIOLATION }.freeze + OP_STATUS_SS_READ_TTL_EXTENDERR = { code: 23, error_msg: 'Error extending TTL by SS', + client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze + COMMIT_FAILURE = { code: 25, error_msg: 'Commit Failure', + client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze + INCONSISTENT_STATE = { code: 26, error_msg: 'Inconsistent State', + client_operation_status: Juno::Client::OperationStatus::SUCCESS }.freeze + INTERNAL = { code: 255, error_msg: 'Internal error', + client_operation_status: Juno::Client::OperationStatus::INTERNAL_ERROR }.freeze + + @@status_code_map = nil + + def self.initialize_map + @@status_code_map = {} + + constants.each do |const| + const_obj = const_get(const) + @@status_code_map[const_obj[:code].to_i] = const_obj + end + end + + def self.get(status_code) + initialize_map if @@status_code_map.nil? + return @@status_code_map[status_code] if @@status_code_map.key?(status_code.to_i) + + INTERNAL + end + end +end diff --git a/client/Ruby/juno/lib/juno/version.rb b/client/Ruby/juno/lib/juno/version.rb new file mode 100644 index 00000000..e9bf587c --- /dev/null +++ b/client/Ruby/juno/lib/juno/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Juno + VERSION = '1.0.0' +end diff --git a/client/Ruby/juno/pkg/juno-0.1.0.gem b/client/Ruby/juno/pkg/juno-0.1.0.gem new file mode 100644 index 0000000000000000000000000000000000000000..90a4f1c97047958f95d098bdf00700193df4fee0 GIT binary patch literal 4608 zcmc~zElEsCEJ@T$uVSDTFaQD*6B7my4Fu@4p^1qp1CTH?H8D3c0;w}JHZe0|P%xmC zgOJTFEiOqc0y?QYBQ-S#SptnsNFL%hgy+z-4_2;xHwVL#nrkUc%x`C&&!24|a{T=t z(KvluM~=*`oSC-1v#c_MuKHT%%+g7YJFv3;tXb^NH~I4$mqcD$m7E#e#d4C-{!Dee zU7ZaN-(i8hMxkNnL$)!!y7XP)(wE6>r>6A?9iPIZA~<1DsA^~d@3E@js`HldyPO%F zw$FW(A$PR#*%95Tf|^<9I6a2-mF3g z8|ZR={6*PlLd~?6f~R7F2rtqZ)TgNzhn1- zHIppaAK%VaWLmQOy_f#>_S)xj-`PbabN+q*;?3K@+ZnU?AHLcaopJo*M)O@!)t;Gr z*AGmMYrn+CQ=o87K1wJrkz4wGhlERs{~1<`HNA@xbj1|Z?zA7xJt6AT!++++uPRH& zI<^yuFIo6!FXt@GTXmH^JubCN%Qn%@;+fT#^GBYt{MxV}JLyyB$3#i<$>onuOiuh) zx^4O+6|2r?%cCzXo7BfyA$O|(@*QE5&`Dv(eq2cWon&h8p>m4L$sHBtlYT#r+tg%n zxk^gpt7nPS+2gIdCf^a>s^4{q)5%-yq3V*auRkvR`g-4*@UkB@e#fStKl%TOak%Tp z7Jcntwfo+YmEBuwex&#nHc$Kze+O|L~?A8hWmi0bJrQs1fm^wfrjg6G}M7e{Bm z25+M-FSVBxRk#o&`DNiGuE2A%UMO%p zJ{vV%ZL6BiA}^K#<}F1kj}vYmHQrJ9Pw?g6J7&BG)xt&eZ!SAuwmyW(MAK&KCCl8) zH`gSrTeB%cI9nLGQ@DKXvjo5yB6KBicg&{}35`QPvA-1E-={Vvb-ceWO)DZAyEy5{}0w(GK% z-0|DQ8dF7gA0$r-81*~O)~#d?*AxjD2c1wc(BJncUN6H`O9{10sYnT+OtdZa8w{!g7~xk-TeqGIgdL{++V-?{#(lJ0d5- zj%0DFYDG5$ty^<#FR#JTnMZ%Im}ixJc<5`Szis{gdE77U&leW<9tr-w<%emY*M^Pi z({q@Uc+?j!YN}Q?z1*@nCdwy$Ui-b;FXu{G(q9YSN}t`)BQ}3(XhM;QsN3^XGtDBR zXG{LPKQ~?Zw&kQf*$b|^>=5}r*LK=eHgN}sW$Q#fF8X%TKr=FOTkbnfqeT%{RFBLt z)YfqDFzsP?U;gRG%WHmKZ}xmBHs literal 0 HcmV?d00001 diff --git a/client/Ruby/juno/pkg/juno-1.0.0.gem b/client/Ruby/juno/pkg/juno-1.0.0.gem new file mode 100644 index 0000000000000000000000000000000000000000..06f039901bbb2b0fe174fbd2563a1a5a43bbde2a GIT binary patch literal 31744 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 literal 0 HcmV?d00001 diff --git a/client/Ruby/juno/spec/Client/sync_client.rb b/client/Ruby/juno/spec/Client/sync_client.rb new file mode 100644 index 00000000..aaf3846c --- /dev/null +++ b/client/Ruby/juno/spec/Client/sync_client.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'juno' + +describe 'Sync client tests' do + before(:context) do + Juno.configure do |config| + config.file_path = URI.join('file:///', File.expand_path('./../../../juno.yml').to_s) + config.log_file = 'juno.log' + config.ssl_cert_file = File.expand_path('./../../lib/server.crt') + config.ssl_key_file = File.expand_path('./../../lib/server.pem') + end + + @juno_client = Juno::Client::SyncClient.new + @key = "mykey#{rand(1000)}" + @value = 'myvalue' + end + + it 'create test' do + juno_resp = @juno_client.create(@key, @value, ttl: 12_000) + expect(juno_resp.status[:code]).to eq(Juno::ServerStatus::SUCCESS[:code]) + expect(juno_resp.record_context.time_to_live_s).to eq(12_000) + end + + it 'get request test' do + juno_resp = @juno_client.get(@key) + expect(juno_resp.status[:code]).to eq(Juno::ServerStatus::SUCCESS[:code]) + expect(juno_resp.value).to eq(@value) + expect(juno_resp.record_context.version).to eq(1) + end + + it 'update request test' do + juno_resp = @juno_client.update(@key, 'newvalue') + @record_context = juno_resp.record_context + expect(juno_resp.status[:code]).to eq(Juno::ServerStatus::SUCCESS[:code]) + expect(juno_resp.value).to eq(@value) + expect(juno_resp.record_context.version).to eq(1) + end + + it 'compare_and_set request test' do + juno_resp = @juno_client.compare_and_set(@record_context, 'value99') + expect(juno_resp.status[:code]).to eq(Juno::ServerStatus::SUCCESS[:code]) + expect(juno_resp.value).to eq(@value) + expect(juno_resp.record_context.version).to eq(1) + end +end diff --git a/client/Ruby/juno/spec/IO/metadata_component.rb b/client/Ruby/juno/spec/IO/metadata_component.rb new file mode 100644 index 00000000..16f40803 --- /dev/null +++ b/client/Ruby/juno/spec/IO/metadata_component.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require 'bindata' +require_relative '../../lib/juno/IO/constants' +require_relative '../../lib/juno/IO/MetadataComponentTemplate' +require_relative '../../lib/juno/IO/MetadataComponent' + +describe 'Juno::IO::PayloadComponent buffer write' do + before(:context) do + @payload = Juno::IO::MetadataComponent.new + @NAMESPACE = 'testnamespace123' + @KEY = 'testkey123' + @VALUE = 'testvalue456' + + @payload.payload_key = @KEY # length: 10 + @payload.namespace = @NAMESPACE # length: 16 + @payload.set_value(@VALUE) # length: 12 + # size = 13 + 10 + 16 + 12 + 5 = 56 + buff = StringIO.new + @payload.write(buff) + + @bytes_arr = buff.string.bytes + end + + it 'component size should be 56' do + expect(@bytes_arr.length).to eq(56) + size = @bytes_arr.shift(4).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT32) + expect(@payload.component_size).to eq(56) + expect(size).to eq(56) + end + + it 'tag_id should be 1' do + expect(@payload.tag_id).to eq(1) + expect(@bytes_arr.shift).to eq(1) + end + + it 'namspace_length should be 16' do + expect(@payload.namespace_length).to eq(16) + expect(@bytes_arr.shift).to eq(16) + end + + it 'key_length should be 10' do + key_length = @bytes_arr.shift(2).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT16) + expect(@payload.key_length).to eq(10) + expect(key_length).to eq(10) + end + + it 'payload_length should be 13' do + payload_length = @bytes_arr.shift(4).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT32) + expect(@payload.payload_length).to eq(13) + expect(payload_length).to eq(13) + end + + it 'namespace as expected' do + namespace = @bytes_arr.shift(@NAMESPACE.length).pack(Juno::IO::OffsetWidth.UINT8('*')) + expect(@payload.namespace).to eq(@NAMESPACE) + expect(namespace).to eq(@NAMESPACE) + end + + it 'key as expected' do + key = @bytes_arr.shift(@KEY.length).pack(Juno::IO::OffsetWidth.UINT8('*')) + expect(@payload.payload_key).to eq(@KEY) + expect(key).to eq(@KEY) + end + + it 'payload_type should be Juno::IO::PayloadType::UnCompressed' do + ptype = @bytes_arr.shift + expect(@payload.payload_type).to eq(Juno::IO::PayloadType::UnCompressed) + expect(ptype).to eq(Juno::IO::PayloadType::UnCompressed) + end + + it 'payload value expected' do + value = @bytes_arr.shift(@VALUE.length).pack(Juno::IO::OffsetWidth.UINT8('*')) + expect(@payload.value).to eq(@VALUE) + expect(value).to eq(@VALUE) + end + + it 'payload length' do + padding = @bytes_arr.shift(5).pack(Juno::IO::OffsetWidth.UINT8('*')) + expect(padding.length).to eq(5) + expect(@payload.padding.length).to eq(5) + expect(padding).to eq([0, 0, 0, 0, 0].pack(Juno::IO::OffsetWidth.UINT8('*'))) + expect(@payload.padding).to eq([0, 0, 0, 0, 0].pack(Juno::IO::OffsetWidth.UINT8('*'))) + end +end + +describe 'Juno::IO::PayloadComponent buffer read' do + before(:context) do + @payload = Juno::IO::PayloadComponent.new + @NAMESPACE = 'testnamespace123' # length: 16 + @KEY = 'testkey123' # length: 10 + @VALUE = 'testvalue456' # length: 12 + + # size = 13 + 10 + 16 + 12 + 5 = 56 + buff = StringIO.new("\x00\x00\x008\x01\x10\x00\n\x00\x00\x00\rtestnamespace123testkey123\x00testvalue456\x00\x00\x00\x00\x00") + @payload.read(buff) + end + + it 'component size should be 56' do + expect(@payload.component_size).to eq(56) + end + + it 'tag_id should be 1' do + expect(@payload.tag_id).to eq(1) + end + + it 'namspace_length should be 16' do + expect(@payload.namespace_length).to eq(16) + end + + it 'key_length should be 10' do + expect(@payload.key_length).to eq(10) + end + + it 'payload_length should be 13' do + expect(@payload.payload_length).to eq(13) + end + + it 'namespace as expected' do + expect(@payload.namespace).to eq(@NAMESPACE) + end + + it 'key as expected' do + expect(@payload.payload_key).to eq(@KEY) + end + + it 'payload_type should be Juno::IO::PayloadType::UnCompressed' do + expect(@payload.payload_type).to eq(Juno::IO::PayloadType::UnCompressed) + end + + it 'payload value expected' do + expect(@payload.value).to eq(@VALUE) + end + + it 'payload length' do + expect(@payload.padding.length).to eq(5) + expect(@payload.padding).to eq([0, 0, 0, 0, 0].pack(Juno::IO::OffsetWidth.UINT8('*'))) + end +end diff --git a/client/Ruby/juno/spec/IO/payload_component_spec.rb b/client/Ruby/juno/spec/IO/payload_component_spec.rb new file mode 100644 index 00000000..d064f783 --- /dev/null +++ b/client/Ruby/juno/spec/IO/payload_component_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require 'juno' + +describe 'Juno::IO::PayloadComponent buffer write' do + before(:context) do + @payload = Juno::IO::PayloadComponent.new + @NAMESPACE = 'testnamespace123' + @KEY = 'testkey123' + @VALUE = 'testvalue456' + + @payload.payload_key = @KEY # length: 10 + @payload.namespace = @NAMESPACE # length: 16 + @payload.set_value(@VALUE) # length: 12 + # size = 13 + 10 + 16 + 12 + 5 = 56 + buff = StringIO.new + @payload.write(buff) + + @bytes_arr = buff.string.bytes + end + + it 'component size should be 56' do + expect(@bytes_arr.length).to eq(56) + size = @bytes_arr.shift(4).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT32) + expect(@payload.component_size).to eq(56) + expect(size).to eq(56) + end + + it 'tag_id should be 1' do + expect(@payload.tag_id).to eq(1) + expect(@bytes_arr.shift).to eq(1) + end + + it 'namspace_length should be 16' do + expect(@payload.namespace_length).to eq(16) + expect(@bytes_arr.shift).to eq(16) + end + + it 'key_length should be 10' do + key_length = @bytes_arr.shift(2).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT16) + expect(@payload.key_length).to eq(10) + expect(key_length).to eq(10) + end + + it 'payload_length should be 13' do + payload_length = @bytes_arr.shift(4).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT32) + expect(@payload.payload_length).to eq(13) + expect(payload_length).to eq(13) + end + + it 'namespace as expected' do + namespace = @bytes_arr.shift(@NAMESPACE.length).pack(Juno::IO::OffsetWidth.UINT8('*')) + expect(@payload.namespace).to eq(@NAMESPACE) + expect(namespace).to eq(@NAMESPACE) + end + + it 'key as expected' do + key = @bytes_arr.shift(@KEY.length).pack(Juno::IO::OffsetWidth.UINT8('*')) + expect(@payload.payload_key).to eq(@KEY) + expect(key).to eq(@KEY) + end + + it 'payload_type should be Juno::IO::PayloadType::UnCompressed' do + ptype = @bytes_arr.shift + expect(@payload.payload_type).to eq(Juno::IO::PayloadType::UnCompressed) + expect(ptype).to eq(Juno::IO::PayloadType::UnCompressed) + end + + it 'payload value expected' do + value = @bytes_arr.shift(@VALUE.length).pack(Juno::IO::OffsetWidth.UINT8('*')) + expect(@payload.value).to eq(@VALUE) + expect(value).to eq(@VALUE) + end + + it 'payload length' do + padding = @bytes_arr.shift(5).pack(Juno::IO::OffsetWidth.UINT8('*')) + expect(padding.length).to eq(5) + expect(@payload.padding.length).to eq(5) + expect(padding).to eq([0, 0, 0, 0, 0].pack(Juno::IO::OffsetWidth.UINT8('*'))) + expect(@payload.padding).to eq([0, 0, 0, 0, 0].pack(Juno::IO::OffsetWidth.UINT8('*'))) + end +end + +describe 'Juno::IO::PayloadComponent buffer read' do + before(:context) do + @payload = Juno::IO::PayloadComponent.new + @NAMESPACE = 'testnamespace123' # length: 16 + @KEY = 'testkey123' # length: 10 + @VALUE = 'testvalue456' # length: 12 + + # size = 13 + 10 + 16 + 12 + 5 = 56 + buff = StringIO.new("\x00\x00\x008\x01\x10\x00\n\x00\x00\x00\rtestnamespace123testkey123\x00testvalue456\x00\x00\x00\x00\x00") + @payload.read(buff) + end + + it 'component size should be 56' do + expect(@payload.component_size).to eq(56) + end + + it 'tag_id should be 1' do + expect(@payload.tag_id).to eq(1) + end + + it 'namspace_length should be 16' do + expect(@payload.namespace_length).to eq(16) + end + + it 'key_length should be 10' do + expect(@payload.key_length).to eq(10) + end + + it 'payload_length should be 13' do + expect(@payload.payload_length).to eq(13) + end + + it 'namespace as expected' do + expect(@payload.namespace).to eq(@NAMESPACE) + end + + it 'key as expected' do + expect(@payload.payload_key).to eq(@KEY) + end + + it 'payload_type should be Juno::IO::PayloadType::UnCompressed' do + expect(@payload.payload_type).to eq(Juno::IO::PayloadType::UnCompressed) + end + + it 'payload value expected' do + expect(@payload.value).to eq(@VALUE) + end + + it 'payload length' do + expect(@payload.padding.length).to eq(5) + expect(@payload.padding).to eq([0, 0, 0, 0, 0].pack(Juno::IO::OffsetWidth.UINT8('*'))) + end +end diff --git a/client/Ruby/juno/spec/IO/protocol_header_spec.rb b/client/Ruby/juno/spec/IO/protocol_header_spec.rb new file mode 100644 index 00000000..b64a02e0 --- /dev/null +++ b/client/Ruby/juno/spec/IO/protocol_header_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'juno' + +describe 'Juno::IO::ProtocolHeader buffer write' do + before(:context) do + @header = Juno::IO::ProtocolHeader.new + @header.message_size = 16 + @header.opaque = 12 + buff = StringIO.new + @header.write(buff) + @bytes_arr = buff.string.bytes + end + + it 'number of bytes should be 16' do + expect(@bytes_arr.size).to eq(16) + end + + it 'magic should be 0x5050' do + magic = @bytes_arr.shift(2).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT16) + expect(magic).to eq(0x5050) + expect(@header.magic).to eq(0x5050) + end + + it 'version should be 1' do + expect(@bytes_arr.shift).to eq(1) + end + + # by default it is an operation message and RequestType is a TwoWayRequest + it 'message_type_flag should be 64' do + expect(@bytes_arr.shift).to eq(64) + end + + it 'message_size should be 16' do + message_size = @bytes_arr.shift(4).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT32) + expect(message_size).to eq(16) + expect(@header.message_size).to eq(16) + end + + it 'opaque should be 12' do + opaque = @bytes_arr.shift(4).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT32) + expect(opaque).to eq(12) + expect(@header.opaque).to eq(12) + end + + it 'opcode should be 0' do + expect(@bytes_arr.shift).to eq(Juno::IO::ProtocolHeader::OpCodes::Nop) + expect(@header.opcode).to eq(Juno::IO::ProtocolHeader::OpCodes::Nop) + end + + it 'flag should be 0' do + expect(@bytes_arr.shift).to eq(0) + expect(@header.flag).to eq(0) + end + + it 'shard_id should be 0' do + shard_id = @bytes_arr.shift(4).pack(Juno::IO::OffsetWidth.UINT8('*')).unpack1(Juno::IO::OffsetWidth.UINT16) + expect(shard_id).to eq(0) + expect(@header.shard_id).to eq(0) + end +end + +describe 'Juno::IO::ProtocolHeader buffer read' do + before do + @header = Juno::IO::ProtocolHeader.new + buff = StringIO.new("PP\x01@\x00\x00\x00\x10\x00\x00\x00\f\x00\x00\x00\x00") + @header.read(buff) + end + + it 'number of bytes should be 16' do + expect(@header.message_size).to eq(16) + end + + it 'magic should be 0x5050' do + expect(@header.magic).to eq(0x5050) + end + + it 'version should be 1' do + expect(@header.version).to eq(1) + end + + # by default it is an operation message and RequestType is a TwoWayRequest + it 'message_type_flag should be 64' do + expect(@header.message_type_flag.message_request_type).to eq(Juno::IO::ProtocolHeader::RequestTypes::TwoWayRequest) + expect(@header.message_type_flag.message_type).to eq(Juno::IO::ProtocolHeader::MessageTypes::OperationalMessage) + end + + it 'message_size should be 16' do + expect(@header.message_size).to eq(16) + end + + it 'opaque should be 12' do + expect(@header.opaque).to eq(12) + end + + it 'opcode should be 0' do + expect(@header.opcode).to eq(Juno::IO::ProtocolHeader::OpCodes::Nop) + end + + it 'flag should be 0' do + expect(@header.flag).to eq(Juno::IO::ProtocolHeader::OpCodes::Nop) + end + + it 'shard_id should be 0' do + expect(@header.shard_id).to eq(0) + end +end diff --git a/client/Ruby/juno/spec/spec_helper.rb b/client/Ruby/juno/spec/spec_helper.rb new file mode 100644 index 00000000..253df0f1 --- /dev/null +++ b/client/Ruby/juno/spec/spec_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + config.shared_context_metadata_behavior = :apply_to_host_groups +end diff --git a/client/Ruby/sample.rb b/client/Ruby/sample.rb new file mode 100644 index 00000000..09e4125f --- /dev/null +++ b/client/Ruby/sample.rb @@ -0,0 +1,41 @@ +require 'juno' + +Juno.configure do |config| + config.file_path = URI.join('file:///', '') + config.log_file = '' + config.ssl_cert_file = '' + config.ssl_key_file = '' +end + +# Rails cache store +juno_client = Juno::Client::CacheStore.new +juno_client.write('key', 'value') + +# Synchronous Client +juno_client = Juno::Client::SyncClient.new +resp = juno_client.create('key', 'value') +resp = juno_client.get('key') +resp = juno_client.update('key', 'newvalue') + + +# Return Juno::Client::JunoResponse +resp = juno_client.create('15', '99') +resp = juno_client.get('15') +resp = juno_client.update('15', '100') + + +# Asyn Client +class Callback + def update(time, value, reason) + puts time + if reason.to_s.empty? + puts value + else + puts "failed: #{reason}" + end + end +end + +juno_client = Juno::Client::ReactClient.new +juno_client.create('mykey', 'myvalue') +juno_client.add_observer(Callback.new) From da92a37daa0f728b37a70972b8ebed3e115a28ed Mon Sep 17 00:00:00 2001 From: wcai Date: Mon, 4 Dec 2023 12:10:47 -0800 Subject: [PATCH 16/27] add swaphost and host_expansion instruction document --- docs/host_expansion.md | 83 ++++++++++++++++++++++++++++++++++++++++ docs/swaphost.md | 86 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 docs/host_expansion.md create mode 100644 docs/swaphost.md diff --git a/docs/host_expansion.md b/docs/host_expansion.md new file mode 100644 index 00000000..dcebfa57 --- /dev/null +++ b/docs/host_expansion.md @@ -0,0 +1,83 @@ +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +# Juno Host Expansion Instruction +Juno is a high scalable and available distributed key value store. In Juno architecture, the whole storage space is partitioned into a fixed number (e.g. 1024) of logical shards, and shards are mapped to storage nodes within the cluster. When a cluster scaling out or scaling in (storage nodes are either added or removed to/from the cluster), some shards must be redistributed to different nodes to reflect new cluster topology. Following are the instuctions on how to expand(or shrink) the juno storageserv nodes. + +## Expand or Shrink Storage Host +### Step0 (pre-requisite) +Deploy juno storageserv (and or junoserv) to all new boxes + +junostorageserv on all the original boxes need to be up + +Pre-insert data to original cluster so it can be used for later validation(optional but suggested) + +Overall, the expansion contains below steps: +```bash +1. Markdown one zone at a time to stop incoming real time traffic to this zone +2. Run command to update cluster topo to new cluster in etcd +3. Start storageserv on new box for relavant zone. +4. Run command to start redistribution + 4a. if requests failed to forward after serveral auto retry, run resume command to do redistribution again + 4b. if after 3a still some requests fail, restart source storageserv so the failed one can start forward again. +5. Run command to commit if previous steps are all successful. +``` + +Loop through zone0 to zone5 to finish redistribution for all zones +Retrieve pre-inserted data from storageserv for validation (optional) + + +### Step1 +under junoclustercfg, run +```bash + ./clustermgr --config config.toml --cmd zonemarkdown --type set -zone 0 (1,2,3,4) +``` +verify markdown works by checking junostorageserv state log that no new request coming + +### Step2 +under junoclustercfg, run +```bash + ./clustermgr -new_config config.toml_new -cmd redist -type prepare -zone 0 (1,2,3,4) +``` +NOTE: A UI monitoring http link will be generated in redistserv.pid. It can be used for monitoring + +### Step3 +start junostorageserv on new box for relavant zone -- zone 0(1,2,3,4) + +### Step4 +under junoclustercfg, run +```bash + ./clustermgr -new_config config.toml_new -ratelimit 5000 -cmd redist -type start_tgt --zone 0 (1,2,3,4) + ./clustermgr -new_config config.toml_new -ratelimit 5000 -cmd redist -type start_src --zone 0 (1,2,3,4) +``` +NOTE: the ratelimit needs to be tuned for each different system. Depending on old/new cluster, the rate setting + will be different. For example,expansion from 5 to 10 boxes or expansion from 5 to 15 boxes, rate will be + different + +#### Step4a (only if requests forward final failed after several auto retry) +under junoclustercfg, run +```bash + ./clustermgr --new_config config.toml_new --cmd redist --type resume -ratelimit 5000 -zone 0(1,2,3,4) +``` + +#### Step4b (only if 4a still doesn't fix the failure) +restart source storageserv and wait for redistribution complete + +### Step5 +under junoclustercfg, run +```bash + ./clustermgr -new_config config.toml_new --cmd redist --type commit -zone 0(1,2,3,4) +``` +Loop around zone0 to zone5 to complete all zones' redistribution + +## Validation (Optional but suggest) + +### Steps +run juno client tool to get shard map which contains ss ip:port +```bash + ./junocli ssgrp -c config.toml_new -hex=false key +``` + +run juno client tool to verify if key exists in the expected ss. ip:port is the one get from previoius command +```bash + ./junocli read -s ip:port key +``` + diff --git a/docs/swaphost.md b/docs/swaphost.md new file mode 100644 index 00000000..6df77992 --- /dev/null +++ b/docs/swaphost.md @@ -0,0 +1,86 @@ +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +# Juno Host Swap Instruction +Bad Juno nodes may have to be swapped out on a live cluster. The node that needs to be swapped can be running etcd (junoclusterserv), or juno storage or both. Following is a step-by-step guide to perform the swap. + +## Swapping a Storage Host +### Step0 (optional) +Pre-insert some data via junocli tool before host swap, after host swap, retrieve data and see if all data are able to be retrieved. + +### Step1 +Deploy junostorageserv (and or junoserv) on New_Host1, both services don't need to be start up, just deploy. +update the junoclustercfg config.toml by changing old box into new box, make a package and deploy it to new box. + +```bash +Old Config New Config +SSHosts=[ SSHosts=[ +# Zone 0 # Zone 0 +[ [ + "Host1" "New_Host1" +], ], +# Zone 1 # Zone 1 +[ [ + "Host2" "Host2" +], ], +# Zone 2 # Zone 2 +[ [ + "Host3" "Host3" +], +# Zone 3 # Zone 3 +[ [ + "Host4" "Host4" +], ], +# Zone 4 # Zone 4 +[ [ + "Host5" "Host5" +] ] +] ] +``` +Make sure storageserv are up on all the boxes other than the bad box. + +### Step2 +If to be replaced box is a bad box, this step can be skipped. If to be replaced box is a good box, shutdown +junostorageserv on to be replaced box, copy rocksdb_junostorageserv from it to new box on the same location. + +### Step3 +On the new box (the cluster config contains New_Host1), from junoclustercfg directory, run ./swaphost.sh. +This step will bump up the junocluster config version in etcd and all the running junoserv and junostorageserv +hosts will update their cluster map accordingly after script run. + +### Step4 +Start up junostorageserv (and or junoserv) on New_Host1. It will fetch the latest junoclustercfg from etcd. + +### Step5 (Optional) +Validation - use junocli to retrieve pre-inserted data, all data should be able to retrieve. + +### Step6 +Once junoserv on New_Host1 works fine, if there is LB in front of junoserv, fix LB to replace Host1 with New_Host1 + +Deploy the updated junoclustercfg package which contains New_Host1 to all the junoclustercfg boxes. All boxes have +same version of junoclustercfg package after that. + +## Swapping host which has etcd server runs on +The etcd cluster has three or five hosts depending on 3 quorum or 5 quorum - Host1^Host2^Host3^Host4^Host5 + +Identify a new host (New_Host1) for the swap. Make sure etcd servers are up on all hosts except the bad one. +Host1 is to be swapped with New_Host1 + +### Step1 +Change the etcdsvr.txt under junoclusterserv +```bash +Old etcdsvr.txt New etcdsvr.txt +[etcdsvr] [etcdsvr] +initial_cluster = "Host1^Host2^Host3^Host4^Host5" initial_cluster = "New_Host1^Host2^Host3^Host4^Host5" +``` +Build the junoclusterserv package and deploy to new box (New_Host1) + +### Step2 +On the old box (Host1), shutdown junoclusterserv by shutdown.sh under junoclusterserv + +On the new box(New_Host1), under junoclusterserv, first run join.sh, then run start.sh to have the new box +join the members of quorum + +### Step3 +Deploy and start the new junoclusterserv package one by one to all other junoclusterserv boxes + +### Step4 +Fix LB of etcd to replace old Host1 with New_Host1. From 7f288b4d209b6280eb5ed6ef2c03f327c4bb9a04 Mon Sep 17 00:00:00 2001 From: nepathak Date: Mon, 4 Dec 2023 15:22:38 -0800 Subject: [PATCH 17/27] Comment out macos-latest docker build, facing docker issue on image https://github.com/actions/runner-images/issues/17 --- .github/workflows/juno_server_docker_build.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/juno_server_docker_build.yml b/.github/workflows/juno_server_docker_build.yml index 7d620cac..a6236891 100644 --- a/.github/workflows/juno_server_docker_build.yml +++ b/.github/workflows/juno_server_docker_build.yml @@ -18,7 +18,8 @@ jobs: build-juno-docker: strategy: matrix: - os: [ubuntu-latest, macos-latest] + # os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] runs-on: ${{ matrix.os }} env: PROXY_TLS_PORT: 5080 @@ -32,8 +33,15 @@ jobs: run: | echo "Openssl Version:" `openssl version` - name: Set up Docker - uses: docker-practice/actions-setup-docker@master + # uses: docker-practice/actions-setup-docker@master + # uses: crazy-max/ghaction-setup-docker@v2 if: ${{ matrix.os == 'macos-latest' }} + run: | + brew install docker + colima start + # For testcontainers to find the Colima socket + # https://github.com/abiosoft/colima/blob/main/docs/FAQ.md#cannot-connect-to-the-docker-daemon-at-unixvarrundockersock-is-the-docker-daemon-running + sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock - name: Docker Version Check run: docker version - name: Build docker containers From 6f0f334f401039a967ce05f32cde87e8091c39da Mon Sep 17 00:00:00 2001 From: wcai Date: Tue, 5 Dec 2023 13:00:51 -0800 Subject: [PATCH 18/27] add more info into host_expansion.md --- docs/host_expansion.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/host_expansion.md b/docs/host_expansion.md index dcebfa57..8425258c 100644 --- a/docs/host_expansion.md +++ b/docs/host_expansion.md @@ -30,7 +30,26 @@ under junoclustercfg, run ```bash ./clustermgr --config config.toml --cmd zonemarkdown --type set -zone 0 (1,2,3,4) ``` -verify markdown works by checking junostorageserv state log that no new request coming +verify markdown works by checking etcd cluster info (zonemarkdown flag added) and junostorageserv state log +that no new request coming +```bash +--- etcd cluster info shows markdown flag is set --- +run command "export ETCDCTL_API=3; ~/junoclusterserv/etcdctl --endpoints=: --prefix=true get "" | tail -8 " +juno.junoserv_numzones +3 +juno.junoserv_version +1 +juno.junoserv_zonemarkdown (markdown flag is set for zone 0) +0 +juno.root_junoserv +2023-12-05 12:16:17|u20box|/home/deploy/junoclustercfg + +--- junostorageserv/state-logs/current shows the number of incoming requests(req) didn't change, i.e. no new traffic coming --- +12-05 12:39:49 id free used req apt Read D C A RR keys LN compSec compCount pCompKB stall pCPU mCPU pMem mMem +12-05 12:39:49 3-0 453110 42899 34267 98 3303 0 15482 0 0 4338 0 0 0 0 0 0.1 0.2 0.1 15.6 +12-05 12:39:50 3-0 453110 42899 34267 98 3303 0 15482 0 0 4338 0 0 0 0 0 0.1 0.2 0.1 15.6 +12-05 12:39:51 3-0 453110 42899 34267 98 3303 0 15482 0 0 4338 0 0 0 0 0 0.1 0.3 0.1 15.6 +``` ### Step2 under junoclustercfg, run From 566714b8b32b2defc66b1e87ada7f3f33c163c91 Mon Sep 17 00:00:00 2001 From: wcai Date: Wed, 13 Dec 2023 17:51:15 -0800 Subject: [PATCH 19/27] update deploy.sh to copy swaphost etc. script, update help message for zonemarkdown --- cmd/clustermgr/clusterctl/clusterctl.go | 4 ++-- cmd/clustermgr/clustermgr.go | 4 ++-- cmd/etcdsvr/tool.py | 1 + cmd/proxy/stats/shmstats/shmstats.go | 2 +- script/deploy.sh | 7 +------ 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cmd/clustermgr/clusterctl/clusterctl.go b/cmd/clustermgr/clusterctl/clusterctl.go index 24a3d289..d806cf62 100644 --- a/cmd/clustermgr/clusterctl/clusterctl.go +++ b/cmd/clustermgr/clusterctl/clusterctl.go @@ -64,7 +64,7 @@ func main() { flag.StringVar(&flagScale, "scale", "", "n:n:n:n:n") flag.BoolVar(&flagVerbose, "verbose", false, "verbose -- print more info") - flag.StringVar(&flagCmd, "cmd", "", "command -- store, redist, redistserv") + flag.StringVar(&flagCmd, "cmd", "", "command -- store, redist, redistserv, zonemarkdown") flag.StringVar(&flagType, "type", "cluster_info", "type -- cluster_info, auto, abort") flag.IntVar(&flagZoneid, "zone", -1, "specify zone id") @@ -173,5 +173,5 @@ func printUsage() { fmt.Printf("\nAbort redist: ./%s --cmd redist --type abort\n", progName) fmt.Printf("redist resume: ./%s --cmd redist --type resume --zone [n] --ratelimit 10000 (optional, in kb)\n", progName) - fmt.Printf("Zone markdown: ./%s --cmd markdown --type set/get/delete --zone [n] (--zone -1 disables markdwon)\n", progName) + fmt.Printf("Zone markdown: ./%s --cmd zonemarkdown --type set/get/delete --zone [n] (--zone -1 disables markdwon)\n", progName) } diff --git a/cmd/clustermgr/clustermgr.go b/cmd/clustermgr/clustermgr.go index 4bc94f1f..756f8f07 100644 --- a/cmd/clustermgr/clustermgr.go +++ b/cmd/clustermgr/clustermgr.go @@ -59,7 +59,7 @@ func main() { flag.StringVar(&flagConfigNew, "new_config", "", "new configfile") flag.BoolVar(&flagDryrun, "dryrun", false, "dry run -- do not save to etcd") flag.BoolVar(&flagVerbose, "verbose", false, "verbose -- print more info") - flag.StringVar(&flagCmd, "cmd", "", "command -- store, redist") + flag.StringVar(&flagCmd, "cmd", "", "command -- store, redist, redistserv, zonemarkdown") flag.StringVar(&flagType, "type", "cluster_info", "type -- cluster_info, auto, abort") flag.IntVar(&flagZoneid, "zone", -1, "specify zone id") flag.IntVar(&flagSkipZone, "skipzone", -1, "specify zone id to skip") @@ -180,5 +180,5 @@ func printUsage() { fmt.Printf("Dump redist start_src to stdout: ./%s --new_config redist.toml --cmd redist --type start_src --zone [n] --dryrun --automarkdown=false\n", progName) fmt.Printf("Dump redist commit to stdout: ./%s --new_config redist.toml --cmd redist --type commit --dryrun\n", progName) fmt.Printf("Dump redist resume: ./%s --new_config redist.toml --cmd redist --type resume --zone [n] --ratelimit 10000 (optional, in kb)\n", progName) - fmt.Printf("Zone markdown: ./%s --config config.toml --cmd markdown --type set/get/delete --zone [n] (--zone -1 disables markdwon)\n", progName) + fmt.Printf("Zone markdown: ./%s --config config.toml --cmd zonemarkdown --type set/get/delete --zone [n] (--zone -1 disables markdwon)\n", progName) } diff --git a/cmd/etcdsvr/tool.py b/cmd/etcdsvr/tool.py index 65b10dbe..8a26f4cb 100755 --- a/cmd/etcdsvr/tool.py +++ b/cmd/etcdsvr/tool.py @@ -1,3 +1,4 @@ +#!/usr/bin/python # # Copyright 2023 PayPal Inc. # diff --git a/cmd/proxy/stats/shmstats/shmstats.go b/cmd/proxy/stats/shmstats/shmstats.go index 29b5878f..46debf13 100644 --- a/cmd/proxy/stats/shmstats/shmstats.go +++ b/cmd/proxy/stats/shmstats/shmstats.go @@ -98,7 +98,7 @@ type ( Type uint16 CapQueue uint16 Addr [256]byte - Name [12]byte + Name [256]byte } serverStatsManagerT struct { stats *ServerStats diff --git a/script/deploy.sh b/script/deploy.sh index 27733dc3..4261eef6 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -54,12 +54,7 @@ do $i/shutdown.sh fi - if [ $i != "junoclustercfg" ]; then - cp $BUILDTOP/package_config/package/${i}/script/shutdown.sh $i - cp $BUILDTOP/package_config/package/${i}/script/start.sh $i - fi - - cp $BUILDTOP/package_config/package/${i}/script/postinstall.sh $i + cp $BUILDTOP/package_config/package/${i}/script/*.sh $i cp $BUILDTOP/package_config/script/postuninstall.sh $i cp $BUILDTOP/package_config/script/logstate.sh $i cp $BUILDTOP/package_config/script/log.sh $i From 4aa682ed0204be6d08477671bb94849828444549 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 15 Dec 2023 20:46:24 -0800 Subject: [PATCH 20/27] Added otel collector and prometheus configs --- docker/manifest/config/otel/config.yaml | 29 +++++++++++++++++++ .../config/prometheus/prometheus.yaml | 6 ++++ docker/manifest/config/proxy/config.toml | 10 +++++++ docker/manifest/docker-compose.yaml | 26 +++++++++++++++++ pkg/logging/otel/config/otelconfig.go | 4 +++ 5 files changed, 75 insertions(+) create mode 100644 docker/manifest/config/otel/config.yaml create mode 100644 docker/manifest/config/prometheus/prometheus.yaml diff --git a/docker/manifest/config/otel/config.yaml b/docker/manifest/config/otel/config.yaml new file mode 100644 index 00000000..b5de724c --- /dev/null +++ b/docker/manifest/config/otel/config.yaml @@ -0,0 +1,29 @@ +# connfig ref : https://opentelemetry.io/docs/collector/configuration/ +receivers: + otlp: + protocols: + http: + endpoint: "0.0.0.0:4318" + + +exporters: + # Data sources: metrics + prometheus: + endpoint: "0.0.0.0:8889" + namespace: default + send_timestamps: true + metric_expiration: 180m + # resource_to_telemetry_conversion: + # enabled: true + +extensions: + health_check: + pprof: + zpages: + +service: + extensions: [health_check, pprof, zpages] + pipelines: + metrics: + receivers: [otlp] + exporters: [prometheus] diff --git a/docker/manifest/config/prometheus/prometheus.yaml b/docker/manifest/config/prometheus/prometheus.yaml new file mode 100644 index 00000000..cd1efea2 --- /dev/null +++ b/docker/manifest/config/prometheus/prometheus.yaml @@ -0,0 +1,6 @@ +scrape_configs: + - job_name: 'otel' + scrape_interval: 10s + static_configs: + - targets: ['otel:8888'] + - targets: ['otel:8889'] diff --git a/docker/manifest/config/proxy/config.toml b/docker/manifest/config/proxy/config.toml index 95544697..347c16eb 100644 --- a/docker/manifest/config/proxy/config.toml +++ b/docker/manifest/config/proxy/config.toml @@ -39,6 +39,16 @@ DefaultTimeToLive = 1800 CalType = "file" Enabled = true +[OTEL] + Enabled = true + Environment = "prod" + Host = "otel" + Port = 4318 + Poolname = "proxy_openSource" + Resolution = 10 + UseTls = false + UrlPath = "" + [Sherlock] Enabled = false diff --git a/docker/manifest/docker-compose.yaml b/docker/manifest/docker-compose.yaml index 9a6f6a31..b4f97470 100644 --- a/docker/manifest/docker-compose.yaml +++ b/docker/manifest/docker-compose.yaml @@ -98,6 +98,7 @@ services: start_period: 10s depends_on: - storageserv + - otel volumes: - ${PWD}/config/proxy/config.toml:/opt/juno/config/config.toml - ${PWD}/config/secrets:/opt/juno/bin/secrets @@ -118,6 +119,31 @@ services: - ${PWD}/config/client/config.toml:/opt/juno/config.toml - ${PWD}/config/secrets:/opt/juno/secrets + otel: + <<: *service_default + image: otel/opentelemetry-collector:latest + container_name: otel + command: ["--config=/etc/otel-collector/config.yaml"] + volumes: + - ${PWD}/config/otel/config.yaml:/etc/otel-collector/config.yaml + ports: + - "1888:1888" # pprof extension + - "8888:8888" # Prometheus metrics exposed by the Collector + - "8889:8889" # Prometheus exporter metrics + - "13133:13133" # health_check extension + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP http receiver + - "55679:55679" # zpages extension + + prometheus: + <<: *service_default + container_name: prometheus + image: prom/prometheus:latest + volumes: + - ${PWD}/config/prometheus/prometheus.yaml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + networks: junonet: driver: bridge diff --git a/pkg/logging/otel/config/otelconfig.go b/pkg/logging/otel/config/otelconfig.go index 458dc90a..b5e7088d 100644 --- a/pkg/logging/otel/config/otelconfig.go +++ b/pkg/logging/otel/config/otelconfig.go @@ -27,6 +27,7 @@ var OtelConfig *Config type Config struct { Host string Port uint32 + UrlPath string Environment string Poolname string Enabled bool @@ -57,6 +58,9 @@ func (c *Config) Default() { if c.Environment == "" { c.Environment = "OpenSource" } + if c.UrlPath == "" { + c.UrlPath = "v1/datapoint" + } } func (c *Config) Dump() { From b06d739ff6652096dc3b8080ccc2425f5583a4ce Mon Sep 17 00:00:00 2001 From: Neetish Pathak Date: Mon, 18 Dec 2023 15:42:01 -0800 Subject: [PATCH 21/27] Adding prometheus , otel mornitoring setup and docs (#168) LGTM --- docker/monitoring/docker-compose.yaml | 41 +++++++++++++ docker/monitoring/grafana-datasources.yml | 15 +++++ docker/monitoring/otel-collector-config.yaml | 32 ++++++++++ docker/monitoring/prometheus.yml | 12 ++++ docs/otel_mon.png | Bin 0 -> 148016 bytes docs/otel_monitoring.md | 59 +++++++++++++++++++ docs/prometheus.png | Bin 0 -> 137798 bytes 7 files changed, 159 insertions(+) create mode 100644 docker/monitoring/docker-compose.yaml create mode 100644 docker/monitoring/grafana-datasources.yml create mode 100644 docker/monitoring/otel-collector-config.yaml create mode 100644 docker/monitoring/prometheus.yml create mode 100644 docs/otel_mon.png create mode 100644 docs/otel_monitoring.md create mode 100644 docs/prometheus.png diff --git a/docker/monitoring/docker-compose.yaml b/docker/monitoring/docker-compose.yaml new file mode 100644 index 00000000..e0146bf0 --- /dev/null +++ b/docker/monitoring/docker-compose.yaml @@ -0,0 +1,41 @@ +version: "3" +services: + otel-collector: + container_name: "otel-collector" + image: otel/opentelemetry-collector-contrib + restart: always + command: + - --config=/etc/otelcol-contrib/otel-config.yaml + volumes: + - ./otel-collector-config.yaml:/etc/otelcol-contrib/otel-config.yaml + ports: + - "1888:1888" # pprof extension + - "8888:8888" # Prometheus metrics exposed by the Collector + - "8889:8889" # Prometheus exporter metrics + - "13133:13133" # health_check extension + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP http receiver + - "55679:55679" # zpages extension + + prometheus: + image: prom/prometheus + container_name: prometheus + restart: always + command: + - --config.file=/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus-data:/prometheus + + grafana: + container_name: grafana + image: grafana/grafana + volumes: + - ./grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml + ports: + - "3000:3000" + +volumes: + prometheus-data: diff --git a/docker/monitoring/grafana-datasources.yml b/docker/monitoring/grafana-datasources.yml new file mode 100644 index 00000000..22b668e8 --- /dev/null +++ b/docker/monitoring/grafana-datasources.yml @@ -0,0 +1,15 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + uid: prometheus + access: proxy + orgId: 1 + url: http://prometheus:9090 + basicAuth: false + isDefault: false + version: 1 + editable: false + jsonData: + httpMethod: GET diff --git a/docker/monitoring/otel-collector-config.yaml b/docker/monitoring/otel-collector-config.yaml new file mode 100644 index 00000000..c0080ff9 --- /dev/null +++ b/docker/monitoring/otel-collector-config.yaml @@ -0,0 +1,32 @@ +receivers: + otlp: + protocols: + http: + +processors: + # batch metrics before sending to reduce API usage + batch: + +exporters: + prometheus: + endpoint: "0.0.0.0:8889" + const_labels: + label: juno + + +# https://github.com/open-telemetry/opentelemetry-collector/blob/main/extension/README.md +extensions: + # responsible for responding to health check calls on behalf of the collector. + health_check: + # fetches the collector’s performance data + pprof: + # serves as an http endpoint that provides live debugging data about instrumented components. + zpages: + +service: + extensions: [health_check, pprof, zpages] + pipelines: + metrics: + receivers: [otlp] + processors: [batch] + exporters: [prometheus] diff --git a/docker/monitoring/prometheus.yml b/docker/monitoring/prometheus.yml new file mode 100644 index 00000000..d543277a --- /dev/null +++ b/docker/monitoring/prometheus.yml @@ -0,0 +1,12 @@ +global: + scrape_interval: 10s + evaluation_interval: 10s + +scrape_configs: + - job_name: 'otel-collector' + static_configs: + - targets: ['otel-collector:8889'] # Otlp + # uncomment to enable prometheus metrics + #- job_name: 'prometheus' + # static_configs: + # - targets: ['localhost:9090'] # Prometheus itself diff --git a/docs/otel_mon.png b/docs/otel_mon.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe3292af6a8236689c51004eec837690194b903 GIT binary patch literal 148016 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�G3!5G)$<{X+&9w1=*`OkbBx*N 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

    Cfo0!}Hn`5dGshx^tZqJvM7PPMMn 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 literal 0 HcmV?d00001 diff --git a/docs/otel_monitoring.md b/docs/otel_monitoring.md new file mode 100644 index 00000000..6fc58b6c --- /dev/null +++ b/docs/otel_monitoring.md @@ -0,0 +1,59 @@ +## Monitor Juno Metrics using Prometheus + +#### A simple setup to push the metrics on prometheus using otel-collector is shown below. Grafana can be further used to create visualizations from the available metrics. + + + + #### Setup + + ##### Configure proxy and storage to push metrics to otel endpoint + +- Juno proxy and storage services are configured to push the metrics on open telemetry collector endpoint http://localhost:4318/v1/metrics . Add/Update the [OTEL] section in the respective config.toml files + +```yaml +[OTEL] + Enabled = true + Environment = "qa" + Host = "0.0.0.0" + Poolname = "junoserv-ai" + Port = 4318 + Resolution = 10 + UrlPath = "/v1/metrics" + UseTls = false + +``` + +- Now the proxy and storage services are uploading metrics to otel endpoint. + +##### Set up otel-collector, prometheus and grafana +- Open telemetry collector, prometheus and grafana are run as docker containers. +- otel-collector , prometheus and grafana configurations are required to be mounted as volumes in the containers +- docker-compose.yaml and configuration files for each of the services available in junodb/docker/monitoring + + +```bash +cd junodb/docker/monitoring + +docker compose up -d +``` + +- Check the running containers. prometheus, otel-collector and grafana should be running + +```bash +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +bcb1e7ece6b7 prom/prometheus "/bin/prometheus --c…" 3 hours ago Up 3 hours 0.0.0.0:9090->9090/tcp prometheus +c3816c006f85 otel/opentelemetry-collector-contrib "/otelcol-contrib --…" 3 hours ago Up 3 hours 0.0.0.0:1888->1888/tcp, 0.0.0.0:4317-4318->4317-4318/tcp, 0.0.0.0:8888-8889->8888-8889/tcp, 0.0.0.0:13133->13133/tcp, 0.0.0.0:55679->55679/tcp, 55678/tcp otel-collector +e41e33696606 grafana/grafana "/run.sh" 3 hours ago Up 3 hours 0.0.0.0:3000->3000/tcp grafana + +``` + +- Check the promethus server running at :9090 as shown below. Search for juno metrics. + + + + + diff --git a/docs/prometheus.png b/docs/prometheus.png new file mode 100644 index 0000000000000000000000000000000000000000..d54cc18ec6d4884de3b4dc2bad6a06e20570cfce GIT binary patch literal 137798 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
    ?7%x9 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�vA>?Jwn^@diAwLpTrD~tBS31 z{%9=>KXYOcL_>1%;Pb(UtHwlWi~U{w%7BPBa}QPCzkEhJcx7s{cj?6zaae4Eu1A~; z&D+1#;C*nBNNws~>~74^qo7MS8_ZQIaq3m&b))5LcvtkTPbpa9Xg_D_eMJ*Y8CllX z#qjSDTiqs;*)s^9VeXp$K($=zFzYBWhj(0;F71bc&|r3mB^s-SnMV5~R@PzMxG{?8^M%$a zsBScNAY+Cj+i=j4K5#M{5kSsuogE7_9mY-kzIWk!-Z8<)VlN3b_x)wOBaD4gQu3)jo%H6YVqjxd@dr1R|25|x_o&AwV3Mq$r>|k_qIMVg)HGORnp-f z=WF1Nj|27K^ngilek}3X#C#|Tps%G0lMLAT+G^ro%!o!k&r3&Kedc|+rU;SzutAU| zrTz#aNXjDm5gnRL6>C7A+oFa?;(Y|4Q>fY`hXjdvoB{wSsQ7k&*gU@1AcHOuh8`+{3zK9QY7-&?B z3`v9)?L#<7dY=s)eIv78QztS7M{&)6-4*A|uhMjH`BD@cnYh|og065Pw=O-q#0;%v z(*8S~t%;FZ9~QqIq>mR%D@qbXE~5GslAjpHoGnp8=!U!gZB{7(4WhPo?c z35!2C2L2P``6iLk(A^c@&=?K;i;*cO$Iyrl%c=Z|1>auu+AoS^3y+c^l9PEdax0Wt zm!U%K)Z&>E;P6J*EjWSp&od!d!;=VBo{v{XP={*S05Gwi@jz^PP1xB?j)YTc3><&@I(>0XO zo}i=4BeVWa7K;UjM!FWVG=QG+8>jI38fetc200=Tzh-$PdMk~{aDg@0Fw6uO^E>HF zI0=*4*(c|%!S2Z1wBdH3?hFrDFFke?m%U75aKML4!6g{hjgyf8Kbws{n zomm(8dMwUa-g~*(x2T{qEc$a4+Y=G!5O)&)LuK$5`L>pXE{psTW!$G%fvjrRns|s0 z9}p>f&yv{9Fp#P4GUU*x!g6paiHsOOh19t#;}-zHg&QXe`jF8o)~Q`bRL^?gq# z?Qv}egAhX(V>=bHD=C^;pOaljS*wecBA@x_fg*hfDB~AWXN>R(2-YeH^LwZnho14P zsbUVmMqw;-?yY5})6qhSUG6)a(^@R^czUf;I4~UIHsx7Ph1*%Tk?D0ZJWC?oxi=BN zMU|nHqDN%VM1x)3T++}ctA5~d%O2=IFTK;G?LfSPi2xj_ z_6m1K-}E(|w3PWJj9xjtT9GQhJt&*Lt7l@RC0=O0Ru|#U`E2_F7<42)jZd1;(j=9v zdM6LY0rp$7 zOWWcb@Yw@LI+Vw)nO!xhpUiq$5u=BLD4z#XawK-Oz{ms@>|6UiwMrz=z97VjoD{uR?rR64M!$kF8d3sjNwFeyx!bLg z0*E$!|FQ-an}W)spsI4gk6U|m+5=gvd&h}98Hr?NiE3XS!RAoOHl!QhlJ5#Io zdM+k|XlPqz4LOEigCXy497tfXw@%BEg_!p>7{15wlmyrtYs+#kHPVGBzfmQevG~Gm&hw>pncnMhSND#H$4?fEH_%p3dD6sU$mk5dqo!&syh{GSN-tZ$uTB} z{+LVlqtE@P6M;>ghDRdCRzdZ2^4n2DysZ>!N@;{52A6(h3kRK1VJy8%^I(?E45Yu; zl5c=WRtu`z;IbL6DNoGz)BqVe%~WemuQ}5c3$CJ)>r-LQm7&Xn>8MWdl_d|PrD4;I zy-*hOipeaiU$7ST;@5HtVYCSY-vjAA0k^jZSzFg7yUXQH2SIdTn^YX*)xeitrZRqU zuQS=87|28B``!-squtY5m**{A^)Y& zY5)JEAiRK$++y`Tm*mfvamx@4BHP}KQ+5Vtk7q~{XE+UOIt5wG&fkr=wBZ~<`=cgR zp#U#njaQsV&AAQa;vzP(@=>NWEt?|vf%pv5$b7@>RWC`YTBRts#3{XN8U?le zup}~v)|65m)luzQQ+cNg(FKVdKk!7^b?k)2O6*|{4RqqNvBw*Ep?+)sMrrXW%$1skf7>6q|dh5 zPmrP5q!UjvIj2w1)$iN}gY`GDEMn9JXDh071KAkFJwGRN_w2#UP8Hz)*sGgdrP zf{ixk*0vu$^a}jyY(PPdI?U<>F&eBvm8%h)T zBv{&?8)YQ{8gs5HiDqz$^BJv+txjvPy-`j`(Wp~I9=;#vIzAMSAkfMIaI|0A+}zy$kof?n2>srw=PzxxP?sAIg@sxR5<*Wd#n;G( zWs4!f!O>I-9`OJXubpexiAnwwrJHqtIU*WFbMj)<=&7$3@&%kqDe#? zp|@MaFJJEDIGuO^)qH=kN``>*?Qv6?!9+Hde5(gHm0G1=wsfj`T|`BbJcq+R5il+f z4ER|NbcgVt_uw@TwFF}`kdugb5Idi5MSV7z=u72vy1FTFI%z@2#Wiu9ELl7NkO7fD zbUn=$H#Gsl&LO?hVt@!Dy5jk)_2<^mA4-tBsdKq9?KVu1VwtZp08)jDW#7Ji>pNa{HScLh`uh}mccJltY=)DjVOwk1tX8IG zaZkszDmG-=t!#TjakI6XIquk2ZRu=9wD8~tfQ*0oOPkiRbFO5|M5d6!RdS06GC?)h z!(bvyKx2!uHml`QvB&eMF()CRmBX^4ik7FrWb4IZ-AOc1Hs`P2G~4cup;`DI6aXK! z$ZW@Le}Ove?hysd`~09$s@YH&aS`?>1V(ed^e1Bf?MIZ!=jM!h#aD{IO!J-pB~x*; z?N$`^qrZgjnMBufqOh&=sACH4p~_A=N^4v!a_`xyj~`cp6s&bePRm6u@(U22)L?$t3yNI&)uITg>U(;8bNTa? znNO68o)CGaB( zy#TU51lVM~-f+Z2Vvc(CNW|^2jLy5IHoci*ogwl8FayQy`RzeSM(dM03DBjAIfo7| z%<)0?rVo(t$=?96YEdH?i|$A5`oKT}6G@IORNQu>OUz;YDuCx1_^Nu0Gv%^g-w7N6 zdoP+^OC6VS4WBNp4b^Qhj;5$9JEYR(YE|97KaK{7ca8RY<_jG=+D+G0`^yiTrWIyQJ5xk7;SR(+*~s0BuhZ zMMS`XqP+bCBr6;F@vF7$5sJdB$xp?6J7+u8ugDp%XN`s1zaU-z3=rAXGHy~T0$+AA zz%xfh)lHwcAT47PH2xymYPDEYmd}^ve`KU0BH|0{m*CDfD<-sW_y3D13+yeIwy(ea z-l37{PEu2yYT7}^&nnMxZdIIZ-O9K)-LfSYd7?}7;To`=CBo@$)xw{7Bm$_2KlaB> z6e?*P!Fds8M*T_6xj4(%H2KYF#Dl&dR1`k!ctb`tU}$b9Psni!9#|U_C!n zX_sjrlFq#x#fDb_=ENwltc#3;VAg%}(F_uJQs4D*0+3Pe z%M4`vMq@O1Juf3M0da4bTPP6Q&hHN5S;t?eInHA48A6tybe`R0aV?wuz&b zG2=J9*tXc`U*Oj&Q?D72&k$gTBBf}Iq(|KTQp7v^pU{Nno!}(XZ*1LfKzVQEMgu>~ zFDg}mg%7w$4|E(G?3bF-(?}xhAq_hYhLZ)m@u)^`1kmszt`M$z-SRj3H3a zIJ4>9f#;=*o7E|o_f6;h35u*_B@OoE7iw54by@EKSP1d)YHOLGN9gq$)Enj`Gmhr@ zv*Q7orWRVRK1Npa-lXUB-^ggBB9=C`fAcy!Oa&YR2B`6uV(EkZyO-M&IfJa&c)~i3 zJ~39n@-fnZfg=0oyFgr2-BT@ZHdIKi^?DRQ@9pknP6N8?1Jgw+H4@gw#`#z}W2ikZ z+F#2gY0~YYd>2AZWsK)`y-qSOUgQ9sMm7L((2=eUkPHkiz40~#s45HZ-=xP8OA;3k zw(x0cl&`Vb-3%wkQ&_?u2=M<-kOM5GOXxc?)9Z2X8~%~vSckn3*t7}5!~yY#Fyvck zmaWvI{a|$js&Xpj;=mTEEXwU2XSAZ||;_PEBlg49m?wO!EpKy;GwLZn=X3^7UH$da*is>+WxJQGbEq zEBFV(UuRNJlUbLYcZQ5CbvP?k$^?sY~a^SEJWQiYI@Ze@^P!)<11p*?$$Z)NDAfCVj#si-Nn9cT( z@2@IyzYB(dOKM8c2K1XF4*ong@*@JkP03Xq&%En@E^8f$^$h?v$fDr!^aYYb6(vB>6*0Ghd@VX=>=ABI{eB8_I3dP?PqJM8{Y*D5 zp4oj@scUu(s4DxS#&4&7LR(+-G9piA#m8rig5is6teoX)d41A{#qjw0Gmp)%6%K-W zhXZh2fH;lw-CnSdU*#WQ$1d{MQ;Yk(V*ap3coK6&`ygehPh{GxwyRP==V$XdK2PKs zVkW1lo$%3Iu^23yFW;+Hp9DI#f5qSlP2ad(Y?my2SZAXYR-GQpAUrst*J?bdH!~kf z=Uo)m^zQTKqMhk}y4QCf8s~rnJw4df1oxLx0VcuI-?a%nzEb+Xc=esM^t}De$==!8 z$@QWYooCERGaH|_dyPY}5|D0gETvP(P_slEjNi|;2h#AXOY)Dznm&pY;(BIwQigJ%YMZ)WrnbHI=}e=_ zQY?*=?pX(cf3GuJMiOHf^hZxC-iHm0%;D4qseO_}Ad^_1*ob0H{atYSNkyVB(fB0b zla#i?x@dtC%!;ESOM6~;!of)Cng||?pu^$s#!~q#VTWeF1fP#|qV~xcaG5W}cSi8- zGqpg9s;)Yk4Yo=;#%mqzS~=oz;)W`sq9H#(32Phh2uG0=ui9|4-%HVoMBuZ{67CE%wx>G4tVEZr#@^}e>_I5@+^T{D-X3`YS237#JwqMD zUbn)E(K&A;lggnsT*@Ly#&%TvK7?MQmP=U4$oz+*txa#qfr@>lCMlLB#~uR-bP=Tn9dy5<(s-cmzW=?{3+In|a2 zCwz3@uF4Q>hG~||sS*v5VL!!XNmx7`-X0sSp!TBBBtu+-6$o4paq~ycYcpsKlk~95 zX9=K?%Zr{botAMB$Zw}yE8`h8&Q z`rv%Ks%H$wGrI>u@|z^nd6dXy(lWVK(4*hLZ>$Z(4LVG$(rMI+?M;wP7fW|L?8cy# zSu9&=9<)BzejD|H4F`yqTBEjpCe>PWqAiGLP=LuIzb<9s0K?OjQ1y>6gEXzJLXTqV z!~vHZ%988V(}}f}@ng~3IF(~%UurJ#Mgl;eD5 zJcRbQ-0Sox5|QMq7GQdq^q9YzeWQKQHCLncx{ryTT1DtZyDw$aV{I~g)!tM=bIXgi zMhAep6BDYEVGb~5IwL=x z=>E`hWdTJ+23E|kICeRUOWI(E2iP(Fu&mI!#(2r_M+w%jR4KrtYba)Yd#sW zRdKC9 zU!hl*_7#R$gficEs|kdoulZj0{&3!IK#;9aGM8Q0x8~o=PdBSlF2M(t z1`@p02uhE)3Acukn+B7)bORU7jV`XGVh47_#u4 zVoqVD(GKeytWrXs7q80vg{6{{U z&o8QVwi-BUasUMJF88*r-FKp95fA;rx;90NvsRo#snz^v-f%62$4$wU-1`V>qO(*CFVsznnPmk^m2frSI8<+sBI zt_?H9G;DY#pMxR?0rh(qKovSJfYd@G502QI9J``eo#Me8B&SsX(lYW3>C>xrNeq=V~^C7l{@GVREPtt=$iVI=qU z;qAjXEV%0|QbVA~UntoA@SSfFCFY!HOPOqW|J6w6+Jk*pHV>5h1p#CZ`Zve!+m{os z@wUv`J1PBOyOoIQ*SrgByxu3%mENGPELCipsgJ4ETL*>VhLgbJ&AOld4Muz|lH>9M zCBTXj^}};vq2L$^w^~1apuu#@mtP1TTr7ThrqHnL8Ham1EwKOd=##bA1>J4#;hy&s1lBcbGT(%Zm$y_ zO$M?Bknd#8tUs)ojUa zmT~1#45pNmbnQyva1ainNsQYmg=Xy4S}>KpUqgB)gZCGTDOvy6(9V8^VJhXZm5j`Qa@rPQ;YSpAPFJ>~}#CEUEa#47MTn zWA*kO%+&;@_6S~u8&?cV>mN`TB^fy%Ye zt_|l^z59Lm0Nf22msS5zCfK0e=hh)mK8`xh-VQYPr<$_aeyJdTIm-G+uJuQ#Rq|qT zb67j+6_+}y$9K}XZfxO72riUCyVdoGXOv z>)%6YQPE#I2|L9*9&w0?_DKb3aa6X)wt?6clw0o$SzWE4OPAi(k_y}9YRfMYndc}K z)ru{f2b-W&y&=nsUh0WSnX~@8w{x0}{u>IOH#Hm?qV8h%MLu9fKjC>x0CeWivI$Os zf;&Spin@CD^+f~s+p#~cAw+7Z)@2)O9aOEHYxsN17gG2Prdq=u@!+i9a5X}pgrxgU zNv8Ni6aD$XwU#>u?dx=G{<7y(kC%xoF6=+1aG=5_cxisGUV3{+1CErn>;Ew9fe8F> zKctv1BeG2?sTK=vmh2iE3H2+#N0$oM>e9DpLee9H%B!c-#T?~gw^Xkp*#9S$xy zJnw2Hd_m}U55ul?y*W0q8nO8MF8OrLUK!R59Qb6{)9JX#k=-|v?M>!^ zXG*k#uKe8H-Jz?VPcsB`27|Ft193Ea5NcGbL&RLj&t3T}moI7%>lX6`h>(n(R+X>= zcaywZzueK{ChLE^|9?KqS3x2*m$npcS7maUL~39lSR8#S=iXi(oB0xHoz*Jq=$<#% z*g-3u_NNwV8|*`(7G0M;l?BKLi=rQ@VKCk~CKuqXuKpP6Dp$2VpicAtZM!|B=3HTL z2ooU({>zH#fBUJ3QhF;kJR%CwU`SV=|8&CG!C;q4oqdHzQ(2M0_xBXCbM(=)(HNlzzxD3@uNx%M294cmV?7wUptZDv8 z@f&J*9fe^?Pq)xTEn<@?HcsvbcF8gnAHHdg&gYCeKPgYEG+1&SNFdOOi0O zD#9pI_~L03bIj+N7FVjj+VnZ0$p|0(qT2K@RH`6>aJjehMxTqXKE zb^UCEfo(K(tV80oUvT?$2J)P=MX4h--~bXqWKuynj+DxJlh7M8Iq)j(z0m<1;Xd*5 zSLd(nNMU-!FTn^HRPw5oMmI5`xGasDV9b9%cmIk=lxnb;#vL4zB)3lUC6w0Ke$P}eMduFT}G$Dl}2);M1wkD`_Y)d z$uUPHqI3e{tk4aJo6^t2@Y*MeRCNAaZ}Z;(orw7DPLJW=UI42t&D|nodM!>TX@_$a z5&&l;e-GS3=t3D6dLBV~gHI)sBXt)m*FTv@JWJwYl6K zA6z;a67Te9pcH;2v)djLB_aI-51{Zqd~-Zm$tt?P--$tZ-x3AXza~8V{QT!5oG1G& zy0V$yY`USI=bb1q2G@rTjei(n(d(VO3T08IkRrHH&j*`K z@=v{LaI`NYnhkM38;BAqyu_nN>R~{8Hu}&Hb=Rl~5=r_?S;_R%vBp$sdf1#?t;sPJ z9fu%4xpy{4EJpZW8z}CxxT@Gm%=~s7ag4>>Yd_36{h^IK4dz;#aAEH@*kpk#4buv$ zI1y?Kor+!&{~*O(X4&A@IiU$>BlM3lJ5Um#`fK`2#UW(dE$dKyXynB8b^k0*oSHTjV>#s_sxDpUX5A8?gb#DD1gNy zYHeNq2a@~r2lqh~coEHlmm?T3YmZ+Y63u3%eG3H_sMB^1sd)oe41gayT=4z{`dnVk zXf#(RcyS4rGstE7jaLs20_{Zy+ormGg08d zE{AUyAR#4KBBpl>e_=^1vm$rCo;R{*(PeYJE=zi!0Q8Y0PH{&Pn6!xQetCs~!C!2{HRzJ4GnFg{5#7e=j*hOl&BygNxm&rK-tiYav&$}rctS%xB?83&EQxa-AVT} zImTnwFRwNpmjq@$tl%!FHn$%DAa{JOQ|LI@W@#RZt>c!MdjrIP=J4t5uOt6ezqQ`zST(IMT9SRS4iQ7RY5u`_n~x1>0=qbCi>L&xJbao=*jz zEth2dk?!RYe~i>hasKyVv}W_(?R`8xW{10~^X#ds+!z+NwZ4Y!SI>lS@w(JbJ=sTt zc19@fbHn0zU09>DLh{6>mO1Iur-3~IIDP~tSmF^N_8^svC$O;kTWCtalPAz~9(E+w zOKL6+$9d zrS~Q#{%*XU`&od8L8vCRnJTSPjf1cfQ0m=8~Za30fD7Iwo<;V z^bM2QjCpexp1{5=0KOK1OCjw=-IRcJ&ukGu3Sf4$`CmtZDj4ubqu?zCXf<0?=@$mm z8gyHRZBz{|#(Bm2v+^VPnpCH4jN<;qSXM7m%e&_Gbc2%Z56}q+h=CleW`%5IZcfp< z(;qF|fnteusgpC$z|`{H`a)E*3n{8u=tk<|s%n^1gZcakb*Mqz$jkGm`&V&B-Gm&T zfyCt)|Btt~j;eB7!?%?dkWxxoxUQf4PRR*7AMlJL7(?`+gX;rK)XzzKh0?DvvMi?w(a24+;qh83|}K{-_5s zThO?ej?wn2Bq!xB7w)V6}}G~E+E`YEXJna?H8cBfBAW2)~f4eMwW zZ$jiKjvuJ}_fCS?4^0;#@(GcmD0yLv+gGYL&Q?WV%h5Y?wXas`vyCNsl{0?IX#^vIxHm5~5IA!}#tS z(OQp{#Ik4m7@&xF<#jP!ciOi-Awj7PMn{&!$yFn+UAgrlf6_3%*ke4Omlo`vw^gFr zZhJ}vDy1ce3TEj70c(9K@wq_RN3o8ikXDbs#xH(3{MtgWd|9vEzvyedKu?WrmJ*db zskhkEpo$g2eDtFh;tb|k&Hz}XclM?x4Zmx+GmMaYE0U;MjtuJ14K5>jwF;tYj{fZ~=LB44Y_&1Eui zhGu6%+)9PmKmcGV8jvJBgTrNDpg`h{hoB(TBVSVq$(E*kmqSxGClAE)QonzS=RALk z=P$ck`sE0Cno5cQ(xAmil1nTm3AQdNmc1GCpdK^c#pC=V{mC97zx!8Y=QeL(Cfa^m zX8Q;Z1y6RVbu4cPLC6-1;YQl;fnl_D6Rz=>)m_NahxhI@j16)+0ZMwE&xH|~@q&$E zmn9BzTcd|WpKE3XeT;)Q5Ghb9%GFk%G~aY2BN1h8sJ+)fs}GecayvTbmfL!ZHL5Ee z6Ie#}YUNUL|E?GR`Ija3dmKuz(aOd+WrJL@%ue2Om$!glH#+-xpzz61Vw+@Bn>s{o zI7k#Y}H38^JtUE^u%7{27PM>#KkOXu^PxUa^FEOh zC)a2$$Ws!n0u_Yv^9|-JRo;QGE;y;M9l_Wt94Be!cn}_`+ z^a0LvxnU8lFtept=(D0czs_B`_<00aLh%Ztfsq{q!M2UV5wZ6t{gR!B%m$qa?n^E& z?APhm2FA5PNv_HW7B!_%sVLuZK4dG!z{0?q6*V~YFbs{^H<)DE4s6FytfVF^$!yqn zYyPf$|9M9C?vnnM`o+ATP7tzPAfaS9G-!!JCx4a!4!zvG-e6n#dN7ZU|?NJQgDaUZ#?(1;HrX259^{-M2MsxHSdA4w@JCfmR*Vh1+#|>1d^{7+DmOskIH4U zl%5dPY4YS6T_47}1R5SC!6(QTUvl+SP=Aqt$0@NwN{wiP`#+*YO>m1}-{6{+!xZEj zDhSo}jJr>x)cy&D0a=I~T5b*`=YL2+Jv99%O}z(bY87>f=64?YMMgh_2SdN?)r-$B zk2&&V?eMb4k8C+@J|3GSpyjfA8+UnZ+%>B-mdp5BRkiI^5(LjP1P+)~qJXE68^1OG z&C1)3AOZXtPB$j&g$5c*$;Vr>)zyRZAawu<@8i(?imlUJmcA;m-upSMVQH2`~|u2n)tGliBrVQ&{3-8dGQ z+ZkxKn=kp^S4+7+vT1L#h+=v#Ce+vBeGAHn=38i~`%OtEq)qA4&f%098?FDLrpXTS z5t306Ad1ozzoKM6gLF~&5YHy}Bb__SH5t@+kmn(BFYuAjf!%|9rmY3!YHd=CaITrQ z+J_*R-Ild=o;u>?+y8d-KO+wX51+GG)V`A`!6l4L=xQu;ojOtc#gEYrzk_wOII zH`@OQs+$Ny$F`HAiQR_x`?iR2zsWHXsn=@D(x z|CNUJr^HooAC@*Q8@596yY}_xKQrI|^Z$q6j`!t2CyJ3>5UO(mpz^EA?{|g2OGNVm z5B_Id@2}w6M;9&s5P|BP{uY6BHK5e(tz#4=Ak?>Jvh!PwfpQ?nwj(XO(eO&Z%@``J z{q*#Z6y~GftLNzY!5)^o8db5GUt&jRawO=Ls>~0kV&&Z|J?cjbiz_G8XEMbLU+qa- zlohd&y?TrO=x|}tg*$4)C6YHG;+4(d#xbw`#)AF++@%Zz7?tgbpXF_h8IP11WK6$v=SoKdy<1-* zAUq@?hVk_RFJvjaEglKB3IB{DW@=zfYD#_n{Y)j5SIG-(c>wrf$u_U6yo{*V&1&{;tKUV zuLm8awDfwUAk>>TZ^o<4Wq(u|%gVvI3B=cxTH_ua*igcs?yJ4}$3^(-tA#x2mWP%a z;65$LcP_&}{`^IEebu37yijRT2>xE(dOdKUUxMR!#wmi&^D->8Lsg);2);wiKdC62 z%6h);qmXcTRCqWlCjGXE^N+ail#geI?58j-pMvv~ z*jTQ|tA&MyXi8Z^332h&=9t#^i@6sU5IjBwcEiqO5RM|kW`6ai$(@39yHKeQ;fC0{ zf!RwYl{e<7#dJKHS|Jg@%deFR71w{_*;r|Ov3L_gsdjR3aFDL!c`H)JAoSsbv+3Q9 z-^uNHky_W_po~^MW4@3d;#=#qfDOAnbymNAJ3daaM~CP{BZ7wQ~? z_eGX+u4b*1F002|Nd(x;&+vaLC;d>t7;hXQpK0>QyV{d^YXOT?Cl`QXIbnS$;IJCF zwU(A=jBYkv&b2pJD|u^`X254`OzF5dt~l?BxxTp3=!P1Rrt|o;9$!iX5p2z{$NFD_JvM|l5 zpjMPBB(WDeYLRl+m4^xu8mjheXm)>(YEC!Ad4x5z`j0J6}yojcPbK zIXN^;Dmkwuzl@%uQm5R*l|^z&N@GM6l#JPGYlZi4r<-H48>Tc3r$*L_brU&jQ7ON}O*{ub!ELa$11;@ov9)ldn(hk*&YtzwHHOJ#AnXQ0=q#A=8 z4zHg{nwrTIi@omrgJA@M4lAN$%lj|?nzCBPWYQsr`r~z{Nba)l7k8mx@y9*P3G5u` z(zVTAy&&9NI_IFH3%Sn;+BO*XP&aj5PuS+>uWXll@5U-^6CP0a?HgxF+qV>+=6=Rz z-G~q2%~|il@eaxk%3*wrgG;n{6t+flsJOXBaa|b-xws0k9T|^;88!odEgN>+-gL)a z-pmj6#KJ2~M5BWRlk!)O_hQ7vDA$;e;*M4?q+Fah6=F)P-z*2QZCc$}a+jUqabNBr zq9_P)%8O>(Z#&VcG#Ro_ zF)y9XcT6c)MJPOtrtsO95mkSGQ;0m(e8}+AX9tr@_re(u1p7nN@t`nj#`ue*&IS3C zovFAkZ{x3No>m*>wcaq82kxzl>N}HrZ-thmtkWJ-PDox_Fj`*=4bHik620+@+k1$0(mn_JDXtn9hgFLpRpAl@}2l0QR_sPN4h_{-*nia0mhaufwQ)Y zWFz?F7+<9YA&DkD3ELX?N#^rufnzQRugBvKH5azjh=WyY7 zl@F7XlWP$6Gl4bNe4@dxh_)@7b%d8&SJUeu91)JLZi66;Dn!!cO56d-y9|~Hfme$d zpp;U2UbU{Li5&y}PINya2C?r@mbRe7sc_cOu2z_NQ^*l>stuO=!M$ZH^nYCA*??RVyKYF%0sy*i;56ZdOI%CY-oMz0qE!5PCA7)#W-njQ6 ztk0rl166B0)7{b?X7d0JeDgB7WFp2IC0Tr1GFlVa^ZSp3;3K>(c(`u+K-VGc_4$v{ zgaLtrjZ5l9`L<$l#6*5xk@FiXieP|Ei;u*r!xD}Tz?Q@#@UURt-^A%RlWk=f`t|+zk^>cRX{xSn4PqHV;ve_EX&v$8`mw8+_})bde`M|3Oau z{zH)yf|EvQII1k0N>G*Y$H_<5hPvbVh2IlN_*@-U_QC~9mg$?0Hk_C6ppFeP2sy|Q zEX(dub#)cog@C-SjWou$9;@!&BhO`1=Nd|JJwlLUQ|#GUh&F-TEVlODB|Oxe>GQI` z1#rsw?pQK(g{KWW+uorKD8RXBvRmE62uJA>3vUAOD0i5&QVmHcM*jF&+hby@qke0| zvXe?2-A|8toBr5w^GXBS<9vNggdba47unlHo3^|4Y`Z8WS|x}SPqSax@1XwEqaWsa zQq0)CC|dPKb?}|mW#y&WkXqkG`#rx8xzGmEMamTWs>g?wKs)88M*8(MJ%7mDf>T3- z(wh&OSV>1>{K)@WZac$Zoe~_g9utYElAqx)^hWcQoxC!VND;wJdOQ;0bG>b2hy9@ zQbrS=v|e4GXZj%tj0x)kuY<`_3&|J%(b*-#=vd<2JdNrp&JX~O#JUso3#(y_J9bq8 z6mrPE^mTz0#rIw+jU47HQHpR;@c! zL(IKkve3v-9UUFVif`48Nx2h$v|FI(!r0O)B<$`UN7f@U_U6AzkvvmBupE>2BVAK| z1gYO?f0g=ZqE8g*Vx+TIuFuVdK;nN+CO-uFGz_8rw^x0XCHZC;= z45aGYBUHXPNzu;WMyCX;WNslVz$};5N%z#B9ba*(_Rh{lQ8n5M++C2~Gs|Vz8RzK}fI7icLuB$h>t9DI(A9NCde&gpc# zJ^(Q_I_Z#~L-6k1gcpSo?F7q^jn8uP&jn#%Xi_W^ zj@htbrvEY9%_;D+Ay!4O?etEaE0iET#&i!un7o}4wIy12V9#a2seV$}?+^;X$M*`&HS4Fsgi0-C{yfQ<0=WNe83 zBy>~N;d5D*AGRR*>)Y-G$?ixlMe~E}hJMy zVc=b!ieCScq{ztbs-^?1h?=$+6~}6;h*+wz z*74+rC8L>)-TvYzlgnPkofx$BW9C%pE*<4k3xpsRQGTCj2Vv1s>7=wdnVd+uQ=b zoGAjMM5;?JT)fx4z9tWNmt_QBe8FM(9_Pb0@oW07yXK#%D%uU?o3$Iyj?2fG zmUO30>EAHEv;KKS8yHtpqF#06$vv`tH>ftm@gAR1P|#DM-T!pLLq_RtDISNkir3xb z2|5%QR;#a|YJ;w#WiDYX4RZNAg2()Vu`Nx=I2?b>yl~kdk$AT)aA!O&ytbdjzVjmp zm1WK+fr2!kxT(r4p-p3S)Y~m*eSz(-_3Ifw~vr-@>b8`L+uTtDaq@}J<;<$g5>U6i0PT@>yQ`g zpaCB{F9n)D%#;e*lCF$}DY*{!SHnRfcbove2xU z(2oqqlPF17f=m8X)x5htdVO)tEwwg1ja=d4hA_R-(N^an0<>@+poMeDnQDlWXXd=3 z5*dD+qs7h@sc^7wf(!$7B(_RYDZFz)3zs=c1&3|zYBm|sIU8`di&xygGqDyF*8;TY zi>4OXH}=9(=by^u9x7i2ur;X4qN0X@J$gmSmv6Gt8!;-}qr(~2YMW~Ny0AGVd^ZJK z(d5ZMsR)-IPcWF=Za^K1>yP+4%j~eSN)nv$`4v-SPcEh9Y*RmKn|Pto*Lo=0WnHe7 z-AGWsL*vJdVKZ0WDXBI+1 zJ`WBgf9Shu!u%zczQ>KJ<=fhzFc9TVBzzzHm0Sk>c(pjbQ(ojnX0|neYyVoJ967wV zonI6SFz_=K^TMdP7`2^#EHrW`=m;5h@){!F|L#j8wC*wzaWy^no)EO`ey@_y zHzk^CF+ab@4xnL?a?e1%e1V+{wmZIc`{2Mc8jUj$+f@1MNc8vVECmM5)psp}0RPBd zwK^8UQDnl&!xI%89EE5Yq>>@RQNf3rJHxc0WY-T(SR|51tWZ=*LnNyG{=lgLCb%ijyifBz&T5@D4Sx?iv=azLP! z#O#BPtZSK4ALWOt9Z=Vwk_FCQTd%EvFxFhCToXwV{}v= zt$l@Hy6&HH`!f(TnzTNsmA)_8isKE^s3HRbcxVke0)S-6KKZ8dx9|Mx1uk)f0iSNA zJ=3|bpmw|%_5YOEVX{Quk5nkmA|BwSbCx2W9M`+bxxcNc1u>M9 z#dy@LC!CiT7jh$++77^|r{3gF?r}C9S66~gpfxSp+R+vFEadj95Gmvk8Ik9+aH%=- zj$3a^^8>%T6SiBt&tVa?oz>DRbx$1MLv>wOXh)$oKM!b2b#QThfLWj%hX{cvxQ{y` zCZ3^m_-hS!6@DvVj4@mIxQkRH_k9nCo2Vu%tmIC}nnQhav6->TdQth1TB6tK6^FIx zXu6>F7^tS4kZ?P>e^_|u#>BfKx)lVJ+ubEFHGw6{fODYyNQ5R8P&r_^c5d;NYJ@)kjk)2151g2?cNO*xbI_lcx zTQO`#Z8;KJVQRYF!i7?Q!~|fQesXoxCo-NX@}f|+3~FsKMPtBRU`R`aRSt*G6(&sp z88dz##FuRjaKMigYmC6TCMhPenM&7(U2dy2V`F7p$H&4FZTAn&1_7FO^&f3_cX!86Z+&PK^Ix8wxm<8gmH3VE z?1v0@Ew`@z+U$do@oRSaHG(4^!`Qha$g?wvAEPm4w#3^BZ1HOe5K48Q>eGjbG!zUD zQ;*x-sUMlnC6@T@8e!J+))p%%?5=p%fZQKNkO6^+fS?HHf4xm|;%El2sS@*7Vk%$W zbY~&cUM3@{xgTLyBLDQ#gXKmk$#ut>;U9eSL~Ka^lR}+{7(4q~d0)rXyW1{P8pU?c zr!k&AX-C|cWR)9^4(r*?@2tb797CQqGzO1)J-mHXEAz$bu^j4^pdv7m`%Jy3C~cZi z!^K|srBy5U#kJcErwj63os798B3d63hyO#Veu|6x8Z3Re{}HP*wEiJh<9M9U>$AtE z@SHOO;pPKKe-SKFrmx0Y42o)P@G@f2n(Sc z)*646%yPf9>(ymdI<(Cs;PKf!TBAMbf5EmnYu!AVch8$9vaAUcy6rFARvPxPuOqOp zL!o{IJTcA65WNpN{1mWWa`m@p4k=$Qwd&3^|4e7eo`1_0^4`NeGt1i~};O0Y{r70)h2~&D%wdzSAi1h|K{FsY5Fd{@v z*qJQOxVv3UUwk+F|3)t+yWp#I=U3X}_pQBPH52KI#+JGnKoExCohoJAkxu5kcU$ws z4Ll~x_u4>0Rso<<7T^p&qaeeAct0|gDWTqOeb?a)xKm7QY_VeXsq zTSthE2SWBUm0PGK!d$O$7eOq|&~+4L^U&ox;5sdw0&kzn?O-Ct1mp4Z)@*XDY3X#d z$wO?ON0cXa(#m{iZ%-PCzDB~JM}g(ZWt%YU06vzr_vBDD!SWDjqM9J1VMNxMr`Q_bJ)(7^mHv&hkWXW62Ok{<{T}!Wj%H~qf;x7 zK1krbgmyjbPzH%bitKMqyIQeQB|xu`jmxI%k{*-uR`{P;K)99LU*I!6{BG?Ma?y{|a}6p5Q+}zr&v7vO!SPP3jv4wi zkrIw@UC*l(9u5b`nwO7nYjeb)jz0to4M*d^yJZYkzTRozDO@~A5>ey5G4J}0vgOWZ zp_;~qLcpC#M1*E8%-t|uYPsvZsQv(q ztJb!fmi)k6F0W@73&(38P8M?f8CcTU2=zM<$C_X4(&;osK4t<%fPvuyrkYR!o}kAv z7xlv-x(FnHoU(pf;NMbuuR?<9IBDz>d8s2E_8C&9Z=fxoje1*Y@d~4hosZ>a5Nsv) z#z3JWOCQzW8uKz2jm^l0*JnJHwGNI|EZTBv%x-5(6gISPN zDak&O0LfRS3mjRnkh+(^8JI9qf^`)Z76t>ITI(B-WcLJNpZ8LMSexxx<`}fN3*ff~lUpVho8u5YE~QYlKq&c} zR6OBP0#wAA_wHuBo;PJNb&hVK!iu0Y)ic$~D@6_x88wsGExEHN`9uhixlw7<btNg>ji09Yq#pJ}k^tmDf*D!a)rZw=Jfb?+4w`{9p{(}XGx%kd zh_$}vse0aP3(`glt(I@ye(#6b|8u_lYs3E1=t(yVDvlKE>e9R3I1Zv(EC#8|=stno zaQOwo+hzw;qRKr~q?DFJ56*|y9SqR<9iJwW&&f)I@umyxz5k(v-l_yQi8^C3-boaf zRD^)o`fzn=$~cM0Iua2TQf_K)!_VK(LTzMXgTJ;}Hs1&jtwny+(=XM^#mfteNa{V6 z|74|bRC3$Ej}m*teoiJ~Jr^!;7Z{2t?t@1_P>f94SQBi7+%o9@xY;fh$CvW& zpXF^7R;?sWh9eqy>HQhsK7noL$=YjXjbhhIA1G*OC2>VBmWT^m ztH_U}T}Lcq>sP1>o!c+F+kKQ``G2~T1R&_a68R#W?hAD5<67avOKO62x{x@nh320R z7&}7IsK`En;yR1RK`Tt#4VaHR;m9BfzP|B7?b@yde5FlQMgxou$PUNr($hFN&s;SH z{4S3L$TuGoTwzhdi<)35`?+4Op@1HfizkO&FC4T;VLxrc7yuUxDVG3JBsu9S9R-DG z@o;6d>)CEeBzbdstSPEeD$n`Fv-bS!36)@a|7y0uA%W60r6ikyrc0v_iRTqGBURNR zp2q|b9ySp9QXE*D50N!Ltu_x6;bKA!`kg9LW^OTC|2;rz;6diY!x2(+grc9%zn#TSc{tV7r5 zQJ=Qyoj+DqJMRlIwppL9+g@a*#P@(u7taS5OxGl7jh~~=%6e#~b+)JM_XgzK5=(*}r z0cXBK3_{XFmJs2Zl9kTT9CT8tBF@$^Q&6hIN*?K%0pxW2dS>KD9lFabGPppLgFH17{a}@XmXK_aIYSin_zKh^J z3wMsHdf0ho5i#3&jH|%;>RT)E&N}km{ybjx!T}NRHd))`KN4d6bnK5a($g)-cl+#3 zqaKlcT=PbVVL0K=9aG&`&AYhD2(lfgtm`urnN`gOR<}m7&JLVCql|#hbiO!xuQ#CK zrS!P45P$9wcfFn{RO0kq{X@2t-#KP-RK0{34F)JTb!mb*>I=m>5A-wN3D1a%j=5{f z`7k5ICDn>tB0S|KwWvhm0_MoMAM^Dpe#8lFBl2VIh(f?kl@7933?WLn_s<1WS7zym&7c-<;96YmUTioZhWYxyK!lzT zIwa;wc+ad8lfL@3+572Hm$jHMe2hqSfnpR|D#yKLS?N5wWp}!D7mLrCV=0?SiDq9c z4iE0yQEA8clxM#%+H*?O1%)TH0iNu>#E?{x%J1<-y?DzxiJPk!eOZ4-e!_Hso_I+X z6OoOp*r)&->Tk89g_MLCE?&$>3xx&-RHD=feNGhR0l_ayg^1BUgMp*FYqvoH4tRCX z+Wdv4+++4Sv89NanVILggc+%O?8vFc`5r-8C!91e>Fdk;yFp^W`@ zG(D)qKtshTqD@O7WLoQuQMzQXnnu=R&NOgDEck(C!`ORzzE3{7Cq65hqzE4&@%vyp zfC0Z8PkE5pOZ~_jU*aXNF5$AD;9bPY*FYIpgLFQI??LEnBmZ7oe=W4-US3*QA=JW% z4}X8t|9ri8bTsG?Tx9D1(@zl7A_P99pv~`%|NGMY=gj@P$^QG>=|NX=O?wifw+?JS~CRaX?1Z9c5>8b)-F3RsV(z+M;B~hdHR0bL) zEDIOhV7Oc`Hw?ObwX%gJH{>st`KI~fLI|1SSQ-?8M(13QhfNO`9|HB%L9*KX)=jlSOIR1h z;v{-(85A&gzg$N@INz6U{@Ii|H#f&(Er5ReMpRyH z_sz*m1q;XrUJpfqs$zq>Hy^i8^VNs+ECN4pmWTTog$&Qy%P#{mv(oK{8*ffhUieye zL64?y3n}T+j)p%k?_5u7>+;6|K@>We|GH#R(!`RFnZkt!<4zbD8?=@#BNLZ98mz~s zSDxDLKHzf2GPu1dA$&@NYgH1N+2FV%iujmMhM%8bu`(ZfzScefL@>Ti^Ek`-QxdT` zJj{VJ5GWf)(B^&LhO#@nquzg&YPZ4oGDE;a`X}5c($yfa-HQ3~YCgT?@b1&pYI|vS z#%D^c1%j6%g%e=cw(yk5F~ujhQMgPG`=pG-uga3UiF(&qYhYuOj~_qH~Sc|-vHV_9~``*d7Q1-N^g0c zwMJg*fp}o|J0@h^CgY*hERIF|SJhS`_WMQyiK7>X20a}eenaQoAG28Bb~6Qqhi^C? z>`WBFdtC21k5?F@c65Kp%*w(nU53#LL?_sc`45q5BrGB%^xSf~T%;_)cOCh4kuoFI zWP5>JhLE_}tEp0*yz_-?BEXWOhOewBBx5loOKNLt_j3Vs4`ltnHXe*Wq6daU?ZaDv ztDECeleJ!@xjfqyQC7p=fbAEd?A8lHHOe~fhXr;1&BI8HtSVEV5XdPOaxRatN6!~a z*ZBd^m6^zP3yK-;4Zs)9vL2Gcv4HcEQa1Iw>m|m&9(P{4`{%{=TMys%9Z=vd{_iSq z8TZNC+a#65urM$*(Cr?l!{^+9`B0x?%gmykrCNRCALBmgN5_AOr0%cL@)?Q%Ud5U4 z^=ucH85Hyr2Z`#A!{6&_mXul1{|t5U74Bp?sU(rh@L(Bo>0 zSK6-{5+Sw-*a(`15{J!UJz#&#=5{nw(44bmf4BT_VUXc6?bT4~d<|-+-&0!Ukfv)Z zX1u<^lxqNt*1aN^h~B`?^ZF|bART}$8Yk~b5W0YtiiY=4CYkf~aH(7dRJg&3`CxLw z$&9&HwB8zFnx}X-3BNNcDk@>-y(ovns+3ayWbYgLt2joT4CB;-j1YRAoq&Urlhs;e zLf(i_7L`(M1B1t$Ci<`7>s}bkth}88&5Cmu-(9B+Ee;LI;&mQi!BtQzP!zau9<$BY zz7UT=8m$;&UuU*?tX!hm+t7N~;tlQF><8L&Z+>WK{Wdc4@v+{F_xzk@?cjVPlS&4` zREF`;5BEW)+|>bdSyEBtteeZ@iow?Rt(BfX1r5K|_gphc=nLbi+uunW+v<*MJ zKHceaJ@|ZaJhZ3D&JmFgmCDGZS!FdJW;IvS$_y7Ln3}$sB3r%TY8Vr|4n{c zTU(pmb4AhWUo5hQu(hcU;Jicdxk9Q}x?~kosb*^$-rWuZMV{Ih|>Y7|hW zm*77EfAT%J*km-jz(SRUrDXHB!~CxoEI`b2^b~dFGpqyXWY!{FWiBZ#cBF>i_PBee zgxOJR+^S+Fryo%C-VuFE4EuRRPfkwpHeO0d2ntVU7@ZIqST3&K=d;J76G(Fu(<4); zGVrV))mAF9C=$7BGif0$bih$NRQ4GlF58?I_iHoe^Hf?e?7%Rle$c|2Dp#u1uhSSf z1{vw-y)B1`0Y${f@PMr}s2s-ky#X?rufPF!j9K099xi0)mg7{FR$S|FF z5Px5P|LrR#irQc?!`riCo+h7=o(c zE~c-d2LL5GJG)FVzM-{tP6Nv5RM}?4V;*%*WuZ@o@%~Z9wzr#8!tZO54MHhoL_EOt zoCO@E(lRCdWrin+p6{(W;MuqxMy5k(^`5Jb%SNUsxuWF1L&2-6)$%VO(tgN0&65b%7jnX(3Fm+3@ALU+_0(> zk!lPC`hEUX2_s4vSbK=r`8p8mT?Y?b^S!;$ye$yM1#O7H1C9FMW0la$%a<6}Ydt-L z=74a7jCwgtf(|cl?`RT~h_$H;Z2X={oa1+hkWm^|$yLq>E+E?X#4$3uvBgi}jxbMo zek)ID>`5Xo4W{M=3Yjmuu>Til5CxwRZ^)_#`9iK zN`b9@4o2m>+<+ioU*F*t?|(~n5#PT2q8|Y|CaoeZH$^~5X*uICykmi>JM1Di(iT#v zBsseR{W=M$0F4Ic39D>0$Za|%v7QU8NyS-^eCgWVBV|4!q1 zCkC>i=-T%V$xlIZT&5q4$h@(`u~2f0^PP5LdzSKC1WkMFnX%eR4ny90rmZ%!F!Y6NyTVAq`1`T+B+#Z< zVtNMo8EaK~AwCoN_B{)5O_p^9HraDD53QX&)c=P%cj|&a+BpsG zfw0HbSM^{e4DfQyzfKBp^yX_@a&m~yq}=!u^5r$eV|Y(j7Mey zCe(>S7DuG^E->+aTY}}kn4M^F48cdxQ)R-^>oKNBFPW6(;rMg{14^$X*3xTmnxZTQ zf$NycwRYqST`KMV6aj7E~7qnG%gDf#~mA`biBdTw@3;l-wuZ6eAB;r27$t2+Nd}mlE zA;)9C>3wa3o_f;9xPw4@!uqG`mI`5OTrt^0Aa4_Up$}3)vL?UIs#Wl3xEvc5DTOP` zfzDBg9}wl>Fa%^!g4c@5`W=iO9mNt0(({VkYi@cB+S;kUU%!Hkn(w>};-Wmj2Y2wd1*zkea z5>i0zonJ-n9OO?|?O0d2pZ_dSN{&716;UmmiGII9sAB*rcR!ub=bwzBrC7n^efL?B zBi63Volp_5b8qrw7>`+I5u${Sj38%UzkcQ2j2M;u_km0BB{CBhr~Hql3{)3wP)#bu z)BpU51;#&a$=b+Hqc^qFYzh570rFjhC@L?+^J;4YE9IXYq2E~ie{+OyCN)JE&GiUW zGiTM$+>aP0i1>mLV+tcI+-q$Tod)nti_PqbMB}jN6SrK{(C(oB!x5U{8i}Fc9i7X9 zSX1}Q@)84@_;r_`?;@2N*BII?T+e^BW{6THA;n6j{>c%VO&Xfm<;Sm7vYdi9R76qlRw!$t-I70v7k}Fb^9iU{iV*RP~@lC z&>yoroFDBEM|V#nan5y9VGbHz2BOD0u*+hY^TDqQ3PNd-#qzzofdnxznBNA>pP$la z@ya)BuJ$POns>6iK57rMoF^hNQN4wC(duvXWaVQjR2vIFo;B&?Hn&V@>^S;{z{PF1 zDjr1M^um?S7r)gUeikTV+2T|ouD&>}w7Un(Sy;=uGi4D(2!yl;{G&%pCYHbLkI|uO z7XOqS-~sWf`bK8xfkDolTz#UgNS|fojP4f)v`%J+O^pJ_v7HFoEOS&(Gj7928wSin z2JbOb?m;!jiY&S8K=|8(_olD)G`c|Y=Ncu;jR9!ZqFD1esTg#U8#A`g50}otpA58N zw%ppS%*8@G$UA~0Br8{p|MCL-AsQ@wL_>q_Cv9bb$Apq4fA~nyHH48z$RPq}GPdP` zK^zY%mn#LMWXR`?VKyDZ`a{@bDe%X6a9Hx5j}w}YB0h*65i%I%PK%Gj{A;l)32DsZ+yDmB{oXJFo}TIHXwCBscvj8_M*-QCq7I{Y(0jJ% zDk1qF!hZjVzeaNeERdOX`{4;OXrpy=k|v5g#hLxTxIR{v{2x`WKXx81E3b&faCKR2 zuqS9F@9jE!y~_W(8h`3?;cVXcm_9I(2+*Lyrc~5&Bf8cXmjwljnEi;8fg!9taxksA zrAMo*P6{Ln7XY`d>6v%H06@%@`ry-6`3VULLGitq^)yHD|MUr{lEhsuEu7p~>Dby3 zk^wogJ>UI>MoAsd#z4?cK^nNR6+vqw8IyXec~|atH#NDj`t_g@54Yl~>#IpY~LYhH>sX*9Z4J>3ACT7E;D51Fo~ z+wy&}^tA*Do4epR6eQjoKR3kyF-nQ zV)r?LI(J+f>i+A}9a8I|9Aw5A9lY-2nqR7-*gKK8P~p&jA%3!_ouT5-crW(4^k!JZ z{wX&;1TTQ24IkSlyUfu8YRbj@&T&lwXCI4N z{%b=bFCYCm{j&ilU5F{W2NxUui-1Us?^~s>p||#@vtnMnW-w!NjWyE{1bklNX+$O( z952O81hBvDH8J8t~@_ign&k*Atz5=R!)2PaSjTF#y&MPNa9EE2JKwyP(*=Ro1 zk_!zk^hPTAawK)OD{#Qx{V_$WoZyk(Ycyx)v01ZYnM7L(7M95VP$o#0rQ{s#ROYtF z(&2aE>cKSqcFEPPEZ4aIldkzt(qljMnoxFYeanE$)QlyL($@=k?a#1mZEPUAC**{UcyRyv4Tnl-=0FrH?|$8!x0%(EhgI;m7558tPsU)Rhewb6=PLA6)wJ)L)T;FnLaMPpU*xDqy6`{z`Kq`FywMv`1X>LcLBA~I zoX?-%h8F>UW|8Kr|BJM@j0$pF+l7}1(j`)of`W8PBi$iLcXxMpr!+`+NOz|o-HmjI zNQ3k@ap~G?@3r6WJ>#5T4u_0KePYgeUzs)%uR)y&jycj@!RRw8@M}8X1tBRsC6dlj zom^aOUaJ8R^S+qy^wrz9i`s#J9H%A5;;@wb8&taIyt9+-pziKIobM4(i5|iw2cx0m z9iVJ$%NRz$6$zw8gWVt59WW+~c7*0yo56T=3(n(-KApIG|Fd2t^w4mh`^Uc^ zP*co-;yU%mwz1a1Fm;B@170wa^(3y`^55`rqf3)tlt8W?D5-OpgU9=_&OCHX{E`cRBuhDfu@=U z;)7o6$Vuyot7LAu)3Zr}M9>{Dmpn)snHn6OoahZqO4(Ix;+Xz(q4wo=hO};iMv(ji z9}4tjAqNl@*iul^4xH{^H#q-L8#K%Bd<;?s5c9P@KOxDHZMnU@HR_FeW$Nqo;)xq@ zY-b{{d}WOfal-t+6){fXAxHc_iWvW^=^fP{X{J;|V+#1vqeP>cQu-YanJ^t&+AJp< z?AVb+_;5P-!3Q8xQsNgA*`d?{&0fk#whN7ZV+G zWVSzd*2Lv>e0{@A|99YXqhRu5%e{NGJ=P+TkTmFAusguoSHuuC`qs963`C}tR>Cr~ zRYpGaKYo;zL#I;HzGgBSOVG3hu@+(Ol6rkH^4*LZ?DUz@ktg2epZEe497O^0mY%^E z@ESS&5u@F%?b(4bnFq3|G|_%jnl9>n3%PRvd14m$o=1mmH$i z6H)u49Zd@xCPerhN-mw{GPkI>_&T(LV*+=oONmIL8W0}}{VV~iM{3T$42>;v`8RbV zBXrrQXBiom1|h)sS8}S0qakoc%dKF{z1t!C^@z1B^@Z09KHE|`V*1G)6E9&lMycV; z^u!lh=$$*a(;@wvvatv#8;vjbxDX{Cr6SUrgXCr2SEA?XgGsf!i|AN?q|();UVL9z ziy$zQ+CTVXQ2;1ar#?9~+H6rTNO{2xsys%%rEOb&{1_pW8HCo_o^I2rwVEnLN5X!= za3wsn^#YJFgWsdg-ANM+@kyv;k%K(9q-Mfc%6J$;V+T8qUIH zv*k}CCI@|s>KF@wl7d5fAeYot+_ilf@-ae_YlVvOME;im)$)#03siQPC7(x6QhBja zu{Ive>&u?4c!N^@v<&}E0EOBk{UiaJlZ^6JF{dpK-s3`2rP6fWLW{BHMqB+8eJ~8cT+m3k*{H-{q{PJ$Uc7?? zRiiKUXssp#fPb>tK$uLndnsr@UzI3%Qddco+7LEl7KMu>yNMQ5?zWD_Xobpq%lX-SpOu`OaS+e|+kmCGk7= z<6kGH2qnl=n8)J!?ZPIw(0a077l9-qunw}?vn*!uwL@K^C!!*L6cbVnTd%2!Sjh_g zGAF~6R4P{fkZ}CrBNR}N_3!v+tq^d=eh@{Xv8I*}M!8zgm;W%fJ~TLSvTwM*sp31^ zbh)W1>HqbY{X4n|*uUTRDsYW|Rsr_!7=>|^lMVVUUXDH{O0o4T89n>d^wIFT2 zcn!PTur>$sl3!GRC)<6%Kb`JxVdsJFkpSxdaIb#Fbp>`cuh%EH=DJronLWSI;v+s>rvp*if=3;ecbPstu zwEw$6k>pXo@AHqW$7rVXs8L->_rE~s!UG-`2=|-0+i`lXD;Y;^Pb7MN)h1Ww@*V99_UsR(lJ;! z8h*iCZvos-c9&M zF|=&yEdQoAu@d!Z5r;FXreQK5GF)D*<;JDCACQA%{Zmk(h{LSDtwH}B^5}U1q_#5x zhr1MT(o`2$C`hGWoO?x_E2ECdEXh%bqaGZu^|}8`?cDg~erqm+cHe{3det`#6v@1Y zmA5(iBsrxlXj^oxYkPl=C`0`mR6|{F5H!t63w8(ov{}VCkP6Z+TSm3 zrtODO6;?`PFf((gH1>lI7|Yqee$C{k}d`NRjL_p%gt64D~EwYki$ z!S=pSS7dTT^~_b8uY}-Jlq$vh9YJX$@2ixQ&Q$utIKcQ(HC*@!1O&{?0?FW3El<>v z{$MH)uePqVBA&b`?;8QbA}s!U7zJDm#WuRYhOMkbANVZbmSXMX8pRVoZkLR;DsC3#s3LwCh{78p50Am8D$$PP*ynihrLr!RXnR_*>(rV)AkS z`WX!%Wz`uAMH(HC=8a;BiT2NvXWr*YS_|_TpYRRA4;zSQAZbDSyqBRhh-5CcK(5SQKcN4`LhqTnV-CI#)khf7%2$bVZL-XTAlw+Q*USXB6UI=1OX z&@Tpa6|Hn#MK~^hZFESptL}0)W&@o<(o~Oc;`~z2kmRLy=RT(;N-A|+Dh`~?@Taiq z7Y|(@y7nT7fN_^uGOZq)W=C{|F0a&O%Q({wIM1s%hXQ!7NtpV?4&F(NCb(XZ*@m^?{H;h zWk=oRS2EVWedwwD5Kp~z?c~Pcc`Dv})~k zOmkS+?`%`4Az+{=)EZ)xkGAPuNI2CDXbk@4YUSvKdC-&1 zH0jd{LSO%bJ^Y2%!2gso_c0RvWAc6g!@wo@<2^ZdFD3P@TU z2$0Erue}<*2eqbJ#JF-&Co;>lP+5v>Gp@5hfCw$U$GJ&KdpZ`LpmaPN zm;306;HEbeeDEWZ?pydM*x!EgGKq9YGL5frW8@D{q~|4s zlDTJZw$s}&t>3U-)SpttwDBprg?!;N=Y|&~t37Yw66ts?hDH3Y`#M|yO{O?Tcz8Cw zG;+T0F$&_H$44TJ|1n*Izh?*Nf}@Afjal@O+D`;d9O;;w5*)(VT#|{x@s^v&NB#-J zt*@=|34}P00^r5)@;Da5VkB%k8;d0t4wFE`nD!hkByPEj;q|vRq~?;Yq60FAJBVjj za?Yhjy)^G*iykMB&ao;l0;-=D-_qv_)baPPnIpek&o7+|B zTgVQZUlw09`NjW3fuj4pTk~#h+D{!cd(&GO|6?Vk}446iYpOvPNCxuo>PkPX^2yInJZ zkut*lYRBOAhoq#Wt2zsTFIopNq^h^~-IQG@1_(#k24W3q+z`vu{9nStsm449N_KX3 zyZx4Q;DC$(pj=0F6OeW-IvGHwu-e3RADy@erhGQFyS>XcNHY`1LUaA{Ax*f^et+u2 z8KIwo`c8iM8(+R~e+2AYn4HmE*0>j4@} z^M!`4i{eAw+x^0>z3HhwiE&R-qU_4Vm#_nD&p?kLR;*ZPA$#eie0TZCqRQs(g z*g+?nZLI8CqXJe}wT(bk_%m=^-#>o!^K;sPtYCz-kmR~N-GnBF3njaV{yu)T){d*K zyU<-8$UKS7=BGTQA@(**WxN_?+$TjXb$;p-+4+%<|6$a>QZ@6K7adc;Y*hR!9`^Y4 zXVJG?yclUWPz#jKzjTr%)8|&hw<+w zIpJ_kADoX@f;yO1s-f#TU%RMXC&oV zHe`#E&k_hPLG1Q}z;(dx?>6PnykOnCe1h-<-}mc45_3QsVb&N(*i7B)jn11m+BfP9 z)QSQ9Ic^E3;eLC*^`O$oZwo%_Az~9y;dp7tPdX3@epDHq5aFobU73*HrwpXs2VLR3 z&L*J9@{3nDen%U=LW1VxGyp|;c;V+UEV6r#h}EN6^cNd=Scm#ry$P$@{1)OqE|twL z@!%CGS;_kT0uCSujA=-=hu1j3{SK_Fv)EoQrCZf^5#M07Q{;3j?Q6ZMIUx@ehlXaI@j?UMLYUUdlzkrDKaod#(m}xDan&K}aqyR_HKtyWzH?9WxB(*N zpSEfB=1C)9oBrrxnf@37cmJ!yM6-OA&XMMm7WvYpG_>CHlxccquWqZ zD?9-34t>IUtn(t^C#Bp|Zh1Rb?V@B5;-x>3@M$sljhw}V@(3`&?TmYR6+eKNt-u>) ze8(3vYV0}aO6_b=2*D9>chPF~?RLCiywEkvC@0e#$z=qjK>sjXwrDNR0j=SRj!%{L z`hTBEy^@8~jm8Zo-9|X=XetD;MYS*QR&(O#Dhxtu`gTB4GALDHaHYvj@oDuyCI^`` zD>+E~jWl!@80W|I3x)jrss?yX91IH~rWq$&G>5k!T+PkVWh1z^zfLkG(KKSV+T??C zq`mJoARvP`Wz z*qfejkxW>xLYK6KeKuewgud4P2=rgP3{NgB;`ha0uOViWq=m|av5QL4N z^~b%z{-|l%4h?|}FT6kZMg~iBIa1rbG6OG!U3V3jzr3ytVFr(7GMEpI+n2&ny`-VW zie^t3Ej2y#ZoX!lyASPch1K{Y?&Ev!YY{ i#2PFiBmRXVP@3zn!IKm%lrA=5=L~bv(Sa_<6TQcQ(b2>% zke?&0ukD*k)>c-AE{q*tWWr)#)Y*`F?HzT%y%?e0ZTkVy-d~-Npm=uuy{9|i0-m?Y zYB*qNDe9G*B64dZiZ9Kd^Bq1k1Tecf#zuW#$YDtGSROu+Y02?biG0TB<`}$qxo~&# zR*DN(5E6N36GB1U7w_XwYxB3)I$fl~yx*uoKC?bxtAZP6Bbw$u<$`E=nYjewW=m7{ zw^tekW?wPy)0!!~3P1h0BZY)inwOH|>4HM6m9pw*oS2w@FJmL2QdBj`S?ST(STob2 zQV^RHtCVPz@aj8`dEHugCWLoi=8v=kr@@UD=%x!P{vS>t^nNjg-A2ED>3VtSc8Jv= zK?;lAzKTd&km7!Ys!vUiQU%}FcVc~4p+uLwBfDz!*?_!^%v#tF5Dg-*s_o4j+wT}_yKq)cQjY(phFkw{j<+UY|7c3EFrfQ4| z-s-_Xf)<_(ti&?2F2)UKJekaw9jTSM{kf|q;k3)n zcE+O@5_#&jouGT3a>Q3_s>9QFQCi}4@IpcAhJK3L#}3}ASN}G`=7{02ty!M_rZ!LQ z5uq1CZvyW08xIj zRyJ?nm#FSz`+#|@& zDsOnML+%69&oBr@y0HO4FNzF~$rBViVnOcBrVSV2iRKGEj7}SRc+1*b7p8bI}V2Yj1EgNRsR6ol*56-j5Y$KW_7@RVp;LGgz< zN&5cLK&VtF@4_QK!kn8Oo!aGJ< ztJmb2q|}GSQ%`Ymi=yR)6o)|uVswS?PJf|dd%IkiwORRCrGW$Sf#9p;okCG`vE`+D zn^p#Yt7$cd=SDV}%5VC8QJoSfc~8ToTvyjI?LFEotiH`0ee1hO4OY{^iH4yznuCCj zmW&915q?bs0|VjhNk|Cu>=k5HSFrt_|Aq*t=DU1~Vu|U!+@PpCvL4bUk-zA{h7tAT z(b0*cBVV7D+>~TfC&}FwDS#vC;KqdK;MKA5oR(zF7>(SN*Oq-o-w=CvtX{m{ zo{DWOb*L1i2%CR2Rl9IW?&}Y1)scR0(d+4IWy78hS_+< zaooda+`peT`O=uH+6<>%S5tfdzZjs0&F@BBHb0Vn?DRCjpwtH2ac+&Rx)`t>GuW0g zuZU}K-JZf9c>7)-ZNzI7&@JTL3{z~*yrJwtVQ~Q8CoaPiQ38(}rSOI!U2S-iZ_oUw zzT}BJ@uAYwD9U|4p9v)MU80zeb-y+XlB-Yt^{8PW{P4U08Cev!8(uW?GXKuTT zy4Xj68S9333#1=4J${;ckdaY4K2DpwpU z(1Od!ZIb(;Lay2cX$*=6lzS_$RxD>AxaNxYI?sBnh4td57fU3;Y$V3$ImK{N2k9j% zld+nl}mCp#=9tX>0KExbNOxQUb(1R_EBafV&l?Q-fz&!$9D}B*zeC6J;^W zIHu}n;PULBrqk==$3M~V5U99LFn{*}wiPNu=m$4YfpjNqTN`}Xq-#CwxGxCOXSoQ# zO9t^s-Dsb)v-y$Witbaj1fY0D`2SjPMPZUTUBVrn!yy(I*Tk5&Q40h<(y7s%1r1CV zxR2tAVRi{I5HJW%Pw=-M#w{Kj3Yonrs#u&8fm8?0uSPLEhx5Qp{MvYqQFZW=_kLVN z{)C-?coAV&K%jU-;t@zqE`&H%F0vXCl(hO$x5D9iOVj#w{m~O?bg(o)a4*-u@wCq_ zrNgJMb<7EL9pBjpJfVQNXHTq@oR53m*bolKY#Kd`M17nfox-C@{`+;(oJ!>tW zz^2|~CS~HZzBjr0e9+2tREJvVdA3+z(C2KmDAWEFPKT3IlC(C>8t2|RM>BJ*GDxow zRB;Vviy=5B6ZR){E}r>3hqEA_T-Ro;VzPD7=G)EVF$fSH890Ad<3zltv(eYx9Z*&& zYYy@L$&nV5=}cR@vCTMzMoY4?*Qc(QIk2SkNlI9*=0_{uTUj65dPOi#I2Cu1o=<#$O#uyuLj!B%4dtgwAPs>@U^XSsL?3l&+A4Nd&t1ap3N!GS^<@-ntmxt#h+lV>p@# zuA9SRIdw@=fE^kX!b8ec3O(O$0QH#-c1drWSA-PZ*1L}RkPTNlF8z0khblRNFji)N zDyG$(VhH6`S!jEAWZM81L1mkoxCMdP5OW?e@qMm>dby(W$CLdWo8A5*WD{(~OO~gd zwO)G>O6C&@HdiWdE-3zWLMtO4X>vlVdFur)wb94(41Y1A*vA268JTUv!YZ-j>rvd$ zd|q4ySz&KrAqU%i%A3TrqDc_zr>@Rs(+G(cFvr!_E6^W4MbN^#%#5^Sq*`r^gM)*6AInvj0Gyf3&deu1Te1QlRzK zKvcBCOg@84$n?2TtHAa%60%?&YVjYvia$>oD3Es!s4%bv?R1)_=(#yL<6}7<&&~PR zxVXYWPLn=n&Y`84r8=d3iAt?kI6h|_!Jrt>SHgr>*a}*Uam>j}V10Jd5lRXDp`;@G}8Jq6>zuqh2 zKc_YSvi|cY%=I93ad!9W zuHVLb*V`W-@lB74wks5m7H~xdn|oNVi8fg7I<_NoJ3o6km;de3zkX*<2Vll}f1K|M zd32K=mM@*4A+7gg>U64ZlWNJ_yRS?Pi!}>1RoX|OQ!2tq@P1zUdgkRpPZp~Ltga2iJc@poAjoTfa!wx(q3#vcP`<*5mMtwheoscNhIKW7h}Tq9VVgs@ac^%9(fFpZ@%UyXJDBs?-Vj{G#eS)w$(5ep^ayLRXK2aN z#((u%zumx=#Txa6y!e@<8yVFOZgDcL@0oJ*6`51qSsvORQGshSiEO5-fT#qUn6yl4 z4!o66SdH<8Yr?0ReID~yu4HTP;qa8|1MLUzXI?un0kg+v&^%_~_YUg)JnAbFl94j4 zcMsiCVI}vJN9S?3vB+kNCo_^-NF4)6Le~U~-;K*u*3z8dv#rf&?XO6HxgE z!4!bhHKy|cG3#V2y7&|~#dh2BLx&Y)EV&$9hX3}r*-A$DoBL7FtqS5yk({?uK7!c0 zosvf7sM=a)5Sv+e)XZ-(RkW6CG6^R=SHM$ zl-YyuciJX0@)0yY|CMm85EbxIDL9Z%q1O8+2MTTNhnei@D&#K6H6lCGJ__#}-172J zv5)N-_KRhQn9!+Anr=D1FrTXma+anNiHxK`Z?}xacUpe|!2GO?z0mLsQ__`Bfw8M^ zbBf;Lzv@?<{G%KKZ+Of-xpdenOqrXOhqO)&nIy^FratWKvwusvTz-4R=rijj6zdCW zk=G6i=E`r_kkiY$NwC<4UOtq$ec*Z<16n0SDCGIy%gR2BeJ$+`0@?Isp+)GlI!<;4 z76WR`LUKX%mNZD`+PS@0qO*;Ffq_|W0D1rK9piHWo_pB&f}^S2*-=CL7@p>mnp(-@ zDd;R*V>85j4-5`hS9>%Glp$f91HQr_dS~bQU`u)OF{-bXH~%B8N&u~RyMrjsIy{3G z%YDNvkZ)jIQ4s?21lyy->TL{E6@&_GMMzYnFr#Tz74jr6 z=#P$vxe6~A2NTaQ(3nQr-#9rr(e?V-2!ldjR^(`UgRWWo+;?fb0Nu|7_R4(Nji)W8 zT3TB23zW)Lo`5wd?8!fI zTT}DZ?#vOsyZIWnQl@WSVy%DSmKCh1NIB&YHz{G81{Hr-L4BWOY+B|fa=USf^irLA zj8A?$t_1uhYfmFUdkr%7K|aUHDkjXwi3!Gsin$9Y`X}Bj{=hqtxxDQ&^)=`cqH!}5lBA}f%XvmGjp67K{mnl`|o-VprQ1qwLnMOjrhnJTo?Dt1-iYX-e zgC-CU_3!H*s#SabICK;A-MVv8cP{eYIJo~H^qX(I5RP5k&acxHP#|tJbwK$ z+rL6{wo}O&NwWq638riS9&8N#Y>FHQRj zfFURgf9-Vwj*HwUh`UF)&uyOn`7eNDhfwOj^>fQ+wsQTt)x_7zwHRsdevKbnAeS~L zc^#+Rd<^TP0F%8v%V5}xhG#T9TuH|8xnpEYFJJSFr-fTH<&Wtnx9Ma1EFgTrhTWUC z-F}vgRNF?TSH`{L4$s#4>vNK9snM=#mT4L_&X{6#%L=!luSJ#nAv>!6rDJb)v5bUZ z&7A1%zX@-I+igwhTq!>>C@^p8;{;^7y zr!XCtP=D;nT0IPgMpawI8!D=llkz}c0fGoNLJdMiRWXtJHlb7GJJ)Qnk)y%M zl8oBKk_45TV98D&!oa3~>n*I)O)7llcOt*wbPfG{%Rr~b>bY(h#sM4;+s+|OM~2Oo z3#)k#AY9~F z#irNf=6ai0i+p}~xE|zLPxD%f*ZNr3UM%u#ud)Zt{M+-EfjH{CcPj1_Wib`8^H<(y zK}-vicB^Y^tgw|@FeIE29`|a4-6pqVkduqe*SJhtQ|;@pXvOdEm55o3l852O^I8kE zABP!;k<=k-d9%1(iI4fllkjVy>$F_d9RPpK@+hGoYNsh(6_{^_qW|2^!R<=Ve}RH! zXvHPOfk_1-L6RrrDa_4FjHN%3Ho|B$#261SbH^pK$EOz_HDTxmD=o8SZ@nSZkwq`W zav16AK4s$v#i3U+sf;!45Jpd6vdfpfoadTVY5XZQ@C!CQX1AmFY=pQq?r-I~l$~gk z)XlLSEkVDoRe57$qw{hM-Hf|hH#T0e3#U*W8IPy}{3}dmhvP<0%HhFRUEkmA${tL- zt0zX6dE|JF;{*#$CH*Z7f%K`Pp^ia5j}N}~{^lSRH3OjxO4h4ZRRs`SN?ptK6Wece zaR}vkl1}&&+%NG%SoSYn5o@eTC$~rR?(Edn8o;)1IVDHejx#Kf>E>-CRHW1$ClTYo z?i{2GF>AuJQIa&OQ}kN*Dxi}u9?ryG`LW0oMLlWu*=IB&lahRjPw0Hrb@-2j9^gA z730K_p6#h%7@u|^dhcm#iAK|4FlRWseoV~fIdls&N8TZkpfqn}BSC=J<+ariW;W5c zs-4=`eXT$mxcKH7y!a+ErIEnzB`Odq)Tgn^x-YG_+IfQTqV7r(UtUG)2@eDwK+9|a5xAy#r<5HBxNTJR|&RR@(sGRv3f_OxRADv z6t2thz>TPQed^@5JX_}n+4SU7VmTzJvgJn$g6uPWUrj~U^A~!fmzJUTX~jw#*A?#uh16%A2j-cuC{QtKDCC(7Zl2U;wAt#1qhv#_%?k!|277W`R#}dAY=OwPG~O zIFj&iju__2cOk%W=Z%ca&12SVPq}YD5D6Cx({fPDZc)8Nbvf-sxh@yxu4;ftSi@q^ zM?NXDw${r{tshOYn0$zs?36nJ{p891WPp77e9!y}onTF&RJ@nwZ zGD4vjK>poD{GY1(FL~S(Q@9z}7Lm`ej(!)A>Xk)it>pJOBYE@uj^2VH%oT;CCDtTC zF0@MBs10MGrsA%6t6(+(TlSN2G3D{b_9JBmx0vEj3pNqdK+a6}8~FdIKj8v^f8(ys zkOluTl`ssvB)+slOY`I==gFsS%oIy(P!2KRK-h-%NA{pMRjk3F^M_PdYpiMu4`dIi z@LoTr$gX-XiMK^xaM|r+1jAi129FcIeDE0UBkz{h0PcJP`cEd)L#JPGO0C(kN*4zM z7$&B(J|O8FLp7dnr?_Yig}6jueq^(Ia_y`GU7(hmIQfd6;0y_K2+X#R_ z6!b=SjX_Sm2BCs781EiCnwYAt0Wv;`+qE zM++Qkm6`V^0D6#fl3~S_=q~q{DSk4FW z1!xz$>PjzR;|bj+^xuB|o}D`^u~}4Y5v~)0Z^Jr9sP?X>egq86ledjp+a{r)|vqE&%PEs}*+9WYj#nyc1aO}m6ISU^BP#LVuy zr^cvSxYV4HN;rqXre1&F%L$C{Z<0F&)v=+lVZ|+3Hp_uOSQUB3zVdC!LVq`>r4w|; zk_cSS-g=EpEmT(1Zq*&0BBvBalZvzr$ z9bSWWZ;he}sqi0+2^Z~|U)0m&(eaX#S@u=G2|^DFm;c-okWf+Ju>w$(C_Hmo)mOV3 z1jI(9h1hd7U$x6am*885l#vGdj5@TT1SkAJ@7AH~E*D{|WAFY?x}fZDbir0ixTar5 zZPt*;yR8){JV*f^SW1F{DdeGZFfhA*K4q$9lgS&ZBKN*5W@@h{@A(XCsoH3t^7;A9 z?x_8>Zk_i|ae|ErA^ow1-qCS`bm&rH)^((ehTvmzuNkh*bhie|(zV`VyaoL5vYuY^ zH+oZL*;?9R@65gT7Ay2j%)6p!{-U^HArqM`I;KbyJY-97`N8xw`xG;Tye29lqIxaT z?Bm;EqYQy~ki8pC1tJbYH^Lq#GM0B{tF*dD%hQGbcMicR_y3neK;a)8O`wY#5I4EV z_kO+<_8pp}X^xV)w&nf%*yz~;4tH1DBI5@BzKF575 zco82LSE8MJYgC80b92KYU#$F5EvDl$j+Kq#H`QA6$9SC1lw67NOn+{`vM_=0QG@%I z>)yIq?rM2&Os3Mavrp{R6q84-^if{jkeAI(QefMXwLirg{OFu%V}sH*H9BLBN-Y@< zla$2JVX}u_A_bHNli@<7GaWl~%8|;y#~e>WOJY>dJ=B_j^V3sJkZI(2A$Yd4ww9&c z<3yp5E4!f_W7s*~D1AFyLqRQE6WCJ^UaI*AdqX)-4IwO6Qptww@Avnw9S9EV0prKA z+>u17^Fy_)_bXMS{vQbez#8&!!z4g>DY5-^pMEc5zkwVTY;ZyIjYgVj>few4KdFcR zBXXhw@yu@CElsptmHqNZ#-3r$a^ckWM4GTbEmytOD>W9ore$bCPPjky6aV=))M5d0 zL-jM_Md5hDE*DEw(_@Hwd(nBixx2xy&sueE-&Dz$$(0prZa;o!nHNt(0{=*+>`=_2 z76k$%(wvS89gMDawSqK2>L$MuRcg6^+%;?9fJjJ|>U}lL+Y~C*DTb25Rgre*v=3{2 za-8A(I4x^aM_)>sNZY!ezZXc7yj*$P#>ag?%^Ba(k%6ygFR6GA_HUTt7b}wfn)s(^ zX_%%8b&>!fB%X5h)k+(6)@E?|ZNBSm?h{lS{Vl=ZM5eLVG~z)(RXo;E+kF!86!{yq zIA3)6f8K##FU@`B!@RUSRfBa+(ta$U(v%Q{X)~G4=dwNUeQ*RfKx z<}?^xjEqDd843zY+7IqgOkA9x&6c~^gHruBq>3Y(lK8=;UsUHS=J;##lU70XM(ibN zx(`pERW38I;noMDP~`?vLg?I?0eu-G&GQs zk+;+4GJX8 z$J5I#jj?3b6T{vp4Y_6s5GKN+=67>*1K837BM>xvIwn?XeYambooh5sB6E4XA&Z5F z$!7oUH6pskBA63#zO2=-~pjMkbJ0L3m};BmPp`y=5A8Vn`VC0gc|GMNz>cKBMY@9p&`e4DQiNp;?36_t>9GsmMn zkPsPKotH=YC<$z@JLTQ}qn9VW^3L@@waxXRsk!-~xNKKlEeDX!nd;5Xj(zp55}AyaHqc7F z;S@Qm<>l{h+$%GXTS|(s+Hf>x3uS9KZOe(Gh~4Af4g1y0m-$<{&<|P2gCTt<4B)bA zgJI$0K(|IlOzcBbuPdSf-hKwN#im&M?^Tp{m)G%mAmDlo*z&iR+ZCryj*jF{a$(-w zlNY9T?Eox69}eDx8_c&^YK_{8&?f~YV3Vl9g{clEv5H;kF*@1J$Dj%KDVk>!6~rmw>fPkjC0(X+l__g}b>Mk} zm`0J{P}N?V;tw3hD4?)xbi_`uTdF47Qm#|fD($^I!G|clB4bZQuW$n+uA%9zIzcMJ zgmkp|66k1&+WPq~S6X};BjgjBJ%(`g7BKZL@h@WqcwE;Pe0|<* zV81)Py|5|X^@8bO;mPzYn{9L;;AsW>)_gdFMxA}tC=jr{QJNKCxBs@>`vp|q6@Oyi zrFfa0OB6~%=XH3{Ds2l??hR|h zBy39yDgZK$sH7wuGIGpKskB(0@hVWV3EV+IYya zqvPXMIwSN5zMjwPlKsGlkq>+qAei|PNf5WKpP%23_FJ~+3^71ZGdNvlGEE&E9xfj} zNG)&H6ZE&i8K@ii@z@tXpP+IOi^Uwuo=jPu;ZpICk;QdkZ!g#5VPfWOlC#UX2Pbkt z7S;>8t-Y5<%6wwONR_Zfc+lY&lI&OGa<)4ZqCu$pUS|BkFf)(Yg9U?Odv(4q0QO%p zR#pL2&hCkXrisvz>)(Lq~~vcjtnhHwXz!uXzb!67s*Z1>yzo zbLwCq-yJMojB{R8<$wV=`XpjVcKs_&>rLNQY3m(5*NujUTU6x5m>Aj*0UZ7jho!;k zg+tUrMzzLs+9DlW4S7){N$$J0U8v((0|Gjyu`;a$Wq~tP|=@I5e_5f3e{0Q1-CrBcGil zipbcLA;HRg!yGzR(+$KAM`0yF34T5mK4#oFPazjzOT8Put=_t%~O zJ4;}J{V$fla@tEwV$j?tf4|DYiP5Ogopa8)jO9&8@0l~IpHoH0Fij3=3 zI7xS4$!Q=EKau`4-}6|!X&sh2#tmQxp;856a6vmI8Y8Um)qCjou^MGJ3C+M z&|+g_11p>z2{+huXNa))`+!5qU$WlXodeUWs~kfy8)%7a-eN?SZ9jewy$%lUz7DmBTj`YU?v|2K^?_7^{R zAk#UoYhLGh{>S)@p}YkumudpHKa-R4)j;zIpr(Z z)Hh!}i}o9CV8V|pIA-|JKFfrwq|XQjIBd>d7||kwMZ6?9aY#uBWUt378!Q>Y zfb3rlEZW@gk0w?F5$5{scqdeybI34=LP?J1l)v0Yu!PFtrwqG%$TB>R^C_0@rm zh|ypItpmHb1PLMH><0q_W>(J+2C$T79ascue2!l?r_}`Cn2Jz;RQ1(phbJ0*Mvf?| zgb4^a`STm`6hcl@9PvIpNn#$`RiQuD@B1Ba2obcuKi-*hoAVI#W7w<`?XvIpwPtR6 za7$Sm#1x7HsfypnJpQBrhL4G&I3VO)oi%}_Z{NPfdaJVISG@H-v!s?m<+;vBTexV- z`?H4_rR2R;$38fZO3Oqzi-uEJ87{u^ym(s(94i_$)Ti{&V(pm?h0Hh-M%QXd55+hX z5104*_LGlOhB$VNUre|6oAxj7KX)ALR(x3?ue&cT5?A=pAiW`0KmISVpbh2vZ?OO+ z?zdQw5Qo7JkMe&k79f;=FVNt9G-&kIjq0f?Z}n&3KXdqD6#M8SQd`4O8{sxnbra?! zVW}#G5LW>f5mC?B$*NooJm99s`sd0Xbwoe}fWMVfd(>Xj^1}m0quHaEGNOpnmP}>Q z5@PefkMiX1)@RDc?)uyj9%`j8aWJj&y7W|7f*cW=`7etYjx;Q0I;PWVuz4%m7D&un z%D^LLMv-tZ?vPM@w#s-91}AXaxV-g>ctf~~yiQJG(|3#`6e?b06-oplmF#VaCpfTo$O$I5ND>Q5q7j{7>R8XuE9{6HI2j&(<+CX{gKViS0b|5w*K|BZ%Kowx%ksDtVS%gzfn8h`j36 z@}xu8Wu{Xe=vLT_H7l(yHLbi**N5llv?y`DX9fCstRLFv(EPej|BV2M4mNvpYvr^G*7R%&Qjrn!-LjLjyit!scs!j)8V z9?B-7Rln1xCN74+q!T_f+H4EzJ&x0n$n!Z_KLyLnX*W__DM2HQ)Cr zCnzJZ9iwWp!|k9-n}I?YP;7nm|_CzBc+T)(yS7NLSz zW2VY3tXxjw_wayT0daS(-gplJd0Xw>f%hE}>nF_+jINy6A^;bvUgp($jl{8SatPg; zg$tWZF1E;n9y&Z-DVc9O^*ALDiGW0WH3L;PipQ&5amQo`jReJIaXO4N%7ijPI{tqg z_9INUtH@E9mX$n)ZR@RK%X3Z&v~XgP`Q`Kf|8?5m?Ir(*%^%HPa6b-VfBK%>~)y;23={r{?lDn2?TWHC(%|Pz4 z!XsIcPzz6`mCj}>^SG6KwwiIjFfbnAY&`5Jnnmv@a;I2EJw4sb{w8-Yo$bEw&(F5+ zUlCBg%0;Hw1m_OB>T}d>2^#YXNM;cH{U{wGhovGp_xLzzA(efpf{`%jW}$XpLLvNR zO5JtbI#ZnuFBU`^?0BZ(__>|eXWsP2bDGICa+sy%RnLDA|MphYU*qfe3NRF_`ApsQ zmw*1_0e5e34np(?B+8+RBqXs##W#2&fPmdXF+QBQ+98FjhD7`cLb*v2^`WmYc103oQY}JIrylu zXHP!S0pEy|4NVc=l{dMN$EMeZ&-xRCgOSy9^ML6PFPzkF(Vm6O%>dV1RY`AuT|@_J zHOua>pIymbJMrUchJU!$f1CTw(MEoo`^{v4xqlxNzTcbsEpz@f_t!vS{GXfq#elb8 z;#tWb*8Uy5IGPZ>0{1Uw3Mv9;3k!jU{I)Zk>5tH57N8{QO_y~@BHKwiX}1>bD+=Gg zSyOx^*O7}OQ2nA_OT)weX6fyVk|QU9t=U?^0~c5(R9o3SITLZHfUFGIaiVF{M{GW< z27C+bjr}F_fR8Q-F?m&_>`;PyGayWFP%!EYB#>BN^yNQA2rbyv)|lcZ4JY)?1A~=*BQ>YL<~mfb#ei*b@)Gtq zvL`N(UZ{ame|dkhoO4Etn)=zEM{LQDX4GT6(JF!aVj)Z}1dNWO4k+ah5Yyojokj`^%;>6-q9pa3-DvJIdgo}w& zVm?$4G;7(s9UdOm>iZ&!R!y0jPTpCl#Z(ch?&0ys9z8U5XWT9Wn2XnBkB8s<+WUi< zrla`hTG9xEDBICbJ6|mNi5TbG{(FmmEkB~kj+JdKB0in$>;8Vi%oE1HC)#&Kh%Kjx zGm)$lUAbfUBKt@r`3nAn6;AK$ARo%dE-NbEhpFH)f~Oeqd!LWPpr~A3kWKjfVMv%Wu)O9gCkH1#RspSK`^`F^(s*-yxBi3nfe-CTYhrJ{gO76fI6*JWC+6{p z##Z`7T1F*jC%swpO#Ve8p;8YHF4aee8&q>HX(WFTY@-H(gf}AuxAC{1UR_;jfMZp@ zQKJB_c}iLu*{tu#N9n+nMg|J`yuaVtuQ%Gl@>(p-Fv_0NHcatUBcAFMA(HyQ?-!7O z&4z^FM!3e*@iY7AG+DjTe~{xp4Ey7VlcL*T>QP3%KVz1L^#{X}@0&v_RuiR(RF6#w zKmLbLm6seyF(k7vkouM*8oK3n1MaQwWC9g0IXy3QOnoAU*XX$^=Zm9R8co~PL*7kE zf{101rT-u|H+N{Zc3~Khv!Q7E&-@z?MssnjMGzrp%~FN0esp^R0`iA$8oRiPs9TMREo5mJBvM6)u$f z`9s=!X&4$~Rl;2*ui4BE6f!~c)==pMIq}*6?HhET#5%XV;vWTKA%awkz{8>BdQp5K(qs8B#-T8Ym#Kb2>Pu{xV+C-Mu8mfb=aPO>&YH4)7#gVGbnl& z4Tgxait*Ql+jd!C>$t_Zy0IaPg8xdDNvAX`!N@D__(gLg4;acM^(3(6F{c}B0t+7a zWt=$p?mhVv8R9-(XLHq?F{&bYAldr~urlO>P?%Yfj(s^`mPLPRdvhh4s9XtV79hw) z>I26L%k7?0(hG@^`I^QMpfFbMP{jbO&8 z`&1ynZNHC-XsPFTITY^*J>2(4tL({2Bue-zzy410=1=TbNx;8b@-<(`{F%W=-HQ3_ zgQnyco2_0J+oSJ*rl5$=W|Sj(XV7XdvC`2f29DKSCnPk5S|}gER7VZqw5wZN-4pjq zs_Q4CibB0o1OCgs>U3w4$k5QxOEhmK@JOfP<<;r9Ioa~?`Z@L9Vt%)%K}loGwKlbRc+l*EhWypw!U% zbentIlepI_(f7-9{LFP@^NZTLzhwbVJfD9e1w|iZ4KmDoZ?n$NIi=C`2tR>u!)zAf z>Kxn_9yBsFGRPiI(%ITgvx|M`&O9^rGVYVy(x6h#&x%)>8S$iBE++!xj*cwx$`d>= zY#9!!^jd|~(lVu*u-~-2R^PwP*T{=_Q^+as=5y-nA}Z~WtJu$%9D$?$i$vTDzm$! z$OAIPpUl^rK8{&8TDv$nCqJK_Q7sD=loIvl&*!PKeE`b9(a)cmb~9O9z}-FC;Hlwt zcjJyqN)3z>HT$ClF*x(NNZKSGp6D1aipk~^60aqcup6SD6Y`ajPl(rJ*35hVRs}wS ztVNCg0u_jyj7 zF0X#>(Yd?x67e7LL+e6`H)4o*B=k-c>nc(R0@%9a0wiTu*$fVK0~KHr*XHgP#)hiq z^?@t|0;#eg6|5R`6{=YPGmczfq#8b2^X1qI*nxhtzrU|-x8y!qIA&<`a7ucsv!Hv? z{+7l6+P^Oe#C>3Z^sW1#u<;5TF_qsdoIlv%{N@X)R&@1$^94Rgl?3nmbf8IvctK!~ z74VKQic0s71eDCi{?-dtDF0nA&@8S6dI26kg~P?cOf^0nf{qr7G(*TH|HqEiT)gf5 zwIEiIlz6|o-5ak^2n5~bV(2@wZZjC&-L>#Yh2z@0M0=uFB+aLmBwa5>fNzyIqqiWz z>VVC7xM^X%Jo(}5`y;AGuhTVrwO?X^oa9@PEF<~^J?^Ej2ranem^MicoQtm}1JXYh zcjEeb3BSA=57m6p!l~32TWpY;>~egR+f!2SL?p;;mo6B({nIzZ5vi^MW=~t2z2MzI ziMr6`=C@gNW}<)U1j~hHovbeVv;5<_U#y_GZAPO&VsWlqywt#maaqmg_smcb;!&u{ zzzW?uQh#=1JN2WJ+}DY$nC~&}#m`>0<|7bt-`t0BH({LqY_;)Z81~f=Jl3Mw3`0YL`G8}&4;=9o!@Ezi5^HJMhHryGq-OEGE{D8-Ag+Yi8q&cG zo}4=oon*uVTDO-w7+<$zb=mPC6I>wQZT{tfo%4GwB7$n0vH0T*1RqG_{eY6z(*TEo z*Y`HEC)$d3CxZ<$+!GL5jM!ZnZ^?*kG~2h6OD2gix=KOqDe)PiE69Uy!{Y}{0{1jw zwGH~7YBG;xxG&U3;FAF9K2moslOlQuDjMMteFEhZj#B-~fFpX2OJvDO`#NLjShPF-3t?~y!e5oph(;N(dlIJ%!t&^ywFvnsnlk+=;LPV;$ zwgPcN>(s^f*xkp93q%c_2AAhUdp@UQyh<&8uc4q|j^N`d%bdPna@#=UXroxp#Z362 zt!HbV@_-#B^?LW4{ZOXKi;%Eu{aWbL4Z}BOk59g$tbkh)!rk<$XE`^Tid4nf4veeO zV2c=(ghKGyN`I7vA?&kT4nVLCBRY;JjG4mKTHBcbj#mXe#H1>d8Tfk2&U&s6O{qn* zn4qQ71Pj)`@*N(e$B(_YXz_As9rmH^P*(dF*Bzb}qokS7ykwjH8HBv{TuZmh)f$Dd zL9v0!NhN-}R{*F9+dbZ3Ve(sA!pk!6j%KdT1`kmrC^HJ0KiNC~tk(|=RyaBVX$Tv` zMo^#jiI$d@Fd_Q=`^;juqGO@9nVC?0Kd~GGV2Iz0W!mU;<~|OEbIi|EuUf!D=Y5)k|qmOwwT;X*C!9 zv9bTIg5ky+T3VU(eMyyU(p@q9Xy7-(A+`}ce74!D@dpE&o0o8|qx+C8`~iK}tvzz9 zl=Sea4(ljxy#^)-S>omF{2_=#%pOQRFsCxvog4}YChX_n196YNw561k+_jf+Oi3Z^ z40c>_6=S2L9z}fgb>GK7%WVYGLp@c~x!{%r{l%TK&SBUX7^-QQiB|y+X{jCL%i~UW zcqc9RUD213;(5`C-w>7xmf1k@`*nX8y(?aKGckl>LP0cb@{4k~89Y_SmNYnDNXR~H zftEp`6~t7^I#Ot95s*dcfE|iwps1ptQ2gSzTZ;#6ae2baPm3le;LTp1EaF4Ef7o=x}axc@a)s5<|&z|ePc50&!Xo1yNG+Lh86!_!2bT338 z!vG-y*OMT4BMa^sqLh9Kg_~Zwi0~8~;p@;eHb3UpV@;IxN(zlW=!wjcJQfrM3bQg1 zt9pmm8)ZhD-vub13%Ha)rasJ!lq#(Y$)o&Q0|@TTx9{qg3*>MikyhPGsv7?0Y}*-HW@BvTgyqs8O7QmJako#Y(I_=+!g?No_P9+cW>FE+`{p!vVnOcw{bLw zxdF`lQO6i#EaG0g94^v+h*p~1UNaNFae8g)O%lTpoHXpxhEur`*&hCN7CksY(AXm( z1Lo_~oed4G;<(-ViNu}65k!L!v0O~p8)n10u-2c?g)Lpa?4Ob>eS~E&WQjGHo$Xew z#b48kHmxWoFLd9|n|&Wr@Sx!)+~Og62Z6OE_HU?RnelgEQOKgXh$t-EQdDaof>VZJ zn<0n3U|VPU(mQxsbY4BoE?g!VtwPNu56+eh%R#c)y`DHcd{(6h%zrcH{UC#r;gFfLN~POfXTuQ>ZZ?))7?L&{Rry7n1Q8iT7R9 zqulGU!*Qa5UT(G(xvIdz-2$)9zI;N*%z+(==m+pzoQ?@{XiZ(DR7+ zIzt+Bc6IfcbQtO~h?m1)&!k|?=>p~_!ME%JX|ymRe$&!xtyRIWj;2} z%RaPXBKMJ6;YYivoG{`juFQZz!LVENU#zHWs!LG(c#*;AB6dlYB;6 zyu&v`escXMNWu7gfus{pxaw5AE+>UgX_}_HYIe$*SM|ja5~@W}LRct&=YYc~9y945 z>Pr-4#tY&Ir52b`4o>Uc>mBArzKt7`KJ*k6Vo3n2mhV2Tau+@=S9rbH@bV0=WQ-ir z(qi)rOh(V=U3c7?r;+;N`6?n&i?tS=KvML8t$xJJCu>9G3QOCY@P))QF1_b`?(n?q zKv&r-2fB(=DfiGXa^(JdZqEvxcqUP(gP|RYU1HrEZ)Nt^Hx(Fg8(ONOjZw> z4@fB~E*uMif`=L6H;LbM%NFsOxMRP;5g&^{MkjYh3bO)Zu=7$w=QcA48GeN}wopRD z*+2!}ExF#PGf-9%*lB6gW5sDqUOt<##xTp+Z!aAtW?9LPFea+4Gsqq3+1rei)b(en zG(}j8J~2s!JvwgqS}NHL@)fgP_ef)gLx!efXg2MokG`m=-yTic)r--u zwYvolH`@y2Q+|WqtnBI$5TP+eiUdnsoGR9ofm)>h6L!X>`6#5_6xH=eVS06x=hgFz zC5bo98nDWEe_PZZLxa7*(;e)Z2T$obfTPTow0LD8=@ScOtK+rwo`_v(-pF6V45L$S z?B`OuE@@!tao>Bx&Bbgd_3QaeUxcl##Y&-dE4(k=O#0&~^t%7_yDxh#e*Oz# z^{P%~bE_`+0*R0miJqJhG3hUfxe-*3n6dbuMTb&nwUP~=L#iZIbT|x;7c9O*GQ*g5#TWFa41X+LTaNEeB)o@{*~oat5yH>!hzF~%fcls$$F2>^&7lq3);8W z6Yqnh6%?GHA`4CG0~wBm&z{*)z7zYJEfc;X-qzVEcv&>+I4(Zr^GyAq{oCU9SbTF zgQrXVOTMY*gD#WccORIBzLAlDF0%Lj39Os0d_>O^T|(`zNC zTIs`um`D_o;Xi55-6-0cdVGpsY+6QBSz^#t6V75`du%sYe*7NqONC0ipQpRCZYquJ zzu*%0&!IoJ9Y?tSyUa38Jn3VQjXP!t=FD1;?-gsRjy3F1Nr^!uChA%fYYml~6oV}n@=Z8lMC z*`W3fMZd933ERhQ+;*K9t<*x%(QDQV zos?6cCXhP+!ab!b{V?MrE-Rs-*+#-GBb3@N~1DMpKLVB0IX0*J#JZT~hpdWse?LY33-g^8(H$3FdlXVCg zH}Jk zFV7anYPaS6fA2W%KjVoIKU+zkk}i2+a8GqHL0^2+TdmZgI}y+4qcZ;9$6+oWW4s!g z{nX#Xn$wlF5|A_IAM=JqKM9LJw~SD$7o3v;sbNpdkMj-YXd^zncM@NWmo)(iC7?pn z7yuiO-OBTUB_9+s1@G5nHyH{u{D<(fj52VUIZTiR5hFS_@TY^fG2XQQK@q)6cnwn@ zX7j%h1OGw(%t{~vvwb*-(S8IHs_Jz>)df0{uoubRG_IMBD)N7jZRvM^K|$!P({S-@ z{8)bnjq#TvpZtHw0~H&@{9nTZX|tU?s%h-&Q@Xr9W{Z2-D^%4nS>uQevU5~)C;=0y zSM%c~h?>O(W9y<6->bb!>*+7unoZvP<`=6Hoe`uUvn)Ncrba`i5v06~)E_iSr+Xjq z15)&j%|xj*s1w#$nQk-V#YvR|4VKZL$fe*qUT!{N7XCTOF5oMY^t{>{JJZ0Wk zY{mzmsbI4pFD-j?RFhHdYYZ5UDs^{v+cc~(FfhCWk)f(!zn|U1rq=ulV;Brh;%?cu zNAePe|4J?!AQx~U0R1Cxt{fd5)gV5;E{I>VXYgpoV30OZ0D zgf%o(VQGDRewPhE`r(-ugBc*SI^V;wci()$SM=?S1PmZx_`nQ2edeoc<9SvPy;KJ{ zHcE-!)ZU(xSg5NF}1&Jj?HZ)fzik<}SKqYvl63I{NWr@ugq$Mx@B)r~-mreN;>qT&>)}httzCx%>beHVMN@a)5IaS6%?-t81G#LzYTgazO->bu(YL?8RSvJkGF`=; zxPAoCBfNutyk@hmB~WikOgSD};_>eaqqd`CB{?R6f`|2g%`MYvd@Ujc>f5>M9RC$P z8s8_U(9T%VosQHWzDJLMi3ajN2bY~4EoC(S_X?vlV=M(k)e6edkAQN1UgV3NKG%|Y z)t}Ij6|TrK4XG;-@^XH#Dwh?v{GC@;WZo-?Bo!F{CvxO3*av3Atl zfqQ78kSv(u1kMzOx&>sAAYM|i9Z|h#;!h>>$f%~$F8y%|(4mdzY&xYRyx0%bHQtNI zj8?uZI;a0d8?6d6gdFnq#Iy9?1Y$z*e+C1s0fbsOr~!f(gK2YPZC3bu zljVVQJmAQpor?#>`lmR^xV*$h%MT5GeJ@jX&&?SYVf_(o)@=7zuvy)`p*K)|^rNyq z&inYTjZKV=b84DY-sxZZr6+jX%T?bKblKBDAm5tZ<;J`59J{)UD5W1+0+wNK+B4el2=7i4@wpl^*+<*nAL5yrI&N+b5$r&qI{gsA=IW z`aBy=V%uViM+?<2*pNX;DPDp)&XBWGc(Mx;=ag=BW&!#mrz<$SVLIDYlPlRt2L(Chs+n#Y@WiP3RI&X4Sk znlx)&bySS`a;qNxM~!lE?euP{9{mSnNU0BEDh?tn^y??6{olj53KX!SKGIX9=4bc? zb8U%I$$g??44|TyR(s+_hsCYQ;4hXxjP#mJK~C;`LwI6px6zsy@ggAQSmuBopWZ0ZvUAz2)Dfh z`Y%FWTiHN5{bK$67Ols&(W;Cf0RP@-wV~2}|Drd(Wbs%nTV(z1UA~%Gm=Hv0F_%~> zDq9Oq+ZuTJpF%n}n`+9RHJOmA)w?OD6?0jApCn)_rep8+KiQc`2ChtWOLbT}Chcp5 z_RuSK|Ey1>Y!@vKGgYFAs2~-OMLObo0Gq&mae)n!u*}OJWc@4AI-8VyetAZ=-)ucH z5%%04y^9=D$OT|k?NWnqW^>Qc&`7}DDiK$g%x*Zrp|5T0NPz~z2TsdUHMp|| zw>>X!5wi|1du&H@^@pR?a__&wKHe;;t|QvB$wDBc&;-b5kG9O|_7tLKl(6sZMLm_) z%~TJvSGxCOgZHk4lL9v5%*1MMt{`OXF$@NnDL;Rs= zqyPYRkYKs*ttG5GY0YS?)%^QyWk?$+^qnByrFLO-2$~D*l!$qQ4DpF#!-TN6C`*av ze~HE=ElHlldM)2p>w7L9pQxg6^odkZEa9hJZkgpx*G3%AYHatq`ttrtuQr?2^Sf(n z0Ai_PX>z|&+E||pO~^#4x2VlVs*a2t7gL%WV}aYTI^E+%m!9h`cg#uA`Y4NZ9Xb!{<-h)3?9j)2EZNfFe}Pl8-5K( zc|8M?>#YywecaCAo)N;vbi$!5BMu}cUhNVgEMu}hqfv-8X-t20nG|{1Dy<0`@WE9CkMK)apS9-Hyp*l^>J!@{1m#ay*-H~;E=iDCGYTe@jl)@9TUf58h3kl zb03oS3rN{lXlyxBa0&Z#LDO1Ev3F{%FQmDpop`SWtzHi6#dv5)$oiw-v2Pv^L4{Dq zvSEl8n>yY2U0-^F%wTSQ@&vt=ce+Pq#ab1|<#spLC+1AlhMeD_Z@2*C(#xq7)E?dA ztHwInS=Q~^yj9o~Q3)&_T2^oQ*)qBzPbv=0WqpJkg9NU*j+BxwEzIRl(iYlMhgSc# zwl>DEZF5%07DeyhFOdX(>$>J$veM1zEoTh$pfis%=F9=ntKM9lq}}l>f~tspKH^B& z(J1(~+6)F;3*hcsMnrRNisPcCipnYGGSSWI2L?(|;F;3$hukXNYYLAK;;} zvkbqSOZk9SYbm;<#kO}-UhVWsR2+`WI1%zN5}4#lR3H_uPYSmXkk6VvFbf3r3t3eX z0m*pFM=iF*Qh11eCp1ztQa#CkB2GrMTx$hI2tB0D6pS=cce=AZwcZER))=6+3W@(# zTPcMn9uQ_15;$&rAzQ~6SWg{qoy~nJnXFYb{!Oj^andpMuT!;{mlS}$P~2(2gXbRl z;@M6^W_~G~el#>zL~sxro2Lnv`!agpAOfYwCU4Ur03k&8hdK&STeU$zT;vD09@`3w z?qv(-L3#O??|GMJLk${30P~eHwy&nQZ&;o5Vf#2IqKp76@npLgvxQ?jpaX^m!Qu4m zAb(c9>9K)MKwuz!FR`4_&Io*k%9rj@lPFHT&4wODY}&_d2V3 zimG=t93~t08*i2GP)lWJGlz`h3Us|XAqe=U(qd%_`}=7#CReVHw1L8!eXp=O`gSCw zJhr67STl-MuXTJpFx`^psJ|8ws|L4dXUXNfCzb!c>ccNAjM5D$N3Rn99Pu6sOY^DX zN*CS6?}C%t7$D{QzMmDHudQa?g5}M<`MZJSY*&1g~J*Yi@~;zLiF3M;ErqF=fNDt zF&r0b%MW4Ii;LeA(6ahzfb-nSotAe)%YtVL zL?ddj&D#i$G|c?lYH1zjR|Ro&EU*39awJuMzAa4!JJJ3%me$zTUV;bzsq(N10`+}( z9HW-)F`8XF5tbv$R71f7y3q*~HqGT6dpdz>9@(+_H>OxU*Ye@*cyrZ%r15&BAm&U$ z(|*gHJE8E6>*W9FGUm4UeSpNT+NJ0$|2C(cIi?nS-lWnB<9dr zppuH8lr!Y76SE_mU`7K^u51dz=0aJQS90Vv;!ZoCRqhN)Afuvcmg_)!#34A!f(S1w zNaUoJR~L30tT+iSB&IKgUlOk6D#)Es%)}J7r=4dOkxS5`j~=9nBuh-{x>ac(9#Nkh83AyGz^+nb$-2Cj=vZ|1NmCYy-0`KC*{ zC(d-|pCVBso`7&xe|LPF$sgvG`s(!cw=LX=*kvFfjNm3m2D**D>bPTHcBX2N!gyiW zMz$z=SKZ~(*x0q^MXIQ88WN|iVT4~F1;UcVTbAmX%8K`&v1ygGYrF^cmB`3yJu5LL z{5&LYZw=MWYX0ORz<};Y7b0+1C<&kS{)it$z<6^M0F!3vF<|o*cANnc8!(2lt{O~? z5Q>ioq+5sjpcXq4?Me_=kt5{k*{d zj;6VgCbC;>zY1lunC7;W?ktsfNCz5e7|ZV)X%;;-@=YTnJ(I0h1KAy!3}%Z>b50dt zsy5wXNi`6LP{@@*oV{ZEdp666k@0soBw8^-2wMU8hahOZjEt*u;dEPhDl;s|sY;kVhoYhyr1y-51+;eIY z3oY=RL9$W6PRMxyJwdc*1cR-=2<@+kTI>&Q840k3LE3oAd~fIca{Ia2A{-{-=ZfIV zpJCHk7m}s$#h}f^0`j007exx0_6eX1)}OcSE3_CIvoQin{hb-XQv`-x1z0Goa)+lW zYp6%x$>hjj6+o+y?=#01GgzZHfdBqlhWZV79OEtmPd;$(@V!CazX6%Xp8D3S+2n2J zLub&>(AvC-PayrC#j&ds;InBRP&^kCb5c$d>UX%2FiK#Z+w5f%&C8Mwb!ebk&1_aq zhK_o6pMtNPbVTTL{#4a9-D}&H);$dS57*57Aob1+7Ni992%7m?u;@L$~L|VVWDs++xvn(8fv*l?h6Z|>&$yE zZ;P-q*WF8H8VK7wkdUWkCUY_*f<9l1r&~SB0$3%(=3((o`Zp~mv_%-wM2(Ro`YEH1xIt5f4_8Ku`KTFfjszZBT|kmK^8F)l>7w>kAL2%mo>s;F=XBYzp%9!WRMZ ztr+9C_WFx_1LAAf73k%O*yzYTRo~fMIS%_Q*Zrx_KJqY!x^h}MpR^{Ubr;{!uy0&c z51-&^y;U)Kk~{hES~qED7-9G8;i6Afnr$OKRkl+_ZWlu_bk07BRDdiEIgplE)5B-w zF6(MNZ1IosXbq^G&n6oZ(Qam+5_DKJ2{3y#D&pb;xo%mbr>3T!|GBP*AUn&pJM5yd zH|((5g;hoUIAMotI^e=hu8R_>r%3$wkU^|Y;=!>|aHJ|n<>nF9-W0a2En5jfhzASg zL4e{DQ|Pa7gSMRhF;T4RFC7lSpoIlnKrx=VLgRPc&4--JFGY!c-4}}Ero6NxVK~3+ zZBfk77}71&Guzn3PuSBfKZF~_FbP!2OOVp#!stXAHO{N%e|Z0vGwZ0XSeJYMVGu%d>m9LYG${4GI_> zVf$*G9qUP{d7ADNL=0w6+!o#$YKXNQ^h8@~n*Jkb?wSrOU%LcZKfMJm-{I+tLW2uW zBUJqwhw`Vj(R%nsg_=lJ!TgTP)Gt_{-mAMPrQ68M|W2uk1Vv+b;&-9qaq}Tn-*XcJj5pH-rUOE@G zy^1o;ruT)to?ffe7G(e>AqVh19|RsB?s0BECq2EESb8;sTC!QC;h^@A zh;KC19v{ACKiCire)q9{!q$K2p=NAf9y*k*dH5dbl1dy2|H-KTBYfuHQjk#4AK)6W z^@khU0IjhT4i5UDzP6TL=cC-b78bRVUI?Tmhh};>f#QeDeJ0tr8&yT8i z!e9PepbI6D3*Mj(8#c`pHYIKOd4l3!cA$UxVJLA5WpI+BRW^zL%a;FNe;t(gqV@eR z%)8j{t^9Rs{gTA~RK|V_WFNpWUFky$^WXC0zbH&CBVj<`xj}Sa06YGP2)?tyM_;0U zYK8LJ%jRUw7*FELRkNijUO~x4{>wl7^A%b&-hkE^499Q6J(^iZsh81;w-N?d+LOA| zci*^>Maxzw^%Q3qC$+zy_1n*G-3VeywB)c{59>2h{kYf|p?_h7VSfm6twysu5eg>j zxPC2r|MDKTsVG?G7+SE~6z2|6@-O`uaENi`eZzKTW@vqL!sH)6sf?s8_#6~lZ5xO4 zg}(59h0aIBVbObiea$Fj!?{)XD(P_(02q;NX3yhXxLFDROgH(*%LobG2NgjHVl0Gc zHgPf=HEgYKnJG0G2)_EBUu!L<-=ovS7`O91`CVc!Wj(!>gmJczFb$cIhuBOW+B&77 z=qq|*H02H|#2iF_43W+LOVB=|7SHafp~>6Gw9y@tT=Ehbze;y1_w2Ce{NJy%CrBNX zhkx{oJfj5U*=dL9+pVL_Iq>Caznq#qR$r}js8DD)0Zb4B*9p7l%a&J##GD}Qjl<}u2dg=y3HYHFsmZLsaqu$z%5I`Wa`Ls zB(8H`VT40JwQQVD@B6sxDYSL{^FfbJDe_vEeiYRUgUk#^^5MNQP8t1NW8dVE@#o@6 z&NXT$bNVg<4*aAB*OhAa+(VT+Go!`2*g>jLpvuOu`BiwG`<)+Hf}e5$LwM0iD}rzr zC|%iKXjTP?$wv{NOJ_Z%)6GLN5PSCF4cb*AsE7Nu*WS@FT)ps07St3!dmd<5fBW&( z4g5Ca>e7<9V#2MIzrViK`43gTyD3Y>k=ICO48~0jJxAT|2GWGH4UE0iAd}MBMnTAH zpbjT*xL9{V9rQsKxNND%a+t|*mo|IuzR3z|lO;c0+Zjv~7JS-pckLWK?^7)}*Fe}> zd9R9qK_WFTFK^R)R8-(-h2@Kou&`C9#ffsWmmdVrUT%NSZ2a04L`6k~W8{Nahsh*x zUw~-J1!Cj_T;V&gadDM_{YrB(Osd?xd^iG^w3k%xi7nWPx*l9T0gF8***oy3L?FT) z`QB7T!t<9e@v{gyKdRkcUtBc34GK~NUFM*Aq~=_;3E8a(yC1`1XdDa8I5*!CdQlAL#9!#<5)uV1F#kz^?Z8xSx9{AM4!@ z)Ir<(7RcS;bct#S<=h50o6ORl3Q!X=fGJZAAX}S2D^z+lR;(MClCpeDE#P9+>-wFw z4X6=KcdyW6%)x9l3y2|VOJ9RP!7JY6ePv7&Xs}nIumw1Y!gn^ULY!)|)8{?0?@rc} zOK{2`O4i&iF5YwXx5N|aqX7j%2g89b2QEH)^tBmSOcfxyti%j*c@b+Xx! z)7KuFkXQ_bu2;M%=DytKs9B&c4e}BBN{kxcd|DVBROL0hu*xN65{MU6lEH$V-yC{` z%UXI(e6{n5B5pt)7?V~%oGUZyoxm*kUgNnP%qySU4Wx?>QYd63(y5X$YIe<=!ZpLO zeX40DcR!nby`Y=)*gVl&S~$w8E70%z#npkZ(wC)cz`%J&2raVyl~(hYgF=-RJn3TE z8b_#mrZQYLS92|TvQ}KpIgZ|bUOiXPb*oWN;yKVr8^+yywQ4Bvc~?-N&W7?=eGAR- zXLI7yEZhM$SMYe#Vp=@N$DgfqQ-%zc7cDwC-K0N3CKyg~-U;(O(K?yD8v0^($<8>@ zgpbX^k8~CtIS5`ON7vgNEl^Ig&ldv`3fbZ-;2Sr*nH}3?87M zK#;`xV37u%UO+Dws%3w4Io;NJhGn4sm&mXtu(9QdYI|L9RF)@cPHmC57 z*G|HE_bLM=X;D|Td9U`5i+nY5=LT{J=Vrswx3}s5A3~D)4w6bOgm2)A5-j@l2*x?; z*bLJ~%S~DXKvbnNZ7dO&m2DV4;B1=wPIfGvTL}eRYCcO%^?ClOHqaLpO1P{mkvy#d zYJxfcNT4gv3nSUIv0hL;( z;6JDe;+@vNR8&;>V=?D}gH;Cs2GO%eSh8W~qBhfCIKXUQTsOC`ax_x6z;R{W<>JWv z{(#&wZ1Di3YZ|||^2O>R_jWiCZ&|u)=}W5U*ZL0@8C7~{$KD&&O}L!)va`XvFzx1v zU~CjYg!<{GY+PtLhn;aD%B*&+E9ezBTvfPHyJoV9 zbT0AFi9J$PZdy z#b-0iiBYubjR7s&i~%cXZW#W==pvXkj#d*SHP_4#_DIn(#QQ=OwcE?*MdB72yO*xS z;+!w$F^Z8N!b9JKPMMjRZ$yI85WDc^64Do(Q*;W3kG;8kTc_K0Kjd6J-l2{!HM)Ug zoo*Iw+axC}i}GtnhozQQQVJ7qqIj{=q0E<`@ukY~@ZDtqaTs3B3rYtU&=T*0M;mgaZYEG-`H;MN8+nriEwq+6y0jY&1)9L zfC_^9blKjU>d^s5LGDUM*>JOQM7(qzsuYq@NN;>4mK5+VtYsO$(k;}A#g%evnpki8 zY|-E2V_0KSp}-NVWeV8N^%0z0(0k#pC1ncDgcjv&frNpvF(MJv&^Hy9J@BCRf#Y>5 z)N>Z3Eu|E`Aj3+N!{dHlPGE5DCrOu?DEInuzaSRf(B$UHbJx71p`}Qruy~C2j6teC z@bFzb+wnpL?HUP7Ju?rD%|Ac7Uk|Duu#{lShi9+tCSBO#C8cOPDvS&H86mQkU8KV? z&%mHsfUIyvJy%PDA3LTpv_uWWiux3g8(5|l$UhPip?jw`qLO4c&a6A@lv`8KMeMjQ zU1}r^Fra~Z4Ct)Xc&PhXWSNWqOc)46bHea4^wAssU!1*lSk>#^?M+H4ASsQcGzds{ zNq46L3QBjU2uLd3Qj*f$-JR0iUDEL0)8%sSXRm#p>%8Y*FD`J--<)@hF}|O1GI;&D z(Nh8&{fsVD$VdFQFx_2&0Drrj7=HZmQ9`!)449jeX#M!;f6{3M+KQx@ zY`UlUQ$c#D11qgMqcsW&ocTv>%vrB8%1W$&|n?P#+%}gx*Wd;?0fp3ssvV~<>E@iJ4te!GY8D^IByNno?RmHYSKWYeBJQ@$ZQ&e#{Ic9X568a<;=CC2R6jbzW#5 zwbNSElu3|<6ZJ~e%TVK_iIO1`1_diz-TB1DDiPP#J%rsM*=H% zI!lixGY!6I`oq$*P1Q z$_3D}hCuben8UJuu5PA>g0@qoa&v@lH&5ZVXfV=(S^} zKlUDmDnLZzKQ|&h_yia8;^gNJ+Dye5=09%F_m2Q8hr*XfuLON#O=S_;H=wzq2OXX1 zx*?&ixsQxq%b59R0N@GpV@V{!G<+aHFoAxPD zGy;bE@vSu%?_k|JLJ|zdu1`mcX{mx)Yad-+cWM#?8F_xFn9^*;?T~ch>B1dW0aFrp z`CD~z(8)1p3i2*O2aOB5ds-*YK_^F2)G4YACr45769hzij4yvlppLkE#~9ey?M7Iq zOQ>*ZIhv(Zr{HvIh51CfP|rPNy$n8#Kds4Hqeg}Vb40QHhcFxu%n~g0Fst)1TkCBg zYs75{SZb51LSIUuJI(B|2s{>tk% za$v!wrROI8Lv3in^b!0BGvB`4C?xRI*ORc7K9*^~k2LjXv1>387|(ryz=)KiF)&lj z`to}=$+v-|g;(mBRot8%kBZX_t3@(5x72-ee-ihqR>= zI?giaAoA1@@`DT5J?(vw@vs-Jo1@h0wXz8>UL%kAMmB6 z5G*k3UFuU5*vZi>9-B{JD<=(~a1sY`g+6kytHHIN8eTl&_H<;ayLL(bdxa3$j`}-AeF*jU zfA5C)v+AA(d1*9p&L)(VdC@4QRoN(4#buxbVH5dFzdU5~mw+DdM8}kMEToj3rugAy z^k<&+_vzqX2(ArgUBF3s?F*c@-6tSAp>j|$AU*N_Z$%9wkfMf>Uqua0pB`JeeEQ#u z8rUF34Q&5gQ9~A_s3Cd%e=BM*g|tYU{%jN#`1z82A3qMGf+JUANE1mLbBL2e4XSoem+qTH4^YZ%c z>~yL#Q5}Cjravm_>3ML zrTG$7rJ=NJWSFqfrbrshO@ERG*VB&~@V$tUw5ne~JW<3C#Z|k&P_wF${-Z!)U|B z0GPC`NJdr8*9zs{%6n2j`{M*()!wrmZ_8hOe*UIcC{?;4RT;V$sra=Uer2jFM}g9! zg6BluZMRuZa%aSVCE8cFwOobJI(VAn4s2xn^QUvDJcebeS7((uCiOUxT>b=N3-w}I zurZ^}?fL|3YM8pU4YK|5?1~pyr0qn*V1dteJxANTIKmXiWYDUUB5@n?1puY3yW8us z_mdVN{Vb4Gt`tUmpqTLDyXlXjupG~O{#iXUGkW*yJx4arJ7KNTH-5S29DpvMp#BtK zzqD#jsgWPf3b*=uwpbx=^OlAi5OC6JMmKT2Bd*j-)CD7uOV1Tisik|8s2 z+*0BM#+p92H5uarjC_ucFp_@vSA+oIM0P4*wTM0q%(rt$>gxh3222$UgUjoag|A=D zJDk2qd)Wb(Pdc8<_6391DP|rZ>iCL>-HEz)41;4h7ut_UPlm#JB9AB+v3?7Bw;^mb zMbDFaqUeUJtW)qO%ij*_r8kVXb&{Z^PnW+xW(RhFFUSpoR*1Tno1TDv>R<>y=5!1o z3F(=2(Rg?=D19-ny(fM8_m|3hOt5LD79H@4qEb9LEaz99>F5PP-MKsf6Dki*m~C_2 zr^Vy0t!!wks6@sSB!S%d9R$lmEIg2C0cb{;)Ag=Qejk3|-1G9CD1wTl&BDftT<12) z30r%6#glK`@9v@`_zKg<>RxeNUaEctH3P%VzAqg0`?5fj#P{_9zp>e8m~+*(+I%M* z%1#$+V^un)zxtWHJ43;@oD!PlB~~~S+G~=bsMc%5Vm-okE`6cg+4pShu?@j>?_Z=z zH_vFc(n~CWT3Z20J;)(^P$(g^9IAa*{aW}@5MS53ldeUCz3K&~=OFkQ1-j!-kD5Gw4 z^vCB`!}zTZN`a!W3}CW8d-g2AX0=O*pMUVlxOSyQPM4q1Hsu6FAEXM$0(D54U`oAj zNQlhy?ncM)=7I&;(s8=+9!j3@@U{EKfG$^pOdrm@cF{vtCHQ- zZe<9{XU#_`*5k_5TbB=A2!e8&L;BGFnOp)1#Iy53pS5PJOjG&(>}4g{qZcRTuj;hI z@6Q)p$KW;^$V7>)@HcoIegvRyg2bipV{yX^%KJe9-_4s#l+#&TY?J+2OFLFK8H>KA zOr7cyyQLO*Vw9ILHO{&NldzY$nfpIX7agvM86c zlIpFeU-yFXMokVE!+z9A?tJSg26E@8!>X5-_2c6v1Hq|LN1Hofu_dT@CoBStjH#2+L?xq zW9L`enR80O6JHx{PZ>@% z)G)91BBp&BsnMU1_5hl>Bd&WpzZIUaMmOdnV1<|@*Qghr3K=m&w{ zkYcm5O~~E1MkuLpPgVmza&A~O9wt8U`|RPdu^JUn%_#Kz%-Zb~&4eqW=LaCo zd?FO7)8H28SRtebfRELiG5XQ(u)(2&XW{)ES}15%Uu$60L1aC(gGjgBam(aXxJ4A* zkuFTr`roU)3sF-Dmk<|M80{YS%4jG%aOO`A- z2q*k0x_P-ztQ8QX)sFh!ofLS*ro4KEY=ksxU*42N{8!sG?zh9l_N%WerD!K8eg33c z2u8B4AJ4|J_&p7IXU8}x8?@k+{9Mf<_hxQn+(S>e!Uo|&3_ef>DRg%Rso^EOCVx~lA z2J9D(sqf!jwLWkoS=36Wl!8E{fSRBsBms(r-;~dt!H_@M1txbU$AVdA z9&KJ|!aMkU(g@LjJs5BJP|c!uA2qnYYn8LBjrFamjb4}w3VH$RH@}28JQmFHM82l( z(!|H_)(_B#vuBIcigTlm0Hsf9GRj;1%&swD_?Q#kI|kK3!&=8YWa<%E6skm+K6gRb91%?W{vxO;90syLV7Ru2QR)T zuhXxl&IT#ba>x<0?I=< z9383UM=`n{34RZklX;3xa>0pw?Ixhl@bp9#jrx2YTn1s;>+1K96n>;ufJSXNFI64Rh(Wf zwN@;20vhDEQsV9G?=5cbDeT+ea?t-W0kg_qyW*ysf@q({$|r}lt;cB~u~?SBI6W#F zgYwhPsv*~FctvfTSI65+OU>SoNTlD6WdHq$7QH~^*;^F_E}9y+R%>i9`@-ltxIwV- zZpGC|QP=97;!S@-&$P=1Ri=iiuxZ!ASAMv(OJD8G_Dp&e>)$`aiR~jXf7KJUDh>Je zdfNp94Tr+k7RKLU9jY!g>J4)m# z9pC3ZQT;kJ@GgqaS1U>XQi1!3Hj{Z}f86Rw3Vrpy7N2{)|ReblufE*7?Wp zw?-CXe`Ko{qGH;mruwhgdlN+?Z{KxsHfdaE`?%n~_F$dH?0s>0kmerXe7-qPMkShNafS*a>a3z1&GJlzxnNg@ zc3RjVErS$jJamw58IS}MTg)7BCit~1-fq6^X!O=aQLZ^klzXQ_CIz~+UmOLdE6n{z z82Y)bWJmYq)H-|@Yf1G#J|bvge1-TE!SiP)!;hZ?#es*lo%eoV#)EgCG1L8T2!z*T zPMg(Om;D&UAlhEqsDFm&|VKr+gdcv4n#UNeEecFG8;_vd7rZMpu5nm!w{sh{}x z*Ui6uMr&#NZq-6}>yinrZO79Sn&Z3a7e=L6ke0dJ6~o$Ty3^(nnk$*?Yu6lPIaOk; zfre(h$ZDZ8D}9xf61oOu{v!l|XWE3z^R7h2T)0ZZc$sv#;W65!MYO)i5$ z_^ELOHN4nGC~DcJrrQTbpbSfPb5vensRq<&d%xs}o{e4lG+v-fVBtAiHO!mbp8)$q zS9;Pf6T?9ppJn;}2RoAY5jxs~8NHn-Iy3ppyK9n>)$SD}A+ICvCcYjPyMY$cBP9@c_x1S5|Q375EjjJLZ|wY1>i-WOYYm;q7avb=WSq37qa#F0ed( zVY6ywfuP0`OSt`hl8?$>Mn-+3ljw2AIX42&1Lp_cYp7jhfAO7wHbKgO4#xdx0aB_R zg|~A_>BFxA4&B&F+l1gCbBVbU}g5n^N!1OgFNU;&F;%ir_TOw zpFnfw+e4$@P1+Vo0%CvwRXn@xyuQo0s+IjjyIypm+PFMEi8%iWB!xUVS9|^=Do%d! z8+6)(0?xl;$i`s9XLHzv7J{iH*xN|N-{Y=;7N)eEuiJJkQm!VQ(rF{K&rgJvXd8qM z^qq?&Rx=%!pr8;78A1IR!$`&j7EqkiRnmcRvo%GCZELJP8YOZEcLJR z5egjhf#1aimU0jy(?~L3bH~q-8c%?X0{k2w3M9yg>}VicEz z-}5--0R$OEF`lK{ZX`FLUxHK1689!U!DBK{%7(9$)NflcRMJsb~sxaWeZ3RFi|17c=VJoO^Y^kw7gqN$jwyHP2k zuz#nHCr&SpBBso#ijPkd(JG5UTB-~%H7aVn#~)G|+i$ZKv6+ocYKu^-JtbXqG&VLY zeJCkI&2aMlj;O+d2p5Hhp_AJE*NqDz&eDi|)heeobBDntt>QAQd>T%Ig3I`VUR`PW z&Fia7sVIY>SWId;P^Y1q%R9(JS)f_!ZD1XbLk1_K^_AWoXvt9M=(riw7miT4M4(UO z2c%DgZ^!G5z1th@cW;Bp9$YY%(E&yV0+8 z5Xp^TGHEm!ZrM#!sV-sQWYYOkVYv|ZW;Lim{4YXhI1XX}d@yW}sVTH_nyB@B4F?S_ z4jngYO!%THY8Xu7Jt8O4^N?ZB z-L(`5*0>+(Orr_9{1r0|@pNzhOijjK%02<1SBEwhWBik$h9U@FqeV z)|yspuJCEFbXbu)SO^5sYxAX;d8GiaO2?=8+;-^wDUAVlHz$Qy6FC3&2>y)zji5nN zmf$A4oVkAiuj1Z>^(o|2hJd(3x_N|vQU?WI_OwKV%9L=Ac7+tO7M7~e;+AOBLL(-q zT=cQo7)YXk($mv}gauvptB)nO`9l1RdoG@T3?XPdX47lXDgBQJaM%VpALCfW`CE$7 zF5uxX&D))vvQX(25DA!!&9Z%ESfkUA>apMK7wuaM*TrH>EmTZS#y}(H{kk{$;3T|& zL?ANxBbA}^C-lQ3=a0whXJ2=xN~xX4Az8}1TZEBj-_&3K3FqV0HxE%jba|OCf!^#c#eTCHz8K1ix^S4vFpUB}dp zYopNT$(DZFqY+?DWDX6ruI}y0!XvL{0byX+2R>*suka0CjhfxVH3|%id43i1MHQY9 zcUvljANv9!bypD{8NpLF3Y%XnH&vQ!g!7-IbeM0>hvZ>#j-{Nv^L(CAvT>AgG6VZe zznV6Z+*0UMowc7&hx^OQ)`2JHQ69JEv|5Gm#sf>@!;Y52TTN?rR!5WK)#IX*1(wQS z569w%e`zgYcYDRYJ%0-eIMg48MT2k(M-WI(Nx|2u2G}T|)gl2jSPzOxllbq{wG#mNr021(ZOU z!835=e5d1i^5c;hN?;iOcJCxw8qCV=MoPr#e(l`WdOu=;E^uDf(pbZ1XJ;>5M1)b5 z29xV)o#BTa$$z8jbvI~9kw>uEq`!-nDOzEz8dGxR6wGC zI)<(bc=Qj@?gXYw-!g%QkicZm`7lVJEY={gim{95piV*}jCh5u;QbF~0>yw}rp83c z#+#DJU#y%reX-(~CmS2Ck8yCsA-#+pzOH8=aFFeKK>$f~c93#k@k7pN9yiv-79Gbd zIXlASV8dIw)&pm7zgJWp?l%{bWdLWFj;3+@;lqdQg+)?wCQ<`F^LfITfRVRUXTPI1 z0;W_WS>MVXpSee$c-*_AJbJ?XVHDIRP~Yf&MLP{#a8psiis?_f=Ij5@AoT<~rzedC z@!RhoO@3`uhgOIIC`uSr#1U1%_H8N)hFJ6aaJ`@Y5v3MnGXZU(48K}3THWq%38{L4^x94#_CHe8f@WTD;F|GF zh-6hv%IJ51dJM(k;nJ0_@A*mjMt?#~0*z+L2QuG|?Dvj}M~XfWZ=^>x3kJODk79VZ zj90B!yCeLs5uUK~wDTY{md<(`+e}}4#q9Bggg6Dyv=(Ih*gePa` z29RHB@WgIDuyQ$rysia`dmQ|VX4xh}19g=E$eOS3#0q#4V`2{3J2&1jVS& zF2vTlTC`XegKt+bG_7yAFfrT*+(Q^VglfC3FWyVVhMipUe<7a#Y$uSj`7I;^DLIQN zo0j-Ut23HGY^T0}Ui?lWnoysn7ge2hblts2xdvuVeF9D*+Fe3IoxzBMvWh%YR0|Zr z>!r0^!rK7nq&POu5B>YZma5j>!iPFj~e%>BJ zd7@fmAe5_HONA-*2%qgm$8{fTyNc^8om${_xDj&OaYm4FY8Ef6gF;t-BrNy61P+8} zD9N9DuGY|~e7POmq(754Di^;NQw`yS{tTUm+XXaCqvr?{wBT_s-6gzm@2|qy^typ& z0S(LU0B^=#61k?x#1DFsljTzQ`%TfGLQ>v*Afxg*0bmR3`-Mw;-^XmEw4l*($C|z(Zz;gdc|L zz%zW1%!Lb`R>_!>;(sU=`T@O=ON@exG7AbGlYDzJSXxb1L%GiY+=8uH9&_+gK3 zF7J3kQNX*fSTRO;WW^#IX|2)kkBRQg8QaBYW}%d9tgVQkE`f9iJ1ctHn`;5G>l3oO zh%In4#tYn@&sn0d*7SKCycqXpdOl#$avS&boXxqrO>M(dUvJ18CV|b`2Crc@s8bX6 zNn9XB*;+TmxM)6lP+QidD^{D|Bqmb3-n90%*~j_DTMQ9yLM-%nQeXSgb6DeP4A=}P zIEsgGt07rgStWvq><~bUfqnY*@ri5b39YJ2a@cwyaDt{Dl)>EI*7jr-=*5?o2wHSTDfk^OJb-r&rE@hZleoH z@s6=8odrJX9A%#~guP+0#~&b zqZTjdTh)ibcV;2OppO^?fN+}O5il?@^AIr|)9Zl~OWPsI*(t>G{9cw*3unuw&8iut z&;b|h5e&F-E1^cc(MggILGv|?w*z1U(TY|2uY?j=fB49?AcY^z_?wqv=(+i#wY8N? z6PUyUO8crWUMn0iJ&1#S{s4#jjdx}cPF1%_mq5`E zc9iN-&h;7LlXPU%Mp=*IaU`_%`Utotc@Ii8K$kTb!k{VbkEaYu=;x!R+!LD*aD}!% zqm{C9w^6H=Fs*jH{RHy&A2NGu4}LBhR6%Tjd}S>gD0R>K&>nE|ZndZ61LB1!mf?h$kE@L!lUoeR}Y2ns+v0D`KI8jsDJ4a6oDC2H}W?n{BBeKa^x zvBW_s!7cCX^h%&p&_n^H*Y>Nyn1~KW7f9?}a02CDrs3wW73ONU6fe;qsyRS0eQkHj zHc?b35xkRFzD*-O@vaU+#Mx*Hg-~)P&Z}Cz~Ft&*d(I{23Z&iLcO{`Ds z%P*evzpowM_sqX0#`^0qi8~<8(<*zca-ey7!5q3xI^<~zrkx!z{AId+<0YVV)p#CvPLI9m1HMxrMh z=CFcwy4rzk?4`Es;u;Q*$S=SMdx9%!7gn1IOXI1QIqOrtaFlSs5wrTLyDOSLmlTe+ z^I#SQF57+9hcpiz&YnGJ3i(%%+>et+>vxR2qk3+$WHmI3ZU*t;=GNEh)YOV=BuDBl zA9#V|$NFHnih1N^lM<|`!h9X^k>5q{#C-fxBSDCOw6WNM$*;?u1>~iFb z*6%(=?X+0JzrJH2Bei)@{wOl;VQt}EM<28w82URM5(xV?`sIHJ zU1krkU)ryUY_;V@wglIzw{mGP!k}=(l{W{hefjdnLZ4e+J_@K^i?Srnt_L%c$NqizF z_G%A1E}g)`VTz9B_Qp*zez|xyRZUMBwOV3^jHV_*Y)eYP#}pof7uYlEh6!6^7;OxH z(?EQgaq6nbnOR`gUK3cr#S^i1(L*Lj9Z8brzD1iA>zmE?C(n@RiAO_q5Jf^(CuaPN2OX>;!?GtsJDZ_M9&zkj?B2__(#>WsF2 zayVSZFKJE6^Otj12I?tbp%J3_ooBex%JPI>-=-GP$t@+F&zaOEe^M$!Synv{jKk<|QNZIV3AsDQUVzxCkcT zXwE+0$}+?rUhJ~~coOSq3D6(NVP}T)3&LUFRVkob>%a%*$dYwK!e5{`B**c)Kd8U= za4aY9@4XS?@)_MGinX#ZI0-0V(yg(3dkkN|K3)HSMa&Du==Lf^h7D9Rec-=6#T;nW za5>xVHN7?V?8iqS+&h>9VAO{rP#Qo==qrNqp~DHlYMX-;KIxM?sORQ62#rhz-F1Ye zw6O#i`C>Ux`ZP&_fD>mg-fAjeI4@%4&Ci*;t~4N|AE_WP3m5o(Djt_UCA#K(qBi5DjWf%Q&OAX#F;UFF z{LU-88<=CrH7=aV1TpD%>B-j@yy^5x$C&gac1_a~=B&k6R<11r{8q*wvu@&U@L?}) z>5K8a`^y+=M?%&&6cLhz4bw`G<9&!hSwIR9#$tR)+`AVNYQ zHV5L;YwYlCKi+HQeh_VlfJ!K1)fYDHU)>P3a)?RL->vIyV%Ckbm zECupsR=kvd*|ER22X8i!#T&}^#RQ{ff+L^7f_L*4q9`v-Q2is<8su`IleY*s~8~{50=W;IR4jTT@>&F@!osygl93Nh`dk1MSTR* zCLIHxN=Fg>$qO22=#@woOfFX!ymx=1?GH)+MB6>6|3eS1esYUjz`>^vTd^y*e}KUK zS2X00`1xyj$anx&`W3I&05)MTvjFx6s;!pSVx3DE7`**Uie4uqZK9)d?M|0q(?1cf z|M_O}8iV!q1f63xU51i&A&04Ym8q@V(l06VRzN2G5SE8Ht-S2}7oo>Y|3j#)oYfvk z9Ba8zQsPf_D=^EF8iB-!HvfUcAF8zxbEd}_B0oGpLb z+l!|)jwkP2@Z*Ciq2U~M5z`Di0Y%**3FDb;eLjd$lRG*(=IY(HkG)sX`cp9ixk)6E zTiuPc+wlwIB}2ZN>gebwu}1qwXVA)s(M;3J?Vp{7$1QJcV;M=NWl0Ubru6eMiRqzC zwj&X9m}^M`$GdxGCIK0%Oa6R+E*->fht>7_pPzo;e9|LHF8V!fDeXyzZT7T z;ya&F-@e2vUHdvjxiFODwe~@S1q=xe)f1@GSY-?BjNci(Fdkfy+^9rQXs}UzCNb}W z<-CFnA95XT;h)4ZO?!Jaqf(}a(v#R4l6EkkHtOBGwYXduVv`qGpu6zxO@I6|H{>B$ zF>Jm6N32A1?&^$XWBTO@?npd4ol1RUj(($e4YIS7)6ht!fTF=74nF@4=78tVsS&nU zEgGQDxDYvgMHD1{Q;GQiRzhRew-yZdoL+7yn@;3g=6KxJsMOfe^nm`g%CO?BXyEzy zbc9?-BID4%1d?eQ2&~=l0k{RUn7BCp+}L+*{~9~zpk(K7Pr_FIEeNlP(1udfo6J{R z^X*}oaWB6A(Xvk`0_HzvTlAs<^PdWD&K?+xoe1b_y?RQNC4G+)j6 zgA1ykm8jiX4>msU#k~m-c|Xc2Aqg~YjIqn8ov%9rm@HLwRh5FMDBL3=?nIzl45%h0 zCXU_S{+*+LO_wGe)3Viyc0Ce^Sy3em95t2mtv}KD$vn-{2O-3_fmgxl;HF_iTtvY9 zl_ZL0rI!1fm36)cVg^JJ?95H6jJ>m13A@g`dCCn{pnqCqapDemcl@}Q5~!lN!-|q z3j;w`3D4VdRlW#$o8-K_pm>gP#gjtbcXF7S>yK)?2Mc}T33k(c5kxc*PI78Mg16fC zDSK9T#yWoGc&Q7kpTGXP&0n*kr1j*awWO5R{s8SOF}wWUjpvTw>bt$!B>gPl=~2Mrar4Bll4ozh(~BAvKmwJ} z4gfD>shxnSLhJtmmJ#P|{d0FJN$>~2-$i~!BO_(c1Wnm0+2I`W9|i5a+MWS;ErZTQ z{jlt>tUS~k9>=@yFMa!i9~Wq&9N*u$hQ&XdQ>SQ$m0NM^4CQPQ-J!C#v$f4+YP?CL zHU)Wp@^GTU`U#z+TBZHjeQ{OvH>7CcVXjw``a-lx5rJ4DG;wfLwoO&mE9$XMmn&h7 z7aYv)S1ME#3Pk56zf<)M7U7JSC$7)=o8Wz1*LHXNKH$I2W(vl3j)i&`$7xOItpjiz zRq=KOORfhS8(Be@7XU*v23JZGq2YO7pFo`kV%@St+!?`G-4WDbHH8%yN;R;-qrG(d z2SorQD|70#3glFV`(zZ{pA*~vYQiMO^9G~wW5RvCH+AhNOcJ5H@8-2M`N(aDzH2|8 z!NZ&C_jQVg9A6qOmv}&5v8uv~*0V=tS(JEiA zW-0zT{PPDsVrd}pp#+4>@zVQfGM9l|7Op(M8g(acq~GU}3`Y=qqzdF&J!J~Cd0|3? z?VJ-=vgxF&8GMsU&()w5O;n)rt^YbQ%MQMV#5pFE{yH?YK_%1S~iu*_`gqrTb^ zvsQx}js4ygYNJZrOi`NQgrki9?~wd1BqWcMb@5NJSp2tG{O zuk0w6dsP}0rO1Mf0raGS#_LJF_r`4q&%>y(Yr?613x~-zkTiT|G7HOgb%V-d%F0Ww z+wDBrA0_ZXFTz3VcV2rS=8epV)#Y_%))&g4lNPk&)04Sh)bG7GPeA=H`1m&4yrD#~ z|IxsqTb^{xfLona^BZHG>f;lcF0;8B5eD85R`t26bcz6~6v=J{&276*ztR0yi{KWX zcU5K4DR0mH(T|^iD6&`p8xxPw&!YrzB2J>`kvp=%r3?=$1eO;mSqts*dM2f%$U_=t^&yS4ra1(b^1VR{m ze$x2lc7$(L94{M#?&L;W?rJ8DWUtvL%wG##oHiiL??@CS!c>^vCsqATw=j$XTEN@l zMA{)kNBd3u{y(JPeU^lOF~6}N{2TMz^>WhP+E3-8rI(*x+ko9KPyz&Fv56~=RtJ(2 z81)18Tx>W6-AD!#ra87&G`NE+9I2FB@YpkrF*EDv^h|q>d zjP1F&IhZ@1>x0ti8%ksS@3Gh#v_6oL~2GciY@maicO1naaL&lF_}8PB`j`Fj7q0N(-LFoA)A z#=Vc`5c<@}$y@i9zivLSdQU;25e2G~)j)Of;xhE*T1=)}n>v6)ItxS5JuWp6wpkL6 z28e5EvX>G|DRm)VB z%z(viy7;?8Vg+jYMfO0p2s5-wVfK3OHB$2D^g zr)*AsUvPOt%BP57RdYUnT|+wFHk3{{6JO?b>8y*ERw^aPEYz$c%Bb(LPnt5E0X%hk zPXt}A#>7+2T~`C+uF=gpUdodfH_ivHOq-1SDJmbu)@;L*wdmA6auYC3E?*>$OCzC^ z3IY|rso`4gp=-Yzqq`I{Mb1I_m&@^JdS^|%iUcodizif+8ea}(`PXV*G)E3criz@; zvRXe%G?3XxTjw@oTdU=sWRY}FhI=o^xpbL6)pO$UY%~(q0sLkI1Wa_VI1%675qQ-R z*9>{I+vSb9wi2|{gf;6*_w|^lFLwq@tTlCxkBBdtMIE~(<0^_iGl9QB5)il__N{sq zufOs8prgyFsAY7LW6Fh4J^RM>)NMi|uj9xj0vXM-fOangpVpIghW#^VA*ddF~v(DL)FMykOSt2~Sp* zcQ?jMvuQzMqTOTWmRZBqt<5r*<2}k8O?o|YcZ3osiBM@{=(ee^my&Nj)> zW9L$m2dVtVuN#jdlU3rv&o|S!$fXHL7Jg;V_F=x`o{V=Nz$rJpQB3P8>6q-`}4}5O&)oZbP~Iw zrr+MaU;mOdSj!NG_bsxl(r|BFSqi%xV&XD_@Z!?eIudE``IfGd51YKl@fgVtSM*bU&9i5=NNZ%qcQ?*+ z^W8MM-lOJK_q=la+}9H|7FCKPN1Y@yw@EME!(u#mT8l;qDCa(Rx~7`Zbd5Dy)O6nd zOsIO&cjWOzfl5Md+gCtc=cu?ONnp@@U$J$=dE}f?yKFP3^;Uw|x$W?BGr|q0pz$t` z@<+^|c;bjLHw~R7C6VjyNzY2f&E>-W{VOW^-O)hmJH>mu9>YFXi4n>e9ktLTs}_{3 z)nZ>LXc#y|v+sfnhM6Q2Ny*f4n*ubL`&4VSv>e~A7JJ*6Q?sHB-NUIO)~U}ndQaMe z(E3}gVt<0LM2`cQJwX+?nKIV*Oh%|pm6N>9P1WSGJJok-k^&!|3t99b*f-H?HTw+G zy>N91$v5i0xcL{Napy)GbLWJ+WhV!bP0}uPH_Oxn(u-^RJJcV^`)qn>{DH=kW?`Qc z=78zO$Pf|hv2Uv`kjAU5R~E(N;d1ctxuyjbj>l1>+bhnP-igF@>hpY(S=Y`zU%uLE zy`P$Hvx`WB{0>gZ^lN<65hfKU$?iXEdvMLE>iQF(O2;JY>46;5I_coJx@W^flgEOQ^S{T&6mvK$nXa{6WiyBoohu2N#WIbfnyynIt~dekZ4v9<`x zkapI&HC6YI>sBeLqu25*>(2zlOD!umxDHNIJl!;vxbk*ZJ3T&ftX&REkf^RxUT&-3 z*Z09kNLopZN>bliPc_=CW!0i&@YI|Ts>8T1Y)1=_$j7NuU|#6CZ29-{aw0{yc@%85 zpa`f$KOD>zKq|=C5LY|Jljqv#)xXQq72GszrJNkiqoAYKwQBZTnR+`bcTjY?9w@f% zB6&6B&&U{-ay9n0hxVmr!4)}4b^bLkZ{_I_4T9$xH5~wXd zQO$aMUhjuaj(I2BnDeGD)FPj*insCbD?M(nMp&t&W8U(|Cvu2vj?}J~jjj_%$|`II z%FjAl%{w2^K&R#7vnLnSY_H6{y$yZtY^U~8t7LVJvR5bOh*%)S9DKgeNttPj9QKIj z95;pLWTQ5sD8JgaK3#Z=WOoW3aVpn+uIf_1dS!aF{5A#~$~JknOlsRT^UV*Z^-!+E zCiuDX=HxXE0UAqU%&zWYUMiD_6RI<3n^6Xq=+$nkg?z8b_g=GtmsgYOXyA~heta$B z1?5eoD+u)_8m9-hUpJ4Rr%SX}6N$z z0lo8c&^I9_KB{vZyq>biiOXnHH{pF23(wU{1F0*n`v6JM>xcKPg$2=Hl;-u77(` z<5<75`&?)StN@dbWT$V)IRrjZX9b>L9=4*%&hu47rBW4ooaObv3nqtsqmQ|{JNGm= zteL!(#XJ7|ibS%yYO}WMjjKs%(tbzXfjbHPNxae2ii}`PP%NAQMt+xHr*tisOSN_$+6@{ZBMU9lw zDhH$}MSg0jJ1f(Dq4YW2YR!Ixgh%5oIp$p0p0s>(XDxs7`n`%->0zuH& zAuJMh3`;|Rup}%2k`PEIa89-S1^prO)0;W(eeb^S-TCgFb7t;TByRt(AYbR(rIufDL}9brO)*$({!#WIH3x z+VN|0%ad$#YwvI1$+TA_8p_{Fm7xy>M%iI2{per%TwxwbpqQn552v~_>6LaQvaZ~u zEgsGgD{@fCvyrSt8ueKK(fF{?K6jUyV~WD0sc%h;mss@}aEzm4dJZlp>$Cr&@>acA z@@ge;CeqyJ-2V94k}zRkZx(&vo4Q=4n7A*}*f@4{g#Wa?K^O*booROOhTTPh4&M9T z_Qa_CG(CmdjTK84N(z1aBIh;$>@aY?I{b^)V@lZYjzh~LHgg^u(jCY!Jt0!$VYGRrX&<@ChEo7NuzG&Cui*=O{*(z= z3xv^?s^1$z7vGHvnD=h`1BtoM@H$%_KGQFftR2#s>*cu7R$Td~u}|}*Nhtr}4j*f) znT*mBPRf^tQBrA{dlj?*u=%aZu)d(!%`!M2@u%!>A8oKg0d~hRY**#0J$r8s#6-Bw zvcDHD@WS+#I0_DHslTFfH7tJ2E~N{xDFc4+`tL}kVj3SBS049{-jHYI~^7bbWO z*4UsQ&ya_d!MW#RrOMR>Ux)nb=;5hWmwKLL$y>dzS~RjpbzJ7s5Fru2O_0-Yjb3G9 zWz9FpkbfS9u<*P^@sVSim)9ozAHsz#ix{G;Fi}ws%1l>KQ7#G2L1?-3B!~hKfw`F& zr{))mxG@L3;Aq*mIUw{OJ14dvEMJX6f9$}0J&c_rTS;9dmt%V@y=4`@wtQoWT2N>`a1bfup;O{|5-xOgfi0t#p04lFd@q>=Ww{8Ay|W)niwFMU^}9O!=%C4>>r?9@UEu2Mwxkc9;)qvu(QeJSL~+8XGmCuff$b6=!b zJD+jm39dQxP^%f@=0sNnq31utg8i8Cv2sraZ<`(R+96Q%m6V-2Y$b(uZnD(2;)=!5hDiOVeS^`1&7SX~O-zEO zR#$7AQ3iven_oz=xc(=<6KXwOBf-A`bvyVJK8y)$Z7*@dW(+$rNA9eSRi=jqc2~j% zl5M$*s!3gGHfxVM>|723tGI3hjBSv|%_i%|@~ok$yBAmJ904=d&%%?W>oVOQRr`9H zDWZgGN7pnwK>~PPa9rmHHd+zBaWqr!3w`v6g(H5>5B6bZ1%|1Qxrf`%4K_(;7G)VT zBVle%GEo9zNL17;A2qcp@E~5oZZAobCmv$mrnpEZ)}UyzI{`!gK&a>lydupRR|i6n zVcL$bCIYfk2MPAnRR{!HhVnZ88TS4V-1ExHk9hh_C9nS2`+%fwrJCq%(@QEZu7qxeiP*9whe8e$u0MpnSwgZAXr?_W z>-Z)ZJNN+zFJ_^_^=%43d;C2?Gl^8&e`(ohBvU{(^CA)p)+z88os3>a#|6)|FhN`Cz=z1l;uyt_is6 qq)C(QsGUKRYJ%D6|1Q-WRzWV6Q}3bYupdLf1NF%%uj-G(Z~X)G#ccKf literal 0 HcmV?d00001 From 8a2b6e6a3668720077f93a64b4788fe14ebb1ded Mon Sep 17 00:00:00 2001 From: Stanislas Date: Thu, 21 Dec 2023 13:14:19 -0800 Subject: [PATCH 22/27] Updated OTEL metrics --- cmd/proxy/proc/base.go | 62 +-- cmd/proxy/proc/create.go | 4 +- cmd/proxy/proc/inbreqctx.go | 24 +- cmd/proxy/proc/processor.go | 8 +- cmd/proxy/replication/replicaterequest.go | 17 +- cmd/storageserv/storage/proc.go | 10 +- docker/manifest/config/proxy/config.toml | 10 - docker/manifest/docker-compose.yaml | 26 -- go.mod | 42 +- go.sum | 72 ++-- pkg/cluster/clusterstats.go | 20 + pkg/cluster/shardmgr.go | 20 +- pkg/io/conn.go | 38 +- pkg/io/inboundconnector.go | 2 + pkg/io/listener.go | 6 +- pkg/io/outboundprocessor.go | 10 +- pkg/io/ssllistener.go | 12 + pkg/logging/otel/config/otelconfig.go | 52 ++- pkg/logging/otel/defs.go | 267 ++++++++++++ pkg/logging/otel/logger.go | 483 +++++++++------------- pkg/logging/otel/statsLogger.go | 169 ++------ pkg/logging/otel/test/logger_test.go | 23 +- pkg/logging/otel/test/mock_collector.go | 14 +- pkg/sec/sslctx.go | 3 + 24 files changed, 728 insertions(+), 666 deletions(-) create mode 100644 pkg/logging/otel/defs.go diff --git a/cmd/proxy/proc/base.go b/cmd/proxy/proc/base.go index 76d6197f..e5e18c3f 100644 --- a/cmd/proxy/proc/base.go +++ b/cmd/proxy/proc/base.go @@ -173,9 +173,9 @@ func (r *ProxyInResponseContext) OnComplete() { } logging.LogToCal(opcode, r.GetOpStatus(), rht, calData) } - if otel.IsEnabled() { - otel.RecordOperation(r.stats.Opcode.String(), r.stats.ResponseStatus.ShortNameString(), int64(rhtus)) - } + + otel.RecordOperation(r.stats.Opcode.String(), r.stats.ResponseStatus, int64(rhtus)) + r.stats.OnComplete(uint32(rhtus), r.GetOpStatus()) proxystats.SendProcState(r.stats) @@ -339,9 +339,7 @@ func (p *ProcessorBase) replyToClient(resp *ResponseWrapper) { if cal.IsEnabled() { calLogReqProcError(kDecrypt, []byte(errmsg)) } - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Operation, kDecrypt}, {otel.Status, otel.StatusError}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Operation, kDecrypt}, {otel.Status, otel.StatusError}}) msg := p.clientRequest.CreateResponse() msg.SetOpStatus(proto.OpStatusInternal) var raw proto.RawMessage @@ -571,9 +569,7 @@ func (p *ProcessorBase) validateInboundRequest(r *proto.OperationalMessage) bool data.AddReqIdString(r.GetRequestIDString()) data.AddInt([]byte("len"), szKey) calLogReqProcEvent(kBadParamInvalidKeyLen, data.Bytes()) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidKeyLen}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidKeyLen}}) return false } szNs := len(r.GetNamespace()) @@ -584,9 +580,7 @@ func (p *ProcessorBase) validateInboundRequest(r *proto.OperationalMessage) bool data.AddReqIdString(r.GetRequestIDString()) data.AddInt([]byte("len"), szNs) calLogReqProcEvent(kBadParamInvalidNsLen, data.Bytes()) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidNsLen}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidNsLen}}) return false } ttl := r.GetTimeToLive() @@ -597,9 +591,7 @@ func (p *ProcessorBase) validateInboundRequest(r *proto.OperationalMessage) bool data.AddReqIdString(r.GetRequestIDString()) data.AddInt([]byte("ttl"), int(ttl)) calLogReqProcEvent(kBadParamInvalidTTL, data.Bytes()) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidTTL}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidTTL}}) return false } } else { @@ -610,9 +602,7 @@ func (p *ProcessorBase) validateInboundRequest(r *proto.OperationalMessage) bool data.AddInt([]byte("len"), szKey) calLogReqProcEvent(kBadParamInvalidKeyLen, data.Bytes()) glog.Warningf("limit exceeded: key length %d > %d", szKey, limits.MaxKeyLength) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidKeyLen}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidKeyLen}}) return false } if limits.MaxTimeToLive != 0 && ttl > limits.MaxTimeToLive { @@ -621,9 +611,7 @@ func (p *ProcessorBase) validateInboundRequest(r *proto.OperationalMessage) bool data.AddInt([]byte("ttl"), int(ttl)) calLogReqProcEvent(kBadParamInvalidTTL, data.Bytes()) glog.Warningf("limit exceeded: TTL %d > %d", ttl, limits.MaxTimeToLive) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidTTL}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidTTL}}) return false } szValue := r.GetPayloadValueLength() @@ -633,9 +621,7 @@ func (p *ProcessorBase) validateInboundRequest(r *proto.OperationalMessage) bool data.AddInt([]byte("len"), int(szValue)) calLogReqProcEvent(kBadParamInvalidValueLen, data.Bytes()) glog.Warningf("limit exceeded: payload length %d > %d", szValue, limits.MaxPayloadLength) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidValueLen}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidValueLen}}) return false } } @@ -692,11 +678,11 @@ func (p *ProcessorBase) Process(request io.IRequestContext) bool { } } } - if otel.IsEnabled() { - if p.clientRequest.IsForReplication() { - otel.RecordCount(otel.RAPI, nil) - } + + if p.clientRequest.IsForReplication() { + otel.RecordCount(otel.RAPI, nil) } + p.shardId = shardId.Uint16() if err := proto.SetShardId(p.requestContext.GetMessage(), p.shardId); err != nil { @@ -770,9 +756,7 @@ func (p *ProcessorBase) OnRequestTimeout() { b.AddOpCode(p.clientRequest.GetOpCode()).AddReqIdString(p.requestID) calLogReqProcEvent(kReqTimeout, b.Bytes()) } - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kReqTimeout}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kReqTimeout}}) p.replyStatusToClient(proto.OpStatusBusy) } now := time.Now() @@ -794,9 +778,7 @@ func (p *ProcessorBase) OnCancelled() { b.AddOpCode(p.clientRequest.GetOpCode()).AddReqIdString(p.requestID) calLogReqProcEvent(kReqCancelled, b.Bytes()) } - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kReqCancelled}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kReqCancelled}}) p.replyStatusToClient(proto.OpStatusBusy) } now := time.Now() @@ -841,10 +823,8 @@ func (p *ProcessorBase) handleSSTimeout(now time.Time) { writeBasicSSRequestInfo(b, st.opCode, int(st.ssIndex), p.ssGroup.processors[st.ssIndex].GetConnInfo(), p) calLogReqProcEvent(calNameReqTimeoutFor(st.opCode), b.Bytes()) } - if otel.IsEnabled() { - status := otel.SSReqTimeout + "_" + st.opCode.String() - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, status}}) - } + status := otel.SSReqTimeout + "_" + st.opCode.String() + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, status}}) if cluster.GetShardMgr().StatsEnabled() { zoneId, hostId := p.ssGroup.processors[st.ssIndex].GetNodeInfo() cluster.GetShardMgr().SendStats(zoneId, hostId, true, confSSRequestTimeout.Microseconds()) @@ -918,10 +898,8 @@ func (p *ProcessorBase) preprocessAndValidateResponse(resp io.IResponseContext) errStr := strings.Replace(statusText, " ", "_", -1) calLogReqProcEvent(fmt.Sprintf("SS_%s", errStr), buf.Bytes()) //TODO revisit log as error? } - if otel.IsEnabled() { - errStr := strings.Replace(statusText, " ", "_", -1) - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, fmt.Sprintf("SS_%s", errStr)}}) - } + errStr := strings.Replace(statusText, " ", "_", -1) + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, fmt.Sprintf("SS_%s", errStr)}}) st.state = stSSResponseIOError st.timeRespReceived = time.Now() p.self.OnSSIOError(st) diff --git a/cmd/proxy/proc/create.go b/cmd/proxy/proc/create.go index e73e3895..7268698f 100644 --- a/cmd/proxy/proc/create.go +++ b/cmd/proxy/proc/create.go @@ -82,9 +82,7 @@ func (p *CreateProcessor) setInitSSRequest() bool { if cal.IsEnabled() { calLogReqProcError(kEncrypt, []byte(errmsg)) } - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Operation, kEncrypt}, {otel.Status, otel.StatusError}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Operation, kEncrypt}, {otel.Status, otel.StatusError}}) p.replyStatusToClient(proto.OpStatusInternal) return false } diff --git a/cmd/proxy/proc/inbreqctx.go b/cmd/proxy/proc/inbreqctx.go index 7e96b1ed..3314366d 100644 --- a/cmd/proxy/proc/inbreqctx.go +++ b/cmd/proxy/proc/inbreqctx.go @@ -75,9 +75,7 @@ func (r *InboundRequestContext) ValidateRequest() bool { data.AddReqIdString(r.GetRequestIDString()) data.AddInt([]byte("len"), szKey) calLogReqProcEvent(kBadParamInvalidKeyLen, data.Bytes()) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidKeyLen}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidKeyLen}}) return false } szNs := len(r.GetNamespace()) @@ -88,9 +86,7 @@ func (r *InboundRequestContext) ValidateRequest() bool { data.AddReqIdString(r.GetRequestIDString()) data.AddInt([]byte("len"), szNs) calLogReqProcEvent(kBadParamInvalidNsLen, data.Bytes()) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidNsLen}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidNsLen}}) return false } ttl := r.GetTimeToLive() @@ -101,9 +97,7 @@ func (r *InboundRequestContext) ValidateRequest() bool { data.AddReqIdString(r.GetRequestIDString()) data.AddInt([]byte("ttl"), int(ttl)) calLogReqProcEvent(kBadParamInvalidTTL, data.Bytes()) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidTTL}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidTTL}}) return false } } else { @@ -114,9 +108,7 @@ func (r *InboundRequestContext) ValidateRequest() bool { data.AddInt([]byte("len"), szKey) calLogReqProcEvent(kBadParamInvalidKeyLen, data.Bytes()) glog.Warningf("limit exceeded: key length %d > %d", szKey, limits.MaxKeyLength) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidKeyLen}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidKeyLen}}) return false } if limits.MaxTimeToLive != 0 && ttl > limits.MaxTimeToLive { @@ -125,9 +117,7 @@ func (r *InboundRequestContext) ValidateRequest() bool { data.AddInt([]byte("ttl"), int(ttl)) calLogReqProcEvent(kBadParamInvalidTTL, data.Bytes()) glog.Warningf("limit exceeded: TTL %d > %d", ttl, limits.MaxTimeToLive) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidTTL}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidTTL}}) return false } szValue := r.GetPayloadValueLength() @@ -137,9 +127,7 @@ func (r *InboundRequestContext) ValidateRequest() bool { data.AddInt([]byte("len"), int(szValue)) calLogReqProcEvent(kBadParamInvalidValueLen, data.Bytes()) glog.Warningf("limit exceeded: payload length %d > %d", ttl, limits.MaxTimeToLive) - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidValueLen}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kBadParamInvalidValueLen}}) return false } } diff --git a/cmd/proxy/proc/processor.go b/cmd/proxy/proc/processor.go index 18b1599b..64eafb42 100644 --- a/cmd/proxy/proc/processor.go +++ b/cmd/proxy/proc/processor.go @@ -135,9 +135,7 @@ func (p *TwoPhaseProcessor) setInitSSRequest() bool { if cal.IsEnabled() { calLogReqProcError(kEncrypt, []byte(errmsg)) } - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Operation, kEncrypt}, {otel.Status, otel.StatusError}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Operation, kEncrypt}, {otel.Status, otel.StatusError}}) p.replyStatusToClient(proto.OpStatusInternal) return false } @@ -351,9 +349,7 @@ func (p *TwoPhaseProcessor) onRepairFailure(rc *SSRequestContext) { writeBasicSSRequestInfo(buf, rc.opCode, int(rc.ssIndex), p.ssGroup.processors[rc.ssIndex].GetConnInfo(), &p.ProcessorBase) calLogReqProcEvent(kInconsistent, buf.Bytes()) } - if otel.IsEnabled() { - otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kInconsistent}}) - } + otel.RecordCount(otel.ReqProc, []otel.Tags{{otel.Status, kInconsistent}}) p.replyStatusToClient(proto.OpStatusInconsistent) } } diff --git a/cmd/proxy/replication/replicaterequest.go b/cmd/proxy/replication/replicaterequest.go index 5ce53dc8..e3bd8c85 100644 --- a/cmd/proxy/replication/replicaterequest.go +++ b/cmd/proxy/replication/replicaterequest.go @@ -146,9 +146,8 @@ func (r *RepRequestContext) complete(calstatus string, opStatus string, rht time cal.AtomicTransaction(targetType, opCode, calstatus, rht, r.calBuf.Bytes()) } } - if otel.IsEnabled() { - otel.RecordReplication(opCode, opStatus, target, rht.Milliseconds()) - } + + otel.RecordReplication(opCode, opStatus, target, rht.Microseconds()) r.this.OnComplete() } @@ -221,9 +220,7 @@ func (r *RepRequestContext) Reply(resp io.IResponseContext) { } r.calBuf.AddDropReason("MaxRetry") } - if otel.IsEnabled() { - otel.RecordCount(otel.RRDropMaxRetry, []otel.Tags{{"target", r.targetId}}) - } + otel.RecordCount(otel.RRDropMaxRetry, []otel.Tags{{"target", r.targetId}}) r.errCnt.Add(1) r.complete(cal.StatusError, opstatus.String(), rht, opCodeText, r.targetId) return @@ -245,9 +242,7 @@ func (r *RepRequestContext) Reply(resp io.IResponseContext) { } r.calBuf.AddDropReason("QueueFull") } - if otel.IsEnabled() { - otel.RecordCount(otel.RRDropQueueFull, []otel.Tags{{otel.Target, r.targetId}}) - } + otel.RecordCount(otel.RRDropQueueFull, []otel.Tags{{otel.Target, r.targetId}}) r.dropCnt.Add(1) r.complete(cal.StatusError, opstatus.String(), rht, opCodeText, r.targetId) } @@ -261,9 +256,7 @@ func (r *RepRequestContext) Reply(resp io.IResponseContext) { } r.calBuf.AddDropReason("RecExpired") } - if otel.IsEnabled() { - otel.RecordCount(otel.RRDropRecExpired, []otel.Tags{{otel.Target, r.targetId}}) - } + otel.RecordCount(otel.RRDropRecExpired, []otel.Tags{{otel.Target, r.targetId}}) r.complete(cal.StatusSuccess, opstatus.String(), rht, opCodeText, r.targetId) } } diff --git a/cmd/storageserv/storage/proc.go b/cmd/storageserv/storage/proc.go index 647cbbe8..e9cbe750 100644 --- a/cmd/storageserv/storage/proc.go +++ b/cmd/storageserv/storage/proc.go @@ -116,19 +116,13 @@ func (p *reqProcCtxT) OnComplete() { calData.AddOpRequestResponse(&p.request, &p.response).AddRequestHandleTime(rhtus) cal.AtomicTransaction(cal.TxnTypeAPI, opcode.String(), calst.CalStatus(), rht, calData.Bytes()) } - if otel.IsEnabled() { - otel.RecordOperation(opcode.String(), p.response.GetOpStatus().String(), int64(rhtus)) - opst := p.response.GetOpStatus() - calst := logging.CalStatus(opst) - if (opst == proto.OpStatusInconsistent) || calst.NotSuccess() { - otel.RecordCount(otel.ProcErr, []otel.Tags{{otel.Operation, opcode.String() + "_" + opst.String()}, {otel.Status, otel.StatusError}}) - } - } if (opst == proto.OpStatusInconsistent) || calst.NotSuccess() { cal.Event("ProcErr", opcode.String()+"_"+opst.String(), cal.StatusSuccess, nil) } } + otel.RecordOperation(opcode.String(), p.response.GetOpStatus(), int64(rhtus)) + if p.cacheable { if p.prepareCtx != nil { if p.prepareCtx.cacheable { diff --git a/docker/manifest/config/proxy/config.toml b/docker/manifest/config/proxy/config.toml index 347c16eb..95544697 100644 --- a/docker/manifest/config/proxy/config.toml +++ b/docker/manifest/config/proxy/config.toml @@ -39,16 +39,6 @@ DefaultTimeToLive = 1800 CalType = "file" Enabled = true -[OTEL] - Enabled = true - Environment = "prod" - Host = "otel" - Port = 4318 - Poolname = "proxy_openSource" - Resolution = 10 - UseTls = false - UrlPath = "" - [Sherlock] Enabled = false diff --git a/docker/manifest/docker-compose.yaml b/docker/manifest/docker-compose.yaml index b4f97470..9a6f6a31 100644 --- a/docker/manifest/docker-compose.yaml +++ b/docker/manifest/docker-compose.yaml @@ -98,7 +98,6 @@ services: start_period: 10s depends_on: - storageserv - - otel volumes: - ${PWD}/config/proxy/config.toml:/opt/juno/config/config.toml - ${PWD}/config/secrets:/opt/juno/bin/secrets @@ -119,31 +118,6 @@ services: - ${PWD}/config/client/config.toml:/opt/juno/config.toml - ${PWD}/config/secrets:/opt/juno/secrets - otel: - <<: *service_default - image: otel/opentelemetry-collector:latest - container_name: otel - command: ["--config=/etc/otel-collector/config.yaml"] - volumes: - - ${PWD}/config/otel/config.yaml:/etc/otel-collector/config.yaml - ports: - - "1888:1888" # pprof extension - - "8888:8888" # Prometheus metrics exposed by the Collector - - "8889:8889" # Prometheus exporter metrics - - "13133:13133" # health_check extension - - "4317:4317" # OTLP gRPC receiver - - "4318:4318" # OTLP http receiver - - "55679:55679" # zpages extension - - prometheus: - <<: *service_default - container_name: prometheus - image: prom/prometheus:latest - volumes: - - ${PWD}/config/prometheus/prometheus.yaml:/etc/prometheus/prometheus.yml - ports: - - "9090:9090" - networks: junonet: driver: bridge diff --git a/go.mod b/go.mod index b55c9d08..464802f0 100644 --- a/go.mod +++ b/go.mod @@ -12,42 +12,42 @@ require ( github.com/satori/go.uuid v1.2.0 github.com/spaolacci/murmur3 v1.1.0 go.etcd.io/etcd/client/v3 v3.5.4 - go.opentelemetry.io/otel v1.11.2 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 - go.opentelemetry.io/otel/metric v0.34.0 - go.opentelemetry.io/otel/sdk v1.11.2 - go.opentelemetry.io/otel/sdk/metric v0.34.0 + go.opentelemetry.io/otel v1.16.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0 + go.opentelemetry.io/otel/metric v1.16.0 + go.opentelemetry.io/otel/sdk v1.16.0 + go.opentelemetry.io/otel/sdk/metric v0.39.0 go.opentelemetry.io/proto/otlp v0.19.0 - google.golang.org/protobuf v1.28.1 + google.golang.org/protobuf v1.30.0 ) require ( - github.com/golang/protobuf v1.5.2 // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/rogpeppe/go-internal v1.6.2 // indirect - golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect - golang.org/x/net v0.7.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/rogpeppe/go-internal v1.6.2 // indirect go.etcd.io/etcd/api/v3 v3.5.4 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 // indirect - go.opentelemetry.io/otel/trace v1.11.2 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect - google.golang.org/grpc v1.51.0 // indirect + golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect + google.golang.org/grpc v1.55.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 3cd3cb01..f107fb29 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -101,8 +101,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -112,8 +112,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -139,8 +139,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -248,7 +249,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -265,22 +266,22 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= -go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2/go.mod h1:rqbht/LlhVBgn5+k3M5QK96K5Xb0DvXpMJ5SFQpY6uw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 h1:kpskzLZ60cJ48SJ4uxWa6waBL+4kSV6nVK8rP+QM8Wg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0/go.mod h1:4+x3i62TEegDHuzNva0bMcAN8oUi5w4liGb1d/VgPYo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 h1:t4Ajxj8JGjxkqoBtbkCOY2cDUl9RwiNE9LPQavooi9U= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0/go.mod h1:WO7omosl4P7JoanH9NgInxDxEn2F2M5YinIh8EyeT8w= -go.opentelemetry.io/otel/metric v0.34.0 h1:MCPoQxcg/26EuuJwpYN1mZTeCYAUGx8ABxfW07YkjP8= -go.opentelemetry.io/otel/metric v0.34.0/go.mod h1:ZFuI4yQGNCupurTXCwkeD/zHBt+C2bR7bw5JqUm/AP8= -go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU= -go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= -go.opentelemetry.io/otel/sdk/metric v0.34.0 h1:7ElxfQpXCFZlRTvVRTkcUvK8Gt5DC8QzmzsLsO2gdzo= -go.opentelemetry.io/otel/sdk/metric v0.34.0/go.mod h1:l4r16BIqiqPy5rd14kkxllPy/fOI4tWo1jkpD9Z3ffQ= -go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= -go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 h1:f6BwB2OACc3FCbYVznctQ9V6KK7Vq6CjmYXJ7DeSs4E= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0/go.mod h1:UqL5mZ3qs6XYhDnZaW1Ps4upD+PX6LipH40AoeuIlwU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0 h1:IZXpCEtI7BbX01DRQEWTGDkvjMB6hEhiEZXS+eg2YqY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0/go.mod h1:xY111jIZtWb+pUUgT4UiiSonAaY2cD2Ts5zvuKLki3o= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= +go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= +go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -369,8 +370,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -426,8 +427,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -435,8 +436,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -486,7 +487,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -549,8 +550,9 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -568,8 +570,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -583,8 +585,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/cluster/clusterstats.go b/pkg/cluster/clusterstats.go index 804c5134..e7982d57 100644 --- a/pkg/cluster/clusterstats.go +++ b/pkg/cluster/clusterstats.go @@ -21,9 +21,11 @@ package cluster import ( "fmt" + "strings" "sync" "time" + "juno/pkg/logging/otel" "juno/pkg/util" "juno/third_party/forked/golang/glog" ) @@ -180,6 +182,8 @@ func (c *ClusterStats) processStateChange(st *ProcStat) { c.MarkdownTable[idx] = true glog.Infof("markdown: node %d-%d, exp:%d, timout ct: %d", st.zoneid, st.nodeid, c.MarkdownExpiration[idx], timeoutCnt) + targetSS := getIP(st.zoneid, st.nodeid) + otel.RecordCount(otel.SoftMark, []otel.Tags{{otel.Target, targetSS}, {otel.Status, otel.SSMarkDown}}) } } @@ -194,6 +198,20 @@ func (c *ClusterStats) processStateChange(st *ProcStat) { } } +func getIP(zoneid uint32, nodeid uint32) string { + shardMgr := GetShardMgr() + // First check if the connInfo has the element. + if len(shardMgr.connInfo) < int(zoneid+1) || len(shardMgr.connInfo[zoneid]) < int(nodeid+1) { + return "" + } + i := strings.Index(shardMgr.connInfo[zoneid][nodeid], ":") + + if i < 0 { + return shardMgr.connInfo[zoneid][nodeid] + } + return shardMgr.connInfo[zoneid][nodeid][:i] +} + // Naive way of doing markup: markdown expired. // Potentially can add probe logic so that we markup only if it's in good state. func (c *ClusterStats) markup() { @@ -215,6 +233,8 @@ func (c *ClusterStats) markup() { glog.Infof("markup: host %d-%d", i, j) c.MarkdownExpiration[idx] = 0 c.MarkdownTable[idx] = false + targetSS := getIP(i, j) + otel.RecordCount(otel.SoftMark, []otel.Tags{{otel.Target, targetSS}, {otel.Status, otel.SSMarkUp}}) } } } diff --git a/pkg/cluster/shardmgr.go b/pkg/cluster/shardmgr.go index 1d578316..e243f3eb 100644 --- a/pkg/cluster/shardmgr.go +++ b/pkg/cluster/shardmgr.go @@ -759,9 +759,9 @@ func (p *OutboundSSProcessor) GetNodeInfo() (zoneid int, hostid int) { } func (p *OutboundSSProcessor) OnConnectSuccess(conn io.Conn, connector *io.OutboundConnector, timeTaken time.Duration) { + netConn := conn.GetNetConn() if cal.IsEnabled() { b := logging.NewKVBuffer() - netConn := conn.GetNetConn() b.Add([]byte("raddr"), netConn.RemoteAddr().String()) b.Add([]byte("laddr"), netConn.LocalAddr().String()) @@ -771,10 +771,9 @@ func (p *OutboundSSProcessor) OnConnectSuccess(conn io.Conn, connector *io.Outbo cal.AtomicTransaction(logging.CalMsgTypeSSConnect, p.Name(), cal.StatusSuccess, timeTaken, b.Bytes()) } - if otel.IsEnabled() { - netConn := conn.GetNetConn() - otel.RecordSSConnection(netConn.RemoteAddr().String(), otel.StatusSuccess, timeTaken.Milliseconds()) - } + + otel.RecordSSConnection(getIPAddress(netConn.RemoteAddr().String()), otel.StatusSuccess, timeTaken.Microseconds()) + } func (p *OutboundSSProcessor) OnConnectError(timeTaken time.Duration, connStr string, err error) { @@ -784,9 +783,14 @@ func (p *OutboundSSProcessor) OnConnectError(timeTaken time.Duration, connStr st b.Add([]byte("err"), err.Error()) cal.AtomicTransaction(logging.CalMsgTypeSSConnectError, p.Name(), cal.StatusSuccess, timeTaken, b.Bytes()) } - if otel.IsEnabled() { - otel.RecordSSConnection(connStr, otel.StatusError, timeTaken.Milliseconds()) - } + + otel.RecordSSConnection(getIPAddress(connStr), otel.StatusError, timeTaken.Microseconds()) + +} + +func getIPAddress(endpoint string) string { + s := strings.Split(endpoint, ":") + return s[0] } func (m *ZoneMarkDown) MarkDown(zoneid int32) { diff --git a/pkg/io/conn.go b/pkg/io/conn.go index 226810f8..68b5763d 100644 --- a/pkg/io/conn.go +++ b/pkg/io/conn.go @@ -34,6 +34,9 @@ import ( type Conn interface { GetStateString() string + GetTLSVersion() string + GetCipherName() string + DidResume() string GetNetConn() net.Conn IsTLS() bool } @@ -51,6 +54,17 @@ func (c *Connection) GetStateString() string { return "" } +func (c *Connection) GetTLSVersion() string { + return "" +} + +func (c *Connection) GetCipherName() string { + return "" +} + +func (c *Connection) DidResume() string { + return "" +} func (c *Connection) GetNetConn() net.Conn { return c.conn } @@ -91,13 +105,15 @@ func Connect(endpoint *ServiceEndpoint, connectTimeout time.Duration) (conn net. cal.AtomicTransaction(cal.TxnTypeConnect, endpoint.Addr, status, time.Since(timeStart), b.Bytes()) } } - if otel.IsEnabled() { - status := otel.StatusSuccess - if err != nil { - status = otel.StatusError - } - otel.RecordOutboundConnection(endpoint.Addr, status, time.Since(timeStart).Milliseconds()) + status := otel.StatusSuccess + + if err != nil { + status = otel.StatusError + } else { + otel.RecordCount(otel.TLSStatus, []otel.Tags{{otel.Endpoint, endpoint.Addr}, {otel.TLS_version, sslconn.GetTLSVersion()}, + {otel.Cipher, sslconn.GetCipherName()}, {otel.Ssl_r, sslconn.DidResume()}}) } + otel.RecordOutboundConnection(endpoint.Addr, status, time.Since(timeStart).Microseconds()) } else { if conn, err = net.DialTimeout("tcp", endpoint.Addr, connectTimeout); err == nil { if glog.LOG_DEBUG { @@ -115,13 +131,11 @@ func Connect(endpoint *ServiceEndpoint, connectTimeout time.Duration) (conn net. } cal.AtomicTransaction(cal.TxnTypeConnect, endpoint.GetConnString(), status, time.Since(timeStart), data) } - if otel.IsEnabled() { - status := otel.StatusSuccess - if err != nil { - status = otel.StatusError - } - otel.RecordOutboundConnection(endpoint.GetConnString(), status, time.Since(timeStart).Milliseconds()) + status := otel.StatusSuccess + if err != nil { + status = otel.StatusError } + otel.RecordOutboundConnection(endpoint.GetConnString(), status, time.Since(timeStart).Microseconds()) } return diff --git a/pkg/io/inboundconnector.go b/pkg/io/inboundconnector.go index bfc8a862..219f4582 100644 --- a/pkg/io/inboundconnector.go +++ b/pkg/io/inboundconnector.go @@ -34,6 +34,7 @@ import ( "juno/pkg/debug" "juno/pkg/io/ioutil" "juno/pkg/logging/cal" + "juno/pkg/logging/otel" "juno/pkg/proto" "juno/pkg/util" ) @@ -83,6 +84,7 @@ func (c *Connector) Close() { cal.Event(cal.TxnTypeClose, rhost, cal.StatusSuccess, []byte(addr)) } } + otel.RecordCount(otel.Close, []otel.Tags{}) c.cancelCtx() }) } diff --git a/pkg/io/listener.go b/pkg/io/listener.go index c93f3fba..2224fdf8 100644 --- a/pkg/io/listener.go +++ b/pkg/io/listener.go @@ -148,10 +148,10 @@ func (l *Listener) AcceptAndServe() error { cal.Event(cal.TxnTypeAccept, rhost, cal.StatusSuccess, []byte("raddr="+raddr+"&laddr="+conn.LocalAddr().String())) } } - if otel.IsEnabled() { - otel.RecordCount(otel.Accept, nil) - } + otel.RecordCount(otel.Accept, []otel.Tags{{otel.Status, otel.Success}}) l.startNewConnector(conn) + } else { + otel.RecordCount(otel.Accept, []otel.Tags{{otel.Status, otel.Error}}) } //log the error in caller if needed return err diff --git a/pkg/io/outboundprocessor.go b/pkg/io/outboundprocessor.go index 48fcbf28..601ac86e 100644 --- a/pkg/io/outboundprocessor.go +++ b/pkg/io/outboundprocessor.go @@ -314,18 +314,16 @@ func (p *OutboundProcessor) OnConnectSuccess(conn Conn, connector *OutboundConne cal.AtomicTransaction(cal.TxnTypeConnect, p.connInfo.GetConnString(), cal.StatusSuccess, timeTaken, data) } - if otel.IsEnabled() { - otel.RecordOutboundConnection(p.connInfo.GetConnString(), otel.StatusSuccess, timeTaken.Milliseconds()) - } + otel.RecordOutboundConnection(p.connInfo.GetConnString(), otel.StatusSuccess, timeTaken.Microseconds()) + otel.RecordCount(otel.TLSStatus, []otel.Tags{{otel.Endpoint, p.connInfo.GetConnString()}, {otel.TLS_version, conn.GetTLSVersion()}, + {otel.Cipher, conn.GetCipherName()}, {otel.Ssl_r, conn.DidResume()}}) } func (p *OutboundProcessor) OnConnectError(timeTaken time.Duration, connStr string, err error) { if cal.IsEnabled() { cal.AtomicTransaction(cal.TxnTypeConnect, connStr, cal.StatusError, timeTaken, []byte(err.Error())) } - if otel.IsEnabled() { - otel.RecordOutboundConnection(connStr, otel.StatusError, timeTaken.Milliseconds()) - } + otel.RecordOutboundConnection(connStr, otel.StatusError, timeTaken.Microseconds()) } func (p *OutboundProcessor) connect(connCh chan *OutboundConnector, id int, connector *OutboundConnector) { diff --git a/pkg/io/ssllistener.go b/pkg/io/ssllistener.go index db623292..1cf669ad 100644 --- a/pkg/io/ssllistener.go +++ b/pkg/io/ssllistener.go @@ -30,6 +30,7 @@ import ( "juno/pkg/logging" "juno/pkg/logging/cal" + "juno/pkg/logging/otel" "juno/pkg/sec" ) @@ -66,6 +67,8 @@ func (l *SslListener) AcceptAndServe() error { cal.Event(cal.TxnTypeAccept, rhost, cal.StatusSuccess, b.Bytes()) } } + otel.RecordCount(otel.Accept, []otel.Tags{{otel.Status, otel.Success}, {otel.TLS_version, sslConn.GetTLSVersion()}, + {otel.Cipher, sslConn.GetCipherName()}, {otel.Ssl_r, sslConn.DidResume()}}) l.startNewConnector(sslConn.GetNetConn()) } else { logAsWarning := true @@ -95,6 +98,15 @@ func (l *SslListener) AcceptAndServe() error { cal.Event(cal.TxnTypeAccept, rhost, st, b.Bytes()) } } + + otelStatus := otel.Success + if logAsWarning { + otelStatus = otel.Warn + } + + otel.RecordCount(otel.Accept, []otel.Tags{{otel.Status, otelStatus}, {otel.TLS_version, sslConn.GetTLSVersion()}, + {otel.Cipher, sslConn.GetCipherName()}, {otel.Ssl_r, sslConn.DidResume()}}) + if logAsWarning { glog.Warning("handshaking error: ", err) } else { diff --git a/pkg/logging/otel/config/otelconfig.go b/pkg/logging/otel/config/otelconfig.go index b5e7088d..438cec7d 100644 --- a/pkg/logging/otel/config/otelconfig.go +++ b/pkg/logging/otel/config/otelconfig.go @@ -24,28 +24,33 @@ import ( var OtelConfig *Config +type HistBuckets struct { + Replication []float64 + SsConnect []float64 + Inbound []float64 + OutboundConnection []float64 +} + type Config struct { - Host string - Port uint32 - UrlPath string - Environment string - Poolname string - Enabled bool - Resolution uint32 - UseTls bool + Host string + Port uint32 + UrlPath string + Environment string + Poolname string + Enabled bool + Resolution uint32 + UseTls bool + HistogramBuckets HistBuckets } func (c *Config) Validate() { if len(c.Poolname) <= 0 { glog.Fatal("Error: Otel Poolname is required.") } + c.setDefaultIfNotDefined() } -func (c *Config) SetPoolName(name string) { - c.Poolname = name -} - -func (c *Config) Default() { +func (c *Config) setDefaultIfNotDefined() { if c.Host == "" { c.Host = "127.0.0.1" } @@ -56,11 +61,23 @@ func (c *Config) Default() { c.Resolution = 60 } if c.Environment == "" { - c.Environment = "OpenSource" + c.Environment = "PayPal" } if c.UrlPath == "" { c.UrlPath = "v1/datapoint" } + if c.HistogramBuckets.Inbound == nil { + c.HistogramBuckets.Inbound = []float64{200, 400, 800, 1200, 2400, 3600, 7200, 10800, 21600, 43200, 86400, 172800} + } + if c.HistogramBuckets.OutboundConnection == nil { + c.HistogramBuckets.OutboundConnection = []float64{200, 400, 800, 1200, 2400, 3600, 7200, 10800, 21600, 43200, 86400, 172800} + } + if c.HistogramBuckets.Replication == nil { + c.HistogramBuckets.Replication = []float64{200, 400, 800, 1200, 2400, 3600, 7200, 10800, 21600, 43200, 86400, 172800} + } + if c.HistogramBuckets.SsConnect == nil { + c.HistogramBuckets.SsConnect = []float64{100, 200, 300, 400, 800, 1200, 2400, 3600, 10800, 21600, 86400, 172800} + } } func (c *Config) Dump() { @@ -69,5 +86,10 @@ func (c *Config) Dump() { glog.Infof("Environment: %s", c.Environment) glog.Infof("Poolname: %s", c.Poolname) glog.Infof("Resolution: %d", c.Resolution) - glog.Info("UseTls: %b", c.UseTls) + glog.Infof("UseTls: %t", c.UseTls) + glog.Infof("UrlPath: %s", c.UrlPath) + glog.Info("Inbound Bucket: ", c.HistogramBuckets.Inbound) + glog.Info("OutboundConnection Bucket: ", c.HistogramBuckets.OutboundConnection) + glog.Info("Replication Bucket: ", c.HistogramBuckets.Replication) + glog.Info("SsConnect Bucket: ", c.HistogramBuckets.SsConnect) } diff --git a/pkg/logging/otel/defs.go b/pkg/logging/otel/defs.go new file mode 100644 index 00000000..7421864b --- /dev/null +++ b/pkg/logging/otel/defs.go @@ -0,0 +1,267 @@ +// +// Copyright 2023 PayPal Inc. +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package otel + +import ( + "sync" + "time" + + instrument "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/sdk/metric" +) + +//*************************** Constants **************************** +const ( + RRDropMaxRetry CMetric = CMetric(iota) + RRDropQueueFull + RRDropRecExpired + SSL_CLIENT_INFO + CLIENT_INFO + Accept + Close + RAPI + ReqProc + ProcErr + SoftMark + TLSStatus + Inbound + Replication + OutboundConnection + SSConnection +) + +const ( + Target = string("target") + Endpoint = string("target_ip_port") + Operation = string("operation") + Client = string("client_app") + Status = string("status") + Fatal = string("Fatal") + Error = string("Error") + Warn = string("Warning") + Success = string("Success") + SS_RB_expire = string("SS_RB_expire") + SSReqTimeout = string("SSReqTimeout") + SSMarkDown = string("Down") + SSMarkUp = string("Up") + TLS_version = string("tls_version") + Cipher = string("cipher") + Ssl_r = string("ssl_r") +) + +const ( + SvrTypeProxy = ServerType(1) + SvrTypeStorage = ServerType(2) + SvrTypeClusterMgr = ServerType(3) + SvrTypeAll = ServerType(6) +) + +// OTEl Status +const ( + StatusSuccess string = "SUCCESS" + StatusFatal string = "FATAL" + StatusError string = "ERROR" + StatusWarning string = "WARNING" + StatusUnknown string = "UNKNOWN" +) + +const ( + MachineCpuUsed string = string("machineCpuUsed") + ProcessCpuUsed string = string("machineCpuUsed") + MachineMemoryUsed + ProcessMemoryUsed +) + +// default OTEL configurations point to QA collector +const DEFAULT_OTEL_COLLECTOR_PROTOCOL string = "http" +const DEFAULT_OTEL_COLLECTOR__IP string = "0.0.0.0" +const DEFAULT_GRPC_OTEL_COLLECTOR_PORT string = "4317" +const DEFAULT_HTTP_OTEL_COLLECTOR_PORT string = "4318" +const COLLECTOR_POLLING_INTERVAL_SECONDS int32 = 5 + +const JUNO_METRIC_PREFIX = "juno.server." +const MeterName = "juno-server-meter" +const histChannelSize = 1000 +const counterChannelSize = 1000 + +//****************************** variables *************************** + +var ( + apiHistogramOnce sync.Once + replicationHistogramOnce sync.Once + connectHistogramOnce sync.Once + ssConnectHistogramOnce sync.Once + rrDropMaxRetryCounterOnce sync.Once + rrDropQueueFullCounterOnce sync.Once + rrDropRecExpiredCounterOnce sync.Once + acceptCounterOnce sync.Once + closeCounterOnce sync.Once + rapiCounterOnce sync.Once + reqProcCounterOnce sync.Once + procErrCounterOnce sync.Once + softMarkCounterOnce sync.Once + tlsStatusCounterOnce sync.Once + sslClientInfoOnce sync.Once + clientInfoOnce sync.Once +) + +var apiHistogram instrument.Int64Histogram +var replicationHistogram instrument.Int64Histogram +var connectHistogram instrument.Int64Histogram +var ssConnectHistogram instrument.Int64Histogram + +var opsHistChannel chan DataPoint +var replHistChannel chan DataPoint +var ssConnHistChannel chan DataPoint +var outBoundHistChannel chan DataPoint +var counterChannel chan DataPoint + +var opsHistDoneCh chan bool +var replHistCh chan bool +var ssConnHistCh chan bool +var outBoundHistCh chan bool + +var countMetricMap map[CMetric]*countMetric = map[CMetric]*countMetric{ + RRDropMaxRetry: {"RR_Drop_MaxRetry", "Records dropped in replication queue due to max retry failures", nil, &rrDropMaxRetryCounterOnce, nil, nil}, + RRDropQueueFull: {"RR_Drop_QueueFull", "Records dropped in replication queue due to queue is full", nil, &rrDropQueueFullCounterOnce, nil, nil}, + RRDropRecExpired: {"RR_Drop_RecExpired", "Records dropped in replication queue due to expiry of records", nil, &rrDropRecExpiredCounterOnce, nil, nil}, + SSL_CLIENT_INFO: {"SSL_CLIENT_INFO", "Client app Info", nil, &sslClientInfoOnce, nil, nil}, + CLIENT_INFO: {"CLIENT_INFO", "Client app Info", nil, &clientInfoOnce, nil, nil}, + Accept: {"accept", "Accepting incoming connections", nil, &acceptCounterOnce, nil, nil}, + Close: {"close", "Closing incoming connections", nil, &closeCounterOnce, nil, nil}, + RAPI: {"rapi", "Processing of replicated requests", nil, &rapiCounterOnce, nil, nil}, + ReqProc: {"ReqProc", "Request processor", nil, &reqProcCounterOnce, nil, nil}, + ProcErr: {"ProcErr", "Request processor Error", nil, &procErrCounterOnce, nil, nil}, + SoftMark: {"SoftMark", "Proxy marks down storage instances", nil, &softMarkCounterOnce, nil, nil}, + TLSStatus: {"TLS_Status", "TLS connection state", nil, &tlsStatusCounterOnce, nil, nil}, +} + +var histMetricMap map[CMetric]*histogramMetric = map[CMetric]*histogramMetric{ + Inbound: {PopulateJunoMetricNamePrefix("inbound"), "Histogram for Juno API", "ms", nil, &apiHistogramOnce, nil, nil}, + Replication: {PopulateJunoMetricNamePrefix("replication"), "Histogram for Juno replication", "ms", nil, &replicationHistogramOnce, nil, nil}, + OutboundConnection: {PopulateJunoMetricNamePrefix("outbound_connection"), "Histogram for Juno connection", "us", nil, &connectHistogramOnce, nil, nil}, + SSConnection: {PopulateJunoMetricNamePrefix("ssConnection"), "Histogram for Juno SS connection", "us", nil, &ssConnectHistogramOnce, nil, nil}, +} + +var ( + meterProvider *metric.MeterProvider +) + +var ( + machineCpuUsedOnce sync.Once + processCpuUsedOnce sync.Once + machineMemoryUsedOnce sync.Once + processMemoryUsedOnce sync.Once + diskIOUtilization sync.Once + badShardOnce sync.Once + alertShardOnce sync.Once + warningShardOnce sync.Once + keyCountOnce sync.Once + freeStorageOnce sync.Once + usedStorageOnce sync.Once + LNLevelOnce sync.Once + compSecOnce sync.Once + compCountOnce sync.Once + pendingCompOnce sync.Once + stallOnce sync.Once + TCPConnCountOnce sync.Once + SSLConnCountOnce sync.Once +) + +var ( + machTime time.Time + machCpuTick uint16 + machUser uint64 + machSystem uint64 +) + +var GaugeMetricList = []*GaugeMetric{ + {"pCPU", "proc_cpu_used", "CPU utilization of individual Juno instance", nil, nil, &processCpuUsedOnce, SvrTypeAll}, + {"pMem", "proc_mem_used", "Memory utilization of individual Juno instance", nil, nil, &processMemoryUsedOnce, SvrTypeAll}, + + {"nBShd", "bad_shard", "Number of bad shards", nil, nil, &badShardOnce, SvrTypeProxy}, + {"nAShd", "alert_shard", "number of shards with no redundancy", nil, nil, &alertShardOnce, SvrTypeProxy}, + {"nWShd", "warning_shard", "number of shards with bad SS", nil, nil, &warningShardOnce, SvrTypeProxy}, + + {"conns", "conns_count", "number of current TCP connections", nil, nil, &TCPConnCountOnce, SvrTypeProxy}, + {"ssl_conns", "conns_ssl_count", "number of current SSL connections", nil, nil, &SSLConnCountOnce, SvrTypeProxy}, + {"keys", "key_count", "Key Counte in rocksDB", nil, nil, &keyCountOnce, SvrTypeStorage}, + {"free", "free_mb_storage_space", "Free Storage Space (mbytes)", nil, nil, &freeStorageOnce, SvrTypeStorage}, + {"used", "storage_used_mb", "Used Storage Space (mbytes)", nil, nil, &usedStorageOnce, SvrTypeStorage}, + {"LN", "LN_level", "Max LN Level in Rocksdb", nil, nil, &LNLevelOnce, SvrTypeStorage}, + {"compSec", "compaction_sec", "Compaction Sec", nil, nil, &compSecOnce, SvrTypeStorage}, + {"compCount", "compaction_count", "Compaction Count", nil, nil, &compCountOnce, SvrTypeStorage}, + {"pCompKB", "pending_compaction", "Pending Compaction KBytes", nil, nil, &pendingCompOnce, SvrTypeStorage}, + {"stall", "stall_write_rate", "Actural Delayed Write Rate", nil, nil, &stallOnce, SvrTypeStorage}, +} + +var otelIngestToken string + +// ************************************ Types **************************** +type CMetric int + +type Tags struct { + TagName string + TagValue string +} + +type countMetric struct { + metricName string + metricDesc string + counter instrument.Int64Counter + createCounter *sync.Once + counterCh chan DataPoint + doneCh chan bool +} + +type histogramMetric struct { + metricName string + metricDesc string + metricUnit string + histogram instrument.Int64Histogram + createHistogram *sync.Once + histogramCh chan DataPoint + doneCh chan bool +} + +type ( + ServerType int +) + +type GaugeMetric struct { + MetricShortName string + MetricName string + metricDesc string + gaugeMetric instrument.Float64ObservableGauge + stats []StateData + createGauge *sync.Once + stype ServerType +} + +// Represents stats by a worker +type StateData struct { + Name string + Value float64 + Dimensions instrument.MeasurementOption +} + +type DataPoint struct { + attr instrument.MeasurementOption + data int64 +} diff --git a/pkg/logging/otel/logger.go b/pkg/logging/otel/logger.go index 5559085e..e9bbb278 100644 --- a/pkg/logging/otel/logger.go +++ b/pkg/logging/otel/logger.go @@ -19,138 +19,31 @@ package otel import ( - "bufio" "context" "errors" "fmt" - "io" "log" "os" - "strings" - "sync" + "time" + "juno/pkg/logging" otelCfg "juno/pkg/logging/otel/config" + "juno/pkg/proto" "juno/third_party/forked/golang/glog" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/syncint64" - "go.opentelemetry.io/otel/metric/unit" + instrument "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/aggregation" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" -) - -var ( - apiHistogramOnce sync.Once - replicationHistogramOnce sync.Once - connectHistogramOnce sync.Once - ssConnectHistogramOnce sync.Once - rrDropMaxRetryCounterOnce sync.Once - rrDropQueueFullCounterOnce sync.Once - rrDropRecExpiredCounterOnce sync.Once - acceptCounterOnce sync.Once - closeCounterOnce sync.Once - rapiCounterOnce sync.Once - reqProcCounterOnce sync.Once - procErrCounterOnce sync.Once -) - -var apiHistogram syncint64.Histogram -var replicationHistogram syncint64.Histogram -var connectHistogram syncint64.Histogram -var ssConnectHistogram syncint64.Histogram - -type CMetric int - -const ( - RRDropMaxRetry CMetric = CMetric(iota) - RRDropQueueFull - RRDropRecExpired - Accept - Close - RAPI - ReqProc - ProcErr -) - -type Tags struct { - TagName string - TagValue string -} - -const ( - Target = string("target") - Endpoint = string("endpoint") - Operation = string("operation") - Status = string("status") - Fatal = string("Fatal") - Error = string("Error") - Warn = string("Warning") - Success = string("Success") - SS_RB_expire = string("SS_RB_expire") - SSReqTimeout = string("SSReqTimeout") -) - -type countMetric struct { - metricName string - metricDesc string - counter syncint64.Counter - createCounter *sync.Once -} - -var countMetricMap map[CMetric]*countMetric = map[CMetric]*countMetric{ - RRDropMaxRetry: {"RR_Drop_MaxRetry", "Records dropped in replication queue due to max retry failures", nil, &rrDropMaxRetryCounterOnce}, - RRDropQueueFull: {"RR_Drop_QueueFull", "Records dropped in replication queue due to queue is full", nil, &rrDropQueueFullCounterOnce}, - RRDropRecExpired: {"RR_Drop_RecExpired", "Records dropped in replication queue due to expiry of records", nil, &rrDropRecExpiredCounterOnce}, - Accept: {"accept", "Accepting incoming connections", nil, &acceptCounterOnce}, - Close: {"close", "Closing incoming connections", nil, &closeCounterOnce}, - RAPI: {"rapi", "Processing of replicated requests", nil, &rapiCounterOnce}, - ReqProc: {"ReqProc", "Processing of replicated requests", nil, &reqProcCounterOnce}, - ProcErr: {"ProcErr", "Processing of replicated requests", nil, &procErrCounterOnce}, -} - -type ( - ServerType int -) - -const ( - SvrTypeProxy = ServerType(1) - SvrTypeStorage = ServerType(2) - SvrTypeClusterMgr = ServerType(3) - SvrTypeAll = ServerType(6) ) -// default OTEL configurations point to QA collector -var DEFAULT_OTEL_COLLECTOR_PROTOCOL string = "http" -var DEFAULT_GRPC_OTEL_COLLECTOR_PORT string = "30705" -var DEFAULT_HTTP_OTEL_COLLECTOR_PORT string = "30706" -var COLLECTOR_POLLING_INTERVAL_SECONDS int32 = 5 - -const JUNO_METRIC_PREFIX = "juno.server." -const MeterName = "juno-server-meter" - var OTEL_COLLECTOR_PROTOCOL string = DEFAULT_OTEL_COLLECTOR_PROTOCOL -// OTEl Status -const ( - StatusSuccess string = "SUCCESS" - StatusFatal string = "FATAL" - StatusError string = "ERROR" - StatusWarning string = "WARNING" - StatusUnknown string = "UNKNOWN" -) - -var ( - meterProvider *metric.MeterProvider -) - func Initialize(args ...interface{}) (err error) { glog.Info("Juno OTEL initialized") sz := len(args) @@ -166,6 +59,7 @@ func Initialize(args ...interface{}) (err error) { glog.Error(err) return } + c.Validate() c.Dump() if c.Enabled { // Initialize only if OTEL is enabled @@ -174,36 +68,84 @@ func Initialize(args ...interface{}) (err error) { return } +func Finalize() { + // Shutdown the Go routines for histograms + for _, val := range histMetricMap { + if val.histogram != nil && val.doneCh != nil { + close(val.doneCh) + } + } + + // Shutdown the Go routines for counters that are active + for _, val := range countMetricMap { + if val.counter != nil && val.doneCh != nil { + close(val.doneCh) + } + } +} + func InitMetricProvider(config *otelCfg.Config) { if meterProvider != nil { - fmt.Printf("Retrung as meter is already available") return } - //TODO Remove this after testing otelCfg.OtelConfig = config ctx := context.Background() - // View to customize histogram buckets and rename a single histogram instrument. repBucketsView := metric.NewView( metric.Instrument{ - Name: "*replication*", + Name: PopulateJunoMetricNamePrefix("replication"), Scope: instrumentation.Scope{Name: MeterName}, }, metric.Stream{ - Name: "replication", Aggregation: aggregation.ExplicitBucketHistogram{ - Boundaries: []float64{64, 128, 256, 512, 1024, 2048, 4096}, + Boundaries: config.HistogramBuckets.Replication, }, - }) + }, + ) + + ssConnBucketsView := metric.NewView( + metric.Instrument{ + Name: PopulateJunoMetricNamePrefix("ssConnection"), + Scope: instrumentation.Scope{Name: MeterName}, + }, + metric.Stream{ + Aggregation: aggregation.ExplicitBucketHistogram{ + Boundaries: config.HistogramBuckets.SsConnect, + }, + }, + ) - provider, err := NewMeterProvider(ctx, *config, repBucketsView) + inboundBucketsView := metric.NewView( + metric.Instrument{ + Name: PopulateJunoMetricNamePrefix("inbound"), + Scope: instrumentation.Scope{Name: MeterName}, + }, + metric.Stream{ + Aggregation: aggregation.ExplicitBucketHistogram{ + Boundaries: config.HistogramBuckets.Inbound, + }, + }, + ) + + outboundBucketsView := metric.NewView( + metric.Instrument{ + Name: PopulateJunoMetricNamePrefix("outbound_connection"), + Scope: instrumentation.Scope{Name: MeterName}, + }, + metric.Stream{ + Aggregation: aggregation.ExplicitBucketHistogram{ + Boundaries: config.HistogramBuckets.OutboundConnection, + }, + }, + ) + + var err error + meterProvider, err = NewMeterProvider(ctx, *config, repBucketsView, ssConnBucketsView, inboundBucketsView, outboundBucketsView) if err != nil { log.Fatal(err) } - provider.Meter(MeterName) - global.SetMeterProvider(provider) } func NewMeterProvider(ctx context.Context, cfg otelCfg.Config, vis ...metric.View) (*metric.MeterProvider, error) { @@ -216,32 +158,32 @@ func NewMeterProvider(ctx context.Context, cfg otelCfg.Config, vis ...metric.Vie // Set the reader collection periord to 10 seconds (default 60). reader := metric.NewPeriodicReader(exp, metric.WithInterval(time.Duration(cfg.Resolution)*time.Second)) - meterProvider = metric.NewMeterProvider( + metProvider := metric.NewMeterProvider( metric.WithResource(res), metric.WithReader(reader), metric.WithView(vis...), ) - return meterProvider, nil + return metProvider, nil } func NewHTTPExporter(ctx context.Context) (metric.Exporter, error) { + header := make(map[string]string) var deltaTemporalitySelector = func(metric.InstrumentKind) metricdata.Temporality { return metricdata.DeltaTemporality } if otelCfg.OtelConfig.UseTls == true { return otlpmetrichttp.New( ctx, otlpmetrichttp.WithEndpoint(otelCfg.OtelConfig.Host+":"+fmt.Sprintf("%d", otelCfg.OtelConfig.Port)), - //otlpmetrichttp.WithInsecure(), // WithTimeout sets the max amount of time the Exporter will attempt an // export. - //func(metric.InstrumentKindSyncHistogram){return metricdata.DeltaTemporality} - otlpmetrichttp.WithTimeout(7*time.Second), + otlpmetrichttp.WithTimeout(20*time.Second), otlpmetrichttp.WithCompression(otlpmetrichttp.NoCompression), otlpmetrichttp.WithTemporalitySelector(deltaTemporalitySelector), + otlpmetrichttp.WithHeaders(header), otlpmetrichttp.WithRetry(otlpmetrichttp.RetryConfig{ // Enabled indicates whether to not retry sending batches in case // of export failure. - Enabled: true, + Enabled: false, // InitialInterval the time to wait after the first failure before // retrying. InitialInterval: 1 * time.Second, @@ -252,8 +194,9 @@ func NewHTTPExporter(ctx context.Context) (metric.Exporter, error) { // MaxElapsedTime is the maximum amount of time (including retries) // spent trying to send a request/batch. Once this value is // reached, the data is discarded. - MaxElapsedTime: 240 * time.Second, + MaxElapsedTime: 20 * time.Second, }), + otlpmetrichttp.WithURLPath(otelCfg.OtelConfig.UrlPath), ) } else { return otlpmetrichttp.New( @@ -263,12 +206,14 @@ func NewHTTPExporter(ctx context.Context) (metric.Exporter, error) { otlpmetrichttp.WithTimeout(7*time.Second), otlpmetrichttp.WithCompression(otlpmetrichttp.NoCompression), otlpmetrichttp.WithTemporalitySelector(deltaTemporalitySelector), + otlpmetrichttp.WithHeaders(header), otlpmetrichttp.WithRetry(otlpmetrichttp.RetryConfig{ Enabled: true, InitialInterval: 1 * time.Second, MaxInterval: 10 * time.Second, MaxElapsedTime: 240 * time.Second, }), + otlpmetrichttp.WithURLPath(otelCfg.OtelConfig.UrlPath), ) } @@ -284,73 +229,43 @@ func IsEnabled() bool { return meterProvider != nil } -func GetHistogramForOperation() (syncint64.Histogram, error) { - var err error - apiHistogramOnce.Do(func() { - meter := global.Meter(MeterName) - apiHistogram, err = meter.SyncInt64().Histogram( - PopulateJunoMetricNamePrefix("inbound"), - instrument.WithDescription("Histogram for Juno API"), - instrument.WithUnit(unit.Milliseconds), - ) - - }) - return apiHistogram, err -} - -func GetHistogramForReplication() (syncint64.Histogram, error) { - var err error - replicationHistogramOnce.Do(func() { - meter := global.Meter(MeterName) - replicationHistogram, err = meter.SyncInt64().Histogram( - PopulateJunoMetricNamePrefix("replication"), - instrument.WithDescription("Histogram for Juno replication"), - instrument.WithUnit(unit.Milliseconds), - ) - - }) - return replicationHistogram, err -} - -func GetHistogramForReplicationConnect() (syncint64.Histogram, error) { - var err error - connectHistogramOnce.Do(func() { - meter := global.Meter(MeterName) - connectHistogram, err = meter.SyncInt64().Histogram( - PopulateJunoMetricNamePrefix("outbound_connection"), - instrument.WithDescription("Histogram for Juno connection"), - instrument.WithUnit(unit.Milliseconds), - ) - - }) - return connectHistogram, err -} - -func GetHistogramForSSConnect() (syncint64.Histogram, error) { - var err error - ssConnectHistogramOnce.Do(func() { - meter := global.Meter(MeterName) - ssConnectHistogram, err = meter.SyncInt64().Histogram( - PopulateJunoMetricNamePrefix("ssConnection"), - instrument.WithDescription("Histogram for Juno SS connection failure"), - instrument.WithUnit(unit.Milliseconds), - ) - - }) - return ssConnectHistogram, err +func getHistogram(histName CMetric) (chan DataPoint, error) { + if histMetric, ok := histMetricMap[histName]; ok { + histMetric.createHistogram.Do(func() { + meter := meterProvider.Meter(MeterName) + histMetric.histogram, _ = meter.Int64Histogram( + histMetric.metricName, + instrument.WithDescription(histMetric.metricDesc), + instrument.WithUnit(histMetric.metricUnit), + ) + histMetric.doneCh = make(chan bool) + histMetric.histogramCh = make(chan DataPoint, histChannelSize) + go doWriteHistogram(histMetric.histogramCh, histMetric.doneCh, histMetric.histogram) + }) + if histMetric.histogramCh != nil { + return histMetric.histogramCh, nil + } else { + return nil, errors.New("Histogram Object not Ready") + } + } else { + return nil, errors.New("No Such Histogram exists") + } } -func GetCounter(counterName CMetric) (syncint64.Counter, error) { +func GetCounter(counterName CMetric) (chan DataPoint, error) { if counterMetric, ok := countMetricMap[counterName]; ok { counterMetric.createCounter.Do(func() { - meter := global.Meter(MeterName) - counterMetric.counter, _ = meter.SyncInt64().Counter( + meter := meterProvider.Meter(MeterName) + counterMetric.counter, _ = meter.Int64Counter( PopulateJunoMetricNamePrefix(counterMetric.metricName), instrument.WithDescription(counterMetric.metricDesc), ) + counterMetric.doneCh = make(chan bool) + counterMetric.counterCh = make(chan DataPoint, counterChannelSize) + go doWriteCounter(counterMetric.counterCh, counterMetric.doneCh, counterMetric.counter) }) - if counterMetric.counter != nil { - return counterMetric.counter, nil + if counterMetric.counterCh != nil { + return counterMetric.counterCh, nil } else { return nil, errors.New("Counter Object not Ready") } @@ -360,154 +275,130 @@ func GetCounter(counterName CMetric) (syncint64.Counter, error) { } // This is the pp.app.intbound metric -func RecordOperation(opType string, status string, latency int64) { - ctx := context.Background() - if operation, err := GetHistogramForOperation(); err == nil { - commonLabels := []attribute.KeyValue{ - attribute.String("operation", opType), - attribute.String("status", status), +func RecordOperation(opType string, status proto.OpStatus, latency int64) { + if IsEnabled() { + if opsHistChannel, err := getHistogram(Inbound); err == nil { + commonLabels := instrument.WithAttributes( + attribute.String("endpoint", opType), + attribute.String("error_reason", status.ShortNameString()), + attribute.String("status", logging.CalStatus(status).CalStatus()), + ) + dataPoint := DataPoint{commonLabels, latency} + if opsHistChannel != nil && len(opsHistChannel) < histChannelSize { + opsHistChannel <- dataPoint + } } - operation.Record(ctx, latency, commonLabels...) } } func RecordReplication(opType string, status string, destination string, latency int64) { - ctx := context.Background() - if replication, err := GetHistogramForReplication(); err == nil { - commonLabels := []attribute.KeyValue{ - attribute.String("operation", opType), - attribute.String("status", status), - attribute.String("dest_az", destination), + if IsEnabled() { + if replHistChannel, err := getHistogram(Replication); err == nil { + commonLabels := instrument.WithAttributes( + attribute.String("operation", opType), + attribute.String("status", status), + attribute.String("dest_az", destination), + ) + dataPoint := DataPoint{commonLabels, latency} + if replHistChannel != nil && len(replHistChannel) < histChannelSize { + replHistChannel <- dataPoint + } } - replication.Record(ctx, latency, commonLabels...) } } func RecordSSConnection(endpoint string, status string, latency int64) { - ctx := context.Background() - if ssConnect, err := GetHistogramForSSConnect(); err == nil { - commonLabels := []attribute.KeyValue{ - attribute.String("endpoint", endpoint), - attribute.String("status", status), + if IsEnabled() { + if ssConnHistChannel, err := getHistogram(SSConnection); err == nil { + commonLabels := instrument.WithAttributes( + attribute.String("endpoint", endpoint), + attribute.String("status", status), + ) + dataPoint := DataPoint{commonLabels, latency} + if ssConnHistChannel != nil && len(ssConnHistChannel) < histChannelSize { + ssConnHistChannel <- dataPoint + } } - ssConnect.Record(ctx, latency, commonLabels...) } } func RecordOutboundConnection(endpoint string, status string, latency int64) { - ctx := context.Background() - if requestLatency, err := GetHistogramForReplicationConnect(); err == nil { - commonLabels := []attribute.KeyValue{ - attribute.String("endpoint", endpoint), - attribute.String("status", status), + if IsEnabled() { + if outBoundHistChannel, err := getHistogram(OutboundConnection); err == nil { + commonLabels := instrument.WithAttributes( + attribute.String(Endpoint, endpoint), + attribute.String("status", status), + ) + dataPoint := DataPoint{commonLabels, latency} + if outBoundHistChannel != nil && len(outBoundHistChannel) < histChannelSize { + outBoundHistChannel <- dataPoint + } } - requestLatency.Record(ctx, latency, commonLabels...) } } func RecordCount(counterName CMetric, tags []Tags) { - ctx := context.Background() - if counter, err := GetCounter(counterName); err == nil { - if len(tags) != 0 { - // commonLabels := []attribute.KeyValue{ - // attribute.String("endpoint", endpoint), - // } - commonLabels := covertTagsToOTELAttributes(tags) - counter.Add(ctx, 1, commonLabels...) + if IsEnabled() { + if counterChannel, err := GetCounter(counterName); err == nil { + var commonLabels instrument.MeasurementOption + if len(tags) != 0 { + commonLabels = covertTagsToOTELAttributes(tags) + } + dataPoint := DataPoint{commonLabels, 1} + if counterChannel != nil && len(counterChannel) < counterChannelSize { + counterChannel <- dataPoint + } } else { - counter.Add(ctx, 1) + glog.Error(err) } - } else { - glog.Error(err) } } -func covertTagsToOTELAttributes(tags []Tags) (attr []attribute.KeyValue) { - attr = make([]attribute.KeyValue, len(tags)) +func covertTagsToOTELAttributes(tags []Tags) instrument.MeasurementOption { + attr := make([]attribute.KeyValue, len(tags)) for i := 0; i < len(tags); i++ { attr[i] = attribute.String(tags[i].TagName, tags[i].TagValue) } - return + return instrument.WithAttributes(attr...) } func PopulateJunoMetricNamePrefix(metricName string) string { return JUNO_METRIC_PREFIX + metricName } -// getEnvFromSyshieraYaml returns the env: line from /etc/syshiera.yaml -func getEnvFromSyshieraYaml() (string, error) { - filePath := "/etc/syshiera.yaml" - file, err := os.Open(filePath) - if err != nil { - return "", err - } - defer file.Close() - fileReader := bufio.NewReader(file) - scanner := bufio.NewScanner(fileReader) - for scanner.Scan() { - line := scanner.Text() - err = scanner.Err() - if err != nil { - if err == io.EOF { - break - } - return "", err - } - pos := strings.Index(line, "pp_az: ") - if pos == -1 { - continue - } - return strings.TrimSpace(line[3:len(line)]), nil - } - err = errors.New("dc: not found in /etc/syshiera.yaml") - return "", err -} - func getResourceInfo(appName string) *resource.Resource { - colo, _err := getEnvFromSyshieraYaml() - if _err != nil { - colo = "qa" - } hostname, _ := os.Hostname() - resource := resource.NewWithAttributes("empty resource", - semconv.HostNameKey.String(hostname), - semconv.HostTypeKey.String("BM"), - semconv.ServiceNameKey.String(appName), - attribute.String("az", colo), + attribute.String("host", hostname), attribute.String("application", appName), ) return resource } -// func NewGRPCExporter(ctx context.Context) (metric.Exporter, error) { -// ctx, cancel := context.WithTimeout(ctx, time.Second*10) -// defer cancel() - -// // Exponential back-off strategy. -// backoffConf := backoff.DefaultConfig -// // You can also change the base delay, multiplier, and jitter here. -// backoffConf.MaxDelay = 240 * time.Second - -// conn, err := grpc.DialContext( -// ctx, -// string(DEFAULT_OTEL_COLLECTOR__IP+":"+DEFAULT_GRPC_OTEL_COLLECTOR_PORT), -// grpc.WithInsecure(), -// grpc.WithBlock(), -// grpc.WithConnectParams(grpc.ConnectParams{ -// Backoff: backoffConf, -// // Connection timeout. -// MinConnectTimeout: 5 * time.Second, -// }), -// ) -// if err != nil { -// return nil, err -// } - -// return otlpmetricgrpc.New(ctx, -// otlpmetricgrpc.WithGRPCConn(conn), -// // WithTimeout sets the max amount of time the Exporter will attempt an -// // export. -// otlpmetricgrpc.WithTimeout(7*time.Second), -// ) -// } +func doWriteHistogram(histChannel chan DataPoint, doneCh chan bool, hist instrument.Int64Histogram) { + ctx := context.Background() + for { + select { + case dataPoint := <-histChannel: + hist.Record(ctx, dataPoint.data, dataPoint.attr) + case <-doneCh: + return + } + } +} + +func doWriteCounter(counterChannel chan DataPoint, doneCh chan bool, count instrument.Int64Counter) { + ctx := context.Background() + for { + select { + case dataPoint := <-counterChannel: + if dataPoint.attr != nil { + count.Add(ctx, dataPoint.data, dataPoint.attr) + } else { + count.Add(ctx, dataPoint.data) + } + case <-doneCh: + return + } + } +} diff --git a/pkg/logging/otel/statsLogger.go b/pkg/logging/otel/statsLogger.go index f93031f5..8996e68f 100644 --- a/pkg/logging/otel/statsLogger.go +++ b/pkg/logging/otel/statsLogger.go @@ -23,172 +23,81 @@ import ( "errors" "fmt" "juno/pkg/stats" + "juno/third_party/forked/golang/glog" "runtime" "strconv" - "sync" - "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" + "go.opentelemetry.io/otel/metric" ) -const ( - MachineCpuUsed string = string("machineCpuUsed") - ProcessCpuUsed string = string("machineCpuUsed") - MachineMemoryUsed - ProcessMemoryUsed -) - -var ( - machineCpuUsedOnce sync.Once - processCpuUsedOnce sync.Once - machineMemoryUsedOnce sync.Once - processMemoryUsedOnce sync.Once - badShardOnce sync.Once - alertShardOnce sync.Once - warningShardOnce sync.Once - keyCountOnce sync.Once - freeStorageOnce sync.Once - usedStorageOnce sync.Once - LNLevelOnce sync.Once - compSecOnce sync.Once - compCountOnce sync.Once - pendingCompOnce sync.Once - stallOnce sync.Once -) - -var ( - machTime time.Time - machCpuTick uint16 - machUser uint64 - machSystem uint64 -) - -type GaugeMetric struct { - MetricName string - metricDesc string - gaugeMetric asyncfloat64.Gauge - createGauge *sync.Once - stype ServerType -} - -var GaugeMetricMap map[string]*GaugeMetric = map[string]*GaugeMetric{ - "mCPU": {"machCpuUsed", "CPU utilization of the host", nil, &machineCpuUsedOnce, SvrTypeAll}, - "pCPU": {"procCpuUsed", "CPU utilization of individual Juno instance", nil, &processCpuUsedOnce, SvrTypeAll}, - "mMem": {"machMemUsed", "Memory utilization of the host", nil, &machineMemoryUsedOnce, SvrTypeAll}, - "pMem": {"procMemUsed", "Memory utilization of individual Juno instance", nil, &processMemoryUsedOnce, SvrTypeAll}, - - "nBShd": {"badShard", "Number of bad shards", nil, &badShardOnce, SvrTypeProxy}, - "nAShd": {"alertShard", "number of shards with no redundancy", nil, &alertShardOnce, SvrTypeProxy}, - "nWShd": {"warningShard", "number of shards with bad SS", nil, &warningShardOnce, SvrTypeProxy}, - - "keys": {"key_count", "Key Counte in rocksDB", nil, &keyCountOnce, SvrTypeStorage}, - "free": {"free_mb_storage_space", "Free Storage Space (mbytes)", nil, &freeStorageOnce, SvrTypeStorage}, - "used": {"storage_used_mb", "Used Storage Space (mbytes)", nil, &usedStorageOnce, SvrTypeStorage}, - "LN": {"LN_level", "Max LN Level in Rocksdb", nil, &LNLevelOnce, SvrTypeStorage}, - "compSec": {"comp_sec", "Compaction Sec", nil, &compSecOnce, SvrTypeStorage}, - "compCount": {"comp_count", "Compaction Count", nil, &compCountOnce, SvrTypeStorage}, - "pCompKB": {"pending_comp_kbytes", "Pending Compaction KBytes", nil, &pendingCompOnce, SvrTypeStorage}, - "stall": {"stall_write_rate", "Actural Delayed Write Rate", nil, &stallOnce, SvrTypeStorage}, -} - -// Represents the list of workers with stats -type CurrentStatsData struct { - WorkerState []WorkerStats -} - -// Represents list of stats emitted by a worker -type WorkerStats struct { - StatData []StateData -} - -// Represents stats by worker -type StateData struct { - Name string - Value float64 - Dimensions []attribute.KeyValue -} - -var CurrStatsData CurrentStatsData - func InitSystemMetrics(serverType ServerType, workerStats [][]stats.IState) { - meter := global.Meter(MeterName) - var stateLogGauge []instrument.Asynchronous = make([]instrument.Asynchronous, len(GaugeMetricMap)) - var i int = 0 - //InitMachCpuUsage() - for _, element := range GaugeMetricMap { + meter := meterProvider.Meter(MeterName) + var metricList []metric.Observable = make([]metric.Observable, 0, len(GaugeMetricList)) + for _, element := range GaugeMetricList { if element.stype == serverType || element.stype == SvrTypeAll { element.createGauge.Do(func() { - // TODO instead of element use GaugeMetricMap[index] - element.gaugeMetric, _ = meter.AsyncFloat64().Gauge( + var err error + element.gaugeMetric, err = meter.Float64ObservableGauge( PopulateJunoMetricNamePrefix(element.MetricName), - //instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription(element.metricDesc), + metric.WithDescription(element.metricDesc), ) - stateLogGauge[i] = element.gaugeMetric - i++ + metricList = append(metricList, element.gaugeMetric) + if err != nil { + glog.Error("FloatObservable creation failed : ", err.Error()) + } }) } } - if err := meter.RegisterCallback( - stateLogGauge, - func(ctx context.Context) { - wstate := getMetricData(workerStats) - for _, workerState := range wstate { - for _, state := range workerState.StatData { - gMetric, ok := GaugeMetricMap[state.Name] - if ok { - if gMetric.gaugeMetric != nil { - gMetric.gaugeMetric.Observe(ctx, state.Value, state.Dimensions...) - } - } + if _, err := meter.RegisterCallback(func(_ context.Context, o metric.Observer) error { + getMetricData(workerStats) + for _, metric := range GaugeMetricList { + if metric.gaugeMetric != nil { + for _, stat := range metric.stats { + o.ObserveFloat64(metric.gaugeMetric, stat.Value, stat.Dimensions) } } - }, - ); err != nil { - ///Just ignore + } + return nil + }, metricList...); err != nil { + glog.Error("Error Registering call back : ", err.Error()) } + } -func getMetricData(workerStats [][]stats.IState) []WorkerStats { +func getMetricData(workerStats [][]stats.IState) { numWorkers := len(workerStats) - var wsd []WorkerStats - wsd = make([]WorkerStats, numWorkers) - for wi := 0; wi < numWorkers; wi++ { // For number of workers - var sdata []StateData - sdata = make([]StateData, 0, len(workerStats[wi])) - for _, v := range workerStats[wi] { // For number of statistics - if fl, err := strconv.ParseFloat(v.State(), 64); err == nil { - if wrstats, err := writeMetricsData(wi, v.Header(), fl); err == nil { - sdata = append(sdata, wrstats) + for _, metric := range GaugeMetricList { + if metric.gaugeMetric != nil { + metric.stats = make([]StateData, 0, numWorkers) + for wi := 0; wi < numWorkers; wi++ { // For number of workers + for _, v := range workerStats[wi] { // For number of statistics + if metric.MetricShortName == v.Header() { + if fl, err := strconv.ParseFloat(v.State(), 64); err == nil { + if wrstats, err := writeMetricsData(wi, v.Header(), fl); err == nil { + metric.stats = append(metric.stats, wrstats) + } + } + } } } } - wsd[wi].StatData = sdata } - - return wsd + return } func writeMetricsData(wid int, key string, value float64) (StateData, error) { var data StateData - _, ok := GaugeMetricMap[key] - if !ok { - // Only record the metrics in the map - return data, errors.New("Metirc not found in Map") - } - if (key == "mCPU" || key == "mMem") && wid != 0 { + if (key == "free" || key == "used") && wid != 0 { // Log system metric only for worker 0 return data, errors.New("Do not log machine resource utilization for all workers except for worker 0") } data.Name = key data.Value = value - data.Dimensions = []attribute.KeyValue{attribute.String("id", fmt.Sprintf("%d", wid))} + data.Dimensions = metric.WithAttributes(attribute.String("id", fmt.Sprintf("%d", wid))) return data, nil } diff --git a/pkg/logging/otel/test/logger_test.go b/pkg/logging/otel/test/logger_test.go index f8117449..7fad9ded 100644 --- a/pkg/logging/otel/test/logger_test.go +++ b/pkg/logging/otel/test/logger_test.go @@ -19,12 +19,13 @@ // This test uses a mock to validate the metrics and the sends the metric to // QA OTEL collector for verifying the results in Sfx UI. -package test +package otel import ( "fmt" "juno/pkg/logging/otel" config "juno/pkg/logging/otel/config" + "juno/pkg/proto" "juno/pkg/stats" "testing" "time" @@ -50,13 +51,13 @@ func TestJunoOperation(t *testing.T) { time.Sleep(time.Duration(1) * time.Second) - otel.RecordOperation("Create", "SUCCESS", 2000) - otel.RecordOperation("Get", "SUCCESS", 1000) - otel.RecordOperation("Update", "SUCCESS", 3000) - otel.RecordOperation("Destroy", "SUCCESS", 500) - otel.RecordOperation("Set", "SUCCESS", 2500) + otel.RecordOperation("Create", proto.OpStatusNoError, 2000) + otel.RecordOperation("Get", proto.OpStatusNoError, 1000) + otel.RecordOperation("Update", proto.OpStatusNoError, 3000) + otel.RecordOperation("Destroy", proto.OpStatusNoError, 500) + otel.RecordOperation("Set", proto.OpStatusNoError, 2500) - otel.RecordOperation("Create", "ERROR", 2000) + otel.RecordOperation("Create", proto.OpStatusBadParam, 2000) time.Sleep(time.Duration(SfxConfig.Resolution) * time.Second) @@ -357,14 +358,14 @@ func TestJunoStats(t *testing.T) { for i := 0; i < 2; i++ { workerStats[i] = append(workerStats[i], []stats.IState{ - stats.NewUint32State(&mcpu, "mCPU", "Machine CPU usage"), + stats.NewUint32State(&mcpu, "pCPU", "Process CPU usage"), stats.NewUint16State(&bshd, "nBShd", "number of Bad Shards"), }...) } otel.InitSystemMetrics(otel.SvrTypeProxy, workerStats) - time.Sleep(time.Duration(SfxConfig.Resolution) * time.Second) + time.Sleep(time.Duration(SfxConfig.Resolution+10) * time.Second) v1m := mc.GetMetrics() @@ -372,11 +373,11 @@ func TestJunoStats(t *testing.T) { t.Errorf("Test fail") } else { for i := 0; i < 2; i++ { - if v1m[i].GetName() == "juno.server.machCpuUsed" { + if v1m[i].GetName() == "pp.juno.server.proc_cpu_used" { if v1m[i].GetGauge().DataPoints[0].GetAsDouble() != 30 { t.Errorf("CPU utilization is incorrect %f", v1m[i].GetGauge().DataPoints[0].GetAsDouble()) } - } else if v1m[i].GetName() == "juno.server.badShard" { + } else if v1m[i].GetName() == "pp.juno.server.bad_shard" { if v1m[i].GetGauge().DataPoints[0].GetAsDouble() != 5 { t.Errorf("Bad Shard Count is incorrect %f", v1m[i].GetGauge().DataPoints[0].GetAsDouble()) } diff --git a/pkg/logging/otel/test/mock_collector.go b/pkg/logging/otel/test/mock_collector.go index 5b29af06..fc8c1989 100644 --- a/pkg/logging/otel/test/mock_collector.go +++ b/pkg/logging/otel/test/mock_collector.go @@ -16,7 +16,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package test +package otel import ( "bytes" @@ -31,6 +31,7 @@ import ( "encoding/pem" "fmt" "io" + "io/ioutil" "math/big" mathrand "math/rand" "net" @@ -112,7 +113,7 @@ func (c *mockCollector) serveMetrics(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } - + // fmt.Println("---------------raw req--------------", rawRequest) request, err := unmarshalMetricsRequest(rawRequest, r.Header.Get("content-type")) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -122,7 +123,7 @@ func (c *mockCollector) serveMetrics(w http.ResponseWriter, r *http.Request) { c.spanLock.Lock() defer c.spanLock.Unlock() - // fmt.Println("---------------serveMetrics--------------", request) + fmt.Println("---------------serveMetrics--------------", request) c.metricsStorage.AddMetrics(request) } @@ -161,7 +162,7 @@ func readRequest(r *http.Request) ([]byte, error) { if r.Header.Get("Content-Encoding") == "gzip" { return readGzipBody(r.Body) } - return io.ReadAll(r.Body) + return ioutil.ReadAll(r.Body) } func readGzipBody(body io.Reader) ([]byte, error) { @@ -334,8 +335,13 @@ func NewMetricsStorage() MetricsStorage { func (s *MetricsStorage) AddMetrics(request *collectormetricpb.ExportMetricsServiceRequest) { for _, rm := range request.GetResourceMetrics() { // TODO (rghetia) handle multiple resource and library info. + fmt.Println("---------------AddMetrics------------------", rm) + if len(rm.ScopeMetrics) > 0 { s.metrics = append(s.metrics, rm.ScopeMetrics[0].Metrics...) + fmt.Println("Metric added successfully") + } else { + fmt.Println("Metrics added filed") } // if len(rm.InstrumentationLibraryMetrics) > 0 { diff --git a/pkg/sec/sslctx.go b/pkg/sec/sslctx.go index 1dfa0bde..0b4af3ed 100644 --- a/pkg/sec/sslctx.go +++ b/pkg/sec/sslctx.go @@ -28,6 +28,9 @@ import ( type ( Conn interface { GetStateString() string + GetTLSVersion() string + GetCipherName() string + DidResume() string IsServer() bool Handshake() error GetNetConn() net.Conn From 72a9a259e6cf5e46ff6c8d4bce664e3aed1247d6 Mon Sep 17 00:00:00 2001 From: jostanislas <126283099+jostanislas@users.noreply.github.com> Date: Thu, 21 Dec 2023 21:10:23 -0600 Subject: [PATCH 23/27] Fixed build failure (#171) Co-authored-by: Stanislas --- pkg/sec/gotlsimpl.go | 102 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/pkg/sec/gotlsimpl.go b/pkg/sec/gotlsimpl.go index 47b026a5..67e0d443 100644 --- a/pkg/sec/gotlsimpl.go +++ b/pkg/sec/gotlsimpl.go @@ -67,6 +67,108 @@ func (c *TlsConn) GetStateString() string { return statStr } +func (c *TlsConn) GetTLSVersion() string { + if c.conn != nil { + stat := c.conn.ConnectionState() + return GetVersionName(stat.Version) + } + return "none" +} + +func GetVersionName(ver uint16) string { + switch ver { + case tls.VersionTLS10: + return "TLSv1" + case tls.VersionTLS11: + return "TLSv1.1" + case tls.VersionTLS12: + return "TLSv1.2" + case tls.VersionTLS13: + return "TLSv1.3" + default: + return "" + } +} + +func (c *TlsConn) GetCipherName() string { + if c.conn != nil { + stat := c.conn.ConnectionState() + return GetCipherName(stat.CipherSuite) + } + return "none" +} + +func GetCipherName(cipher uint16) string { + switch cipher { + case tls.TLS_RSA_WITH_RC4_128_SHA: + return "RSA-RC4-128-SHA" + case tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + return "RSA-3DES-EDE-CBC-SHA" + case tls.TLS_RSA_WITH_AES_128_CBC_SHA: + return "AES128-SHA" + case tls.TLS_RSA_WITH_AES_256_CBC_SHA: + return "AES256-SHA" + case tls.TLS_RSA_WITH_AES_128_CBC_SHA256: + return "RSA-AES-128-CBC-SHA256" + case tls.TLS_RSA_WITH_AES_128_GCM_SHA256: + return "RSA-AES-128-GCM-SHA256" + case tls.TLS_RSA_WITH_AES_256_GCM_SHA384: + return "RSA-AES-256-GCM-SHA384" + case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + return "ECDHE_ECDSA_RC4_128_SHA" + case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + return "ECDHE_ECDSA_AES128_CBC_SHA" + case tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + return "ECDHE_ECDSA_AES_256_CBC_SHA" + case tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: + return "ECDHE_RSA_RC4_128_SHA" + case tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + return "ECDHE_RSA_3DES_EDE_CBC_SHA" + case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + return "ECDHE_RSA_AES128_CBC_SHA" + case tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return "ECDHE_RSA_AES_256_CBC_SHA" + case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + return "ECDHE_ECDSA_AES128_CBC_SHA256" + case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + return "ECDHE_RSA_AES128_CBC_SHA256" + case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + return "ECDHE_RSA_AES128_GCM_SHA256" + case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + return "ECDHE_ECDSA_AES128_GCM_SHA256" + case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + return "ECDHE_RSA_AES256_GCM_SHA384" + case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + return "ECDHE_ECDSA_AES256_GCM_SHA384" + case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: + return "ECDHE_RSA_CHACHA20_POLY1305" + case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: + return "ECDHE_ECDSA_CHACHA20_POLY1305" + case tls.TLS_AES_128_GCM_SHA256: + return "TLS_AES_128_GCM_SHA256" + case tls.TLS_AES_256_GCM_SHA384: + return "TLS_AES_256_GCM_SHA384" + case tls.TLS_CHACHA20_POLY1305_SHA256: + return "TLS_CHACHA20_POLY1305_SHA256" + case tls.TLS_FALLBACK_SCSV: + return "TLS_FALLBACK_SCSV" + default: + return "unknown" + } +} + +func (c *TlsConn) DidResume() string { + if c.conn != nil { + stat := c.conn.ConnectionState() + if stat.DidResume { + return "Yes" + } else { + return "No" + } + } + return "No" +} + func (c *TlsConn) IsServer() bool { return c.isServer } From 16098b1d5811f46cf023f9c448207aa303e6f54b Mon Sep 17 00:00:00 2001 From: art2ip <126820369+art2ip@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:04:09 -0800 Subject: [PATCH 24/27] Fixed etcd join. --- cmd/etcdsvr/etcdsvr.py | 122 ++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/cmd/etcdsvr/etcdsvr.py b/cmd/etcdsvr/etcdsvr.py index 31b46b1d..b4b6dc49 100755 --- a/cmd/etcdsvr/etcdsvr.py +++ b/cmd/etcdsvr/etcdsvr.py @@ -24,6 +24,7 @@ import logging import os import random +import shlex import signal import socket import subprocess @@ -62,13 +63,27 @@ def ip_in_use(ip, port): s.close() return True - except socket.error as e: + except socket.error: pass - except Exception as e: + except Exception: pass return False - + +def run_cmd(cmd, get_result=False): + result = "" + try: + out = None + if get_result: + out = subprocess.PIPE + re = subprocess.run(shlex.split(cmd), stdout=out, stderr=out, + universal_newlines=True, check=True) + result = str(re.stdout) + + except subprocess.CalledProcessError: + pass + return result + class Config(): # Init default. def __init__(self): @@ -95,8 +110,8 @@ def get_members(self): cmd_list = "%s member list" % (etcd_cmd) members = "" try: - members = subprocess.check_output(cmd_list, shell=False) - except subprocess.CalledProcessError as e: + members = run_cmd(cmd_list, get_result=True) + except subprocess.CalledProcessError: pass return members @@ -105,25 +120,23 @@ def display_status(self): os.environ["ETCDCTL_API"] = "3" etcd_cmd = '%s/etcdctl --endpoints="%s"' % (os.getcwd(), self.cluster_endpoints) - cmd_list = "%s member list 2>&1 | cat" % (etcd_cmd) - cmd_status = "%s endpoint status 2>&1 | cat" % (etcd_cmd) - cmd_health = "%s endpoint health 2>&1 | cat" % (etcd_cmd) - - out = etcd_cmd - out += "\n\n===== member list\n" + subprocess.check_output(cmd_list, shell=False) - print(out) - out = "===== endpoint status\n" + subprocess.check_output(cmd_status, shell=False) - print(out) - out = "===== endpoint health\n" + subprocess.check_output(cmd_health, shell=False) - print(out) + cmd_list = "%s member list" % (etcd_cmd) + cmd_status = "%s endpoint status" % (etcd_cmd) + cmd_health = "%s endpoint health" % (etcd_cmd) + + print(etcd_cmd + "\n\n===== member list\n") + run_cmd(cmd_list) + + print("\n===== endpoint status\n") + run_cmd(cmd_status) + + print("\n===== endpoint health\n") + run_cmd(cmd_health) # Join an existing cluster. def join_cluster(self): etcd_cmd = '%s/etcdctl --endpoints="%s"' % (os.getcwd(), self.cluster_endpoints) - cmd_select = "%s member list | grep ', %s, http' | awk -F',' '{print $1}'" % ( - etcd_cmd, self.etcd_name - ) cmd_add = "%s member add %s --peer-urls=%s" % ( etcd_cmd, self.etcd_name, self.peer_url @@ -133,41 +146,48 @@ def join_cluster(self): ok = True err = None - resp = ">> Members:\n" try: os.environ["ETCDCTL_API"] = "3" - resp += self.get_members() + text = self.get_members() hexid = "" - + resp = ">> Members:\n" + text + print(resp) + # Remove the current entry if any - resp += "\n>> Select:\n%s\n\n" % (cmd_select) - hexid = subprocess.check_output(cmd_select, shell=False) + lines = text.split("\n") + for li in lines: + tokens = li.split(", ") + if len(tokens) > 3 and self.etcd_name == tokens[2]: + hexid = tokens[0] + break if len(hexid) > 0: - cmd_remove = "%s member remove %s" % (etcd_cmd, hexid) - resp += "\n>> Remove:\n%s\n\n" % (cmd_remove) - - resp += subprocess.check_output(cmd_remove, stderr=subprocess.STDOUT, shell=False) + cmd_remove = "%s member remove %s\n\n" % (etcd_cmd, hexid) + print("\n>> Remove:\n%s" % (cmd_remove)) + resp += cmd_remove + + run_cmd(cmd_remove) sleep(5) # Add a new entry - resp += "\n>> Add:\n%s\n\n" % (cmd_add) - - resp += subprocess.check_output(cmd_add, stderr=subprocess.STDOUT, shell=False) - - resp += "\n>> Members:\n" - resp += self.get_members() - resp += "\n" - - resp += cmd_rm - resp += "\n" + msg = "\n>> Add:\n%s\n\n" % (cmd_add) + print(msg) + resp += msg + + run_cmd(cmd_add) + msg = "\n>> Members:\n" + self.get_members() + print(msg) + resp += msg + + msg = "\n" + cmd_rm + "\n" + print(msg) + resp += msg except subprocess.CalledProcessError as e: err = e.output ok = False - print(resp) with open("join.log", "w+") as f: f.write(resp) @@ -189,7 +209,6 @@ def add_json_cfg(self): h["advertise-client-urls"] = client_url h["initial-advertise-peer-urls"] = self.peer_url - dir = self.etcd_name + ".etcd" if self.is_existing_cluster: # Join an existing cluster h["initial-cluster-state"] = "existing" @@ -339,23 +358,22 @@ def sig_handler(self, sig, frame): def is_endpoint_healthy(self, wait_time): os.environ["ETCDCTL_API"] = "3" etcd_cmd = '%s/etcdctl --endpoints="%s"' % (os.getcwd(), self.local_endpoint) - cmd_health = "%s endpoint health 2>&1 | cat" % (etcd_cmd) + cmd_health = "%s endpoint health" % (etcd_cmd) result = "" now = int(time()) - for i in range(50): - sleep(2) + for i in range(10): + sleep(5) t = int(time()) - now if t > wait_time: break - if t > 60: - msg = "unhealthy_%s" % (self.etcd_name) + if t > 50: + msg = "unhealthy_%s retry ..." % (self.etcd_name) self.logger.error("[MANAGER] %s" % (msg)) - result = subprocess.check_output(cmd_health, shell=False) - health_check_bytes = str.encode("is healthy") - if health_check_bytes in result: + result = run_cmd(cmd_health, get_result=True) + if "is health" in result: return True self.logger.info("[MANAGER] %s" % (result)) @@ -418,8 +436,8 @@ def watch_and_recycle(self, cfg): print(" ") self.logger.info("[MANAGER] Started etcd process %d" % (self.pid)) - wait_time = 85 + random.randint(0,10) - while False: + wait_time = 60 + random.randint(0,10) + while False: #not self.is_endpoint_healthy(wait_time): if restartCount > 0: self.shutdown_server() @@ -436,6 +454,8 @@ def watch_and_recycle(self, cfg): self.shutdown(-1) # exit print("Starting etcd process %d succeeded." % (self.pid)) + if os.path.exists("join.log"): + os.remove("join.log") self.server.wait() # etcd server has exited. @@ -469,7 +489,7 @@ def watch_and_recycle(self, cfg): with open("./etcdsvr.pid", "w") as f: f.write("%d\n" % (os.getpid())) - + cfg = Config() err = cfg.parse_cfg(False) if err: From 96b7e5d4beae8bf59c1c781c0bd2bc6d8c79e41b Mon Sep 17 00:00:00 2001 From: nepathak Date: Tue, 21 May 2024 11:00:04 -0700 Subject: [PATCH 25/27] Update util pkg --- test/drv/junoload/tsteng.go | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/test/drv/junoload/tsteng.go b/test/drv/junoload/tsteng.go index 8cdd77f8..2c15ce64 100644 --- a/test/drv/junoload/tsteng.go +++ b/test/drv/junoload/tsteng.go @@ -1,21 +1,19 @@ +// Copyright 2023 PayPal Inc. // -// Copyright 2023 PayPal Inc. +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at // -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// http://www.apache.org/licenses/LICENSE-2.0 // +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package main import ( @@ -31,7 +29,7 @@ import ( "github.com/paypal/junodb/third_party/forked/golang/glog" - "juno/pkg/util" + "github.com/paypal/junodb/pkg/util" uuid "github.com/satori/go.uuid" ) From 3d90c89abab0de21d596e56da07872758cd93de7 Mon Sep 17 00:00:00 2001 From: art2ip <126820369+art2ip@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:16:36 -0700 Subject: [PATCH 26/27] Enhancements in go client library. --- cmd/dbscanserv/prime/replicate.go | 35 ++--- cmd/dbscanserv/prime/result.go | 12 +- cmd/tools/cmd/cfg/rtcfg.go | 6 +- cmd/tools/cmd/cli/cli.go | 11 +- cmd/tools/cmd/cli/sscli.go | 9 +- internal/cli/conn.go | 107 +++++++++++++ internal/cli/{proc.go => connector.go} | 122 ++++++++++----- internal/cli/error.go | 29 +++- internal/cli/processor.go | 201 ++++++++++++++++++++----- internal/cli/tracker.go | 8 +- pkg/client/README.md | 164 ++++++++++++++++++++ pkg/client/client.go | 65 ++------ pkg/client/clientimpl.go | 188 +++++++++++++---------- pkg/client/config.go | 87 ++++------- pkg/client/error.go | 24 +-- test/cli/basic_test.go | 145 ++++++++++++++++++ test/cli/cond_test.go | 54 +++++++ test/cli/define.go | 28 ++++ test/cli/main_test.go | 81 ++++++++++ test/cli/util.go | 181 ++++++++++++++++++++++ test/drv/bulkload/bulkload.go | 10 +- test/drv/junoload/junoload.go | 48 +++--- test/functest/setup_test.go | 12 +- test/testutil/server/server.go | 3 +- test/unittest/setup_test.go | 4 +- 25 files changed, 1287 insertions(+), 347 deletions(-) create mode 100644 internal/cli/conn.go rename internal/cli/{proc.go => connector.go} (72%) create mode 100644 pkg/client/README.md create mode 100644 test/cli/basic_test.go create mode 100644 test/cli/cond_test.go create mode 100644 test/cli/define.go create mode 100644 test/cli/main_test.go create mode 100644 test/cli/util.go diff --git a/cmd/dbscanserv/prime/replicate.go b/cmd/dbscanserv/prime/replicate.go index 2a4d392c..134ce707 100644 --- a/cmd/dbscanserv/prime/replicate.go +++ b/cmd/dbscanserv/prime/replicate.go @@ -21,7 +21,6 @@ package prime import ( "errors" - "runtime" "time" "github.com/paypal/junodb/third_party/forked/golang/glog" @@ -34,7 +33,7 @@ import ( var ( secConfig *sec.Config - processor []*cli.Processor + processor *cli.Processor inChan = make(chan *proto.OperationalMessage, 1000) outChan = make(chan bool, 1000) ) @@ -57,39 +56,31 @@ func InitReplicator(proxyAddr string, numConns int) { } if numConns <= 0 { - numConns = 1 + numConns = 2 } if numConns > 4 { numConns = 4 } - processor = make([]*cli.Processor, numConns) - for i := 0; i < numConns; i++ { + processor = cli.NewProcessor( + io.ServiceEndpoint{Addr: proxyAddr, SSLEnabled: secConfig != nil}, + "dbscan", + numConns, // connPoolSize + time.Duration(500*time.Millisecond), // ConnectTimeout + time.Duration(1000*time.Millisecond), // ResponseTimeout + nil) // GetTLSConfig - processor[i] = cli.NewProcessor( - io.ServiceEndpoint{Addr: proxyAddr, SSLEnabled: secConfig != nil}, - "dbscan", - time.Duration(500*time.Millisecond), // ConnectTimeout - time.Duration(1000*time.Millisecond), // RequestTimeout - time.Duration(60*time.Second)) // ConnectRecycleTimeout - - processor[i].Start() - - runtime.SetFinalizer(processor[i], func(p *cli.Processor) { - p.Close() - }) - - go processRequest(i) - } + processor.Start() + go processRequest() } -func processRequest(k int) { +func processRequest() { count := uint64(0) for { select { case op := <-inChan: for i := 0; i < 3; i++ { - reply, err := processor[k].ProcessRequest(op) + reply, err := processor.ProcessRequest(op) if err != nil { if i < 2 { time.Sleep(10 * time.Millisecond) diff --git a/cmd/dbscanserv/prime/result.go b/cmd/dbscanserv/prime/result.go index 9e589903..6d629916 100644 --- a/cmd/dbscanserv/prime/result.go +++ b/cmd/dbscanserv/prime/result.go @@ -88,14 +88,10 @@ func RepairKey(key []byte, display bool) bool { if !found { clientCfg := client.Config{ - Appname: "dbscan", - Namespace: ns, - RetryCount: 1, - ConnectTimeout: util.Duration{500 * time.Millisecond}, - ReadTimeout: util.Duration{500 * time.Millisecond}, - WriteTimeout: util.Duration{500 * time.Millisecond}, - RequestTimeout: util.Duration{1000 * time.Millisecond}, - ConnRecycleTimeout: util.Duration{60 * time.Second}, + Appname: "dbscan", + Namespace: ns, + ConnectTimeout: util.Duration{500 * time.Millisecond}, + ResponseTimeout: util.Duration{1000 * time.Millisecond}, } clientCfg.Server.Addr = proxyAddr diff --git a/cmd/tools/cmd/cfg/rtcfg.go b/cmd/tools/cmd/cfg/rtcfg.go index b1bdb2e0..80bda9ca 100644 --- a/cmd/tools/cmd/cfg/rtcfg.go +++ b/cmd/tools/cmd/cfg/rtcfg.go @@ -31,6 +31,7 @@ import ( "github.com/paypal/junodb/pkg/client" "github.com/paypal/junodb/pkg/cmd" "github.com/paypal/junodb/pkg/etcd" + "github.com/paypal/junodb/pkg/util" ) const ( @@ -78,7 +79,10 @@ func (c *cmdRuntimeConfig) Parse(args []string) (err error) { return } } - c.clientConfig.SetDefault() + c.clientConfig.DefaultTimeToLive = 1800 + c.clientConfig.ConnPoolSize = 1 + c.clientConfig.ConnectTimeout = util.Duration{1000 * time.Millisecond} + c.clientConfig.ResponseTimeout = util.Duration{1000 * time.Millisecond} if cfg, e := c.config.GetConfig("Juno"); e == nil { cfg.WriteTo(&c.clientConfig) diff --git a/cmd/tools/cmd/cli/cli.go b/cmd/tools/cmd/cli/cli.go index d90f1490..3de3c6bf 100644 --- a/cmd/tools/cmd/cli/cli.go +++ b/cmd/tools/cmd/cli/cli.go @@ -47,13 +47,10 @@ const ( ) var defaultConfig = client.Config{ - RetryCount: 1, - DefaultTimeToLive: 1800, - ConnectTimeout: util.Duration{100 * time.Millisecond}, - ReadTimeout: util.Duration{500 * time.Millisecond}, - WriteTimeout: util.Duration{500 * time.Millisecond}, - RequestTimeout: util.Duration{1000 * time.Millisecond}, - ConnRecycleTimeout: util.Duration{9 * time.Second}, + DefaultTimeToLive: 1800, + ConnPoolSize: 1, + ConnectTimeout: util.Duration{100 * time.Millisecond}, + ResponseTimeout: util.Duration{1000 * time.Millisecond}, } type ( diff --git a/cmd/tools/cmd/cli/sscli.go b/cmd/tools/cmd/cli/sscli.go index 2d29078e..861765a7 100644 --- a/cmd/tools/cmd/cli/sscli.go +++ b/cmd/tools/cmd/cli/sscli.go @@ -37,8 +37,8 @@ import ( ) const ( - connectTimeout = 100 * time.Millisecond - requestTimeout = 1000 * time.Millisecond + connectTimeout = 100 * time.Millisecond + responseTimeout = 1000 * time.Millisecond ) type ( @@ -173,9 +173,10 @@ func (s *shardOptionsT) getShardId(key []byte) uint16 { func newProcessor(cfg *client.Config) *cli.Processor { processor := cli.NewProcessor(cfg.Server, cfg.Appname, + 1, // connPoolSize cfg.ConnectTimeout.Duration, - cfg.RequestTimeout.Duration, - cfg.ConnRecycleTimeout.Duration) + cfg.ResponseTimeout.Duration, + nil) // GetTLSConfig processor.Start() return processor } diff --git a/internal/cli/conn.go b/internal/cli/conn.go new file mode 100644 index 00000000..61f6f1eb --- /dev/null +++ b/internal/cli/conn.go @@ -0,0 +1,107 @@ +// +// Copyright 2023 PayPal Inc. +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cli + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "time" + + "github.com/paypal/junodb/pkg/logging" + "github.com/paypal/junodb/pkg/logging/cal" +) + +func TLSInitialized() bool { + return false +} + +func Dial(addr string, timeout time.Duration, getTLSConfig func() *tls.Config) (conn net.Conn, err error) { + var tlsConn *tls.Conn + + if getTLSConfig == nil { + return nil, errors.New("Unable to get TLS config") + } + timeStart := time.Now() + tlsCfg := getTLSConfig() + if tlsCfg == nil { + err = errors.New("Unable to get TLS config") + } else { + dialer := &net.Dialer{Timeout: timeout} + tlsConn, err = tls.DialWithDialer(dialer, "tcp", addr, tlsCfg) + conn = tlsConn + if tlsConn == nil && err == nil { + err = errors.New("Connect failed.") + } + } + + if !cal.IsEnabled() { + return conn, err + } + + // Cal logging + status := cal.StatusSuccess + b := logging.NewKVBuffer() + if err != nil { + status = cal.StatusError + b.Add([]byte("err"), err.Error()) + } else { + b.Add([]byte("ssl"), getConnectionState(tlsConn)) + } + + cal.AtomicTransaction(cal.TxnTypeConnect, addr, status, + time.Since(timeStart), b.Bytes()) + + return conn, err +} + +func getConnectionState(c *tls.Conn) string { + if c == nil { + return "" + } + + st := c.ConnectionState() + rid := 0 + if st.DidResume { + rid = 1 + } + msg := fmt.Sprintf("GoTLS:%s:%s:ssl_r=%d", getVersionName(st.Version), + tls.CipherSuiteName(st.CipherSuite), rid) + + return msg +} + +func getVersionName(ver uint16) string { + switch ver { + case tls.VersionSSL30: + return "SSLv3" + case tls.VersionTLS10: + return "TLSv1" + case tls.VersionTLS11: + return "TLSv1.1" + case tls.VersionTLS12: + return "TLSv1.2" + case tls.VersionTLS13: + return "TLSv1.3" + default: + return "" + } +} diff --git a/internal/cli/proc.go b/internal/cli/connector.go similarity index 72% rename from internal/cli/proc.go rename to internal/cli/connector.go index d67c13b9..913d5021 100644 --- a/internal/cli/proc.go +++ b/internal/cli/connector.go @@ -20,9 +20,13 @@ package cli import ( + "crypto/tls" "io" + + "math/rand" "net" "os" + "sync/atomic" "syscall" "time" @@ -42,6 +46,14 @@ type ( } ) +var ( + connCount int64 +) + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + func (c *Connection) Close() { if c.conn != nil { c.conn.Close() @@ -72,6 +84,8 @@ func (c *Connection) Shutdown() { c.conn.Close() } c.conn = nil + c.beingRecycle = false + glog.Debugf("Close connCount=%d", atomic.AddInt64(&connCount, -1)) } } @@ -137,53 +151,79 @@ func startResponseReader(r io.ReadCloser) <-chan *ReaderResponse { func StartRequestProcessor( server junoio.ServiceEndpoint, sourceName string, + connDone chan<- bool, + connIndex int, connectTimeout time.Duration, - requestTimeout time.Duration, - connRecycleTimeout time.Duration, + responseTimeout time.Duration, + getTLSConfig func() *tls.Config, chDone <-chan bool, chRequest <-chan *RequestContext) (chProcessorDone <-chan bool) { ch := make(chan bool) - go doRequestProcess(server, sourceName, connectTimeout, requestTimeout, connRecycleTimeout, chDone, ch, chRequest) + go doRequestProcess(server, sourceName, connDone, connIndex, + connectTimeout, responseTimeout, getTLSConfig, chDone, ch, chRequest) + return ch } +func resetRecycleTimer( + server junoio.ServiceEndpoint, + connIndex int, + responseTimeout time.Duration, + connRecycleTimer *util.TimerWrapper) { + if connRecycleTimeout <= 0 { + return + } + + t := connRecycleTimeout + // Reduced by a random value between 0 to 20%. + t = t * time.Duration(1000-rand.Intn(200)) / 1000 + if t < 2*responseTimeout { + t = 2 * responseTimeout + } + glog.Debugf("connection=%d addr=%s", connIndex, server.Addr) + connRecycleTimer.Reset(t) +} + func doRequestProcess( server junoio.ServiceEndpoint, sourceName string, + connDone chan<- bool, + connIndex int, connectTimeout time.Duration, - requestTimeout time.Duration, - connRecycleTimeout time.Duration, + responseTimeout time.Duration, + getTLSConfig func() *tls.Config, chDone <-chan bool, chDoneNotify chan<- bool, chRequest <-chan *RequestContext) { - if connRecycleTimeout != 0 { - if connRecycleTimeout < requestTimeout+requestTimeout { - connRecycleTimeout = requestTimeout + requestTimeout - glog.Infof("conntion recycle timeout adjusted to be %s", connRecycleTimeout) - } - } + glog.Debugf("Start connection %d", connIndex) + connRecycleTimer := util.NewTimerWrapper(connRecycleTimeout) active := &Connection{} recycled := &Connection{} - connect := func() (err error) { + connect := func() error { var conn net.Conn - conn, err = junoio.Connect(&server, connectTimeout) + var err error + if server.SSLEnabled && getTLSConfig != nil { + conn, err = Dial(server.Addr, connectTimeout, getTLSConfig) + } else { + conn, err = junoio.Connect(&server, connectTimeout) + } if err != nil { - return + return err } active.conn = conn - active.tracker = newPendingTracker(requestTimeout) + active.tracker = newPendingTracker(responseTimeout) active.chReaderResponse = startResponseReader(conn) - if connRecycleTimeout != 0 { - glog.Debugf("connection recycle in %s", connRecycleTimeout) - connRecycleTimer.Reset(connRecycleTimeout) - } else { - glog.Debugf("connection won't be recycled") - } - return + resetRecycleTimer( + server, + connIndex, + responseTimeout, + connRecycleTimer) + glog.Debugf("Open connCount=%d", atomic.AddInt64(&connCount, 1)) + return nil } var sequence uint32 @@ -191,24 +231,37 @@ func doRequestProcess( var err error connect() + connDone <- true -loop: for { select { case <-chDone: glog.Verbosef("proc done channel got notified") - active.Shutdown() - break loop + active.Shutdown() ///TODO to revisit + return case _, ok := <-connRecycleTimer.GetTimeoutCh(): + connRecycleTimer.Stop() if ok { glog.Debug("connection recycle timer fired") - connRecycleTimer.Stop() + recycled.Shutdown() recycled = active - recycled.beingRecycle = true active = &Connection{} err = connect() if err != nil { glog.Error(err) + active = recycled // reuse current one. + recycled = &Connection{} + resetRecycleTimer( + server, + connIndex, + responseTimeout, + connRecycleTimer) + } else { + recycled.beingRecycle = true + if recycled.tracker != nil && + len(recycled.tracker.pendingQueue) == 0 { + recycled.Shutdown() + } } } else { glog.Errorf("connection recycle timer not ok") @@ -224,7 +277,7 @@ loop: if ok { recycled.tracker.OnTimeout(now) if len(recycled.tracker.pendingQueue) == 0 { - glog.Debugf("close write for the recybled connection as it has handled all the pending request(s)") + // close write for the recybled connection as it has handled all the pending request(s)") recycled.Shutdown() } else { glog.Debugf("being recycled request timeout") @@ -235,10 +288,10 @@ loop: } case r, ok := <-chRequest: - if !ok { // shouldn't happen as it won't be closed - break loop + if !ok { // shouldn't happen + continue // ignore } - glog.Verbosef("processor got request") + glog.Debugf("connection %d got request", connIndex) var err error if active.conn == nil { @@ -250,14 +303,14 @@ loop: req := r.GetRequest() if req == nil { glog.Error("nil request") - return + continue // ignore } req.SetSource(saddr.IP, uint16(saddr.Port), []byte(sourceName)) sequence++ var raw proto.RawMessage if err = req.Encode(&raw); err != nil { glog.Errorf("encoding error %s", err) - return + continue // ignore } raw.SetOpaque(sequence) @@ -268,7 +321,8 @@ loop: active.Close() } } else { - r.ReplyError(err) + ErrConnect.SetError(err.Error()) + r.ReplyError(ErrConnect) } case readerResp, ok := <-active.chReaderResponse: if ok { diff --git a/internal/cli/error.go b/internal/cli/error.go index 3bd8d263..26cb04a9 100644 --- a/internal/cli/error.go +++ b/internal/cli/error.go @@ -19,7 +19,10 @@ package cli -import () +import ( + "errors" + "sync" +) type IRetryable interface { Retryable() bool @@ -29,6 +32,20 @@ type Error struct { What string } +var ( + ErrConnect = &Error{"connection error"} + ErrResponseTimeout = &Error{"response timeout"} + rwMutex sync.RWMutex +) + +func specialError(err error) bool { + if errors.Is(err, ErrConnect) || + errors.Is(err, ErrResponseTimeout) { + return true + } + return false +} + func (e *Error) Retryable() bool { return false } type RetryableError struct { @@ -38,6 +55,8 @@ type RetryableError struct { func (e *RetryableError) Retryable() bool { return true } func (e *Error) Error() string { + rwMutex.RLock() + defer rwMutex.RUnlock() return "error: " + e.What } @@ -45,6 +64,12 @@ func (e *RetryableError) Error() string { return "error: " + e.What } +func (e *Error) SetError(v string) { + rwMutex.Lock() + e.What = v + rwMutex.Unlock() +} + func NewError(err error) *Error { return &Error{ What: err.Error(), @@ -52,7 +77,7 @@ func NewError(err error) *Error { } func NewErrorWithString(err string) *Error { - return &Error{err} + return &Error{What: err} } /* diff --git a/internal/cli/processor.go b/internal/cli/processor.go index cb7ee3ad..27cb75a1 100644 --- a/internal/cli/processor.go +++ b/internal/cli/processor.go @@ -20,9 +20,10 @@ package cli import ( + "crypto/tls" "fmt" - "os" "sync" + "sync/atomic" "time" "github.com/paypal/junodb/third_party/forked/golang/glog" @@ -37,6 +38,34 @@ type IOError struct { Err error } +var ( + connRecycleTimeout = time.Duration(0 * time.Second) +) + +func SetConnectRecycleTimeout(recycleTimeout time.Duration) { + if recycleTimeout < 0 { + return + } + if recycleTimeout > 0 { + if recycleTimeout < time.Duration(5*time.Second) { + recycleTimeout = time.Duration(5 * time.Second) + } + if recycleTimeout > time.Duration(90*time.Second) { + recycleTimeout = time.Duration(90 * time.Second) + } + } + connRecycleTimeout = recycleTimeout + glog.Debugf("Set conn_recycle_timeout=%v", connRecycleTimeout) +} + +func SetDefaultRecycleTimeout(defaultTimeout time.Duration) { + if connRecycleTimeout > 0 { + glog.Infof("Default conn_recycle_timeout=%v", connRecycleTimeout) + return // already set + } + connRecycleTimeout = defaultTimeout +} + func (e *IOError) Retryable() bool { return true } func (e *IOError) Error() string { @@ -48,89 +77,194 @@ var ( kCalTxnType = "JUNO_CLIENT" kCalSslTxnType = "JUNO_SSL_CLIENT" + + poolMap = make(map[string]*Processor, 10) + execCount int64 + mutex sync.Mutex ) type Processor struct { + PoolSize int server io.ServiceEndpoint sourceName string + connIndex int - connectTimeout time.Duration - requestTimeout time.Duration - connRecycleTimeout time.Duration + connectTimeout time.Duration + responseTimeout time.Duration + getTLSConfig func() *tls.Config chDone chan bool chProcDone <-chan bool chRequest chan *RequestContext startOnce sync.Once + moreConns []*Processor +} + +func addProcessorExecutor(c *Processor) { + if c.PoolSize == 1 { + atomic.AddInt64(&execCount, 1) + return + } + poolMap[c.server.Addr] = c +} + +func getProcessor(addr string) *Processor { + c, found := poolMap[addr] + if !found { + return nil + } + return c +} + +func decrementExecutor() { + atomic.AddInt64(&execCount, -1) +} + +func ShowProcStats() { + glog.Infof("pool_count=%d", len(poolMap)) + mutex.Lock() + defer mutex.Unlock() + + for k, v := range poolMap { + glog.Infof("addr=%s, pool_size=%d", k, v.PoolSize) + } + + count := atomic.LoadInt64(&execCount) + glog.Infof("exec_count=%d", count) } func NewProcessor( server io.ServiceEndpoint, sourceName string, + connPoolSize int, connectTimeout time.Duration, - requestTimeout time.Duration, - connRecycleTimeout time.Duration) *Processor { + responseTimeout time.Duration, + getTLSConfig func() *tls.Config) *Processor { + + if connPoolSize < 1 { + connPoolSize = 2 + } + if connPoolSize > 20 { + connPoolSize = 20 + } + if connPoolSize > 1 { + mutex.Lock() + defer mutex.Unlock() + proc := getProcessor(server.Addr) + if proc != nil { + return proc + } // else need to create processor + } + + if connectTimeout == 0 { + connectTimeout = time.Duration(2000 * time.Millisecond) + } + + glog.Debugf("addr=%s pool_size=%d connect_timeout=%dms response_timeout=%dms", + server.Addr, connPoolSize, connectTimeout.Nanoseconds()/int64(1e6), + responseTimeout.Nanoseconds()/int64(1e6)) + chDone := make(chan bool) + chRequest := make(chan *RequestContext, kMaxRequestChanBufferSize) c := &Processor{ - server: server, - sourceName: sourceName, - connectTimeout: connectTimeout, - requestTimeout: requestTimeout, - connRecycleTimeout: connRecycleTimeout, - chDone: make(chan bool), - chRequest: make(chan *RequestContext, kMaxRequestChanBufferSize), + PoolSize: connPoolSize, + server: server, + sourceName: sourceName, + connIndex: 0, + connectTimeout: connectTimeout, + responseTimeout: responseTimeout, + getTLSConfig: getTLSConfig, + chDone: chDone, + chRequest: chRequest, + moreConns: make([]*Processor, connPoolSize-1), } + for i := 0; i < connPoolSize-1; i++ { + c.moreConns[i] = &Processor{ + PoolSize: connPoolSize, + server: server, + sourceName: sourceName, + connIndex: i + 1, + connectTimeout: connectTimeout, + responseTimeout: responseTimeout, + getTLSConfig: getTLSConfig, + chDone: chDone, + chRequest: chRequest, + } + } + addProcessorExecutor(c) return c } func (c *Processor) Start() { + var connDone = make(chan bool, len(c.moreConns)+1) c.startOnce.Do(func() { c.chProcDone = StartRequestProcessor( - c.server, c.sourceName, c.connectTimeout, c.requestTimeout, c.connRecycleTimeout, c.chDone, c.chRequest) + c.server, c.sourceName, connDone, c.connIndex, + c.connectTimeout, c.responseTimeout, c.getTLSConfig, c.chDone, c.chRequest) + for _, p := range c.moreConns { + p.chProcDone = StartRequestProcessor( + p.server, p.sourceName, connDone, p.connIndex, + p.connectTimeout, p.responseTimeout, c.getTLSConfig, p.chDone, p.chRequest) + } + if len(c.moreConns) > 0 { + glog.Infof("conn_count=%d", len(c.moreConns)+1) + } + if len(c.moreConns) == 0 { + return + } + timer := time.NewTimer(c.connectTimeout) + defer timer.Stop() + select { + case <-connDone: + case <-timer.C: + } }) } -///TODO revisit func (c *Processor) Close() { + if c.PoolSize > 1 { + // Connection pool is persistent + return + } close(c.chDone) <-c.chProcDone + decrementExecutor() } -func (c *Processor) sendWithResponseChannel(chResponse chan IResponseContext, m *proto.OperationalMessage) (ok bool) { +func (c *Processor) sendWithResponseChannel(chResponse chan IResponseContext, m *proto.OperationalMessage) error { + var err error select { case c.chRequest <- NewRequestContext(m, chResponse): - ok = true default: - ok = false + err = fmt.Errorf("Likely queue full, qlen=%d ps=%d", len(c.chRequest), c.PoolSize) } + if glog.LOG_VERBOSE { opcode := m.GetOpCode() buf := logging.NewKVBufferForLog() if opcode != proto.OpCodeNop { buf.AddReqIdString(m.GetRequestIDString()) } - if ok { + if err == nil { glog.Verbosef("proc <- %s %s", opcode.String(), buf.String()) } else { glog.Verbosef("Failed: proc <- %s %s", opcode.String(), buf.String()) } } - return + return err } -func (c *Processor) send(request *proto.OperationalMessage) (chResponse <-chan IResponseContext, ok bool) { +func (c *Processor) send(request *proto.OperationalMessage) (<-chan IResponseContext, error) { ch := make(chan IResponseContext) - chResponse = ch - ok = c.sendWithResponseChannel(ch, request) - return + return ch, c.sendWithResponseChannel(ch, request) } func (c *Processor) ProcessRequest(request *proto.OperationalMessage) (resp *proto.OperationalMessage, err error) { timeStart := time.Now() - glog.Verbosef("process request rid=%s", request.GetRequestIDString()) - if ch, sent := c.send(request); sent { + ch, err := c.send(request) + if err == nil { if r, ok := <-ch; ok { resp = r.GetResponse() err = r.GetError() @@ -138,10 +272,9 @@ func (c *Processor) ProcessRequest(request *proto.OperationalMessage) (resp *pro resp = nil err = fmt.Errorf("response channel closed by request processor") } - } else { - err = fmt.Errorf("fail to send request") } - if err != nil { + + if err != nil && !specialError(err) { err = &IOError{err} } if cal.IsEnabled() { @@ -153,16 +286,16 @@ func (c *Processor) ProcessRequest(request *proto.OperationalMessage) (resp *pro } rht := time.Since(timeStart) - // Get user name from OS to log in CAL - username := os.Getenv("USER") - if err == nil { status := resp.GetOpStatus() b := logging.NewKVBuffer() - b.AddOpRequestResponseInfoWithUser(request, resp, username) + b.AddOpRequestResponseInfo(request, resp) cal.AtomicTransaction(txnType, request.GetOpCode().String(), logging.CalStatus(status).CalStatus(), rht, b.Bytes()) } else { - cal.AtomicTransaction(txnType, request.GetOpCode().String(), cal.StatusError, rht, []byte(err.Error())) ///TODO to change: data to cal + tail := fmt.Sprintf("raddr=%s&res_timeout=%dms&ns=%s&%s", c.server.Addr, + c.responseTimeout.Nanoseconds()/int64(1e6), request.GetNamespace(), err.Error()) + cal.AtomicTransaction(txnType, request.GetOpCode().String(), + cal.StatusError, rht, []byte(tail)) ///TODO to change: data to cal } } diff --git a/internal/cli/tracker.go b/internal/cli/tracker.go index 28af2b13..fdced7dc 100644 --- a/internal/cli/tracker.go +++ b/internal/cli/tracker.go @@ -86,12 +86,12 @@ func (p *PendingTracker) OnTimeout(now time.Time) { } else { // st.cancelFunc() seq := pr.sequence - err := fmt.Errorf("request timeout") + err := ErrResponseTimeout if _, found := p.mapRequestsSent[seq]; found { req := pr.reqCtx.request if req != nil { - glog.Warningf("Timeout <- server: %s elapsed=%d,rid=%s", - req.GetOpCode(), now.Sub(pr.timeSent), pr.reqCtx.request.GetRequestIDString()) + glog.Debugf("Timeout <- server: %s elapsed=%dns,rid=%s", + req.GetOpCode(), now.Sub(pr.timeSent).Nanoseconds(), pr.reqCtx.request.GetRequestIDString()) } pr.reqCtx.ReplyError(err) delete(p.mapRequestsSent, seq) @@ -119,7 +119,7 @@ func (p *PendingTracker) OnResonseReceived(readerResp *ReaderResponse) { pending.reqCtx.Reply(resp) pending.reqCtx = nil } else { - glog.Warningf("no pending response found. seq:%d,rid=%s\n", connSequence, resp.GetRequestIDString()) + glog.Debugf("No pending response found. seq:%d,rid=%s", connSequence, resp.GetRequestIDString()) } } else { p.responseTimer.Stop() ///TODO diff --git a/pkg/client/README.md b/pkg/client/README.md new file mode 100644 index 00000000..742b9488 --- /dev/null +++ b/pkg/client/README.md @@ -0,0 +1,164 @@ +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +# Juno Golang SDK + +## Sample Code + +``` +package main + +import ( + "crypto/tls" + "fmt" + "time" + + "github.com/paypal/junodb/pkg/client" + cal "github.com/paypal/junodb/pkg/logging/cal/config" + "github.com/paypal/junodb/pkg/util" +) + +// addr is a Juno server endpoint in the form "ip:port". +// getTLSConfig is a func to get *tls.Config. +func createClient(addr string, getTLSConfig func() *tls.Config) (client.IClient, error) { + + cfg := client.Config{ + Appname: "example", + Namespace: "example_namespace", + DefaultTimeToLive: 60, // seconds + ConnectTimeout: util.Duration{1000 * time.Millisecond}, + ResponseTimeout: util.Duration{500 * time.Millisecond}, + } + + cfg.Server.Addr = addr + cfg.Server.SSLEnabled = true // Set to true if addr has an SSL port. + + client, err := client.NewWithTLS(cfg, getTLSConfig) + return client, err +} + +// Show metadata. +func showInfo(ctx client.IContext) { + fmt.Printf("v=%d ct=%d ttl=%d\n", ctx.GetVersion(), ctx.GetCreationTime(), + ctx.GetTimeToLive()) +} + +func basicAPI(cli client.IClient) { + key := []byte("test_key") + val := []byte("test_payload") + ctx, err := cli.Create(key, val) + if err != nil { + // log error + } + + // Update val slice before call Update + ctx, err = cli.Update(key, val) + if err == nil { + showInfo(ctx) + } else if err != client.ErrNoKey { + // log error + } + + _, err = cli.Set(key, val) + if err != nil { + // log error + } + + val, _, err = cli.Get(key) + if err != nil && err != client.ErrNoKey { + // log error + } + + err = cli.Destroy(key) + if err != nil { + // log error + } +} + +// Extend TTL if the value of WithTTL is greater than the current. +func basicAPIwithTTL(cli client.IClient) { + + key := []byte("test_key") + val := []byte("test_Payload") + ctx, err := cli.Create(key, val, client.WithTTL(uint32(100))) + if err == nil { + showInfo(ctx) + } + + // Update val slice before call Update + ctx, err = cli.Update(key, val, client.WithTTL(uint32(150))) + if err == nil { + showInfo(ctx) + } + + ctx, err = cli.Set(key, val, client.WithTTL(uint32(200))) + if err == nil { + showInfo(ctx) + } + + val, ctx, err = cli.Get(key, client.WithTTL(uint32(500))) + if err == nil { + showInfo(ctx) + } + + err = cli.Destroy(key) + if err != nil { + // log error + } +} + +// Test conditional update based on record version. +func condUpdate(cli client.IClient) error { + key := []byte("new_key") + val := []byte("new_payload") + + ctx, err := cli.Create(key, val) + if err != nil { + return err + } + + ctx, err = cli.Update(key, val) + if err != nil { + return err + } + + // Update succeeds if current record version is equal to ctx.GetVersion(). + // After the update, record version is incremented. + _, err = cli.Update(key, val, client.WithCond(ctx)) + if err != nil { + return err + } + + // Expect ErrConditionViolation + // because current record version is not equal to ctx.GetVersion(). + _, err = cli.Update(key, val, client.WithCond(ctx)) + if err != client.ErrConditionViolation { + return err + } + + err = cli.Destroy(key) + if err != nil { + return err + } + return nil +} + +func main() { + // Init variables + var addr string // = ... + var getTLSConfig func() *tls.Config // = ... + + // A client object should be created only once per unique addr. + cli, err := createClient(addr, getTLSConfig) + if err != nil { + // log error + return + } + + basicAPI(cli) + basicAPIwithTTL(cli) + if err := condUpdate(cli); err != nil { + // log error + } +} +``` + diff --git a/pkg/client/client.go b/pkg/client/client.go index 9e078650..ed93763d 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -20,57 +20,42 @@ /* package client implements Juno client API. -possible returned error if client successfully received the response from Proxy +Possible returned errors. - Create + Common Errors * nil * ErrBadMsg * ErrBadParam - * ErrInternal * ErrBusy + * ErrConnect + * ErrInternal * ErrNoStorage - * ErrUniqueKeyViolation + * ErrResponseTimeout + + Create + * Common Errors * ErrRecordLocked * ErrWriteFailure + * ErrUniqueKeyViolation Get - * nil - * ErrBadMsg - * ErrBadParam - * ErrInternal - * ErrBusy - * ErrNoStorage - * ErrNoKey + * Common Errors + * ErrNoKey // Normal if key has not been created or has expired. * ErrTTLExtendFailure Update - * nil - * ErrBadMsg - * ErrBadParam - * ErrInternal - * ErrBusy - * ErrNoStorage - * ErrRecordLocked + * Common Errors * ErrConditionViolation + * ErrRecordLocked * ErrWriteFailure Set - * nil - * ErrBadMsg - * ErrBadParam - * ErrInternal - * ErrBusy - * ErrNoStorage + * Common Errors * ErrRecordLocked * ErrWriteFailure Destroy - * nil - * ErrBadMsg - * ErrBadParam - * ErrInternal - * ErrBusy - * ErrNoStorage + * Common Errors * ErrRecordLocked * ErrWriteFailure @@ -88,31 +73,13 @@ type IContext interface { PrettyPrint(w io.Writer) } -///TODO check API input arguments - type IClient interface { Create(key []byte, value []byte, opts ...IOption) (IContext, error) Get(key []byte, opts ...IOption) ([]byte, IContext, error) Update(key []byte, value []byte, opts ...IOption) (IContext, error) Set(key []byte, value []byte, opts ...IOption) (IContext, error) Destroy(key []byte, opts ...IOption) (err error) + UDFGet(key []byte, fname []byte, params []byte, opts ...IOption) ([]byte, IContext, error) UDFSet(key []byte, fname []byte, params []byte, opts ...IOption) (IContext, error) } - -//type IResult interface { -// Get() -// GetWithTimeout() -// Poll() -//} -//type IValueResult interface { -// IResult -//} -// -//type AsyncClient interface { -// Create(key []byte, value []byte, opts ...IOption) IResult -// Update(key []byte, value []byte, opts ...IOption) IResult -// Set(key []byte, value []byte, opts ...IOption) IResult -// Get(key []byte, value []byte, opts ...IOption) IResult -// Destroy(key []byte) IResult -//} diff --git a/pkg/client/clientimpl.go b/pkg/client/clientimpl.go index 7ab57804..13e635a0 100644 --- a/pkg/client/clientimpl.go +++ b/pkg/client/clientimpl.go @@ -21,14 +21,17 @@ package client import ( + "crypto/tls" + "errors" "fmt" "runtime" + "time" "github.com/paypal/junodb/third_party/forked/golang/glog" "github.com/paypal/junodb/internal/cli" - "github.com/paypal/junodb/pkg/io" "github.com/paypal/junodb/pkg/logging" + cal "github.com/paypal/junodb/pkg/logging/cal" "github.com/paypal/junodb/pkg/proto" ) @@ -40,98 +43,113 @@ type clientImplT struct { processor *cli.Processor } -// newProcessorWithConfig initializes a new Processor with the given configuration. -func newProcessorWithConfig(conf *Config) *cli.Processor { +func newProcessorWithConfig(conf *Config, getTLSConfig func() *tls.Config) *cli.Processor { if conf == nil { return nil } c := cli.NewProcessor( conf.Server, conf.Appname, + conf.ConnPoolSize, conf.ConnectTimeout.Duration, - conf.RequestTimeout.Duration, - conf.ConnRecycleTimeout.Duration) + conf.ResponseTimeout.Duration, + getTLSConfig) return c } -// New initializes a new IClient with the given configuration. Returns an error if configuration validation fails. -func New(conf Config) (IClient, error) { - if err := conf.validate(); err != nil { +func NewWithTLS(conf Config, getTLSConfig func() *tls.Config) (IClient, error) { + if conf.Server.SSLEnabled && getTLSConfig == nil { + return nil, errors.New("getTLSConfig is nil.") + } + if err := conf.validate(true); err != nil { return nil, err } + glog.Infof("client cfg=%v withTLS=%v", conf, getTLSConfig != nil) + if conf.ConnPoolSize < 2 { + conf.ConnPoolSize = 2 + } + cli.SetDefaultRecycleTimeout(time.Duration(30 * time.Second)) client := &clientImplT{ config: conf, - processor: newProcessorWithConfig(&conf), + processor: newProcessorWithConfig(&conf, getTLSConfig), appName: conf.Appname, namespace: conf.Namespace, } + if conf.Cal.Enabled { + cal.InitWithConfig(&conf.Cal) + } client.processor.Start() - runtime.SetFinalizer(client.processor, func(p *cli.Processor) { - p.Close() - }) return client, nil } -// NewClient initializes a new IClient with the provided server address, namespace and app name. -func NewClient(server string, ns string, app string) (IClient, error) { - c := &clientImplT{ - config: Config{ - Server: io.ServiceEndpoint{Addr: server, SSLEnabled: false}, - Namespace: ns, - Appname: app, - RetryCount: defaultConfig.RetryCount, - DefaultTimeToLive: defaultConfig.DefaultTimeToLive, - ConnectTimeout: defaultConfig.ConnectTimeout, - ReadTimeout: defaultConfig.ReadTimeout, - WriteTimeout: defaultConfig.WriteTimeout, - RequestTimeout: defaultConfig.RequestTimeout, - }, - appName: app, - namespace: ns, - } - c.processor = newProcessorWithConfig(&c.config) - if c.processor != nil { - c.processor.Start() - } else { - errstr := "fail to create processor" - glog.Error(errstr) - return nil, fmt.Errorf(errstr) - } - runtime.SetFinalizer(c.processor, func(p *cli.Processor) { - p.Close() - }) - return c, nil +func New(conf Config) (IClient, error) { + if err := conf.validate(false); err != nil { + return nil, err + } + glog.Debugf("client cfg=%v", conf) + if conf.ConnPoolSize <= 1 { + conf.ConnPoolSize = 1 + } + client := &clientImplT{ + config: conf, + processor: newProcessorWithConfig(&conf, nil), + appName: conf.Appname, + namespace: conf.Namespace, + } + if conf.Cal.Enabled { + cal.InitWithConfig(&conf.Cal) + } + client.processor.Start() + if conf.ConnPoolSize == 1 { + runtime.SetFinalizer(client.processor, func(p *cli.Processor) { + p.Close() + }) + } + return client, nil } -///TODO to revisit - -// Close closes the client and cleans up resources. func (c *clientImplT) Close() { if c.processor != nil { c.processor.Close() c.processor = nil - } + } } -// getOptions collects all provided options into an optionData object. func (c *clientImplT) getOptions(opts ...IOption) *optionData { - data := &optionData{} + data := &optionData{} for _, op := range opts { op(data) } return data } -// newContext creates a new context from the provided operational message. func newContext(resp *proto.OperationalMessage) IContext { recInfo := &cli.RecordInfo{} recInfo.SetFromOpMsg(resp) return recInfo } -// Create sends a Create operation request to the server. +func (c *clientImplT) logError(op string, err error) { + if err == nil || err == ErrNoKey || + err == ErrConditionViolation || + err == ErrUniqueKeyViolation { + return + } + + addr_type := "tcp" + if c.config.Server.SSLEnabled { + addr_type = "ssl" + } + msg := fmt.Sprintf("[ERROR] op=%s %s_addr=%s response_timeout=%dms ns=%s. %s", + op, addr_type, c.config.Server.Addr, + c.config.ResponseTimeout.Nanoseconds()/int64(1e6), c.config.Namespace, err.Error()) + glog.Error(msg) + if err == ErrBusy || err == ErrRecordLocked { + time.Sleep(20 * time.Millisecond) + } +} + func (c *clientImplT) Create(key []byte, value []byte, opts ...IOption) (context IContext, err error) { - glog.Verbosef("Create ") var resp *proto.OperationalMessage options := newOptionData(opts...) recInfo := &cli.RecordInfo{} @@ -140,10 +158,12 @@ func (c *clientImplT) Create(key []byte, value []byte, opts ...IOption) (context if len(options.correlationId) > 0 { request.SetCorrelationID([]byte(options.correlationId)) } - if resp, err = c.processor.ProcessRequest(request); err == nil { - if err = checkResponse(request, resp, recInfo); err != nil { - glog.Debug(err) - } + resp, err = c.processor.ProcessRequest(request) + if err == nil { + err = checkResponse(request, resp, recInfo) + } + if err != nil { + c.logError("Create", err) } return } @@ -158,16 +178,19 @@ func (c *clientImplT) Get(key []byte, opts ...IOption) (value []byte, context IC if len(options.correlationId) > 0 { request.SetCorrelationID([]byte(options.correlationId)) } - if resp, err = c.processor.ProcessRequest(request); err == nil { - if err = checkResponse(request, resp, recInfo); err == nil { - payload := resp.GetPayload() - sz := payload.GetLength() - if sz != 0 { - value, err = payload.GetClearValue() - } - } else { - glog.Debug(err) - } + resp, err = c.processor.ProcessRequest(request) + if err == nil { + err = checkResponse(request, resp, recInfo) + } + if err != nil { + c.logError("Get", err) + return + } + + payload := resp.GetPayload() + sz := payload.GetLength() + if sz != 0 { + value, err = payload.GetClearValue() } return } @@ -187,10 +210,12 @@ func (c *clientImplT) Update(key []byte, value []byte, opts ...IOption) (context r.SetRequestWithUpdateCond(request) } } - if resp, err = c.processor.ProcessRequest(request); err == nil { - if err = checkResponse(request, resp, recInfo); err != nil { - glog.Debug(err) - } + resp, err = c.processor.ProcessRequest(request) + if err == nil { + err = checkResponse(request, resp, recInfo) + } + if err != nil { + c.logError("Update", err) } return } @@ -205,26 +230,31 @@ func (c *clientImplT) Set(key []byte, value []byte, opts ...IOption) (context IC if len(options.correlationId) > 0 { request.SetCorrelationID([]byte(options.correlationId)) } - if resp, err = c.processor.ProcessRequest(request); err == nil { - if err = checkResponse(request, resp, recInfo); err != nil { - glog.Debug(err) - } + resp, err = c.processor.ProcessRequest(request) + if err == nil { + err = checkResponse(request, resp, recInfo) + } + if err != nil { + c.logError("Set", err) } return } // Destroy sends a Destroy operation request to the server. func (c *clientImplT) Destroy(key []byte, opts ...IOption) (err error) { + var resp *proto.OperationalMessage options := newOptionData(opts...) request := c.NewRequest(proto.OpCodeDestroy, key, nil, 0) if len(options.correlationId) > 0 { request.SetCorrelationID([]byte(options.correlationId)) } - if resp, err = c.processor.ProcessRequest(request); err == nil { - if err = checkResponse(request, resp, nil); err != nil { - glog.Debug(err) - } + resp, err = c.processor.ProcessRequest(request) + if err == nil { + err = checkResponse(request, resp, nil) + } + if err != nil { + c.logError("Destroy", err) } return } @@ -286,6 +316,9 @@ func (c *clientImplT) NewRequest(op proto.OpCode, key []byte, value []byte, ttl request = &proto.OperationalMessage{} var payload proto.Payload payload.SetWithClearValue(value) + if ttl == 0 && op == proto.OpCodeCreate { + ttl = uint32(c.config.DefaultTimeToLive) + } request.SetRequest(op, key, []byte(c.namespace), &payload, ttl) request.SetNewRequestID() return @@ -297,6 +330,9 @@ func (c *clientImplT) NewUDFRequest(op proto.OpCode, key []byte, fname []byte, p request = &proto.OperationalMessage{} var payload proto.Payload payload.SetWithClearValue(params) + if ttl == 0 { + ttl = uint32(c.config.DefaultTimeToLive) + } request.SetRequest(op, key, []byte(c.namespace), &payload, ttl) request.SetNewRequestID() request.SetUDFName(fname) diff --git a/pkg/client/config.go b/pkg/client/config.go index 860cf076..e93cca3f 100644 --- a/pkg/client/config.go +++ b/pkg/client/config.go @@ -1,29 +1,30 @@ -// Copyright 2023 PayPal Inc. +// Copyright 2023 PayPal Inc. // -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // Package client handles the configuration for a Juno client. package client import ( "fmt" - "time" "github.com/paypal/junodb/pkg/io" "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/internal/cli" + cal "github.com/paypal/junodb/pkg/logging/cal/config" ) // Duration is a type alias for util.Duration. @@ -31,52 +32,19 @@ type Duration = util.Duration // Config holds the configuration values for the Juno client. type Config struct { - Server io.ServiceEndpoint // Server defines the ServiceEndpoint of the Juno server. - Appname string // Appname is the name of the application. - Namespace string // Namespace is the namespace of the application. - RetryCount int // RetryCount is the maximum number of retries. - DefaultTimeToLive int // DefaultTimeToLive is the default TTL (time to live) for requests. - ConnectTimeout Duration // ConnectTimeout is the timeout for establishing connections. - ReadTimeout Duration // ReadTimeout is the timeout for read operations. - WriteTimeout Duration // WriteTimeout is the timeout for write operations. - RequestTimeout Duration // RequestTimeout is the timeout for each request. - ConnRecycleTimeout Duration // ConnRecycleTimeout is the timeout for connection recycling. -} - -// defaultConfig defines the default configuration values. -var defaultConfig = Config{ - RetryCount: 1, - DefaultTimeToLive: 1800, - ConnectTimeout: Duration{100 * time.Millisecond}, - ReadTimeout: Duration{500 * time.Millisecond}, - WriteTimeout: Duration{500 * time.Millisecond}, - RequestTimeout: Duration{1000 * time.Millisecond}, - ConnRecycleTimeout: Duration{9 * time.Second}, -} - -// SetDefaultTimeToLive sets the default time to live (TTL) for the configuration. -func SetDefaultTimeToLive(ttl int) { - defaultConfig.DefaultTimeToLive = ttl -} + Server io.ServiceEndpoint + Appname string + Namespace string -// SetDefaultTimeout sets the default timeout durations for the configuration. -func SetDefaultTimeout(connect, read, write, request, connRecycle time.Duration) { - defaultConfig.ConnectTimeout.Duration = connect - defaultConfig.ReadTimeout.Duration = read - defaultConfig.WriteTimeout.Duration = write - defaultConfig.RequestTimeout.Duration = request - defaultConfig.ConnRecycleTimeout.Duration = connRecycle + DefaultTimeToLive int + ConnPoolSize int + ConnectTimeout Duration + ResponseTimeout Duration + BypassLTM bool + Cal cal.Config } -// SetDefault updates the current Config to match the default Config. -func (c *Config) SetDefault() { - *c = defaultConfig -} - -// validate checks if the required fields of the Config are correctly populated. -// It validates the Server field and checks if Appname and Namespace are specified. -// It returns an error if any of the above conditions are not met. -func (c *Config) validate() error { +func (c *Config) validate(useGetTLS bool) error { if err := c.Server.Validate(); err != nil { return err } @@ -86,6 +54,11 @@ func (c *Config) validate() error { if len(c.Namespace) == 0 { return fmt.Errorf("Config.Namespace not specified.") } - // TODO to validate others + if c.DefaultTimeToLive < 0 { + return fmt.Errorf("Config.DefaultTimeToLive is negative.") + } + if c.Server.SSLEnabled && !useGetTLS && !cli.TLSInitialized() { + return fmt.Errorf("getTLSConfig is nil.") + } return nil } diff --git a/pkg/client/error.go b/pkg/client/error.go index 5dd6991f..5a930446 100644 --- a/pkg/client/error.go +++ b/pkg/client/error.go @@ -27,10 +27,13 @@ import ( // Error variables for different scenarios in the application. var ( - ErrNoKey error // Error when no key is found. - ErrUniqueKeyViolation error // Error when there is a violation of a unique key. - ErrBadParam error // Error when a bad parameter is provided. - ErrConditionViolation error // Error when a condition violation occurs. + ErrConnect error + ErrResponseTimeout error + + ErrNoKey error + ErrUniqueKeyViolation error + ErrBadParam error + ErrConditionViolation error ErrBadMsg error // Error when a bad message is encountered. ErrNoStorage error // Error when no storage is available. @@ -48,11 +51,14 @@ var errorMapping map[proto.OpStatus]error // init function initializes the error variables and the errorMapping map. func init() { - ErrNoKey = &cli.Error{"no key"} // Error when the key does not exist. - ErrUniqueKeyViolation = &cli.Error{"unique key violation"} // Error when unique key constraint is violated. - ErrBadParam = &cli.Error{"bad parameter"} // Error when a bad parameter is passed. - ErrConditionViolation = &cli.Error{"condition violation"} // Error when there is a condition violation. - ErrTTLExtendFailure = &cli.Error{"fail to extend TTL"} // Error when TTL extension fails. + ErrResponseTimeout = cli.ErrResponseTimeout + ErrConnect = cli.ErrConnect + + ErrNoKey = &cli.Error{"no key"} + ErrUniqueKeyViolation = &cli.Error{"unique key violation"} + ErrBadParam = &cli.Error{"bad parameter"} + ErrConditionViolation = &cli.Error{"condition violation"} //version too old + ErrTTLExtendFailure = &cli.Error{"fail to extend TTL"} ErrBadMsg = &cli.RetryableError{"bad message"} // Error when an inappropriate message is received. ErrNoStorage = &cli.RetryableError{"no storage"} // Error when there is no storage available. diff --git a/test/cli/basic_test.go b/test/cli/basic_test.go new file mode 100644 index 00000000..016ead1e --- /dev/null +++ b/test/cli/basic_test.go @@ -0,0 +1,145 @@ +// +// Copyright 2023 PayPal Inc. +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cli + +import ( + "errors" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/third_party/forked/golang/glog" +) + +var ( + server *Cmd + server2 *Cmd +) + +func TestBasic(t *testing.T) { + glog.Info("") + glog.Info("=== TestBasic") + + server, _ = NewCmdWithConfig(serverAddr, 5) + + if server == nil { + t.Errorf("Failed to init") + return + } + sendRequests(server, 5, t) +} + +func TestTls(t *testing.T) { + glog.Info("") + glog.Info("=== TestTls") + + server, _ = NewCmdWithConfig(serverTls, 5) + if server == nil { + t.Errorf("Failed to init") + return + } + sendRequests(server, 5, t) +} + +func sendRequests(server *Cmd, count int, t *testing.T) { + for i := 0; i < count; i++ { + if server.deleteKey(i) != nil { + t.Errorf("Delete failed") + } + _, err := server.createKey(i) + if err != nil { + t.Errorf("Create failed") + } + _, err = server.updateKey(i) + if err != nil { + t.Errorf("Update failed") + } + if server.setKey(i) != nil { + t.Errorf("Set failed") + } + if server.getKey(i) != nil { + t.Errorf("Get failed") + } + if server.deleteKey(i) != nil { + t.Errorf("Delete failed") + } + } +} + +func TestConcurrent(t *testing.T) { + glog.Info("") + glog.Info("=== TestConcurrent") + + a := 20 + b := 1000 + total := a * b + + server, _ = NewCmdWithConfig(serverAddr, 5) + if server == nil { + t.Errorf("Failed to init") + return + } + + var wg sync.WaitGroup + wg.Add(a) + st := time.Now() + + failCount := int64(0) + timeoutCount := int64(0) + for i := 0; i < a; i++ { + go func(k int) { + for j := 0; j < b; j++ { + if (j % 300) == 0 { + glog.Infof("loop k=%d j=%d", k, j) + } + err := server.setKey(k*b + j) + if err != nil { + atomic.AddInt64(&failCount, 1) + if errors.Is(err, client.ErrResponseTimeout) { + atomic.AddInt64(&timeoutCount, 1) + } + } + } + wg.Done() + }(i) + } + wg.Wait() + elapsed := time.Since(st).Seconds() + + var wg2 sync.WaitGroup + wg2.Add(a) + for i := 0; i < a; i++ { + go func(k int) { + for j := 0; j < b; j++ { + server.deleteKey(k*b + j) + } + wg2.Done() + }(i) + } + wg2.Wait() + + glog.Infof("elapsed=%.1fs fails=%d timeouts=%d total=%d", elapsed, + failCount, timeoutCount, total) + if failCount > int64(total/10) { + t.Errorf("Concurrent test failed") + } +} diff --git a/test/cli/cond_test.go b/test/cli/cond_test.go new file mode 100644 index 00000000..8e00cc20 --- /dev/null +++ b/test/cli/cond_test.go @@ -0,0 +1,54 @@ +// +// Copyright 2023 PayPal Inc. +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cli + +import ( + "testing" + + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/third_party/forked/golang/glog" +) + +func TestCond(t *testing.T) { + glog.Info("") + glog.Info("=== TestCond") + + server, _ = NewCmdWithConfig(serverAddr, 5) + + if server == nil { + t.Errorf("Failed to init") + return + } + for i := 100; i < 105; i++ { + ctx, err := server.createKey(i) + if err != nil { + t.Errorf("Create failed") + } + _, err = server.updateKeyWithCond(i, ctx) + if err != nil { + t.Errorf("Update WithCond failed") + continue + } + _, err = server.updateKeyWithCond(i, ctx) + if err == nil || err != client.ErrConditionViolation { + t.Errorf("Expected condition violation") + } + } +} diff --git a/test/cli/define.go b/test/cli/define.go new file mode 100644 index 00000000..3997e746 --- /dev/null +++ b/test/cli/define.go @@ -0,0 +1,28 @@ +// +// Copyright 2023 PayPal Inc. +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cli + + +// For TLS serevr, need to provide server.crt and server.pem. +// See main_test.go: cert, err := tls.LoadX509KeyPair("./server.crt", "./server.pem") +var ( // Change ip:port to that of JunoDB proxy service + serverAddr = "127.0.0.1:8080" // TCP + serverTls = "127.0.0.1:5080" // TLS +) diff --git a/test/cli/main_test.go b/test/cli/main_test.go new file mode 100644 index 00000000..39bc2743 --- /dev/null +++ b/test/cli/main_test.go @@ -0,0 +1,81 @@ +// +// Copyright 2023 PayPal Inc. +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cli + +import ( + "crypto/tls" + "crypto/x509" + "os" + "sync" + "testing" + "time" + + "github.com/paypal/junodb/third_party/forked/golang/glog" +) + +var ( + tlsConfig *tls.Config + mutex sync.RWMutex +) + +func GetTLSConfig() *tls.Config { + mutex.RLock() + defer mutex.RUnlock() + return tlsConfig +} + +func loadCertificate() { + caCert, err := os.ReadFile("./server.crt") + if err != nil { + glog.Exitf("%s", err.Error()) + } + rootCAs, _ := x509.SystemCertPool() + if rootCAs == nil { + rootCAs = x509.NewCertPool() + } + rootCAs.AppendCertsFromPEM(caCert) + + cert, err := tls.LoadX509KeyPair("./server.crt", "./server.pem") + if err != nil { + glog.Exitf("%s", err.Error()) + } + + mutex.Lock() + defer mutex.Unlock() + tlsConfig = &tls.Config{ + RootCAs: rootCAs, + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: true, + SessionTicketsDisabled: false, + ClientSessionCache: tls.NewLRUClientSessionCache(0), + } +} + +func TestMain(m *testing.M) { + + glog.Infof("Start testing") + + loadCertificate() + + code := m.Run() + glog.Finalize() + time.Sleep(1 * time.Second) + os.Exit(code) +} diff --git a/test/cli/util.go b/test/cli/util.go new file mode 100644 index 00000000..0d0bfbd1 --- /dev/null +++ b/test/cli/util.go @@ -0,0 +1,181 @@ +// +// Copyright 2023 PayPal Inc. +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cli + +import ( + "encoding/binary" + "errors" + "time" + + "github.com/paypal/junodb/pkg/client" + "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/third_party/forked/golang/glog" +) + +type Cmd struct { + addr string + client client.IClient + payload []byte + ErrCount int +} + +func createPayload() []byte { + k := 1024 + p := make([]byte, k) + + for i := 0; i < k/2; i++ { + val := byte((uint(i) * 12345678) & 0xff) + p[i] = val + p[i+k/2] = val + } + return p +} + +func verifyPayload(p []byte) bool { + off := len(p) / 2 + for k := 0; k < off; k++ { + if p[k] != p[k+off] { + return false + } + } + return true +} + +func showMetaInfo(ctx client.IContext) { + glog.Infof("v=%d ct=%d ttl=%d", ctx.GetVersion(), ctx.GetCreationTime(), ctx.GetTimeToLive()) +} + +func NewCmdWithConfig(addr string, poolSize int) (*Cmd, error) { + cfg := client.Config{ + Appname: "testcli", + Namespace: "juno_cli_qa", + DefaultTimeToLive: 60, + ConnPoolSize: poolSize, + ResponseTimeout: util.Duration{1000 * time.Millisecond}, + } + + cfg.Server.Addr = addr + if addr == serverTls { + cfg.Server.SSLEnabled = true + } + client, err := client.NewWithTLS(cfg, GetTLSConfig) + if err != nil { + glog.Errorf("%s", err) + return nil, err + } + + cmd := &Cmd{ + addr: addr, + client: client, + payload: createPayload(), + } + if !verifyPayload(cmd.payload) { + cmd = nil + } + return cmd, err +} + +func (c *Cmd) newRandomKey(s int) []byte { + off := 2 + key := make([]byte, 16+off) + r := uint32(((int64(s+1)*25214903917 + 11) >> 5) & 0x7fffffff) + binary.BigEndian.PutUint32(key[0+off:], r) + binary.BigEndian.PutUint32(key[4+off:], uint32(s)) + binary.BigEndian.PutUint32(key[12+off:], 0xff) + + return key +} + +func (c *Cmd) createKey(ix int) (ctx client.IContext, err error) { + key := c.newRandomKey(ix) + + for i := 0; i < 2; i++ { + ctx, err = c.client.Create(key, c.payload, client.WithTTL(uint32(30))) + if err == nil { + break + } + } + + return ctx, err +} + +func (c *Cmd) getKey(ix int) error { + key := c.newRandomKey(ix) + + val, _, err := c.client.Get(key, client.WithTTL(uint32(30))) + if err == nil && !verifyPayload(val) { + glog.Errorf("validate failed") + err = errors.New("Bad payload") + } + return err +} + +func (c *Cmd) setKey(ix int) error { + key := c.newRandomKey(ix) + + var err error + var ctx client.IContext + stop := 2 + for i := 0; i < stop; i++ { + ctx, err = c.client.Set(key, c.payload, client.WithTTL(uint32(60))) + if err == nil { + showMetaInfo(ctx) + break + } + } + return err +} + +func (c *Cmd) updateKey(ix int) (ctx client.IContext, err error) { + key := c.newRandomKey(ix) + + for i := 0; i < 2; i++ { + ctx, err = c.client.Update(key, c.payload, client.WithTTL(uint32(60))) + if err == nil { + break + } + } + return ctx, err +} + +func (c *Cmd) updateKeyWithCond(ix int, ctxIn client.IContext) (ctx client.IContext, err error) { + key := c.newRandomKey(ix) + + for i := 0; i < 2; i++ { + ctx, err = c.client.Update(key, c.payload, client.WithCond(ctxIn)) + if err == nil { + break + } + } + return ctx, err +} + +func (c *Cmd) deleteKey(ix int) error { + key := c.newRandomKey(ix) + + var err error + for i := 0; i < 2; i++ { + err = c.client.Destroy(key) + if err == nil { + break + } + } + return err +} diff --git a/test/drv/bulkload/bulkload.go b/test/drv/bulkload/bulkload.go index f7460e3f..58b20e2c 100644 --- a/test/drv/bulkload/bulkload.go +++ b/test/drv/bulkload/bulkload.go @@ -74,13 +74,9 @@ func (c *CmdLine) Init(server string, ns string, prefix string, payloadLen int, var err error c.clientCfg = client.Config{ - RetryCount: 1, - DefaultTimeToLive: ttl, - ConnectTimeout: Duration{500 * time.Millisecond}, - ReadTimeout: Duration{500 * time.Millisecond}, - WriteTimeout: Duration{500 * time.Millisecond}, - RequestTimeout: Duration{500 * time.Millisecond}, - ConnRecycleTimeout: Duration{300 * time.Second}, + DefaultTimeToLive: ttl, + ConnectTimeout: Duration{500 * time.Millisecond}, + ResponseTimeout: Duration{500 * time.Millisecond}, } c.clientCfg.Server.Addr = server c.clientCfg.Appname = "bulkload" diff --git a/test/drv/junoload/junoload.go b/test/drv/junoload/junoload.go index ea3f2aa9..f86604fe 100644 --- a/test/drv/junoload/junoload.go +++ b/test/drv/junoload/junoload.go @@ -35,11 +35,12 @@ import ( "github.com/BurntSushi/toml" + "github.com/paypal/junodb/internal/cli" "github.com/paypal/junodb/pkg/client" "github.com/paypal/junodb/pkg/cmd" "github.com/paypal/junodb/pkg/logging/cal" "github.com/paypal/junodb/pkg/sec" - "github.com/paypal/junodb/pkg/util" + "github.com/paypal/junodb/pkg/util" "github.com/paypal/junodb/pkg/version" ) @@ -62,24 +63,25 @@ type ( CmdOptions struct { cfgFile string - server string - requestPattern string - sslEnabled bool - numExecutor int - payloadLen int - numReqPerSecond int - runningTime int - statOutputRate int - timeToLive int - httpMonAddr string - version bool - numKeys int - dbpath string - logLevel string - isVariable bool - disableGetTTL bool - keys string - randomize bool + server string + requestPattern string + sslEnabled bool + numExecutor int + payloadLen int + numReqPerSecond int + runningTime int + statOutputRate int + connRecycleTimeout int + timeToLive int + httpMonAddr string + version bool + numKeys int + dbpath string + logLevel string + isVariable bool + disableGetTTL bool + keys string + randomize bool } ) @@ -100,7 +102,11 @@ const ( ) func (d *SyncTestDriver) setDefaultConfig() { - d.config.SetDefault() + + d.config.DefaultTimeToLive = 1800 + d.config.ConnPoolSize = 1 + d.config.ConnectTimeout = util.Duration{1000 * time.Millisecond} + d.config.ResponseTimeout = util.Duration{1000 * time.Millisecond} d.config.Sec = sec.DefaultConfig d.config.Cal.Default() @@ -145,6 +151,7 @@ func (d *SyncTestDriver) Init(name string, desc string) { d.IntOption(&d.cmdOpts.runningTime, "t|running-time", kDefaultRunningTime, "specify driver's running time in second") d.IntOption(&d.cmdOpts.timeToLive, "ttl|record-time-to-live", kDefaultRecordTimeToLive, "specify record TTL in second") d.IntOption(&d.cmdOpts.statOutputRate, "o|stat-output-rate", kDefaultStatOutputRate, "specify how often to output statistic information in second\n\tfor the period of time.") + d.IntOption(&d.cmdOpts.connRecycleTimeout, "rt", 0, "connection recycle timeout") d.StringOption(&d.cmdOpts.httpMonAddr, "mon-addr|monitoring-address", "", "specify the http monitoring address. \n\toverride HttpMonAddr in config file") d.BoolOption(&d.cmdOpts.version, "version", false, "display version information.") d.StringOption(&d.cmdOpts.dbpath, "dbpath", "", "to display rocksdb stats") @@ -200,6 +207,7 @@ func (d *SyncTestDriver) Parse(args []string) (err error) { return } d.setDefaultConfig() + cli.SetConnectRecycleTimeout(time.Duration(d.cmdOpts.connRecycleTimeout) * time.Second) if len(d.cmdOpts.cfgFile) != 0 { if _, err := toml.DecodeFile(d.cmdOpts.cfgFile, &d.config); err != nil { diff --git a/test/functest/setup_test.go b/test/functest/setup_test.go index 27dd384e..08f1d480 100644 --- a/test/functest/setup_test.go +++ b/test/functest/setup_test.go @@ -34,6 +34,7 @@ import ( "github.com/BurntSushi/toml" + "github.com/paypal/junodb/internal/cli" "github.com/paypal/junodb/cmd/proxy/config" "github.com/paypal/junodb/pkg/client" "github.com/paypal/junodb/pkg/cluster" @@ -72,20 +73,12 @@ var ( defaultClientConfig = client.Config{ DefaultTimeToLive: 1800, ConnectTimeout: util.Duration{4000 * time.Millisecond}, - ReadTimeout: util.Duration{1500 * time.Millisecond}, - WriteTimeout: util.Duration{1500 * time.Millisecond}, - RequestTimeout: util.Duration{3000 * time.Millisecond}, + ResponseTimeout: util.Duration{3000 * time.Millisecond}, } ) func setup() { - client.SetDefaultTimeToLive(defaultClientConfig.DefaultTimeToLive) - client.SetDefaultTimeout(defaultClientConfig.ConnectTimeout.Duration, - defaultClientConfig.ReadTimeout.Duration, - defaultClientConfig.WriteTimeout.Duration, - defaultClientConfig.RequestTimeout.Duration, - defaultClientConfig.ConnRecycleTimeout.Duration) sigs := make(chan os.Signal) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) go func(sigCh chan os.Signal) { @@ -176,6 +169,7 @@ func TestMain(m *testing.M) { cal.InitWithConfig(&testConfig.CAL) } + cli.SetConnectRecycleTimeout(time.Duration(0 * time.Second)) sec.Initialize(&testConfig.Sec, sec.KFlagClientTlsEnabled|sec.KFlagEncryptionEnabled) ProxyAddr = testConfig.ProxyAddress diff --git a/test/testutil/server/server.go b/test/testutil/server/server.go index 9f1c0c10..2efacacb 100644 --- a/test/testutil/server/server.go +++ b/test/testutil/server/server.go @@ -144,7 +144,8 @@ func (s *ServerBase) IsUp() bool { if frwk.LOG_DEBUG { glog.DebugInfof("Testing if %s is up", s.Address()) } - clientProcessor := cli.NewProcessor(io.ServiceEndpoint{Addr: s.Address(), SSLEnabled: s.IsSSLEnabled()}, "testFramework", s.startWaitTime, s.startWaitTime, 0) + clientProcessor := cli.NewProcessor(io.ServiceEndpoint{Addr: s.Address(), SSLEnabled: s.IsSSLEnabled()}, "testFramework", 1, + s.startWaitTime, s.startWaitTime, nil) clientProcessor.Start() defer clientProcessor.Close() request := &proto.OperationalMessage{} diff --git a/test/unittest/setup_test.go b/test/unittest/setup_test.go index a7881275..3e2d30c2 100644 --- a/test/unittest/setup_test.go +++ b/test/unittest/setup_test.go @@ -95,9 +95,7 @@ func mainSetup() { Namespace: "ns", DefaultTimeToLive: 3600, ConnectTimeout: util.Duration{4000 * time.Millisecond}, - ReadTimeout: util.Duration{1500 * time.Millisecond}, - WriteTimeout: util.Duration{1500 * time.Millisecond}, - RequestTimeout: util.Duration{3000 * time.Millisecond}, + ResponseTimeout: util.Duration{3000 * time.Millisecond}, } Mockclient = mock.NewMockClient(config.Conf.ClusterInfo.ConnInfo, cliCfg) } From 969b9d838d1437c10abfb6ff2fd2160600b49d46 Mon Sep 17 00:00:00 2001 From: art2ip <126820369+art2ip@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:13:59 -0700 Subject: [PATCH 27/27] Fixed test failure. --- pkg/client/config.go | 4 ---- test/functest/setup_test.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/client/config.go b/pkg/client/config.go index e93cca3f..9653e0d2 100644 --- a/pkg/client/config.go +++ b/pkg/client/config.go @@ -23,7 +23,6 @@ import ( "github.com/paypal/junodb/pkg/io" "github.com/paypal/junodb/pkg/util" - "github.com/paypal/junodb/internal/cli" cal "github.com/paypal/junodb/pkg/logging/cal/config" ) @@ -57,8 +56,5 @@ func (c *Config) validate(useGetTLS bool) error { if c.DefaultTimeToLive < 0 { return fmt.Errorf("Config.DefaultTimeToLive is negative.") } - if c.Server.SSLEnabled && !useGetTLS && !cli.TLSInitialized() { - return fmt.Errorf("getTLSConfig is nil.") - } return nil } diff --git a/test/functest/setup_test.go b/test/functest/setup_test.go index 08f1d480..2b969df0 100644 --- a/test/functest/setup_test.go +++ b/test/functest/setup_test.go @@ -110,7 +110,7 @@ func setup() { var err error if proxyClient, err = client.New(cfg); err != nil { - glog.Exitf("proxyClient create in set up is null, fail") + glog.Exitf("%s proxyClient create in set up is null, fail", err) } cfgShare = cfg cfgShare.Namespace = "NS2"