Files
tasks/common/persistence/cassandra/cluster_metadata_store.go
Jacob BarzeeandGitHub 06171037f0 Import alignment (#6426)
## What changed?
Make import formatting a part of the build, and test validity with
"ensure-no-changes" target

## Why?
So we never discuss import order again (hopefully)

## How did you test it?
its a build change, so the build will test it!

## Potential risks
As long as build works, the only thing I risk is the ire of coworker who
want imports to be different ;)

## Documentation
N/A

## Is hotfix candidate?
No
2024-08-23 14:48:10 -06:00

311 lines
9.7 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package cassandra
import (
"context"
"net"
"strings"
"time"
"github.com/pborman/uuid"
"go.temporal.io/api/serviceerror"
"go.temporal.io/server/common/log"
p "go.temporal.io/server/common/persistence"
"go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
)
const constMetadataPartition = 0
const constMembershipPartition = 0
const (
// ****** CLUSTER_METADATA_INFO TABLE ******
templateListClusterMetadata = `SELECT data, data_encoding, version FROM cluster_metadata_info WHERE metadata_partition = ?`
templateGetClusterMetadata = `SELECT data, data_encoding, version FROM cluster_metadata_info WHERE metadata_partition = ? AND cluster_name= ?`
templateCreateClusterMetadata = `INSERT INTO cluster_metadata_info (metadata_partition, cluster_name, data, data_encoding, version) VALUES(?, ?, ?, ?, ?) IF NOT EXISTS`
templateUpdateClusterMetadata = `UPDATE cluster_metadata_info SET data = ?, data_encoding = ?, version = ? WHERE metadata_partition = ? AND cluster_name = ? IF version = ?`
templateDeleteClusterMetadata = `DELETE FROM cluster_metadata_info WHERE metadata_partition = ? AND cluster_name= ?`
// ****** CLUSTER_MEMBERSHIP TABLE ******
templateUpsertActiveClusterMembership = `INSERT INTO
cluster_membership (membership_partition, host_id, rpc_address, rpc_port, role, session_start, last_heartbeat)
VALUES (?, ?, ?, ?, ?, ?, ?) USING TTL ?`
templateGetClusterMembership = `SELECT host_id, rpc_address, rpc_port, role, session_start, last_heartbeat, toTimestamp(now()) as now, TTL(session_start) as ttl_sec FROM
cluster_membership
WHERE membership_partition = ?`
templateWithRoleSuffix = ` AND role = ?`
templateWithHeartbeatSinceSuffix = ` AND last_heartbeat > ?`
templateWithRPCAddressSuffix = ` AND rpc_address = ?`
templateWithHostIDSuffix = ` AND host_id = ?`
templateAllowFiltering = ` ALLOW FILTERING`
templateWithSessionSuffix = ` AND session_start > ?`
)
type (
ClusterMetadataStore struct {
session gocql.Session
logger log.Logger
}
)
var _ p.ClusterMetadataStore = (*ClusterMetadataStore)(nil)
// NewClusterMetadataStore is used to create an instance of ClusterMetadataStore implementation
func NewClusterMetadataStore(
session gocql.Session,
logger log.Logger,
) (p.ClusterMetadataStore, error) {
return &ClusterMetadataStore{
session: session,
logger: logger,
}, nil
}
func (m *ClusterMetadataStore) ListClusterMetadata(
ctx context.Context,
request *p.InternalListClusterMetadataRequest,
) (*p.InternalListClusterMetadataResponse, error) {
query := m.session.Query(templateListClusterMetadata, constMetadataPartition).WithContext(ctx)
iter := query.PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
response := &p.InternalListClusterMetadataResponse{}
for {
var clusterMetadata []byte
var encoding string
var version int64
if !iter.Scan(
&clusterMetadata,
&encoding,
&version) {
break
}
response.ClusterMetadata = append(
response.ClusterMetadata,
&p.InternalGetClusterMetadataResponse{
ClusterMetadata: p.NewDataBlob(clusterMetadata, encoding),
Version: version,
},
)
}
if len(iter.PageState()) > 0 {
response.NextPageToken = iter.PageState()
}
if err := iter.Close(); err != nil {
return nil, gocql.ConvertError("ListClusterMetadata", err)
}
return response, nil
}
func (m *ClusterMetadataStore) GetClusterMetadata(
ctx context.Context,
request *p.InternalGetClusterMetadataRequest,
) (*p.InternalGetClusterMetadataResponse, error) {
var clusterMetadata []byte
var encoding string
var version int64
query := m.session.Query(templateGetClusterMetadata, constMetadataPartition, request.ClusterName).WithContext(ctx)
err := query.Scan(&clusterMetadata, &encoding, &version)
if err != nil {
return nil, gocql.ConvertError("GetClusterMetadata", err)
}
return &p.InternalGetClusterMetadataResponse{
ClusterMetadata: p.NewDataBlob(clusterMetadata, encoding),
Version: version,
}, nil
}
func (m *ClusterMetadataStore) SaveClusterMetadata(
ctx context.Context,
request *p.InternalSaveClusterMetadataRequest,
) (bool, error) {
var query gocql.Query
if request.Version == 0 {
query = m.session.Query(
templateCreateClusterMetadata,
constMetadataPartition,
request.ClusterName,
request.ClusterMetadata.Data,
request.ClusterMetadata.EncodingType.String(),
1,
).WithContext(ctx)
} else {
query = m.session.Query(
templateUpdateClusterMetadata,
request.ClusterMetadata.Data,
request.ClusterMetadata.EncodingType.String(),
request.Version+1,
constMetadataPartition,
request.ClusterName,
request.Version,
).WithContext(ctx)
}
previous := make(map[string]interface{})
applied, err := query.MapScanCAS(previous)
if err != nil {
return false, gocql.ConvertError("SaveClusterMetadata", err)
}
if !applied {
return false, serviceerror.NewUnavailable("SaveClusterMetadata operation encountered concurrent write.")
}
return true, nil
}
func (m *ClusterMetadataStore) DeleteClusterMetadata(
ctx context.Context,
request *p.InternalDeleteClusterMetadataRequest,
) error {
query := m.session.Query(templateDeleteClusterMetadata, constMetadataPartition, request.ClusterName).WithContext(ctx)
if err := query.Exec(); err != nil {
return gocql.ConvertError("DeleteClusterMetadata", err)
}
return nil
}
func (m *ClusterMetadataStore) GetClusterMembers(
ctx context.Context,
request *p.GetClusterMembersRequest,
) (*p.GetClusterMembersResponse, error) {
var queryString strings.Builder
var operands []interface{}
queryString.WriteString(templateGetClusterMembership)
operands = append(operands, constMembershipPartition)
if request.HostIDEquals != nil {
queryString.WriteString(templateWithHostIDSuffix)
operands = append(operands, []byte(request.HostIDEquals))
}
if request.RPCAddressEquals != nil {
queryString.WriteString(templateWithRPCAddressSuffix)
operands = append(operands, request.RPCAddressEquals)
}
if request.RoleEquals != p.All {
queryString.WriteString(templateWithRoleSuffix)
operands = append(operands, request.RoleEquals)
}
if !request.SessionStartedAfter.IsZero() {
queryString.WriteString(templateWithSessionSuffix)
operands = append(operands, request.SessionStartedAfter)
}
// LastHeartbeat needs to be the last one as it needs AllowFilteringSuffix
if request.LastHeartbeatWithin > 0 {
queryString.WriteString(templateWithHeartbeatSinceSuffix)
operands = append(operands, time.Now().UTC().Add(-request.LastHeartbeatWithin))
}
queryString.WriteString(templateAllowFiltering)
query := m.session.Query(queryString.String(), operands...).WithContext(ctx)
iter := query.PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
var clusterMembers []*p.ClusterMember
cqlHostID := make([]byte, 0, 16)
rpcAddress := net.IP{}
rpcPort := uint16(0)
role := p.All
sessionStart := time.Time{}
lastHeartbeat := time.Time{}
ttl := 0
cassNow := time.Time{}
for iter.Scan(&cqlHostID, &rpcAddress, &rpcPort, &role, &sessionStart, &lastHeartbeat, &cassNow, &ttl) {
member := p.ClusterMember{
HostID: uuid.UUID(cqlHostID),
RPCAddress: rpcAddress,
RPCPort: rpcPort,
Role: role,
SessionStart: sessionStart,
LastHeartbeat: lastHeartbeat,
RecordExpiry: cassNow.UTC().Add(time.Second * time.Duration(ttl)),
}
clusterMembers = append(clusterMembers, &member)
}
var pagingToken []byte
// contract of this API expect nil as pagination token instead of empty byte array
if len(iter.PageState()) > 0 {
pagingToken = iter.PageState()
}
if err := iter.Close(); err != nil {
return nil, gocql.ConvertError("GetClusterMembers", err)
}
return &p.GetClusterMembersResponse{ActiveMembers: clusterMembers, NextPageToken: pagingToken}, nil
}
func (m *ClusterMetadataStore) UpsertClusterMembership(
ctx context.Context,
request *p.UpsertClusterMembershipRequest,
) error {
query := m.session.Query(
templateUpsertActiveClusterMembership,
constMembershipPartition,
[]byte(request.HostID),
request.RPCAddress,
request.RPCPort,
request.Role,
request.SessionStart,
time.Now().UTC(),
int64(request.RecordExpiry.Seconds()),
).WithContext(ctx)
err := query.Exec()
if err != nil {
return gocql.ConvertError("UpsertClusterMembership", err)
}
return nil
}
func (m *ClusterMetadataStore) PruneClusterMembership(
_ context.Context,
request *p.PruneClusterMembershipRequest,
) error {
return nil
}
func (m *ClusterMetadataStore) GetName() string {
return cassandraPersistenceName
}
func (m *ClusterMetadataStore) Close() {
if m.session != nil {
m.session.Close()
}
}