github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/externalresource/manager/service.go (about)

     1  // Copyright 2022 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package manager
    15  
    16  import (
    17  	"context"
    18  
    19  	"github.com/pingcap/log"
    20  	pb "github.com/pingcap/tiflow/engine/enginepb"
    21  	"github.com/pingcap/tiflow/engine/model"
    22  	resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model"
    23  	pkgOrm "github.com/pingcap/tiflow/engine/pkg/orm"
    24  	"github.com/pingcap/tiflow/engine/pkg/tenant"
    25  	"github.com/pingcap/tiflow/pkg/errors"
    26  	"go.uber.org/zap"
    27  	"google.golang.org/grpc/codes"
    28  	"google.golang.org/grpc/status"
    29  )
    30  
    31  var _ pb.ResourceManagerServer = (*Service)(nil)
    32  
    33  // Service implements pb.ResourceManagerServer
    34  type Service struct {
    35  	metaclient pkgOrm.Client
    36  }
    37  
    38  // NewService creates a new externalresource manage service
    39  func NewService(metaclient pkgOrm.Client) *Service {
    40  	return &Service{
    41  		metaclient: metaclient,
    42  	}
    43  }
    44  
    45  // QueryResource implements ResourceManagerClient.QueryResource
    46  func (s *Service) QueryResource(
    47  	ctx context.Context,
    48  	request *pb.QueryResourceRequest,
    49  ) (*pb.QueryResourceResponse, error) {
    50  	jobID := request.GetResourceKey().GetJobId()
    51  	resourceID := request.GetResourceKey().GetResourceId()
    52  
    53  	if err := checkArguments(resourceID, jobID); err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	record, err := s.metaclient.GetResourceByID(ctx, pkgOrm.ResourceKey{JobID: jobID, ID: resourceID})
    58  	if err != nil {
    59  		if pkgOrm.IsNotFoundError(err) {
    60  			return nil, errors.ErrResourceDoesNotExist.GenWithStackByArgs(resourceID)
    61  		}
    62  		return nil, errors.ErrResourceMetastoreError.Wrap(err).GenWithStackByArgs()
    63  	}
    64  
    65  	if record.Deleted {
    66  		// This logic is currently not used.
    67  		return nil, status.Error(codes.NotFound, "resource marked as deleted")
    68  	}
    69  	return record.ToQueryResourceResponse(), nil
    70  }
    71  
    72  // CreateResource implements ResourceManagerClient.CreateResource
    73  func (s *Service) CreateResource(
    74  	ctx context.Context,
    75  	request *pb.CreateResourceRequest,
    76  ) (*pb.CreateResourceResponse, error) {
    77  	if err := checkArguments(request.GetResourceId(), request.GetJobId()); err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	resourceRecord := &resModel.ResourceMeta{
    82  		ProjectID: tenant.NewProjectInfo(request.GetProjectInfo().TenantId, request.GetProjectInfo().ProjectId).UniqueID(),
    83  		ID:        request.GetResourceId(),
    84  		Job:       request.GetJobId(),
    85  		Worker:    request.GetCreatorWorkerId(),
    86  		Executor:  resModel.ExecutorID(request.GetCreatorExecutor()),
    87  		Deleted:   false,
    88  	}
    89  
    90  	err := s.metaclient.CreateResource(ctx, resourceRecord)
    91  	if errors.Is(err, errors.ErrDuplicateResourceID) {
    92  		return nil, errors.ErrResourceAlreadyExists.GenWithStackByArgs(request.GetResourceId())
    93  	}
    94  	if err != nil {
    95  		return nil, errors.ErrResourceMetastoreError.Wrap(err).GenWithStackByArgs()
    96  	}
    97  
    98  	return &pb.CreateResourceResponse{}, nil
    99  }
   100  
   101  // RemoveResource implements ResourceManagerClient.RemoveResource
   102  func (s *Service) RemoveResource(
   103  	ctx context.Context,
   104  	request *pb.RemoveResourceRequest,
   105  ) (*pb.RemoveResourceResponse, error) {
   106  	jobID := request.GetResourceKey().GetJobId()
   107  	resourceID := request.GetResourceKey().GetResourceId()
   108  	if err := checkArguments(resourceID, jobID); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	res, err := s.metaclient.DeleteResource(ctx, pkgOrm.ResourceKey{JobID: jobID, ID: resourceID})
   113  	if err != nil {
   114  		return nil, errors.ErrResourceMetastoreError.Wrap(err).GenWithStackByArgs()
   115  	}
   116  
   117  	if res.RowsAffected() == 0 {
   118  		return nil, errors.ErrResourceDoesNotExist.GenWithStackByArgs(resourceID)
   119  	}
   120  	if res.RowsAffected() > 1 {
   121  		log.Panic("unexpected RowsAffected",
   122  			zap.String("job-id", jobID),
   123  			zap.String("resource-id", resourceID))
   124  	}
   125  
   126  	return &pb.RemoveResourceResponse{}, nil
   127  }
   128  
   129  // GetPlacementConstraint is called by the Scheduler to determine whether
   130  // a resource the worker relies on requires the worker running on a specific
   131  // executor.
   132  // Returns:
   133  // (1) A local resource is required and the resource exists: (executorID, true, nil)
   134  // (2) A local resource is required but the resource is not found: ("", false, ErrResourceDoesNotExist)
   135  // (3) No placement constraint is needed: ("", false, nil)
   136  // (4) Other errors: ("", false, err)
   137  func (s *Service) GetPlacementConstraint(
   138  	ctx context.Context,
   139  	resourceKey resModel.ResourceKey,
   140  ) (resModel.ExecutorID, bool, error) {
   141  	logger := log.L().With(
   142  		zap.String("job-id", resourceKey.JobID),
   143  		zap.String("resource-id", resourceKey.ID))
   144  
   145  	rType, _, err := resModel.ParseResourceID(resourceKey.ID)
   146  	if err != nil {
   147  		return "", false, err
   148  	}
   149  
   150  	if rType != resModel.ResourceTypeLocalFile {
   151  		logger.Info("Resource does not need a constraint",
   152  			zap.String("resource-id", resourceKey.ID), zap.String("type", string(rType)))
   153  		return "", false, nil
   154  	}
   155  
   156  	record, err := s.metaclient.GetResourceByID(ctx, pkgOrm.ResourceKey{JobID: resourceKey.JobID, ID: resourceKey.ID})
   157  	if err != nil {
   158  		if pkgOrm.IsNotFoundError(err) {
   159  			return "", false, errors.ErrResourceDoesNotExist.GenWithStackByArgs(resourceKey.ID)
   160  		}
   161  		return "", false, err
   162  	}
   163  
   164  	if record.Deleted {
   165  		logger.Info("Resource meta is marked as deleted", zap.Any("record", record))
   166  		return "", false, errors.ErrResourceDoesNotExist.GenWithStackByArgs(resourceKey.ID)
   167  	}
   168  	return record.Executor, true, nil
   169  }
   170  
   171  func checkArguments(resourceID resModel.ResourceID, jobID model.JobID) error {
   172  	if resourceID == "" {
   173  		return errors.ErrInvalidArgument.GenWithStackByArgs("resource-id")
   174  	}
   175  
   176  	if jobID == "" {
   177  		return errors.ErrInvalidArgument.GenWithStackByArgs("job-id")
   178  	}
   179  	return nil
   180  }