Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identify Stargate nodes #1179

Merged
merged 9 commits into from
Mar 29, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class NodesStatus {
private static final List<Pattern> ENDPOINT_SEVERITY_PATTERNS = Lists.newArrayList();
private static final List<Pattern> ENDPOINT_HOSTID_PATTERNS = Lists.newArrayList();
private static final List<Pattern> ENDPOINT_TOKENS_PATTERNS = Lists.newArrayList();
private static final List<Pattern> ENDPOINT_TYPE_PATTERNS = Lists.newArrayList();

private static final Pattern ENDPOINT_NAME_PATTERN_IP4
= Pattern.compile("^([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})", Pattern.MULTILINE | Pattern.DOTALL);
Expand All @@ -65,6 +66,7 @@ public final class NodesStatus {
private static final Pattern ENDPOINT_SEVERITY_21_PATTERN = Pattern.compile("(SEVERITY)(:)([0-9.]+)");
private static final Pattern ENDPOINT_HOSTID_21_PATTERN = Pattern.compile("(HOST_ID)(:)([0-9a-z-]+)");
private static final Pattern ENDPOINT_LOAD_SCYLLA_44_PATTERN = Pattern.compile("(LOAD)(:)([0-9eE.\\+]+)");
private static final Pattern ENDPOINT_TYPE_STARGATE_PATTERN = Pattern.compile("(X10):([0-9]*):(stargate)");

private static final String NOT_AVAILABLE = "Not available";

Expand Down Expand Up @@ -146,6 +148,7 @@ private GossipInfo parseEndpointStatesString(
Optional<String> tokens = parseEndpointState(ENDPOINT_TOKENS_PATTERNS, endpointString, 2, String.class);
Optional<Double> load = parseEndpointState(ENDPOINT_LOAD_PATTERNS, endpointString, 3, Double.class);
totalLoad += load.orElse(0.0);
Optional<String> stargate = parseEndpointState(ENDPOINT_TYPE_PATTERNS, endpointString, 3, String.class);

EndpointState endpointState = new EndpointState(
endpoint.orElse(NOT_AVAILABLE),
Expand All @@ -156,7 +159,8 @@ private GossipInfo parseEndpointStatesString(
severity.orElse(0.0),
releaseVersion.orElse(NOT_AVAILABLE),
tokens.orElse(NOT_AVAILABLE),
load.orElse(0.0));
load.orElse(0.0),
stargate.isPresent() ? NodeType.STARGATE : NodeType.CASSANDRA);

if (!status.orElse(NOT_AVAILABLE).toLowerCase().contains("left")
&& !status.orElse(NOT_AVAILABLE).toLowerCase().contains("removed")) {
Expand Down Expand Up @@ -207,6 +211,7 @@ private static void initPatterns() {
ENDPOINT_SEVERITY_PATTERNS.addAll(Arrays.asList(ENDPOINT_SEVERITY_22_PATTERN, ENDPOINT_SEVERITY_21_PATTERN));
ENDPOINT_HOSTID_PATTERNS.addAll(Arrays.asList(ENDPOINT_HOSTID_22_PATTERN, ENDPOINT_HOSTID_21_PATTERN));
ENDPOINT_TOKENS_PATTERNS.add(ENDPOINT_TOKENS_22_PATTERN);
ENDPOINT_TYPE_PATTERNS.add(ENDPOINT_TYPE_STARGATE_PATTERN);
}

public static final class GossipInfo {
Expand Down Expand Up @@ -236,6 +241,11 @@ public GossipInfo(
}
}

public enum NodeType {
CASSANDRA,
STARGATE;
}

public static final class EndpointState {

@JsonProperty
Expand Down Expand Up @@ -265,6 +275,9 @@ public static final class EndpointState {
@JsonProperty
public final Double load;

@JsonProperty
public final NodeType type;

public EndpointState(
String endpoint,
String hostId,
Expand All @@ -274,7 +287,8 @@ public EndpointState(
Double severity,
String releaseVersion,
String tokens,
Double load) {
Double load,
NodeType type) {

this.endpoint = endpoint;
this.hostId = hostId;
Expand All @@ -285,6 +299,7 @@ public EndpointState(
this.releaseVersion = releaseVersion;
this.tokens = tokens;
this.load = load;
this.type = type;
}

public String getDc() {
Expand Down Expand Up @@ -322,7 +337,10 @@ public String toString() {
+ hostId
+ " / "
+ "Tokens : "
+ tokens;
+ tokens
+ " / "
+ "Type : "
+ type;
}
}
}
4 changes: 1 addition & 3 deletions src/ui/app/jsx/cluster-list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,15 @@ const Cluster = CreateReactClass({
method: 'GET',
component: this,
complete: function(data) {
console.log(this.component.props.name + " complete.");
this.component.setState({clusterStatuses: setTimeout(this.component._refreshClusterStatus, 30000),
clusterStatus: $.parseJSON(data.responseText)});

if(this.component.state.clusterStatus.nodes_status){
this.component.setState({nodes_status: this.component.state.clusterStatus.nodes_status});
}
console.log(this.component.props.name + " : Next attempt in 30s.")
},
error: function(data) {
console.log(this.component.props.name + " failed.");
console.error(this.component.props.name + " failed.");
this.component.setState({clusterStatuses: setTimeout(this.component._refreshClusterStatus, 30000)});

}
Expand Down
7 changes: 2 additions & 5 deletions src/ui/app/jsx/event-subscription-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import CreateReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import Select from 'react-select';
import {getUrlPrefix} from "jsx/mixin";
import {getNodeOptions} from "../node-utils";


const subscriptionForm = CreateReactClass({
Expand Down Expand Up @@ -197,11 +198,7 @@ const subscriptionForm = CreateReactClass({
},

_getNodeOptions: function() {
this.setState({
nodeOptions: this.state.clusterStatus.nodes_status.endpointStates[0].endpointNames.map(
obj => { return {value: obj, label: obj}; }
)
});
this.setState(getNodeOptions(this.state.clusterStatus.nodes_status.endpointStates, true));
},

_handleSelectOnChange: function(valueContext, actionContext) {
Expand Down
84 changes: 60 additions & 24 deletions src/ui/app/jsx/node-status.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ const NodeStatus = CreateReactClass({
},

render: function() {
const isStargate = this.props.endpointStatus.type === "STARGATE";

let displayNodeStyle = {
display: "none"
}
Expand Down Expand Up @@ -213,8 +215,14 @@ const NodeStatus = CreateReactClass({

let buttonStyle = "btn btn-xs btn-success";
let largeButtonStyle = "btn btn-lg btn-static btn-success";

if(!this.props.endpointStatus.status.endsWith('UP')){

// If the node is a Stargate node we won't have real status for it
// so we'll display it slightly differently
if (isStargate) {
buttonStyle = "btn btn-xs btn-info";
largeButtonStyle = "btn btn-lg btn-static btn-info";
// If it's not a stargate node, then we can appropriately use its status
} else if(!this.props.endpointStatus.status.endsWith('UP')){
buttonStyle = "btn btn-xs btn-danger";
largeButtonStyle = "btn btn-lg btn-static btn-danger";
}
Expand All @@ -234,9 +242,24 @@ const NodeStatus = CreateReactClass({
overflow: "auto",
height: "200px"
}

const modalTitle = (
<Modal.Title>
Endpoint {this.props.endpointStatus.endpoint}
{isStargate &&
<span>&nbsp;(Stargate)</span>
}
</Modal.Title>
);

const tooltip = (
<Tooltip id="tooltip"><strong>{this.props.endpointStatus.endpoint}</strong> ({humanFileSize(this.props.endpointStatus.load, 1024)})</Tooltip>
<Tooltip id="tooltip">
<strong>{this.props.endpointStatus.endpoint}</strong>
{isStargate &&
<span>&nbsp;(Stargate)</span>
}
&nbsp;({humanFileSize(this.props.endpointStatus.load, 1024)})
</Tooltip>
);

const tokenList = this.state.tokens.map(token => <div key={token}>{token}</div>);
Expand Down Expand Up @@ -271,7 +294,7 @@ const NodeStatus = CreateReactClass({
<OverlayTrigger placement="top" overlay={tooltip}><button type="button" style={btStyle} className={buttonStyle} onClick={this.open}>&nbsp;</button></OverlayTrigger>
<Modal show={this.state.showModal} onHide={this.close} bsSize="large" aria-labelledby="contained-modal-title-lg" dialogClassName="large-modal">
<Modal.Header closeButton>
<Modal.Title>Endpoint {this.props.endpointStatus.endpoint}</Modal.Title>
{modalTitle}
</Modal.Header>
<Modal.Body>
<div className="row">
Expand All @@ -284,26 +307,39 @@ const NodeStatus = CreateReactClass({
<p>{this.props.endpointStatus.dc} / {this.props.endpointStatus.rack}</p>
</div>
<div className="col-lg-3">
<h4>Release version</h4>
<p>{this.props.endpointStatus.releaseVersion}</p>
</div>
<div className="col-lg-3">
<h4>Tokens</h4>
<p><OverlayTrigger trigger="click" placement="bottom" overlay={tokens}><button type="button" className="btn btn-md btn-info" style={takeSnapshotStyle}>{this.state.tokens.length}</button></OverlayTrigger></p>
<h4>Node Type</h4>
<p>{isStargate ? "Stargate" : "Cassandra"}</p>
</div>
<div className="col-lg-3">
<h4>Status</h4>
<p><button type="button" className={largeButtonStyle}>{this.props.endpointStatus.status}</button></p>
</div>
<div className="col-lg-3">
<h4>Severity</h4>
<p>{this.props.endpointStatus.severity}</p>
</div>
<div className="col-lg-3">
<h4>Data size on disk</h4>
<p>{humanFileSize(this.props.endpointStatus.load, 1024)}</p>
<h4>Release version</h4>
<p>{this.props.endpointStatus.releaseVersion}</p>
</div>
{!isStargate &&
<div className="col-lg-3">
<h4>Tokens</h4>
<p><OverlayTrigger trigger="click" placement="bottom" overlay={tokens}><button type="button" className="btn btn-md btn-info" style={takeSnapshotStyle}>{this.state.tokens.length}</button></OverlayTrigger></p>
</div>
}
{!isStargate &&
<div className="col-lg-3">
<h4>Status</h4>
<p><button type="button" className={largeButtonStyle}>{this.props.endpointStatus.status}</button></p>
</div>
}
{!isStargate &&
<div className="col-lg-3">
<h4>Severity</h4>
<p>{this.props.endpointStatus.severity}</p>
</div>
}
{!isStargate &&
<div className="col-lg-3">
<h4>Data size on disk</h4>
<p>{humanFileSize(this.props.endpointStatus.load, 1024)}</p>
</div>
}
</div>
{!isStargate &&
<div className="row">
<div className="col-lg-12">
<Tabs defaultActiveKey={1} id="node-tab">
Expand Down Expand Up @@ -342,10 +378,10 @@ const NodeStatus = CreateReactClass({
</p>
</div>
}
<OverlayTrigger trigger="focus" placement="bottom" overlay={takeSnapshotClick}><button type="button" className="btn btn-md btn-success" style={takeSnapshotStyle}>Take a snapshot</button></OverlayTrigger>
<OverlayTrigger trigger="focus" placement="bottom" overlay={takeSnapshotClick}><button type="button" className="btn btn-md btn-success" style={takeSnapshotStyle}>Take a snapshot</button></OverlayTrigger>
<button type="button" className="btn btn-md btn-success" style={progressStyle} disabled>Taking a snapshot...</button>
</div>
<div className="col-lg-12">&nbsp;</div>
<div className="col-lg-12">&nbsp;</div>
{snapshots}
</div>
</div>
Expand All @@ -354,10 +390,10 @@ const NodeStatus = CreateReactClass({
<Tab eventKey={6} title="Streams">
<Streams endpoint={this.props.endpointStatus.endpoint} clusterName={this.props.clusterName}/>
</Tab>
</Tabs>
</Tabs>
</div>
</div>

}
</Modal.Body>
<Modal.Footer>
<Button onClick={this.close}>Close</Button>
Expand Down
7 changes: 2 additions & 5 deletions src/ui/app/jsx/repair-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Moment from 'moment';
import moment from "moment";
import Modal from 'react-bootstrap/lib/Modal';
import Button from 'react-bootstrap/lib/Button';
import {getNodeOptions} from "../node-utils";

Moment.locale(navigator.language);

Expand Down Expand Up @@ -165,11 +166,7 @@ const repairForm = CreateReactClass({
},

_getNodeOptions: function() {
this.setState({
nodeOptions: this.state.clusterStatus.nodes_status.endpointStates[0].endpointNames.sort().map(
obj => { return {value: obj, label: obj}; }
)
});
this.setState(getNodeOptions(this.state.clusterStatus.nodes_status.endpointStates, true));
},

_getKeyspaceOptions: function() {
Expand Down
60 changes: 60 additions & 0 deletions src/ui/app/node-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Utility method for digging through the endpointStates API response structure
* to identify the possible nodes
*
* @returns An array of node/endpoint objects
*/
import {node} from "prop-types";

export const getNodesFromEndpointStates = function(endpointStates) {
const nodes = [];
if (!endpointStates || !endpointStates.length) {
return nodes;
}

for(let endpointState of endpointStates) {
if (!endpointState || !endpointState.endpoints) {
continue;
}
for (let datacenterId in endpointState.endpoints) {
const datacenter = endpointState.endpoints[datacenterId];
if (!datacenter) {
continue;
}
for (let rackId in datacenter) {
const rack = datacenter[rackId];
if (!rack) {
continue;
}
for (let endpoint of rack) {
if (endpoint) {
nodes.push(endpoint);
}
}
}
}
}
return nodes;
}

/**
* Utility function for generating a set of options for a select representing a drop down of nodes.
* If excludeStargateNodes is true stargate nodes will not be included in the set of options generated.
* @param endpointStates
* @param excludeStargateNodes
* @returns {{nodeOptions: {label: *, value: *}[]}|{nodeOptions: *[]}}
*/
export const getNodeOptions = function(endpointStates, excludeStargateNodes) {
let nodes = getNodesFromEndpointStates(endpointStates);
if (!nodes) {
return {
nodeOptions: []
}
}
const includedNodes = excludeStargateNodes ? nodes.filter(node => node.type !== "STARGATE") : nodes;
return {
nodeOptions: includedNodes.map(node => node.endpoint).sort().map(
obj => { return {value: obj, label: obj}; }
)
};
}