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 }