go.temporal.io/server@v1.23.0/common/persistence/cassandra/cluster_metadata_store.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package cassandra
    26  
    27  import (
    28  	"context"
    29  	"net"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/pborman/uuid"
    34  	"go.temporal.io/api/serviceerror"
    35  
    36  	"go.temporal.io/server/common/log"
    37  	p "go.temporal.io/server/common/persistence"
    38  	"go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
    39  )
    40  
    41  const constMetadataPartition = 0
    42  const constMembershipPartition = 0
    43  
    44  const (
    45  	// ****** CLUSTER_METADATA_INFO TABLE ******
    46  	templateListClusterMetadata   = `SELECT data, data_encoding, version FROM cluster_metadata_info WHERE metadata_partition = ?`
    47  	templateGetClusterMetadata    = `SELECT data, data_encoding, version FROM cluster_metadata_info WHERE metadata_partition = ? AND cluster_name= ?`
    48  	templateCreateClusterMetadata = `INSERT INTO cluster_metadata_info (metadata_partition, cluster_name, data, data_encoding, version) VALUES(?, ?, ?, ?, ?) IF NOT EXISTS`
    49  	templateUpdateClusterMetadata = `UPDATE cluster_metadata_info SET data = ?, data_encoding = ?, version = ? WHERE metadata_partition = ? AND cluster_name = ? IF version = ?`
    50  	templateDeleteClusterMetadata = `DELETE FROM cluster_metadata_info WHERE metadata_partition = ? AND cluster_name= ?`
    51  
    52  	// ****** CLUSTER_MEMBERSHIP TABLE ******
    53  	templateUpsertActiveClusterMembership = `INSERT INTO 
    54  cluster_membership (membership_partition, host_id, rpc_address, rpc_port, role, session_start, last_heartbeat) 
    55  VALUES (?, ?, ?, ?, ?, ?, ?) USING TTL ?`
    56  
    57  	templateGetClusterMembership = `SELECT host_id, rpc_address, rpc_port, role, session_start, last_heartbeat, toTimestamp(now()) as now, TTL(session_start) as ttl_sec FROM
    58  cluster_membership 
    59  WHERE membership_partition = ?`
    60  
    61  	templateWithRoleSuffix           = ` AND role = ?`
    62  	templateWithHeartbeatSinceSuffix = ` AND last_heartbeat > ?`
    63  	templateWithRPCAddressSuffix     = ` AND rpc_address = ?`
    64  	templateWithHostIDSuffix         = ` AND host_id = ?`
    65  	templateAllowFiltering           = ` ALLOW FILTERING`
    66  	templateWithSessionSuffix        = ` AND session_start > ?`
    67  )
    68  
    69  type (
    70  	ClusterMetadataStore struct {
    71  		session gocql.Session
    72  		logger  log.Logger
    73  	}
    74  )
    75  
    76  var _ p.ClusterMetadataStore = (*ClusterMetadataStore)(nil)
    77  
    78  // NewClusterMetadataStore is used to create an instance of ClusterMetadataStore implementation
    79  func NewClusterMetadataStore(
    80  	session gocql.Session,
    81  	logger log.Logger,
    82  ) (p.ClusterMetadataStore, error) {
    83  	return &ClusterMetadataStore{
    84  		session: session,
    85  		logger:  logger,
    86  	}, nil
    87  }
    88  
    89  func (m *ClusterMetadataStore) ListClusterMetadata(
    90  	ctx context.Context,
    91  	request *p.InternalListClusterMetadataRequest,
    92  ) (*p.InternalListClusterMetadataResponse, error) {
    93  	query := m.session.Query(templateListClusterMetadata, constMetadataPartition).WithContext(ctx)
    94  	iter := query.PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
    95  
    96  	response := &p.InternalListClusterMetadataResponse{}
    97  	for {
    98  		var clusterMetadata []byte
    99  		var encoding string
   100  		var version int64
   101  
   102  		if !iter.Scan(
   103  			&clusterMetadata,
   104  			&encoding,
   105  			&version) {
   106  			break
   107  		}
   108  		response.ClusterMetadata = append(
   109  			response.ClusterMetadata,
   110  			&p.InternalGetClusterMetadataResponse{
   111  				ClusterMetadata: p.NewDataBlob(clusterMetadata, encoding),
   112  				Version:         version,
   113  			},
   114  		)
   115  	}
   116  
   117  	if len(iter.PageState()) > 0 {
   118  		response.NextPageToken = iter.PageState()
   119  	}
   120  	if err := iter.Close(); err != nil {
   121  		return nil, gocql.ConvertError("ListClusterMetadata", err)
   122  	}
   123  	return response, nil
   124  }
   125  
   126  func (m *ClusterMetadataStore) GetClusterMetadata(
   127  	ctx context.Context,
   128  	request *p.InternalGetClusterMetadataRequest,
   129  ) (*p.InternalGetClusterMetadataResponse, error) {
   130  
   131  	var clusterMetadata []byte
   132  	var encoding string
   133  	var version int64
   134  
   135  	query := m.session.Query(templateGetClusterMetadata, constMetadataPartition, request.ClusterName).WithContext(ctx)
   136  	err := query.Scan(&clusterMetadata, &encoding, &version)
   137  	if err != nil {
   138  		return nil, gocql.ConvertError("GetClusterMetadata", err)
   139  	}
   140  
   141  	return &p.InternalGetClusterMetadataResponse{
   142  		ClusterMetadata: p.NewDataBlob(clusterMetadata, encoding),
   143  		Version:         version,
   144  	}, nil
   145  }
   146  
   147  func (m *ClusterMetadataStore) SaveClusterMetadata(
   148  	ctx context.Context,
   149  	request *p.InternalSaveClusterMetadataRequest,
   150  ) (bool, error) {
   151  	var query gocql.Query
   152  	if request.Version == 0 {
   153  		query = m.session.Query(
   154  			templateCreateClusterMetadata,
   155  			constMetadataPartition,
   156  			request.ClusterName,
   157  			request.ClusterMetadata.Data,
   158  			request.ClusterMetadata.EncodingType.String(),
   159  			1,
   160  		).WithContext(ctx)
   161  	} else {
   162  		query = m.session.Query(
   163  			templateUpdateClusterMetadata,
   164  			request.ClusterMetadata.Data,
   165  			request.ClusterMetadata.EncodingType.String(),
   166  			request.Version+1,
   167  			constMetadataPartition,
   168  			request.ClusterName,
   169  			request.Version,
   170  		).WithContext(ctx)
   171  	}
   172  
   173  	previous := make(map[string]interface{})
   174  	applied, err := query.MapScanCAS(previous)
   175  	if err != nil {
   176  		return false, gocql.ConvertError("SaveClusterMetadata", err)
   177  	}
   178  	if !applied {
   179  		return false, serviceerror.NewUnavailable("SaveClusterMetadata operation encountered concurrent write.")
   180  	}
   181  	return true, nil
   182  }
   183  
   184  func (m *ClusterMetadataStore) DeleteClusterMetadata(
   185  	ctx context.Context,
   186  	request *p.InternalDeleteClusterMetadataRequest,
   187  ) error {
   188  	query := m.session.Query(templateDeleteClusterMetadata, constMetadataPartition, request.ClusterName).WithContext(ctx)
   189  	if err := query.Exec(); err != nil {
   190  		return gocql.ConvertError("DeleteClusterMetadata", err)
   191  	}
   192  	return nil
   193  }
   194  
   195  func (m *ClusterMetadataStore) GetClusterMembers(
   196  	ctx context.Context,
   197  	request *p.GetClusterMembersRequest,
   198  ) (*p.GetClusterMembersResponse, error) {
   199  	var queryString strings.Builder
   200  	var operands []interface{}
   201  	queryString.WriteString(templateGetClusterMembership)
   202  	operands = append(operands, constMembershipPartition)
   203  
   204  	if request.HostIDEquals != nil {
   205  		queryString.WriteString(templateWithHostIDSuffix)
   206  		operands = append(operands, []byte(request.HostIDEquals))
   207  	}
   208  
   209  	if request.RPCAddressEquals != nil {
   210  		queryString.WriteString(templateWithRPCAddressSuffix)
   211  		operands = append(operands, request.RPCAddressEquals)
   212  	}
   213  
   214  	if request.RoleEquals != p.All {
   215  		queryString.WriteString(templateWithRoleSuffix)
   216  		operands = append(operands, request.RoleEquals)
   217  	}
   218  
   219  	if !request.SessionStartedAfter.IsZero() {
   220  		queryString.WriteString(templateWithSessionSuffix)
   221  		operands = append(operands, request.SessionStartedAfter)
   222  	}
   223  
   224  	// LastHeartbeat needs to be the last one as it needs AllowFilteringSuffix
   225  	if request.LastHeartbeatWithin > 0 {
   226  		queryString.WriteString(templateWithHeartbeatSinceSuffix)
   227  		operands = append(operands, time.Now().UTC().Add(-request.LastHeartbeatWithin))
   228  	}
   229  
   230  	queryString.WriteString(templateAllowFiltering)
   231  	query := m.session.Query(queryString.String(), operands...).WithContext(ctx)
   232  
   233  	iter := query.PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
   234  
   235  	var clusterMembers []*p.ClusterMember
   236  
   237  	cqlHostID := make([]byte, 0, 16)
   238  	rpcAddress := net.IP{}
   239  	rpcPort := uint16(0)
   240  	role := p.All
   241  	sessionStart := time.Time{}
   242  	lastHeartbeat := time.Time{}
   243  	ttl := 0
   244  	cassNow := time.Time{}
   245  
   246  	for iter.Scan(&cqlHostID, &rpcAddress, &rpcPort, &role, &sessionStart, &lastHeartbeat, &cassNow, &ttl) {
   247  		member := p.ClusterMember{
   248  			HostID:        uuid.UUID(cqlHostID),
   249  			RPCAddress:    rpcAddress,
   250  			RPCPort:       rpcPort,
   251  			Role:          role,
   252  			SessionStart:  sessionStart,
   253  			LastHeartbeat: lastHeartbeat,
   254  			RecordExpiry:  cassNow.UTC().Add(time.Second * time.Duration(ttl)),
   255  		}
   256  		clusterMembers = append(clusterMembers, &member)
   257  	}
   258  
   259  	var pagingToken []byte
   260  	// contract of this API expect nil as pagination token instead of empty byte array
   261  	if len(iter.PageState()) > 0 {
   262  		pagingToken = iter.PageState()
   263  	}
   264  
   265  	if err := iter.Close(); err != nil {
   266  		return nil, gocql.ConvertError("GetClusterMembers", err)
   267  	}
   268  
   269  	return &p.GetClusterMembersResponse{ActiveMembers: clusterMembers, NextPageToken: pagingToken}, nil
   270  }
   271  
   272  func (m *ClusterMetadataStore) UpsertClusterMembership(
   273  	ctx context.Context,
   274  	request *p.UpsertClusterMembershipRequest,
   275  ) error {
   276  	query := m.session.Query(
   277  		templateUpsertActiveClusterMembership,
   278  		constMembershipPartition,
   279  		[]byte(request.HostID),
   280  		request.RPCAddress,
   281  		request.RPCPort,
   282  		request.Role,
   283  		request.SessionStart,
   284  		time.Now().UTC(),
   285  		int64(request.RecordExpiry.Seconds()),
   286  	).WithContext(ctx)
   287  	err := query.Exec()
   288  
   289  	if err != nil {
   290  		return gocql.ConvertError("UpsertClusterMembership", err)
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  func (m *ClusterMetadataStore) PruneClusterMembership(
   297  	_ context.Context,
   298  	request *p.PruneClusterMembershipRequest,
   299  ) error {
   300  	return nil
   301  }
   302  
   303  func (m *ClusterMetadataStore) GetName() string {
   304  	return cassandraPersistenceName
   305  }
   306  
   307  func (m *ClusterMetadataStore) Close() {
   308  	if m.session != nil {
   309  		m.session.Close()
   310  	}
   311  }