com.aerospike.client.Info Maven / Gradle / Ivy
Show all versions of aerospike-client-bc-jdk8 Show documentation
/*
* Copyright 2012-2024 Aerospike, Inc.
*
* Portions may be licensed to Aerospike, Inc. under one or more contributor
* license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.aerospike.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.aerospike.client.cluster.Connection;
import com.aerospike.client.cluster.Node;
import com.aerospike.client.command.Buffer;
import com.aerospike.client.policy.InfoPolicy;
import com.aerospike.client.util.Crypto;
import com.aerospike.client.util.ThreadLocalData;
/**
* Access server's info monitoring protocol.
*
* The info protocol is a name/value pair based system, where an individual
* database server node is queried to determine its configuration and status.
* The list of supported names can be found at:
*
* https://www.aerospike.com/docs/reference/info/index.html
*/
public class Info {
//-------------------------------------------------------
// Static variables.
//-------------------------------------------------------
private static final int DEFAULT_TIMEOUT = 1000;
//-------------------------------------------------------
// Get Info via Node
//-------------------------------------------------------
/**
* Get one info value by name from the specified database server node.
* This method supports user authentication.
*
* @param node server node
* @param name name of variable to retrieve
*/
public static String request(Node node, String name) throws AerospikeException {
Connection conn = node.getConnection(DEFAULT_TIMEOUT);
try {
String response = Info.request(conn, name);
node.putConnection(conn);
return response;
}
catch (Throwable e) {
node.closeConnection(conn);
throw e;
}
}
/**
* Get one info value by name from the specified database server node.
* This method supports user authentication.
*
* @param policy info command configuration parameters, pass in null for defaults
* @param node server node
* @param name name of variable to retrieve
*/
public static String request(InfoPolicy policy, Node node, String name) throws AerospikeException {
int timeout = (policy == null) ? DEFAULT_TIMEOUT : policy.timeout;
Connection conn = node.getConnection(timeout);
try {
String result = request(conn, name);
node.putConnection(conn);
return result;
}
catch (Throwable e) {
node.closeConnection(conn);
throw e;
}
}
/**
* Get many info values by name from the specified database server node.
* This method supports user authentication.
*
* @param policy info command configuration parameters, pass in null for defaults
* @param node server node
* @param names names of variables to retrieve
*/
public static Map request(InfoPolicy policy, Node node, String... names) throws AerospikeException {
int timeout = (policy == null) ? DEFAULT_TIMEOUT : policy.timeout;
Connection conn = node.getConnection(timeout);
try {
Map result = request(conn, names);
node.putConnection(conn);
return result;
}
catch (Throwable e) {
node.closeConnection(conn);
throw e;
}
}
/**
* Get default info values from the specified database server node.
* This method supports user authentication.
*
* @param policy info command configuration parameters, pass in null for defaults
* @param node server node
*/
public static Map request(InfoPolicy policy, Node node) throws AerospikeException {
int timeout = (policy == null) ? DEFAULT_TIMEOUT : policy.timeout;
Connection conn = node.getConnection(timeout);
try {
Map result = request(conn);
node.putConnection(conn);
return result;
}
catch (Throwable e) {
node.closeConnection(conn);
throw e;
}
}
//-------------------------------------------------------
// Get Info via Host Name and Port
//-------------------------------------------------------
/**
* Get one info value by name from the specified database server node, using
* host name and port.
* This method does not support user authentication.
*
* @param hostname host name
* @param port host port
* @param name name of value to retrieve
* @return info value
*/
public static String request(String hostname, int port, String name)
throws AerospikeException {
return request(new InetSocketAddress(hostname, port), name);
}
/**
* Get many info values by name from the specified database server node,
* using host name and port.
* This method does not support user authentication.
*
* @param hostname host name
* @param port host port
* @param names names of values to retrieve
* @return info name/value pairs
*/
public static HashMap request(String hostname, int port, String... names)
throws AerospikeException {
return request(new InetSocketAddress(hostname, port), names);
}
/**
* Get default info from the specified database server node, using host name and port.
* This method does not support user authentication.
*
* @param hostname host name
* @param port host port
* @return info name/value pairs
*/
public static HashMap request(String hostname, int port)
throws AerospikeException {
return request(new InetSocketAddress(hostname, port));
}
//-------------------------------------------------------
// Get Info via Socket Address
//-------------------------------------------------------
/**
* Get one info value by name from the specified database server node.
* This method does not support TLS connections nor user authentication.
*
* @param socketAddress InetSocketAddress
of server node
* @param name name of value to retrieve
* @return info value
*/
public static String request(InetSocketAddress socketAddress, String name)
throws AerospikeException {
Connection conn = new Connection(socketAddress, DEFAULT_TIMEOUT);
try {
return request(conn, name);
}
finally {
conn.close();
}
}
/**
* Get many info values by name from the specified database server node.
* This method does not support TLS connections nor user authentication.
*
* @param socketAddress InetSocketAddress
of server node
* @param names names of values to retrieve
* @return info name/value pairs
*/
public static HashMap request(InetSocketAddress socketAddress, String... names)
throws AerospikeException {
Connection conn = new Connection(socketAddress, DEFAULT_TIMEOUT);
try {
return request(conn, names);
}
finally {
conn.close();
}
}
/**
* Get all the default info from the specified database server node.
* This method does not support TLS connections nor user authentication.
*
* @param socketAddress InetSocketAddress
of server node
* @return info name/value pairs
*/
public static HashMap request(InetSocketAddress socketAddress)
throws AerospikeException {
Connection conn = new Connection(socketAddress, DEFAULT_TIMEOUT);
try {
return request(conn);
}
finally {
conn.close();
}
}
//-------------------------------------------------------
// Get Info via Connection
//-------------------------------------------------------
/**
* Get one info value by name from the specified database server node.
*
* @param conn socket connection to server node
* @param name name of value to retrieve
* @return info value
*/
public static String request(Connection conn, String name)
throws AerospikeException {
Info info = new Info(conn, name);
return info.parseSingleResponse(name);
}
/**
* Get many info values by name from the specified database server node.
*
* @param conn socket connection to server node
* @param names names of values to retrieve
* @return info name/value pairs
*/
public static HashMap request(Connection conn, String... names)
throws AerospikeException {
Info info = new Info(conn, names);
return info.parseMultiResponse();
}
/**
* Get many info values by name from the specified database server node.
*
* @param conn socket connection to server node
* @param names names of values to retrieve
* @return info name/value pairs
*/
public static HashMap request(Connection conn, List names)
throws AerospikeException {
Info info = new Info(conn, names);
return info.parseMultiResponse();
}
/**
* Get all the default info from the specified database server node.
*
* @param conn socket connection to server node
* @return info name/value pairs
*/
public static HashMap request(Connection conn)
throws AerospikeException {
Info info = new Info(conn);
return info.parseMultiResponse();
}
//-------------------------------------------------------
// Parse Methods
//-------------------------------------------------------
/**
* Parse info response string and return the result code for info commands
* that only return OK or an error string. Info commands that return other
* data are not handled by this method.
*/
public static int parseResultCode(String response) {
if (response.regionMatches(true, 0, "OK", 0, 2)) {
return ResultCode.OK;
}
Info.Error error = new Info.Error(response);
if (error.code >= 0) {
// Server errors return error code.
return error.code;
}
else {
// Client errors result in a exception.
throw new AerospikeException(error.code, "Unrecognized info response: " + response);
}
}
//-------------------------------------------------------
// Member variables.
//-------------------------------------------------------
public byte[] buffer;
public int length;
public int offset;
public StringBuilder sb;
//-------------------------------------------------------
// Constructor
//-------------------------------------------------------
/**
* Send single command to server and store results.
* This constructor is used internally.
* The static request methods should be used instead.
*
* @param conn connection to server node
* @param command command sent to server
*/
public Info(Connection conn, String command) throws AerospikeException {
buffer = ThreadLocalData.getBuffer();
// If conservative estimate may be exceeded, get exact estimate
// to preserve memory and resize buffer.
if ((command.length() * 2 + 9) > buffer.length) {
offset = Buffer.estimateSizeUtf8(command) + 9;
resizeBuffer(offset);
}
offset = 8; // Skip size field.
// The command format is: \n\n...
offset += Buffer.stringToUtf8(command, buffer, offset);
buffer[offset++] = '\n';
sendCommand(conn);
}
/**
* Send multiple commands to server and store results.
* This constructor is used internally.
* The static request methods should be used instead.
*
* @param conn connection to server node
* @param commands commands sent to server
*/
public Info(Connection conn, String... commands) throws AerospikeException {
buffer = ThreadLocalData.getBuffer();
// First, do quick conservative buffer size estimate.
offset = 8;
for (String command : commands) {
offset += command.length() * 2 + 1;
}
// If conservative estimate may be exceeded, get exact estimate
// to preserve memory and resize buffer.
if (offset > buffer.length) {
offset = 8;
for (String command : commands) {
offset += Buffer.estimateSizeUtf8(command) + 1;
}
resizeBuffer(offset);
}
offset = 8; // Skip size field.
// The command format is: \n\n...
for (String command : commands) {
offset += Buffer.stringToUtf8(command, buffer, offset);
buffer[offset++] = '\n';
}
sendCommand(conn);
}
/**
* Send multiple commands to server and store results.
* This constructor is used internally.
* The static request methods should be used instead.
*
* @param conn connection to server node
* @param commands commands sent to server
*/
public Info(Connection conn, List commands) throws AerospikeException {
buffer = ThreadLocalData.getBuffer();
// First, do quick conservative buffer size estimate.
offset = 8;
for (String command : commands) {
offset += command.length() * 2 + 1;
}
// If conservative estimate may be exceeded, get exact estimate
// to preserve memory and resize buffer.
if (offset > buffer.length) {
offset = 8;
for (String command : commands) {
offset += Buffer.estimateSizeUtf8(command) + 1;
}
resizeBuffer(offset);
}
offset = 8; // Skip size field.
// The command format is: \n\n...
for (String command : commands) {
offset += Buffer.stringToUtf8(command, buffer, offset);
buffer[offset++] = '\n';
}
sendCommand(conn);
}
/**
* Send default empty command to server and store results.
* This constructor is used internally.
* The static request methods should be used instead.
*
* @param conn connection to server node
*/
public Info(Connection conn) throws AerospikeException {
buffer = ThreadLocalData.getBuffer();
offset = 8; // Skip size field.
sendCommand(conn);
}
/**
* Internal constructor. Do not use.
*/
public Info(byte[] buffer, int length) {
this.buffer = buffer;
this.length = length;
this.sb = new StringBuilder(length);
}
/**
* Issue request and set results buffer. This method is used internally.
* The static request methods should be used instead.
*/
private void sendCommand(Connection conn) {
try {
// Write size field.
long size = ((long)offset - 8L) | (2L << 56) | (1L << 48);
Buffer.longToBytes(size, buffer, 0);
// Write.
conn.write(buffer, offset);
// Read - reuse input buffer.
conn.readFully(buffer, 8);
size = Buffer.bytesToLong(buffer, 0);
length = (int)(size & 0xFFFFFFFFFFFFL);
resizeBuffer(length);
sb = new StringBuilder(length);
conn.readFully(buffer, length);
conn.updateLastUsed();
offset = 0;
}
catch (IOException ioe) {
throw new AerospikeException.Connection(ioe);
}
}
private void resizeBuffer(int size) {
if (size > buffer.length) {
buffer = ThreadLocalData.resizeBuffer(size);
}
}
private String parseSingleResponse(String name) {
// Convert the UTF8 byte array into a string.
String response = Buffer.utf8ToString(buffer, 0, length);
if (response.startsWith(name)) {
if (response.length() > name.length() + 1) {
// Remove field name, tab and trailing newline from response.
// This is faster than calling parseMultiResponse()
return response.substring(name.length() + 1, response.length() - 1);
}
else {
return null;
}
}
else {
throw new AerospikeException.Parse("Info response does not include: " + name);
}
}
public HashMap parseMultiResponse() throws AerospikeException {
HashMap responses = new HashMap();
int offset = 0;
int begin = 0;
while (offset < length) {
byte b = buffer[offset];
if (b == '\t') {
String name = Buffer.utf8ToString(buffer, begin, offset - begin, sb);
offset++;
checkError();
begin = offset;
// Parse field value.
while (offset < length) {
if (buffer[offset] == '\n') {
break;
}
offset++;
}
if (offset > begin) {
String value = Buffer.utf8ToString(buffer, begin, offset - begin, sb);
responses.put(name, value);
}
else {
responses.put(name, null);
}
begin = ++offset;
}
else if (b == '\n') {
if (offset > begin) {
String name = Buffer.utf8ToString(buffer, begin, offset - begin, sb);
responses.put(name, null);
}
begin = ++offset;
}
else {
offset++;
}
}
if (offset > begin) {
String name = Buffer.utf8ToString(buffer, begin, offset - begin, sb);
responses.put(name, null);
}
return responses;
}
/**
* Parse request name, verify the name is expected and check for error message.
*/
public void parseName(String name) {
int begin = offset;
while (offset < length) {
if (buffer[offset] == '\t') {
String s = Buffer.utf8ToString(buffer, begin, offset - begin, sb).trim();
if (name.equals(s)) {
offset++;
// Check for error message.
checkError();
return;
}
break;
}
offset++;
}
throw new AerospikeException.Parse("Failed to find " + name);
}
/**
* Check if the info command returned an error.
* If so, include the error code and string in the exception.
*/
private void checkError() {
// Error format: ERROR:[:][][\n]
if (offset + 4 >= length) {
return; // Error did not occur.
}
if (!(buffer[offset] == 'E' && buffer[offset + 1] == 'R' && buffer[offset + 2] == 'R' &&
buffer[offset + 3] == 'O' && buffer[offset + 4] == 'R')) {
return; // Error did not occur.
}
// Parse error.
offset += 5;
skipDelimiter(':');
int begin = offset;
int code = parseInt();
if (code == 0) {
code = ResultCode.SERVER_ERROR;
}
if (offset > begin) {
skipDelimiter(':');
}
else if (buffer[offset] == ':') {
offset++;
}
String message = parseString('\n');
throw new AerospikeException(code, message);
}
/**
* Return single value from response buffer.
*/
public String getValue() {
//Log.debug("Response=" + Buffer.utf8ToString(buffer, offset, length) + " length=" + length + " offset=" + offset);
skipToValue();
return Buffer.utf8ToString(buffer, offset, length - offset - 1);
}
/**
* Parse response in name/value pair format:
*
* {@code \t=;=;...\n}
*
* @return parser for name/value pairs
*/
public NameValueParser getNameValueParser() {
skipToValue();
return new NameValueParser();
}
public void skipToValue() {
// Skip past command.
while (offset < length) {
byte b = buffer[offset];
if (b == '\t') {
offset++;
break;
}
if (b == '\n') {
break;
}
offset++;
}
}
/**
* Find next delimeter and skip over it.
*/
public void skipDelimiter(char stop) {
while (offset < length) {
byte b = buffer[offset++];
if (b == stop) {
break;
}
}
}
/**
* Convert UTF8 numeric digits to an integer. Negative integers are not supported.
* Input format: 1234
*/
public int parseInt() {
int begin = offset;
int end = offset;
byte b;
// Skip to end of integer.
while (offset < length) {
b = buffer[offset];
if (b < 48 || b > 57) {
end = offset;
break;
}
offset++;
}
// Convert digits into an integer.
return Buffer.utf8DigitsToInt(buffer, begin, end);
}
public String parseString(char stop) {
int begin = offset;
byte b;
while (offset < length) {
b = buffer[offset];
if (b == stop) {
break;
}
offset++;
}
return Buffer.utf8ToString(buffer, begin, offset - begin);
}
public String parseString(char stop1, char stop2, char stop3) {
int begin = offset;
byte b;
while (offset < length) {
b = buffer[offset];
if (b == stop1 || b == stop2 || b == stop3) {
break;
}
offset++;
}
return Buffer.utf8ToString(buffer, begin, offset - begin);
}
public void expect(char expected) {
if (expected != buffer[offset]) {
throw new AerospikeException.Parse("Expected " + expected + " Received: " + (char)(buffer[offset] & 0xFF));
}
offset++;
}
public String getTruncatedResponse() {
int max = (length > 200) ? 200 : length;
return Buffer.utf8ToString(buffer, 0, max);
}
/**
* Parser for responses in name/value pair format:
*
* {@code \t=;=;...\n}
*/
public class NameValueParser {
private int nameBegin;
private int nameEnd;
private int valueBegin;
private int valueEnd;
/**
* Set pointers to next name/value pair.
*
* @return true if next name/value pair exists; false if at end
*/
public boolean next() {
nameBegin = offset;
while (offset < length) {
byte b = buffer[offset];
if (b == '=') {
if (offset <= nameBegin) {
return false;
}
nameEnd = offset;
parseValue();
return true;
}
if (b == '\n') {
break;
}
offset++;
}
nameEnd = offset;
valueBegin = offset;
valueEnd = offset;
return offset > nameBegin;
}
private void parseValue() {
valueBegin = ++offset;
while (offset < length) {
byte b = buffer[offset];
if (b == ';') {
valueEnd = offset++;
return;
}
if (b == '\n') {
break;
}
offset++;
}
valueEnd = offset;
}
/**
* Get name.
*/
public String getName() {
int len = nameEnd - nameBegin;
return Buffer.utf8ToString(buffer, nameBegin, len);
}
/**
* Get value.
*/
public String getValue() {
int len = valueEnd - valueBegin;
if (len <= 0) {
return null;
}
return Buffer.utf8ToString(buffer, valueBegin, len);
}
/**
* Get Base64 string value.
*/
public String getStringBase64() {
int len = valueEnd - valueBegin;
if (len <= 0) {
return null;
}
byte[] bytes = Crypto.decodeBase64(buffer, valueBegin, len);
return Buffer.utf8ToString(bytes, 0, bytes.length);
}
}
/**
* Info command error response.
*/
public static class Error {
public final int code;
public final String message;
/**
* Parse info command response into code and message.
* If the response is not a recognized error format, the code is set to
* {@link ResultCode#CLIENT_ERROR} and the message is set to the full
* response string.
*/
public Error(String response) {
// Error format: ERROR|FAIL[:][:]
int rc = ResultCode.CLIENT_ERROR;
String msg = response;
try {
String[] list = response.split(":");
String s = list[0];
if (s.regionMatches(true, 0, "FAIL", 0, 4) ||
s.regionMatches(true, 0, "ERROR", 0, 5)) {
if (list.length >= 3) {
msg = list[2].trim();
s = list[1].trim();
if (! s.isEmpty()) {
rc = Integer.parseInt(s);
}
}
else if (list.length == 2) {
s = list[1].trim();
if (! s.isEmpty()) {
try {
rc = Integer.parseInt(s);
}
catch (Throwable t) {
// Some error strings omit the code and just have a message.
msg = s;
}
}
}
}
}
catch (Throwable t) {
}
finally {
this.code = rc;
this.message = msg;
}
}
}
}