//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.cloud.utils;
import java.io.File;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Map;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Formatter;
import org.apache.log4j.Logger;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class SwiftUtil {
private static Logger logger = Logger.getLogger(SwiftUtil.class);
private static final long SWIFT_MAX_SIZE = 5L * 1024L * 1024L * 1024L;
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
public interface SwiftClientCfg {
String getAccount();
String getUserName();
String getKey();
String getEndPoint();
}
private static String getSwiftCLIPath() {
String swiftCLI = Script.findScript("scripts/storage/secondary", "swift");
if (swiftCLI == null) {
logger.debug("Can't find swift cli at scripts/storage/secondary/swift");
throw new CloudRuntimeException("Can't find swift cli at scripts/storage/secondary/swift");
}
return swiftCLI;
}
public static boolean postMeta(SwiftClientCfg cfg, String container, String object, Map<String, String> metas) {
String swiftCli = getSwiftCLIPath();
StringBuilder cms = new StringBuilder();
for (Map.Entry<String, String> entry : metas.entrySet()) {
cms.append(" -m ");
cms.append(entry.getKey());
cms.append(":");
cms.append(entry.getValue());
cms.append(" ");
}
Script command = new Script("/bin/bash", logger);
command.add("-c");
command.add("/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() + " -K " + cfg.getKey() + " post " +
container + " " + object + " " + cms.toString());
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
String result = command.execute(parser);
if (result != null) {
throw new CloudRuntimeException("Failed to post meta" + result);
}
return true;
}
public static String putObject(SwiftClientCfg cfg, File srcFile, String container, String fileName) {
String swiftCli = getSwiftCLIPath();
if (fileName == null) {
fileName = srcFile.getName();
}
String srcDirectory = srcFile.getParent();
Script command = new Script("/bin/bash", logger);
long size = srcFile.length();
command.add("-c");
if (size <= SWIFT_MAX_SIZE) {
command.add("cd " + srcDirectory + ";/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() +
" -K " + cfg.getKey() + " upload " + container + " " + fileName);
} else {
command.add("cd " + srcDirectory + ";/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() +
" -K " + cfg.getKey() + " upload -S " + SWIFT_MAX_SIZE + " " + container + " " + fileName);
}
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
String result = command.execute(parser);
if (result != null) {
throw new CloudRuntimeException("Failed to upload file: " + result);
}
if (parser.getLines() != null) {
String[] lines = parser.getLines().split("\\n");
for (String line : lines) {
if (line.contains("Errno") || line.contains("failed") || line.contains("not found")) {
throw new CloudRuntimeException("Failed to upload file: " + Arrays.toString(lines));
}
}
}
return container + File.separator + srcFile.getName();
}
private static StringBuilder buildSwiftCmd(SwiftClientCfg swift) {
String swiftCli = getSwiftCLIPath();
StringBuilder sb = new StringBuilder();
sb.append(" /usr/bin/python ");
sb.append(swiftCli);
sb.append(" -A ");
sb.append(swift.getEndPoint());
sb.append(" -U ");
sb.append(swift.getAccount());
sb.append(":");
sb.append(swift.getUserName());
sb.append(" -K ");
sb.append(swift.getKey());
sb.append(" ");
return sb;
}
public static String[] list(SwiftClientCfg swift, String container, String rFilename) {
getSwiftCLIPath();
Script command = new Script("/bin/bash", logger);
command.add("-c");
StringBuilder swiftCmdBuilder = buildSwiftCmd(swift);
swiftCmdBuilder.append(" list ");
swiftCmdBuilder.append(container);
if (rFilename != null) {
swiftCmdBuilder.append(" -p ");
swiftCmdBuilder.append(rFilename);
}
command.add(swiftCmdBuilder.toString());
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
String result = command.execute(parser);
if (result == null && parser.getLines() != null && !parser.getLines().equalsIgnoreCase("")) {
return parser.getLines().split("\\n");
} else {
if (result != null) {
String errMsg = "swiftList failed , err=" + result;
logger.debug("Failed to list " + errMsg);
} else {
String errMsg = "swiftList failed, no lines returns";
logger.debug("Failed to list " + errMsg);
}
}
return new String[0];
}
public static File getObject(SwiftClientCfg cfg, File destDirectory, String swiftPath) {
int firstIndexOfSeparator = swiftPath.indexOf(File.separator);
String container = swiftPath.substring(0, firstIndexOfSeparator);
String srcPath = swiftPath.substring(firstIndexOfSeparator + 1);
String destFilePath;
if (destDirectory.isDirectory()) {
destFilePath = destDirectory.getAbsolutePath() + File.separator + srcPath;
} else {
destFilePath = destDirectory.getAbsolutePath();
}
String swiftCli = getSwiftCLIPath();
Script command = new Script("/bin/bash", logger);
command.add("-c");
command.add("/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() + " -K " + cfg.getKey() +
" download " + container + " " + srcPath + " -o " + destFilePath);
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
String result = command.execute(parser);
if (result != null) {
String errMsg = "swiftDownload failed err=" + result;
logger.debug(errMsg);
throw new CloudRuntimeException("failed to get object: " + swiftPath);
}
if (parser.getLines() != null) {
String[] lines = parser.getLines().split("\\n");
for (String line : lines) {
if (line.contains("Errno") || line.contains("failed")) {
String errMsg = "swiftDownload failed , err=" + Arrays.toString(lines);
logger.debug(errMsg);
throw new CloudRuntimeException("Failed to get object: " + swiftPath);
}
}
}
return new File(destFilePath);
}
public static String getContainerName(String type, Long id) {
if (type.startsWith("T")) {
return "T-" + id;
} else if (type.startsWith("S")) {
return "S-" + id;
} else if (type.startsWith("V")) {
return "V-" + id;
}
return null;
}
public static String[] splitSwiftPath(String path) {
int index = path.indexOf(File.separator);
if (index == -1) {
return null;
}
String[] paths = new String[2];
paths[0] = path.substring(0, index);
paths[1] = path.substring(index + 1);
return paths;
}
public static boolean deleteObject(SwiftClientCfg cfg, String path) {
Script command = new Script("/bin/bash", logger);
command.add("-c");
String[] paths = splitSwiftPath(path);
if (paths == null) {
return false;
}
String container = paths[0];
String objectName = paths[1];
StringBuilder swiftCmdBuilder = buildSwiftCmd(cfg);
swiftCmdBuilder.append(" delete ");
swiftCmdBuilder.append(container);
swiftCmdBuilder.append(" ");
swiftCmdBuilder.append(objectName);
command.add(swiftCmdBuilder.toString());
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
command.execute(parser);
return true;
}
public static boolean setTempKey(SwiftClientCfg cfg, String tempKey){
Map<String, String> tempKeyMap = new HashMap<>();
tempKeyMap.put("Temp-URL-Key", tempKey);
return postMeta(cfg, "", "", tempKeyMap);
}
public static URL generateTempUrl(SwiftClientCfg cfg, String container, String object, String tempKey, int urlExpirationInterval) {
int currentTime = (int) (System.currentTimeMillis() / 1000L);
int expirationSeconds = currentTime + urlExpirationInterval;
try {
URL endpoint = new URL(cfg.getEndPoint());
String method = "GET";
String path = String.format("/v1/AUTH_%s/%s/%s", cfg.getAccount(), container, object);
//sign the request
String hmacBody = String.format("%s\n%d\n%s", method, expirationSeconds, path);
String signature = calculateRFC2104HMAC(hmacBody, tempKey);
path += String.format("?temp_url_sig=%s&temp_url_expires=%d", signature, expirationSeconds);
//generate the temp url
URL tempUrl = new URL(endpoint.getProtocol(), endpoint.getHost(), endpoint.getPort(), path);
return tempUrl;
} catch (Exception e) {
logger.error(e.getMessage());
throw new CloudRuntimeException(e.getMessage());
}
}
public static String calculateRFC2104HMAC(String data, String key)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
return toHexString(mac.doFinal(data.getBytes()));
}
public static String toHexString(byte[] bytes) {
Formatter formatter = new Formatter();
for (byte b : bytes) {
formatter.format("%02x", b);
}
return formatter.toString();
}
}