github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/externalresource/model/model.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 model
    15  
    16  import (
    17  	"encoding/hex"
    18  	"fmt"
    19  	"path"
    20  	"strings"
    21  
    22  	"github.com/pingcap/log"
    23  	pb "github.com/pingcap/tiflow/engine/enginepb"
    24  	"github.com/pingcap/tiflow/engine/model"
    25  	ormModel "github.com/pingcap/tiflow/engine/pkg/orm/model"
    26  	"github.com/pingcap/tiflow/engine/pkg/tenant"
    27  	"github.com/pingcap/tiflow/pkg/errors"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  type (
    32  	// WorkerID alias worker id string
    33  	WorkerID = string
    34  	// JobID alias job id string
    35  	JobID = model.JobID
    36  	// ExecutorID alias model.ExecutorID
    37  	ExecutorID = model.ExecutorID
    38  
    39  	// ResourceType represents the type of the resource
    40  	ResourceType string
    41  	// ResourceID should be in the form of `/<type>/<unique-name>`, currently
    42  	// only local type is available.
    43  	ResourceID = string
    44  	// ResourceName is a string encoding raw resource name in hexadecimal.
    45  	// The raw resource name is the ResourceID with its type prefix removed.
    46  	// For example, the raw resource name of `/local/resource-1` is `resource-1`.
    47  	ResourceName = string
    48  )
    49  
    50  // ResourceUpdateColumns is used in gorm update
    51  var ResourceUpdateColumns = []string{
    52  	"updated_at",
    53  	"project_id",
    54  	"tenant_id",
    55  	"id",
    56  	"job_id",
    57  	"worker_id",
    58  	"executor_id",
    59  	"deleted",
    60  }
    61  
    62  // ResourceKey is the unique identifier for the resource
    63  type ResourceKey struct {
    64  	JobID JobID
    65  	ID    ResourceID
    66  }
    67  
    68  // ToResourceKeys converts resource requirements in pb to resource keys
    69  func ToResourceKeys(requires []*pb.ResourceKey) []ResourceKey {
    70  	if len(requires) == 0 {
    71  		return nil
    72  	}
    73  	rks := make([]ResourceKey, 0, len(requires))
    74  	for _, require := range requires {
    75  		rks = append(rks, ResourceKey{JobID: require.GetJobId(), ID: require.GetResourceId()})
    76  	}
    77  
    78  	return rks
    79  }
    80  
    81  // ToResourceRequirement return the resource requirement of pb
    82  func ToResourceRequirement(jobID JobID, resourceIDs ...ResourceID) []*pb.ResourceKey {
    83  	if len(resourceIDs) == 0 {
    84  		return nil
    85  	}
    86  	ress := make([]*pb.ResourceKey, 0, len(resourceIDs))
    87  	for _, rid := range resourceIDs {
    88  		ress = append(ress, &pb.ResourceKey{JobId: jobID, ResourceId: rid})
    89  	}
    90  
    91  	return ress
    92  }
    93  
    94  // ResourceMeta is the records stored in the metastore.
    95  type ResourceMeta struct {
    96  	ormModel.Model
    97  	ProjectID tenant.ProjectID `json:"project-id" gorm:"column:project_id;type:varchar(128) not null;"`
    98  	TenantID  tenant.Tenant    `json:"tenant-id" gorm:"column:tenant_id;type:varchar(128) not null;"`
    99  	ID        ResourceID       `json:"id" gorm:"column:id;type:varchar(128) not null;uniqueIndex:uidx_rid,priority:2;index:idx_rei,priority:2"`
   100  	Job       JobID            `json:"job" gorm:"column:job_id;type:varchar(128) not null;uniqueIndex:uidx_rid,priority:1"`
   101  	Worker    WorkerID         `json:"worker" gorm:"column:worker_id;type:varchar(128) not null"`
   102  	Executor  ExecutorID       `json:"executor" gorm:"column:executor_id;type:varchar(128) not null;index:idx_rei,priority:1"`
   103  	GCPending bool             `json:"gc-pending" gorm:"column:gc_pending;type:BOOLEAN"`
   104  
   105  	// TODO soft delete has not be implemented, because it requires modifying too many
   106  	// unit tests in engine/pkg/orm
   107  	Deleted bool `json:"deleted" gorm:"column:deleted;type:BOOLEAN"`
   108  }
   109  
   110  // GetID implements dataset.DataEntry
   111  func (m *ResourceMeta) GetID() string {
   112  	return m.ID
   113  }
   114  
   115  // ToQueryResourceResponse converts the ResourceMeta to pb.QueryResourceResponse
   116  func (m *ResourceMeta) ToQueryResourceResponse() *pb.QueryResourceResponse {
   117  	return &pb.QueryResourceResponse{
   118  		CreatorExecutor: string(m.Executor),
   119  		JobId:           m.Job,
   120  		CreatorWorkerId: m.Worker,
   121  	}
   122  }
   123  
   124  // Map is used in gorm update
   125  func (m *ResourceMeta) Map() map[string]interface{} {
   126  	return map[string]interface{}{
   127  		"project_id":  m.ProjectID,
   128  		"tenant_id":   m.TenantID,
   129  		"id":          m.ID,
   130  		"job_id":      m.Job,
   131  		"worker_id":   m.Worker,
   132  		"executor_id": m.Executor,
   133  		"deleted":     m.Deleted,
   134  	}
   135  }
   136  
   137  // Define all supported resource types.
   138  const (
   139  	ResourceTypeLocalFile = ResourceType("local")
   140  	ResourceTypeS3        = ResourceType("s3")
   141  	ResourceTypeGCS       = ResourceType("gs")
   142  	ResourceTypeNone      = ResourceType("none")
   143  )
   144  
   145  // BuildPrefix returns the prefix of the resource type.
   146  func (r ResourceType) BuildPrefix() string {
   147  	// For local file, the prefix is `/local`. For S3, the prefix is `/s3`.
   148  	return fmt.Sprintf("/%s", r)
   149  }
   150  
   151  // ParseResourceID returns the ResourceType and the path suffix.
   152  func ParseResourceID(rpath ResourceID) (ResourceType, ResourceName, error) {
   153  	if !strings.HasPrefix(rpath, "/") {
   154  		return "", "", errors.ErrIllegalResourcePath.GenWithStackByArgs(rpath)
   155  	}
   156  	rpath = strings.TrimPrefix(rpath, "/")
   157  	segments := strings.Split(rpath, "/")
   158  	if len(segments) == 0 {
   159  		return "", "", errors.ErrIllegalResourcePath.GenWithStackByArgs(rpath)
   160  	}
   161  
   162  	var resourceType ResourceType
   163  	switch ResourceType(segments[0]) {
   164  	case ResourceTypeLocalFile:
   165  		resourceType = ResourceTypeLocalFile
   166  	case ResourceTypeS3:
   167  		resourceType = ResourceTypeS3
   168  	case ResourceTypeGCS:
   169  		resourceType = ResourceTypeGCS
   170  	default:
   171  		return "", "", errors.ErrIllegalResourcePath.GenWithStackByArgs(rpath)
   172  	}
   173  
   174  	suffix := path.Join(segments[1:]...)
   175  	return resourceType, EncodeResourceName(suffix), nil
   176  }
   177  
   178  // BuildResourceID returns an ResourceID based on given ResourceType and ResourceName.
   179  func BuildResourceID(rtype ResourceType, resName ResourceName) ResourceID {
   180  	name, err := DecodeResourceName(resName)
   181  	if err != nil {
   182  		log.Panic("invalid resource name", zap.Error(err))
   183  	}
   184  	return path.Join("/"+string(rtype), name)
   185  }
   186  
   187  // EncodeResourceName encodes raw resource name to a valid resource name.
   188  func EncodeResourceName(rawResName string) ResourceName {
   189  	resName := hex.EncodeToString([]byte(rawResName))
   190  	return resName
   191  }
   192  
   193  // DecodeResourceName decodes resource name to raw resource name.
   194  func DecodeResourceName(resName ResourceName) (string, error) {
   195  	rawResName, err := hex.DecodeString(resName)
   196  	if err != nil {
   197  		return "", err
   198  	}
   199  	return string(rawResName), nil
   200  }