org.jgroups.protocols.SWIFT_PING Maven / Gradle / Ivy
package org.jgroups.protocols;
import org.jgroups.Address;
import org.jgroups.annotations.Experimental;
import org.jgroups.annotations.Property;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.util.Responses;
import org.jgroups.util.Util;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.util.*;
/**
* Discovery protocol based on Openstack Swift (object storage).
*
* This implementation is derived from Gustavo Fernandes work on RACKSPACE_PING
*
* @author tsegismont
* @since 3.1
*/
@Experimental
public class SWIFT_PING extends FILE_PING {
private static final Log log = LogFactory.getLog(SWIFT_PING.class);
protected SwiftClient swiftClient = null;
@Property(description = "Authentication url")
protected String auth_url = null;
@Property(description = "Authentication type")
protected String auth_type = "keystone_v_2_0";
@Property(description = "Openstack Keystone tenant name")
protected String tenant = null;
@Property(description = "Username")
protected String username = null;
@Property(description = "Password",exposeAsManagedAttribute=false)
protected String password = null;
@Property(description = "Name of the root container")
protected String container = "jgroups";
@Override
public void init() throws Exception {
Utils.validateNotEmpty(auth_url, "auth_url");
Utils.validateNotEmpty(auth_type, "auth_type");
Utils.validateNotEmpty(username, "username");
Utils.validateNotEmpty(password, "password");
Utils.validateNotEmpty(container, "container");
Authenticator authenticator = createAuthenticator();
authenticator.validateParams();
swiftClient = new SwiftClient(authenticator);
// Authenticate now to record credential
swiftClient.authenticate();
super.init();
}
private Authenticator createAuthenticator() throws Exception {
AUTH_TYPE authType = AUTH_TYPE.getByConfigName(auth_type);
if (authType == null) {
throw new IllegalArgumentException("Invalid 'auth_type' : "
+ auth_type);
}
URL authUrl = new URL(auth_url);
Authenticator authenticator = null;
switch (authType) {
case KEYSTONE_V_2_0:
authenticator = new Keystone_V_2_0_Auth(tenant, authUrl, username,
password);
break;
default:
// We shouldn't come here since we checked auth_type
throw new IllegalStateException("Could not select authenticator");
}
return authenticator;
}
@Override
protected void createRootDir() {
try {
swiftClient.createContainer(container);
} catch (Exception e) {
log.error(Util.getMessage("FailureCreatingContainer"), e);
}
}
@Override
protected void readAll(List members, String clustername, Responses responses) {
try {
List objects = swiftClient.listObjects(container);
for(String object: objects) {
List list=null;
byte[] bytes = swiftClient.readObject(container, object);
if((list=read(new ByteArrayInputStream(bytes))) == null) {
log.warn("failed reading " + object);
continue;
}
for(PingData data: list) {
if(members == null || members.contains(data.getAddress()))
responses.addResponse(data, data.isCoord());
if(local_addr != null && !local_addr.equals(data.getAddress()))
addDiscoveryResponseToCaches(data.getAddress(), data.getLogicalName(), data.getPhysicalAddr());
}
}
} catch (Exception e) {
log.error(Util.getMessage("ErrorUnmarshallingObject"), e);
}
}
@Override
protected void write(List list, String clustername) {
try {
String filename = clustername + "/" + addressToFilename(local_addr);
ByteArrayOutputStream out=new ByteArrayOutputStream(4096);
write(list, out);
byte[] data=out.toByteArray();
swiftClient.createObject(container, filename, data);
} catch (Exception e) {
log.error(Util.getMessage("ErrorMarshallingObject"), e);
}
}
@Override
protected void remove(String clustername, Address addr) {
String fileName = clustername + "/" + addressToFilename(addr);
try {
swiftClient.deleteObject(container, fileName);
} catch (Exception e) {
log.error(Util.getMessage("FailureRemovingData"), e);
}
}
@Override
protected void removeAll(String clustername) {
try {
List objects=swiftClient.listObjects(container);
for(String objName : objects) {
swiftClient.deleteObject(container, objName);
}
}
catch(Exception t) {
log.error(Util.getMessage("FailedRemovingObjects"), t);
}
}
private static class HttpHeaders {
private static final String CONTENT_TYPE_HEADER = "Content-type";
private static final String ACCEPT_HEADER = "Accept";
//
// private static final String AUTH_HEADER = "X-Auth-User";
//
// private static final String AUTH_KEY_HEADER = "X-Auth-Key";
//
private static final String STORAGE_TOKEN_HEADER = "X-Storage-Token";
//
// private static final String STORAGE_URL_HEADER = "X-Storage-Url";
private static final String CONTENT_LENGTH_HEADER = "Content-Length";
}
/**
* Supported Swift authentication providers
*/
private static enum AUTH_TYPE {
KEYSTONE_V_2_0("keystone_v_2_0");
private static final Map LOOKUP = new HashMap<>();
static {
for (AUTH_TYPE type : EnumSet.allOf(AUTH_TYPE.class))
LOOKUP.put(type.configName, type);
}
private String configName;
private AUTH_TYPE(String externalName) {
this.configName = externalName;
}
public static AUTH_TYPE getByConfigName(String configName) {
return LOOKUP.get(configName);
}
}
/**
* Result of a successfully authenticated session
*/
private static class Credentials {
private final String authToken;
private final String storageUrl;
public Credentials(String authToken, String storageUrl) {
this.authToken = authToken;
this.storageUrl = storageUrl;
}
}
/**
* Contract for Swift authentication providers
*/
private static interface Authenticator {
/**
* Validate SWIFT_PING config parameters
*/
void validateParams();
Credentials authenticate() throws Exception;
}
/**
* Openstack Keytsone v2.0 authentication provider. Thread safe
* implementation
*/
private static class Keystone_V_2_0_Auth implements Authenticator {
// Using Java's built-in JavaScript engine to parse JSON response. We use
// this approach to avoid introducing an external dependency on a JSON parser.
private final static String JSON_RESPONSE_PARSING_SCRIPT =
"var response = JSON.parse(json);" +
"var result = {};" +
"result.id = response.access.token.id;" +
"var serviceCatalog = response.access.serviceCatalog;" +
"for (var i = 0; i < serviceCatalog.length; i++) {" +
" var service = serviceCatalog[i];" +
" if (service.type == \"object-store\") {" +
" result.url = service.endpoints[0].publicURL;" +
" break;" +
" }" +
"}" +
"result;";
private static Object scriptEngineLock = new Object();
private static ScriptEngine scriptEngine;
private String tenant;
private URL authUrl;
private String username;
private String password;
public Keystone_V_2_0_Auth(String tenant, URL authUrl, String username,
String password) {
this.tenant = tenant;
this.authUrl = authUrl;
this.username = username;
this.password = password;
}
public void validateParams() {
// All others params already validated
Utils.validateNotEmpty(tenant, "tenant");
}
public Credentials authenticate() throws Exception {
HttpURLConnection urlConnection = new ConnBuilder(authUrl)
.addHeader(HttpHeaders.CONTENT_TYPE_HEADER,
"application/json")
.addHeader(HttpHeaders.ACCEPT_HEADER, "application/json")
.getConnection();
StringBuilder jsonBuilder = new StringBuilder();
jsonBuilder.append("{\"auth\": {\"tenantName\": \"").append(tenant)
.append("\", \"passwordCredentials\": {\"username\": \"")
.append(username).append("\", \"password\": \"")
.append(password).append("\"}}}");
HttpResponse response = Utils.doOperation(urlConnection,
jsonBuilder.toString().getBytes(), true);
if (response.isSuccessCode()) {
Map result = parseJsonResponse(new String(response.payload, "UTF-8"));
String authToken = result.get("id");
String storageUrl = result.get("url");
if (authToken == null)
{
throw new IllegalStateException("Missing token id in authentication response");
}
if (storageUrl == null)
{
throw new IllegalStateException("Missing storage service URL in authentication response");
}
log.trace("Authentication successful");
return new Credentials(authToken, storageUrl);
} else {
throw new IllegalStateException(
"Error authenticating to the service. Please check your credentials. Code = "
+ response.code);
}
}
protected Map parseJsonResponse(String json) throws ScriptException
{
synchronized (scriptEngineLock)
{
if (scriptEngine == null)
{
scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");
if (scriptEngine == null) {
throw new RuntimeException("Failed to load JavaScript script engine");
}
}
Bindings bindings = new SimpleBindings();
bindings.put("json", json);
return (Map)scriptEngine.eval(JSON_RESPONSE_PARSING_SCRIPT, bindings);
}
}
}
/**
* Build HttpURLConnections with adequate headers and method
*/
private static class ConnBuilder {
private HttpURLConnection con;
public ConnBuilder(URL url) {
try {
con = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
log.error(Util.getMessage("ErrorBuildingURL"), e);
}
}
public ConnBuilder(Credentials credentials, String container,
String object) {
try {
String url = credentials.storageUrl + "/" + container;
if (object != null) {
url = url + "/" + object;
}
con = (HttpURLConnection) new URL(url).openConnection();
} catch (IOException e) {
log.error(Util.getMessage("ErrorCreatingConnection"), e);
}
}
public ConnBuilder method(String method) {
try {
con.setRequestMethod(method);
} catch (ProtocolException e) {
log.error(Util.getMessage("ProtocolError"), e);
}
return this;
}
public ConnBuilder addHeader(String key, String value) {
con.setRequestProperty(key, value);
return this;
}
public HttpURLConnection getConnection() {
return con;
}
}
/**
* Response for a Swift API call
*/
private static class HttpResponse {
// For later use
private final Map> headers;
private final int code;
private final byte[] payload;
HttpResponse(Map> headers, int code, byte[] payload) {
this.headers = headers;
this.code = code;
this.payload = payload;
}
public List payloadAsLines() {
List lines = new ArrayList<>();
BufferedReader in;
try {
String line;
in = new BufferedReader(new InputStreamReader(
new ByteArrayInputStream(payload)));
while ((line = in.readLine()) != null) {
lines.add(line);
}
in.close();
} catch (IOException e) {
log.error(Util.getMessage("ErrorReadingObjects"), e);
}
return lines;
}
public boolean isSuccessCode() {
return Utils.isSuccessCode(code);
}
public boolean isAuthDenied() {
return Utils.isAuthDenied(code);
}
}
/**
* A thread safe Swift client
*/
protected static class SwiftClient {
private Authenticator authenticator;
private volatile Credentials credentials = null;
/**
* Constructor
*
* @param authenticator Swift auth provider
*/
public SwiftClient(Authenticator authenticator) {
this.authenticator = authenticator;
}
/**
* Authenticate
*
* @throws Exception
*/
public void authenticate() throws Exception {
credentials = authenticator.authenticate();
}
/**
* Delete a object (=file) from the storage
*
* @param containerName Folder name
* @param objectName File name
* @throws IOException
*/
public void deleteObject(String containerName, String objectName)
throws Exception {
HttpURLConnection urlConnection = getConnBuilder(containerName,
objectName).method("DELETE").getConnection();
HttpResponse response = Utils.doVoidOperation(urlConnection);
if (!response.isSuccessCode()) {
if (response.isAuthDenied()) {
log.warn("Refreshing credentials and retrying");
authenticate();
deleteObject(containerName, objectName);
} else {
log.error(Util.getMessage("ErrorDeletingObject") + objectName
+ " from container " + containerName + ",code = "
+ response.code);
}
}
}
/**
* Create a container, which is equivalent to a bucket
*
* @param containerName Name of the container
* @throws IOException
*/
public void createContainer(String containerName) throws Exception {
HttpURLConnection urlConnection = getConnBuilder(containerName,
null).method("PUT").getConnection();
HttpResponse response = Utils.doVoidOperation(urlConnection);
if (!response.isSuccessCode()) {
if (response.isAuthDenied()) {
log.warn("Refreshing credentials and retrying");
authenticate();
createContainer(containerName);
} else {
log.error(Util.getMessage("ErrorCreatingContainer") + containerName
+ " ,code = " + response.code);
}
}
}
/**
* Create an object (=file)
*
* @param containerName Name of the container
* @param objectName Name of the file
* @param contents Binary content of the file
* @throws IOException
*/
public void createObject(String containerName, String objectName,
byte[] contents) throws Exception {
HttpURLConnection conn = getConnBuilder(containerName, objectName)
.method("PUT")
.addHeader(HttpHeaders.CONTENT_LENGTH_HEADER,
String.valueOf(contents.length)).getConnection();
HttpResponse response = Utils.doSendOperation(conn, contents);
if (!response.isSuccessCode()) {
if (response.isAuthDenied()) {
log.warn("Refreshing credentials and retrying");
authenticate();
createObject(containerName, objectName, contents);
} else {
log.error(Util.getMessage("ErrorCreatingObject") + objectName
+ " in container " + containerName + ",code = "
+ response.code);
}
}
}
/**
* Read the content of a file
*
* @param containerName Name of the folder
* @param objectName name of the file
* @return Content of the files
* @throws IOException
*/
public byte[] readObject(String containerName, String objectName)
throws Exception {
HttpURLConnection urlConnection = getConnBuilder(containerName,
objectName).getConnection();
HttpResponse response = Utils.doReadOperation(urlConnection);
if (!response.isSuccessCode()) {
if (response.isAuthDenied()) {
log.warn("Refreshing credentials and retrying");
authenticate();
return readObject(containerName, objectName);
} else {
log.error(Util.getMessage("ErrorReadingObject") + objectName
+ " from container " + containerName + ", code = "
+ response.code);
}
}
return response.payload;
}
/**
* List files in a folder
*
* @param containerName Folder name
* @return List of file names
* @throws IOException
*/
public List listObjects(String containerName) throws Exception {
HttpURLConnection urlConnection = getConnBuilder(containerName,
null).getConnection();
HttpResponse response = Utils.doReadOperation(urlConnection);
if (!response.isSuccessCode()) {
if (response.isAuthDenied()) {
log.warn("Refreshing credentials and retrying");
authenticate();
return listObjects(containerName);
} else {
log.error(Util.getMessage("ErrorListingContainer") + containerName
+ ", code = " + response.code);
}
}
return response.payloadAsLines();
}
private ConnBuilder getConnBuilder(String container, String object) {
ConnBuilder connBuilder = new ConnBuilder(credentials, container,
object);
connBuilder.addHeader(HttpHeaders.STORAGE_TOKEN_HEADER,
credentials.authToken);
connBuilder.addHeader(HttpHeaders.ACCEPT_HEADER, "*/*");
return connBuilder;
}
}
private static class Utils {
public static void validateNotEmpty(String arg, String argname) {
if (arg == null || arg.trim().length() == 0) {
throw new IllegalArgumentException("'" + argname
+ "' cannot be empty");
}
}
/**
* Is http response code in success range ?
*
* @param code
* @return
*/
public static boolean isSuccessCode(int code) {
return code >= 200 && code < 300;
}
/**
* Is http Unauthorized response code ?
*
* @param code
* @return
*/
public static boolean isAuthDenied(int code) {
return code == 401;
}
/**
* Do a http operation
*
* @param urlConnection the HttpURLConnection to be used
* @param inputData if not null,will be written to the urlconnection.
* @param hasOutput if true, read content back from the urlconnection
* @return Response
* @throws IOException
*/
public static HttpResponse doOperation(HttpURLConnection urlConnection,
byte[] inputData, boolean hasOutput) throws IOException {
HttpResponse response = null;
InputStream inputStream = null;
OutputStream outputStream = null;
byte[] payload = null;
try {
if (inputData != null) {
urlConnection.setDoOutput(true);
outputStream = urlConnection.getOutputStream();
outputStream.write(inputData);
}
/*
* Get response code first. HttpURLConnection does not allow to
* read inputstream if response code is not success code
*/
int responseCode = urlConnection.getResponseCode();
if (hasOutput && isSuccessCode(responseCode)) {
payload = getBytes(urlConnection.getInputStream());
}
response = new HttpResponse(urlConnection.getHeaderFields(),
responseCode, payload);
} finally {
Util.close(inputStream);
Util.close(outputStream);
}
return response;
}
/**
* Get bytes of this {@link InputStream}
*
* @param inputStream
* @return
* @throws IOException
*/
public static byte[] getBytes(InputStream inputStream)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len;
for (; ; ) {
len = inputStream.read(buffer);
if (len == -1) {
break;
}
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
/**
* Do a operation that does not write or read from HttpURLConnection,
* except for the headers
*
* @param urlConnection the connection
* @return Response
* @throws IOException
*/
public static HttpResponse doVoidOperation(
HttpURLConnection urlConnection) throws IOException {
return doOperation(urlConnection, null, false);
}
/**
* Do a operation that writes content to the HttpURLConnection
*
* @param urlConnection the connection
* @param content The content to send
* @return Response
* @throws IOException
*/
public static HttpResponse doSendOperation(
HttpURLConnection urlConnection, byte[] content)
throws IOException {
return doOperation(urlConnection, content, false);
}
/**
* Do a operation that reads from the httpconnection
*
* @param urlConnection The connections
* @return Response
* @throws IOException
*/
public static HttpResponse doReadOperation(
HttpURLConnection urlConnection) throws IOException {
return doOperation(urlConnection, null, true);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy