// // 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(); } }