mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge release branch 4.8 to master
* 4.8: Add ability to download templates in Swift
This commit is contained in:
commit
f7c3957af3
@ -18,10 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cloudstack.storage.datastore.driver;
|
package org.apache.cloudstack.storage.datastore.driver;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import com.cloud.configuration.Config;
|
||||||
|
import com.cloud.utils.SwiftUtil;
|
||||||
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import org.apache.cloudstack.api.ApiConstants;
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
@ -43,7 +48,6 @@ import com.cloud.agent.api.storage.DownloadAnswer;
|
|||||||
import com.cloud.agent.api.to.DataObjectType;
|
import com.cloud.agent.api.to.DataObjectType;
|
||||||
import com.cloud.agent.api.to.DataStoreTO;
|
import com.cloud.agent.api.to.DataStoreTO;
|
||||||
import com.cloud.agent.api.to.SwiftTO;
|
import com.cloud.agent.api.to.SwiftTO;
|
||||||
import com.cloud.exception.UnsupportedServiceException;
|
|
||||||
import com.cloud.storage.Storage.ImageFormat;
|
import com.cloud.storage.Storage.ImageFormat;
|
||||||
import com.cloud.template.VirtualMachineTemplate;
|
import com.cloud.template.VirtualMachineTemplate;
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
@ -57,6 +61,8 @@ public class SwiftImageStoreDriverImpl extends BaseImageStoreDriverImpl {
|
|||||||
EndPointSelector _epSelector;
|
EndPointSelector _epSelector;
|
||||||
@Inject
|
@Inject
|
||||||
StorageCacheManager cacheManager;
|
StorageCacheManager cacheManager;
|
||||||
|
@Inject
|
||||||
|
ConfigurationDao _configDao;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DataStoreTO getStoreTO(DataStore store) {
|
public DataStoreTO getStoreTO(DataStore store) {
|
||||||
@ -67,7 +73,29 @@ public class SwiftImageStoreDriverImpl extends BaseImageStoreDriverImpl {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String createEntityExtractUrl(DataStore store, String installPath, ImageFormat format, DataObject dataObject) {
|
public String createEntityExtractUrl(DataStore store, String installPath, ImageFormat format, DataObject dataObject) {
|
||||||
throw new UnsupportedServiceException("Extract entity url is not yet supported for Swift image store provider");
|
|
||||||
|
SwiftTO swiftTO = (SwiftTO)store.getTO();
|
||||||
|
String tempKey = UUID.randomUUID().toString();
|
||||||
|
boolean result = SwiftUtil.setTempKey(swiftTO, tempKey);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
String errMsg = "Unable to set Temp-Key: " + tempKey;
|
||||||
|
s_logger.error(errMsg);
|
||||||
|
throw new CloudRuntimeException(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
String containerName = SwiftUtil.getContainerName(dataObject.getType().toString(), dataObject.getId());
|
||||||
|
String objectName = installPath.split("\\/")[1];
|
||||||
|
// Get extract url expiration interval set in global configuration (in seconds)
|
||||||
|
int urlExpirationInterval = Integer.parseInt(_configDao.getValue(Config.ExtractURLExpirationInterval.toString()));
|
||||||
|
|
||||||
|
URL swiftUrl = SwiftUtil.generateTempUrl(swiftTO, containerName, objectName, tempKey, urlExpirationInterval);
|
||||||
|
if (swiftUrl != null) {
|
||||||
|
s_logger.debug("Swift temp-url: " + swiftUrl.toString());
|
||||||
|
return swiftUrl.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CloudRuntimeException("Unable to create extraction URL");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -1520,12 +1520,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
/*if (snapshot != null && snapshot.getSwiftId() != null
|
|
||||||
&& secondaryStorageURL != null && zoneId != null
|
|
||||||
&& accountId != null && volumeId != null) {
|
|
||||||
_snapshotMgr.deleteSnapshotsForVolume(secondaryStorageURL,
|
|
||||||
zoneId, accountId, volumeId);
|
|
||||||
}*/
|
|
||||||
if (privateTemplate == null) {
|
if (privateTemplate == null) {
|
||||||
final VolumeVO volumeFinal = volume;
|
final VolumeVO volumeFinal = volume;
|
||||||
final SnapshotVO snapshotFinal = snapshot;
|
final SnapshotVO snapshotFinal = snapshot;
|
||||||
|
|||||||
@ -20,8 +20,14 @@
|
|||||||
package com.cloud.utils;
|
package com.cloud.utils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
import java.net.URL;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SignatureException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Formatter;
|
||||||
|
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
@ -29,9 +35,15 @@ import com.cloud.utils.exception.CloudRuntimeException;
|
|||||||
import com.cloud.utils.script.OutputInterpreter;
|
import com.cloud.utils.script.OutputInterpreter;
|
||||||
import com.cloud.utils.script.Script;
|
import com.cloud.utils.script.Script;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
public class SwiftUtil {
|
public class SwiftUtil {
|
||||||
private static Logger logger = Logger.getLogger(SwiftUtil.class);
|
private static Logger logger = Logger.getLogger(SwiftUtil.class);
|
||||||
private static final long SWIFT_MAX_SIZE = 5L * 1024L * 1024L * 1024L;
|
private static final long SWIFT_MAX_SIZE = 5L * 1024L * 1024L * 1024L;
|
||||||
|
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public interface SwiftClientCfg {
|
public interface SwiftClientCfg {
|
||||||
String getAccount();
|
String getAccount();
|
||||||
@ -143,8 +155,7 @@ public class SwiftUtil {
|
|||||||
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
|
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
|
||||||
String result = command.execute(parser);
|
String result = command.execute(parser);
|
||||||
if (result == null && parser.getLines() != null && !parser.getLines().equalsIgnoreCase("")) {
|
if (result == null && parser.getLines() != null && !parser.getLines().equalsIgnoreCase("")) {
|
||||||
String[] lines = parser.getLines().split("\\n");
|
return parser.getLines().split("\\n");
|
||||||
return lines;
|
|
||||||
} else {
|
} else {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
String errMsg = "swiftList failed , err=" + result;
|
String errMsg = "swiftList failed , err=" + result;
|
||||||
@ -161,7 +172,7 @@ public class SwiftUtil {
|
|||||||
int firstIndexOfSeparator = swiftPath.indexOf(File.separator);
|
int firstIndexOfSeparator = swiftPath.indexOf(File.separator);
|
||||||
String container = swiftPath.substring(0, firstIndexOfSeparator);
|
String container = swiftPath.substring(0, firstIndexOfSeparator);
|
||||||
String srcPath = swiftPath.substring(firstIndexOfSeparator + 1);
|
String srcPath = swiftPath.substring(firstIndexOfSeparator + 1);
|
||||||
String destFilePath = null;
|
String destFilePath;
|
||||||
if (destDirectory.isDirectory()) {
|
if (destDirectory.isDirectory()) {
|
||||||
destFilePath = destDirectory.getAbsolutePath() + File.separator + srcPath;
|
destFilePath = destDirectory.getAbsolutePath() + File.separator + srcPath;
|
||||||
} else {
|
} else {
|
||||||
@ -171,7 +182,7 @@ public class SwiftUtil {
|
|||||||
Script command = new Script("/bin/bash", logger);
|
Script command = new Script("/bin/bash", logger);
|
||||||
command.add("-c");
|
command.add("-c");
|
||||||
command.add("/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() + " -K " + cfg.getKey() +
|
command.add("/usr/bin/python " + swiftCli + " -A " + cfg.getEndPoint() + " -U " + cfg.getAccount() + ":" + cfg.getUserName() + " -K " + cfg.getKey() +
|
||||||
" download " + container + " " + srcPath + " -o " + destFilePath);
|
" download " + container + " " + srcPath + " -o " + destFilePath);
|
||||||
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
|
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
|
||||||
String result = command.execute(parser);
|
String result = command.execute(parser);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@ -236,4 +247,59 @@ public class SwiftUtil {
|
|||||||
command.execute(parser);
|
command.execute(parser);
|
||||||
return true;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
92
utils/src/test/java/com/cloud/utils/SwiftUtilTest.java
Normal file
92
utils/src/test/java/com/cloud/utils/SwiftUtilTest.java
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
//
|
||||||
|
// 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 org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
|
public class SwiftUtilTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateRFC2104HMAC() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
|
||||||
|
String inputData = "testData";
|
||||||
|
String inputKey = "testKey";
|
||||||
|
String expected = "1d541ecb5cdb2d850716bfd55585e20a1cd8984b";
|
||||||
|
String output = SwiftUtil.calculateRFC2104HMAC(inputData, inputKey);
|
||||||
|
|
||||||
|
assertEquals(expected, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToHexString(){
|
||||||
|
final byte[] input = "testing".getBytes();
|
||||||
|
final String expected = "74657374696e67";
|
||||||
|
final String result = SwiftUtil.toHexString(input);
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateTempUrl() {
|
||||||
|
|
||||||
|
SwiftUtil.SwiftClientCfg cfg = Mockito.mock(SwiftUtil.SwiftClientCfg.class);
|
||||||
|
when(cfg.getEndPoint()).thenReturn("http://localhost:8080/v1/");
|
||||||
|
when(cfg.getAccount()).thenReturn("test");
|
||||||
|
|
||||||
|
String container = "testContainer";
|
||||||
|
String object = "testObject";
|
||||||
|
String tempKey = "testKey";
|
||||||
|
int urlExpirationInterval = 3600;
|
||||||
|
String expected = "http://localhost:8080/v1/AUTH_test/testContainer/testObject";
|
||||||
|
URL output = SwiftUtil.generateTempUrl(cfg, container, object, tempKey, urlExpirationInterval);
|
||||||
|
|
||||||
|
assertTrue(output.toString().contains(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSplitSwiftPath(){
|
||||||
|
String input = "container/object";
|
||||||
|
String[] output = SwiftUtil.splitSwiftPath(input);
|
||||||
|
String[] expected = {"container", "object"};
|
||||||
|
|
||||||
|
assertArrayEquals(expected, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetContainerName(){
|
||||||
|
|
||||||
|
String inputType = "Template";
|
||||||
|
long inputId = 45;
|
||||||
|
String output = SwiftUtil.getContainerName(inputType, inputId);
|
||||||
|
String expected = "T-45";
|
||||||
|
|
||||||
|
assertEquals(expected, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user