github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/coordination/client.go (about)

     1  package coordination
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1"
    10  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination"
    11  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Operations"
    12  	"google.golang.org/grpc"
    13  
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/coordination"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/coordination/options"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination/config"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/operation"
    18  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    19  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    20  	"github.com/ydb-platform/ydb-go-sdk/v3/retry"
    21  	"github.com/ydb-platform/ydb-go-sdk/v3/scheme"
    22  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    23  )
    24  
    25  //go:generate mockgen -destination grpc_client_mock_test.go --typed -package coordination -write_package_comment=false github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1 CoordinationServiceClient,CoordinationService_SessionClient
    26  
    27  var errNilClient = xerrors.Wrap(errors.New("coordination client is not initialized"))
    28  
    29  type Client struct {
    30  	config config.Config
    31  	client Ydb_Coordination_V1.CoordinationServiceClient
    32  
    33  	mutex    sync.Mutex // guards the fields below
    34  	sessions map[*session]struct{}
    35  }
    36  
    37  func New(ctx context.Context, cc grpc.ClientConnInterface, config config.Config) *Client {
    38  	onDone := trace.CoordinationOnNew(config.Trace(), &ctx,
    39  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination.New"),
    40  	)
    41  	defer onDone()
    42  
    43  	return &Client{
    44  		config:   config,
    45  		client:   Ydb_Coordination_V1.NewCoordinationServiceClient(cc),
    46  		sessions: make(map[*session]struct{}),
    47  	}
    48  }
    49  
    50  func operationParams(
    51  	ctx context.Context,
    52  	config interface {
    53  		OperationTimeout() time.Duration
    54  		OperationCancelAfter() time.Duration
    55  	},
    56  	mode operation.Mode,
    57  ) *Ydb_Operations.OperationParams {
    58  	return operation.Params(
    59  		ctx,
    60  		config.OperationTimeout(),
    61  		config.OperationCancelAfter(),
    62  		mode,
    63  	)
    64  }
    65  
    66  func createNodeRequest(
    67  	path string, config coordination.NodeConfig, operationParams *Ydb_Operations.OperationParams,
    68  ) *Ydb_Coordination.CreateNodeRequest {
    69  	return &Ydb_Coordination.CreateNodeRequest{
    70  		Path: path,
    71  		Config: &Ydb_Coordination.Config{
    72  			Path:                     config.Path,
    73  			SelfCheckPeriodMillis:    config.SelfCheckPeriodMillis,
    74  			SessionGracePeriodMillis: config.SessionGracePeriodMillis,
    75  			ReadConsistencyMode:      config.ReadConsistencyMode.To(),
    76  			AttachConsistencyMode:    config.AttachConsistencyMode.To(),
    77  			RateLimiterCountersMode:  config.RatelimiterCountersMode.To(),
    78  		},
    79  		OperationParams: operationParams,
    80  	}
    81  }
    82  
    83  func createNode(
    84  	ctx context.Context, client Ydb_Coordination_V1.CoordinationServiceClient, request *Ydb_Coordination.CreateNodeRequest,
    85  ) error {
    86  	_, err := client.CreateNode(ctx, request)
    87  	if err != nil {
    88  		return xerrors.WithStackTrace(err)
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  func (c *Client) CreateNode(ctx context.Context, path string, config coordination.NodeConfig) (finalErr error) {
    95  	if c == nil {
    96  		return xerrors.WithStackTrace(errNilClient)
    97  	}
    98  
    99  	onDone := trace.CoordinationOnCreateNode(c.config.Trace(), &ctx,
   100  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination.(*Client).CreateNode"),
   101  		path,
   102  	)
   103  	defer func() {
   104  		onDone(finalErr)
   105  	}()
   106  
   107  	request := createNodeRequest(path, config, operationParams(ctx, &c.config, operation.ModeSync))
   108  
   109  	if !c.config.AutoRetry() {
   110  		return createNode(ctx, c.client, request)
   111  	}
   112  
   113  	return retry.Retry(ctx,
   114  		func(ctx context.Context) error {
   115  			return createNode(ctx, c.client, request)
   116  		},
   117  		retry.WithStackTrace(),
   118  		retry.WithIdempotent(true),
   119  		retry.WithTrace(c.config.TraceRetry()),
   120  		retry.WithBudget(c.config.RetryBudget()),
   121  	)
   122  }
   123  
   124  func (c *Client) AlterNode(ctx context.Context, path string, config coordination.NodeConfig) (finalErr error) {
   125  	if c == nil {
   126  		return xerrors.WithStackTrace(errNilClient)
   127  	}
   128  
   129  	onDone := trace.CoordinationOnAlterNode(c.config.Trace(), &ctx,
   130  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination.(*Client).AlterNode"),
   131  		path,
   132  	)
   133  	defer func() {
   134  		onDone(finalErr)
   135  	}()
   136  
   137  	request := alterNodeRequest(path, config, operationParams(ctx, &c.config, operation.ModeSync))
   138  
   139  	call := func(ctx context.Context) error {
   140  		return alterNode(ctx, c.client, request)
   141  	}
   142  	if !c.config.AutoRetry() {
   143  		return xerrors.WithStackTrace(call(ctx))
   144  	}
   145  
   146  	return retry.Retry(ctx,
   147  		func(ctx context.Context) (err error) {
   148  			return alterNode(ctx, c.client, request)
   149  		},
   150  		retry.WithStackTrace(),
   151  		retry.WithIdempotent(true),
   152  		retry.WithTrace(c.config.TraceRetry()),
   153  		retry.WithBudget(c.config.RetryBudget()),
   154  	)
   155  }
   156  
   157  func alterNodeRequest(
   158  	path string, config coordination.NodeConfig, operationParams *Ydb_Operations.OperationParams,
   159  ) *Ydb_Coordination.AlterNodeRequest {
   160  	return &Ydb_Coordination.AlterNodeRequest{
   161  		Path: path,
   162  		Config: &Ydb_Coordination.Config{
   163  			Path:                     config.Path,
   164  			SelfCheckPeriodMillis:    config.SelfCheckPeriodMillis,
   165  			SessionGracePeriodMillis: config.SessionGracePeriodMillis,
   166  			ReadConsistencyMode:      config.ReadConsistencyMode.To(),
   167  			AttachConsistencyMode:    config.AttachConsistencyMode.To(),
   168  			RateLimiterCountersMode:  config.RatelimiterCountersMode.To(),
   169  		},
   170  		OperationParams: operationParams,
   171  	}
   172  }
   173  
   174  func alterNode(
   175  	ctx context.Context, client Ydb_Coordination_V1.CoordinationServiceClient, request *Ydb_Coordination.AlterNodeRequest,
   176  ) error {
   177  	_, err := client.AlterNode(ctx, request)
   178  	if err != nil {
   179  		return xerrors.WithStackTrace(err)
   180  	}
   181  
   182  	return nil
   183  }
   184  
   185  func (c *Client) DropNode(ctx context.Context, path string) (finalErr error) {
   186  	if c == nil {
   187  		return xerrors.WithStackTrace(errNilClient)
   188  	}
   189  
   190  	onDone := trace.CoordinationOnDropNode(c.config.Trace(), &ctx,
   191  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination.(*Client).DropNode"),
   192  		path,
   193  	)
   194  	defer func() {
   195  		onDone(finalErr)
   196  	}()
   197  
   198  	request := dropNodeRequest(path, operationParams(ctx, &c.config, operation.ModeSync))
   199  
   200  	call := func(ctx context.Context) error {
   201  		return dropNode(ctx, c.client, request)
   202  	}
   203  	if !c.config.AutoRetry() {
   204  		return xerrors.WithStackTrace(call(ctx))
   205  	}
   206  
   207  	return retry.Retry(ctx,
   208  		func(ctx context.Context) (err error) {
   209  			return dropNode(ctx, c.client, request)
   210  		},
   211  		retry.WithStackTrace(),
   212  		retry.WithIdempotent(true),
   213  		retry.WithTrace(c.config.TraceRetry()),
   214  		retry.WithBudget(c.config.RetryBudget()),
   215  	)
   216  }
   217  
   218  func dropNodeRequest(path string, operationParams *Ydb_Operations.OperationParams) *Ydb_Coordination.DropNodeRequest {
   219  	return &Ydb_Coordination.DropNodeRequest{
   220  		Path:            path,
   221  		OperationParams: operationParams,
   222  	}
   223  }
   224  
   225  func dropNode(
   226  	ctx context.Context, client Ydb_Coordination_V1.CoordinationServiceClient, request *Ydb_Coordination.DropNodeRequest,
   227  ) error {
   228  	_, err := client.DropNode(ctx, request)
   229  	if err != nil {
   230  		return xerrors.WithStackTrace(err)
   231  	}
   232  
   233  	return nil
   234  }
   235  
   236  func (c *Client) DescribeNode(
   237  	ctx context.Context,
   238  	path string,
   239  ) (
   240  	entry *scheme.Entry,
   241  	config *coordination.NodeConfig,
   242  	finalErr error,
   243  ) {
   244  	if c == nil {
   245  		return nil, nil, xerrors.WithStackTrace(errNilClient)
   246  	}
   247  
   248  	onDone := trace.CoordinationOnDescribeNode(c.config.Trace(), &ctx,
   249  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination.(*Client).DescribeNode"),
   250  		path,
   251  	)
   252  	defer func() {
   253  		onDone(finalErr)
   254  	}()
   255  
   256  	request := describeNodeRequest(path, operationParams(ctx, &c.config, operation.ModeSync))
   257  
   258  	if !c.config.AutoRetry() {
   259  		return describeNode(ctx, c.client, request)
   260  	}
   261  
   262  	err := retry.Retry(ctx,
   263  		func(ctx context.Context) (err error) {
   264  			entry, config, err = describeNode(ctx, c.client, request)
   265  			if err != nil {
   266  				return xerrors.WithStackTrace(err)
   267  			}
   268  
   269  			return nil
   270  		},
   271  		retry.WithStackTrace(),
   272  		retry.WithIdempotent(true),
   273  		retry.WithTrace(c.config.TraceRetry()),
   274  		retry.WithBudget(c.config.RetryBudget()),
   275  	)
   276  	if err != nil {
   277  		return nil, nil, xerrors.WithStackTrace(err)
   278  	}
   279  
   280  	return entry, config, nil
   281  }
   282  
   283  func describeNodeRequest(
   284  	path string, operationParams *Ydb_Operations.OperationParams,
   285  ) *Ydb_Coordination.DescribeNodeRequest {
   286  	return &Ydb_Coordination.DescribeNodeRequest{
   287  		Path:            path,
   288  		OperationParams: operationParams,
   289  	}
   290  }
   291  
   292  // DescribeNode describes a coordination node
   293  func describeNode(
   294  	ctx context.Context,
   295  	client Ydb_Coordination_V1.CoordinationServiceClient,
   296  	request *Ydb_Coordination.DescribeNodeRequest,
   297  ) (
   298  	_ *scheme.Entry,
   299  	_ *coordination.NodeConfig,
   300  	err error,
   301  ) {
   302  	var (
   303  		response *Ydb_Coordination.DescribeNodeResponse
   304  		result   Ydb_Coordination.DescribeNodeResult
   305  	)
   306  	response, err = client.DescribeNode(ctx, request)
   307  	if err != nil {
   308  		return nil, nil, xerrors.WithStackTrace(err)
   309  	}
   310  
   311  	err = response.GetOperation().GetResult().UnmarshalTo(&result)
   312  	if err != nil {
   313  		return nil, nil, xerrors.WithStackTrace(err)
   314  	}
   315  
   316  	return scheme.InnerConvertEntry(result.GetSelf()), &coordination.NodeConfig{
   317  		Path:                     result.GetConfig().GetPath(),
   318  		SelfCheckPeriodMillis:    result.GetConfig().GetSelfCheckPeriodMillis(),
   319  		SessionGracePeriodMillis: result.GetConfig().GetSessionGracePeriodMillis(),
   320  		ReadConsistencyMode:      consistencyMode(result.GetConfig().GetReadConsistencyMode()),
   321  		AttachConsistencyMode:    consistencyMode(result.GetConfig().GetAttachConsistencyMode()),
   322  		RatelimiterCountersMode:  ratelimiterCountersMode(result.GetConfig().GetRateLimiterCountersMode()),
   323  	}, nil
   324  }
   325  
   326  func newCreateSessionConfig(opts ...options.SessionOption) *options.CreateSessionOptions {
   327  	c := defaultCreateSessionConfig()
   328  	for _, o := range opts {
   329  		if o != nil {
   330  			o(c)
   331  		}
   332  	}
   333  
   334  	return c
   335  }
   336  
   337  func (c *Client) sessionCreated(s *session) {
   338  	c.mutex.Lock()
   339  	defer c.mutex.Unlock()
   340  
   341  	c.sessions[s] = struct{}{}
   342  }
   343  
   344  func (c *Client) sessionClosed(s *session) {
   345  	c.mutex.Lock()
   346  	defer c.mutex.Unlock()
   347  
   348  	delete(c.sessions, s)
   349  }
   350  
   351  func (c *Client) closeSessions(ctx context.Context) {
   352  	c.mutex.Lock()
   353  	defer c.mutex.Unlock()
   354  
   355  	for s := range c.sessions {
   356  		s.Close(ctx)
   357  	}
   358  }
   359  
   360  func defaultCreateSessionConfig() *options.CreateSessionOptions {
   361  	return &options.CreateSessionOptions{
   362  		Description:             "YDB Go SDK",
   363  		SessionTimeout:          time.Second * 5, //nolint:gomnd
   364  		SessionStartTimeout:     time.Second * 1,
   365  		SessionStopTimeout:      time.Second * 1,
   366  		SessionKeepAliveTimeout: time.Second * 10,       //nolint:gomnd
   367  		SessionReconnectDelay:   time.Millisecond * 500, //nolint:gomnd
   368  	}
   369  }
   370  
   371  func (c *Client) Session(
   372  	ctx context.Context,
   373  	path string,
   374  	opts ...options.SessionOption,
   375  ) (_ coordination.Session, finalErr error) {
   376  	if c == nil {
   377  		return nil, xerrors.WithStackTrace(errNilClient)
   378  	}
   379  
   380  	onDone := trace.CoordinationOnSession(c.config.Trace(), &ctx,
   381  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination.(*Client).Session"),
   382  		path,
   383  	)
   384  	defer func() {
   385  		onDone(finalErr)
   386  	}()
   387  
   388  	return createSession(ctx, c, path, newCreateSessionConfig(opts...))
   389  }
   390  
   391  func (c *Client) Close(ctx context.Context) (finalErr error) {
   392  	if c == nil {
   393  		return xerrors.WithStackTrace(errNilClient)
   394  	}
   395  
   396  	onDone := trace.CoordinationOnClose(c.config.Trace(), &ctx,
   397  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination.(*Client).Close"),
   398  	)
   399  	defer func() {
   400  		onDone(finalErr)
   401  	}()
   402  
   403  	c.closeSessions(ctx)
   404  
   405  	return nil
   406  }
   407  
   408  func consistencyMode(t Ydb_Coordination.ConsistencyMode) coordination.ConsistencyMode {
   409  	switch t {
   410  	case Ydb_Coordination.ConsistencyMode_CONSISTENCY_MODE_STRICT:
   411  		return coordination.ConsistencyModeStrict
   412  	case Ydb_Coordination.ConsistencyMode_CONSISTENCY_MODE_RELAXED:
   413  		return coordination.ConsistencyModeRelaxed
   414  	default:
   415  		return coordination.ConsistencyModeUnset
   416  	}
   417  }
   418  
   419  func ratelimiterCountersMode(t Ydb_Coordination.RateLimiterCountersMode) coordination.RatelimiterCountersMode {
   420  	switch t {
   421  	case Ydb_Coordination.RateLimiterCountersMode_RATE_LIMITER_COUNTERS_MODE_AGGREGATED:
   422  		return coordination.RatelimiterCountersModeAggregated
   423  	case Ydb_Coordination.RateLimiterCountersMode_RATE_LIMITER_COUNTERS_MODE_DETAILED:
   424  		return coordination.RatelimiterCountersModeDetailed
   425  	default:
   426  		return coordination.RatelimiterCountersModeUnset
   427  	}
   428  }