pkg/mgmapi/clusterstatus.go (133 lines of code) (raw):
// Copyright (c) 2021, 2022, Oracle and/or its affiliates.
//
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
package mgmapi
import (
"github.com/mysql/ndb-operator/config/debug"
"sort"
)
// NodeTypeEnum identifies the node type used in NodeStatus object
// there are more types defined in c-code, but they are not used here
type NodeTypeEnum int
const (
// NodeTypeNDB identifies a data node
NodeTypeNDB NodeTypeEnum = iota
// NodeTypeAPI identifies a MySQL Server or generic API node
NodeTypeAPI
// NodeTypeMGM identifies a management server
NodeTypeMGM
)
func (t NodeTypeEnum) toString() string {
switch t {
case NodeTypeMGM:
return "MGM"
case NodeTypeNDB:
return "NDB"
case NodeTypeAPI:
return "API"
default:
debug.Panic("unrecognized node type")
return ""
}
}
// NodeStatus describes the state of a node of any node type in cluster
type NodeStatus struct {
// Type of node: data node, API or management server
NodeType NodeTypeEnum
// node id of the node described in status object
NodeId int
// isConnected reports if the node is fully started and connected to cluster
IsConnected bool
// NodeGroup reports which node group the node is in, -1 if unclear or wrong node type
NodeGroup int
// mysql version number as string in format x.y.z
SoftwareVersion string
}
func (ns *NodeStatus) IsDataNode() bool {
return ns.NodeType == NodeTypeNDB
}
func (ns *NodeStatus) IsMgmNode() bool {
return ns.NodeType == NodeTypeMGM
}
func (ns *NodeStatus) IsAPINode() bool {
return ns.NodeType == NodeTypeAPI
}
// setNodeTypeFromTLA sets the nodeId's NodeStatus' node type.
// Allowed TLAs are NDB, MGM, API
func (ns *NodeStatus) setNodeTypeFromTLA(TLA string) {
// node type NDB, MGM, API
switch TLA {
case "NDB":
ns.NodeType = NodeTypeNDB
case "MGM":
ns.NodeType = NodeTypeMGM
case "API":
ns.NodeType = NodeTypeAPI
default:
debug.Panic("unsupported node type : " + TLA)
}
}
// ClusterStatus describes the state of all nodes in the cluster
type ClusterStatus map[int]*NodeStatus
// NewClusterStatus returns a new ClusterStatus object
func NewClusterStatus(nodeCount int) ClusterStatus {
return make(ClusterStatus, nodeCount)
}
// ensureNode returns the NodeStatus entry for the given node.
// If one is not present for the given nodeId, it creates a new
// NodeStatus entry and returns it
func (cs ClusterStatus) ensureNode(nodeId int) *NodeStatus {
nodeStatus, ok := cs[nodeId]
if !ok {
// create a new entry
nodeStatus = &NodeStatus{
NodeId: nodeId,
}
cs[nodeId] = nodeStatus
}
return nodeStatus
}
// Nodegroup of data nodes added to cluster through online add procedure
const (
NodeGroupNewDisconnectedDataNode = 65536
NodeGroupNewConnectedDataNode = -256
)
// IsHealthy returns true if the MySQL Cluster is healthy,
// i.e., if all the data and management nodes are connected and ready
func (cs ClusterStatus) IsHealthy() bool {
for _, ns := range cs {
switch ns.NodeType {
case NodeTypeMGM:
if !ns.IsConnected {
return false
}
case NodeTypeNDB:
if !ns.IsConnected {
// During online add the new data node slots will
// have a nodegroup 65536 and they will not have a
// valid nodegroup until they are inducted into
// the cluster. So, any unconnected node without
// that nodegroup is an unhealthy node.
if ns.NodeGroup != NodeGroupNewDisconnectedDataNode {
return false
}
}
}
}
return true
}
// GetNodesGroupedByNodegroup returns an array of grouped
// and sorted node ids grouped based on node groups.
func (cs ClusterStatus) GetNodesGroupedByNodegroup() [][]int {
// Map to store the nodes based on nodegroup
nodesInNodeGroupMap := make(map[int][]int)
for nodeId, node := range cs {
if !node.IsDataNode() {
// not a data node
continue
}
nodegroup := node.NodeGroup
if !node.IsConnected {
if nodegroup == NodeGroupNewDisconnectedDataNode {
// node is not inducted into cluster yet
continue
}
// data node not connected
debug.Panic("should be called only when the cluster is healthy")
return nil
}
if nodegroup == NodeGroupNewConnectedDataNode {
// ignore new connected data nodes without a nodegroup
continue
}
// append the node id to the map based on nodegroup
nodesInNodeGroupMap[nodegroup] = append(nodesInNodeGroupMap[nodegroup], nodeId)
}
// The map has the required node ids grouped under node groups
// but nothing is sorted yet. Sort the output based on node groups
// and then the sort the node ids under them
nodeGroupIds := make([]int, 0, len(nodesInNodeGroupMap))
for ng, nodeIdsInNodegroup := range nodesInNodeGroupMap {
nodeGroupIds = append(nodeGroupIds, ng)
// sort the node ids
sort.Ints(nodeIdsInNodegroup)
}
// sort the node group
sort.Ints(nodeGroupIds)
// Copy out the sorted output in an [][]int and return
nodesGroupedByNodegroup := make([][]int, 0, len(nodesInNodeGroupMap))
for ng := range nodeGroupIds {
nodesGroupedByNodegroup = append(nodesGroupedByNodegroup, nodesInNodeGroupMap[ng])
}
return nodesGroupedByNodegroup
}
// GetConnectedDataNodesWithNodeGroup returns a sorted list
// of all nodeIds that belong to the given nodegroup ng.
func (cs ClusterStatus) GetConnectedDataNodesWithNodeGroup(ng int) []int {
// Map to store the nodes based on nodegroup
var nodesInNodeGroup []int
for nodeId, node := range cs {
if !node.IsDataNode() || node.NodeGroup != ng {
// not a data node or doesn't have the expected nodegroup
continue
}
if !node.IsConnected {
// data node not connected
debug.Panic("should be called only when the cluster is healthy")
return nil
}
// append the node id to the map based on nodegroup
nodesInNodeGroup = append(nodesInNodeGroup, nodeId)
}
// sort the nodeIds
sort.Ints(nodesInNodeGroup)
return nodesInNodeGroup
}