mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-03 04:12:31 +01:00
Merge remote-tracking branch 'apache/4.19'
This commit is contained in:
commit
d81ffd2d86
@ -23,6 +23,7 @@ This PR...
|
||||
- [ ] Enhancement (improves an existing feature and functionality)
|
||||
- [ ] Cleanup (Code refactoring and cleanup, that may add test cases)
|
||||
- [ ] build/CI
|
||||
- [ ] test (unit or integration test code)
|
||||
|
||||
### Feature/Enhancement Scale or Bug Severity
|
||||
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
// 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 org.apache.cloudstack.api.command.admin.vm;
|
||||
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.command.admin.AdminCmd;
|
||||
|
||||
import org.apache.cloudstack.api.command.user.vm.ListVnfAppliancesCmd;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
|
||||
@APICommand(name = "listVnfAppliances", description = "List VNF appliance owned by the account.",
|
||||
responseObject = UserVmResponse.class,
|
||||
responseView = ResponseView.Full,
|
||||
entityType = {VirtualMachine.class},
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||
authorized = {RoleType.Admin},
|
||||
since = "4.19.1")
|
||||
public class ListVnfAppliancesCmdByAdmin extends ListVnfAppliancesCmd implements AdminCmd {
|
||||
}
|
||||
@ -100,7 +100,7 @@ public class CreateAutoScaleVmProfileCmd extends BaseAsyncCreateCmd {
|
||||
description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " +
|
||||
"This binary data must be base64 encoded before adding it to the request. " +
|
||||
"Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " +
|
||||
"Using HTTP POST(via POST body), you can send up to 1MB of data after base64 encoding." +
|
||||
"Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " +
|
||||
"You also need to change vm.userdata.max.length value",
|
||||
length = 1048576,
|
||||
since = "4.18.0")
|
||||
|
||||
@ -95,7 +95,7 @@ public class UpdateAutoScaleVmProfileCmd extends BaseAsyncCustomIdCmd {
|
||||
description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " +
|
||||
"This binary data must be base64 encoded before adding it to the request. " +
|
||||
"Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " +
|
||||
"Using HTTP POST(via POST body), you can send up to 1MB of data after base64 encoding." +
|
||||
"Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " +
|
||||
"You also need to change vm.userdata.max.length value",
|
||||
length = 1048576,
|
||||
since = "4.18.0")
|
||||
|
||||
@ -72,7 +72,14 @@ public class RegisterUserDataCmd extends BaseCmd {
|
||||
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "an optional project for the userdata")
|
||||
private Long projectId;
|
||||
|
||||
@Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING, required = true, description = "Userdata content", length = 1048576)
|
||||
@Parameter(name = ApiConstants.USER_DATA,
|
||||
type = CommandType.STRING,
|
||||
required = true,
|
||||
description = "Base64 encoded userdata content. " +
|
||||
"Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " +
|
||||
"Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " +
|
||||
"You also need to change vm.userdata.max.length value",
|
||||
length = 1048576)
|
||||
private String userData;
|
||||
|
||||
@Parameter(name = ApiConstants.PARAMS, type = CommandType.STRING, description = "comma separated list of variables declared in userdata content")
|
||||
|
||||
@ -154,7 +154,11 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG
|
||||
private String hypervisor;
|
||||
|
||||
@Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING,
|
||||
description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. This binary data must be base64 encoded before adding it to the request. Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. Using HTTP POST(via POST body), you can send up to 1MB of data after base64 encoding.",
|
||||
description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " +
|
||||
"This binary data must be base64 encoded before adding it to the request. " +
|
||||
"Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " +
|
||||
"Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " +
|
||||
"You also need to change vm.userdata.max.length value",
|
||||
length = 1048576)
|
||||
private String userData;
|
||||
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
// 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 org.apache.cloudstack.api.command.user.vm;
|
||||
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
|
||||
@APICommand(name = "listVnfAppliances", description = "List VNF appliance owned by the account.",
|
||||
responseObject = UserVmResponse.class,
|
||||
responseView = ResponseView.Restricted, entityType = {VirtualMachine.class},
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
|
||||
since = "4.19.1")
|
||||
public class ListVnfAppliancesCmd extends ListVMsCmd implements UserCmd {
|
||||
|
||||
@Override
|
||||
public Boolean getVnf() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -60,7 +60,7 @@ public class ResetVMUserDataCmd extends BaseCmd implements UserCmd {
|
||||
description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " +
|
||||
"This binary data must be base64 encoded before adding it to the request. " +
|
||||
"Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " +
|
||||
"Using HTTP POST(via POST body), you can send up to 1MB of data after base64 encoding." +
|
||||
"Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " +
|
||||
"You also need to change vm.userdata.max.length value",
|
||||
length = 1048576)
|
||||
private String userData;
|
||||
|
||||
@ -84,7 +84,7 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
|
||||
description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " +
|
||||
"This binary data must be base64 encoded before adding it to the request. " +
|
||||
"Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " +
|
||||
"Using HTTP POST(via POST body), you can send up to 1MB of data after base64 encoding." +
|
||||
"Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " +
|
||||
"You also need to change vm.userdata.max.length value",
|
||||
length = 1048576,
|
||||
since = "4.16.0")
|
||||
|
||||
@ -69,7 +69,7 @@ public class AutoScaleVmProfileResponse extends BaseResponse implements Controll
|
||||
private Map<String, String> counterParams;
|
||||
|
||||
@SerializedName(ApiConstants.USER_DATA)
|
||||
@Param(description = "Base 64 encoded VM user data")
|
||||
@Param(description = "Base64 encoded VM user data")
|
||||
private String userData;
|
||||
|
||||
@SerializedName(ApiConstants.USER_DATA_ID) @Param(description="the id of userdata used for the VM", since = "4.18.1")
|
||||
|
||||
@ -30,7 +30,7 @@ public class VMUserDataResponse extends BaseResponse {
|
||||
private String vmId;
|
||||
|
||||
@SerializedName(ApiConstants.USER_DATA)
|
||||
@Param(description = "Base 64 encoded VM user data")
|
||||
@Param(description = "Base64 encoded VM user data")
|
||||
private String userData;
|
||||
|
||||
public void setUserData(String userData) {
|
||||
|
||||
@ -17,11 +17,16 @@
|
||||
package org.apache.cloudstack.userdata;
|
||||
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
|
||||
import com.cloud.utils.component.Manager;
|
||||
|
||||
public interface UserDataManager extends Manager, Configurable {
|
||||
String VM_USERDATA_MAX_LENGTH_STRING = "vm.userdata.max.length";
|
||||
ConfigKey<Integer> VM_USERDATA_MAX_LENGTH = new ConfigKey<>("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768",
|
||||
"Max length of vm userdata after base64 encoding. Default is 32768 and maximum is 1048576", true);
|
||||
|
||||
String concatenateUserData(String userdata1, String userdata2, String userdataProvider);
|
||||
String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod);
|
||||
}
|
||||
|
||||
@ -23,18 +23,19 @@ import com.cloud.agent.api.to.DataTO;
|
||||
|
||||
public class CreateEntityDownloadURLCommand extends AbstractDownloadCommand {
|
||||
|
||||
public CreateEntityDownloadURLCommand(String parent, String installPath, String uuid, DataTO data) { // this constructor is for creating template download url
|
||||
public CreateEntityDownloadURLCommand(String parent, String installPath, String fileName, String filePath, DataTO data) { // this constructor is for creating template download url
|
||||
super();
|
||||
this.parent = parent; // parent is required as not the template can be child of one of many parents
|
||||
this.installPath = installPath;
|
||||
this.extractLinkUUID = uuid;
|
||||
this.filenameInExtractURL = fileName;
|
||||
this.filepathInExtractURL = filePath;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public CreateEntityDownloadURLCommand(String installPath, String uuid) {
|
||||
public CreateEntityDownloadURLCommand(String installPath, String filename) {
|
||||
super();
|
||||
this.installPath = installPath;
|
||||
this.extractLinkUUID = uuid;
|
||||
this.filenameInExtractURL = filename;
|
||||
}
|
||||
|
||||
public CreateEntityDownloadURLCommand() {
|
||||
@ -42,7 +43,8 @@ public class CreateEntityDownloadURLCommand extends AbstractDownloadCommand {
|
||||
|
||||
private String installPath;
|
||||
private String parent;
|
||||
private String extractLinkUUID;
|
||||
private String filenameInExtractURL;
|
||||
private String filepathInExtractURL;
|
||||
|
||||
public DataTO getData() {
|
||||
return data;
|
||||
@ -75,12 +77,19 @@ public class CreateEntityDownloadURLCommand extends AbstractDownloadCommand {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public String getExtractLinkUUID() {
|
||||
return extractLinkUUID;
|
||||
public String getFilenameInExtractURL() {
|
||||
return filenameInExtractURL;
|
||||
}
|
||||
|
||||
public void setExtractLinkUUID(String extractLinkUUID) {
|
||||
this.extractLinkUUID = extractLinkUUID;
|
||||
public void setFilenameInExtractURL(String filenameInExtractURL) {
|
||||
this.filenameInExtractURL = filenameInExtractURL;
|
||||
}
|
||||
|
||||
public String getFilepathInExtractURL() {
|
||||
return filepathInExtractURL;
|
||||
}
|
||||
|
||||
public void setFilepathInExtractURL(String filepathInExtractURL) {
|
||||
this.filepathInExtractURL = filepathInExtractURL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,4 +50,6 @@ public interface DataObject {
|
||||
void decRefCount();
|
||||
|
||||
Long getRefCount();
|
||||
|
||||
String getName();
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.cloud.dc.VlanVO;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO;
|
||||
|
||||
import com.cloud.dc.ClusterVO;
|
||||
@ -61,9 +60,6 @@ public interface ConfigurationManager {
|
||||
public static final String MESSAGE_CREATE_VLAN_IP_RANGE_EVENT = "Message.CreateVlanIpRange.Event";
|
||||
public static final String MESSAGE_DELETE_VLAN_IP_RANGE_EVENT = "Message.DeleteVlanIpRange.Event";
|
||||
|
||||
static final String VM_USERDATA_MAX_LENGTH_STRING = "vm.userdata.max.length";
|
||||
static final ConfigKey<Integer> VM_USERDATA_MAX_LENGTH = new ConfigKey<>("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768",
|
||||
"Max length of vm userdata after base64 decoding. Default is 32768 and maximum is 1048576", true);
|
||||
public static final ConfigKey<Boolean> AllowNonRFC1918CompliantIPs = new ConfigKey<>(Boolean.class,
|
||||
"allow.non.rfc1918.compliant.ips", "Advanced", "false",
|
||||
"Allows non-compliant RFC 1918 IPs for Shared, Isolated networks and VPCs", true, null);
|
||||
|
||||
@ -26,19 +26,18 @@ import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.configuration.ConfigurationManager;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class UserDataManagerImpl extends ManagerBase implements UserDataManager {
|
||||
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(UserDataManagerImpl.class);
|
||||
private static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
|
||||
private static final int MAX_HTTP_GET_LENGTH = 2 * MAX_USER_DATA_LENGTH_BYTES;
|
||||
private static final int MAX_HTTP_GET_LENGTH = 2 * MAX_USER_DATA_LENGTH_BYTES; // 4KB
|
||||
private static final int NUM_OF_2K_BLOCKS = 512;
|
||||
private static final int MAX_HTTP_POST_LENGTH = NUM_OF_2K_BLOCKS * MAX_USER_DATA_LENGTH_BYTES;
|
||||
private static final int MAX_HTTP_POST_LENGTH = NUM_OF_2K_BLOCKS * MAX_USER_DATA_LENGTH_BYTES; // 1MB
|
||||
private List<UserDataProvider> userDataProviders;
|
||||
private static Map<String, UserDataProvider> userDataProvidersMap = new HashMap<>();
|
||||
|
||||
@ -67,7 +66,7 @@ public class UserDataManagerImpl extends ManagerBase implements UserDataManager
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey[] {};
|
||||
return new ConfigKey[] {VM_USERDATA_MAX_LENGTH};
|
||||
}
|
||||
|
||||
protected UserDataProvider getUserdataProvider(String name) {
|
||||
@ -90,49 +89,57 @@ public class UserDataManagerImpl extends ManagerBase implements UserDataManager
|
||||
|
||||
@Override
|
||||
public String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod) {
|
||||
byte[] decodedUserData = null;
|
||||
if (userData != null) {
|
||||
|
||||
if (userData.contains("%")) {
|
||||
try {
|
||||
userData = URLDecoder.decode(userData, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new InvalidParameterValueException("Url decoding of userdata failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!Base64.isBase64(userData)) {
|
||||
throw new InvalidParameterValueException("User data is not base64 encoded");
|
||||
}
|
||||
// If GET, use 4K. If POST, support up to 1M.
|
||||
if (httpmethod.equals(BaseCmd.HTTPMethod.GET)) {
|
||||
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_GET_LENGTH, BaseCmd.HTTPMethod.GET);
|
||||
} else if (httpmethod.equals(BaseCmd.HTTPMethod.POST)) {
|
||||
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_POST_LENGTH, BaseCmd.HTTPMethod.POST);
|
||||
}
|
||||
|
||||
if (decodedUserData == null || decodedUserData.length < 1) {
|
||||
throw new InvalidParameterValueException("User data is too short");
|
||||
}
|
||||
// Re-encode so that the '=' paddings are added if necessary since 'isBase64' does not require it, but python does on the VR.
|
||||
return Base64.encodeBase64String(decodedUserData);
|
||||
s_logger.trace(String.format("Validating base64 encoded user data: [%s].", userData));
|
||||
if (StringUtils.isBlank(userData)) {
|
||||
s_logger.debug("Null/empty base64 encoded user data set");
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
if (userData.contains("%")) {
|
||||
try {
|
||||
userData = URLDecoder.decode(userData, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new InvalidParameterValueException("Url decoding of user data failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!Base64.isBase64(userData)) {
|
||||
throw new InvalidParameterValueException("User data is not base64 encoded.");
|
||||
}
|
||||
|
||||
byte[] decodedUserData = null;
|
||||
|
||||
// If GET, use 4K. If POST, support up to 1M.
|
||||
if (httpmethod.equals(BaseCmd.HTTPMethod.GET)) {
|
||||
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_GET_LENGTH, BaseCmd.HTTPMethod.GET);
|
||||
} else if (httpmethod.equals(BaseCmd.HTTPMethod.POST)) {
|
||||
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_POST_LENGTH, BaseCmd.HTTPMethod.POST);
|
||||
}
|
||||
|
||||
// Re-encode so that the '=' paddings are added if necessary since 'isBase64' does not require it, but python does on the VR.
|
||||
return Base64.encodeBase64String(decodedUserData);
|
||||
}
|
||||
|
||||
private byte[] validateAndDecodeByHTTPMethod(String userData, int maxHTTPLength, BaseCmd.HTTPMethod httpMethod) {
|
||||
byte[] decodedUserData = null;
|
||||
byte[] decodedUserData = Base64.decodeBase64(userData.getBytes());
|
||||
if (decodedUserData == null || decodedUserData.length < 1) {
|
||||
throw new InvalidParameterValueException("User data is too short.");
|
||||
}
|
||||
|
||||
if (userData.length() >= maxHTTPLength) {
|
||||
throw new InvalidParameterValueException(String.format("User data is too long for an http %s request", httpMethod.toString()));
|
||||
}
|
||||
if (userData.length() > ConfigurationManager.VM_USERDATA_MAX_LENGTH.value()) {
|
||||
throw new InvalidParameterValueException("User data has exceeded configurable max length : " + ConfigurationManager.VM_USERDATA_MAX_LENGTH.value());
|
||||
}
|
||||
decodedUserData = Base64.decodeBase64(userData.getBytes());
|
||||
if (decodedUserData.length > maxHTTPLength) {
|
||||
s_logger.trace(String.format("Decoded user data: [%s].", decodedUserData));
|
||||
int userDataLength = userData.length();
|
||||
int decodedUserDataLength = decodedUserData.length;
|
||||
s_logger.info(String.format("Configured base64 encoded user data size: %d bytes, actual user data size: %d bytes", userDataLength, decodedUserDataLength));
|
||||
|
||||
if (userDataLength > maxHTTPLength) {
|
||||
s_logger.warn(String.format("Base64 encoded user data (size: %d bytes) too long for http %s request (accepted size: %d bytes)", userDataLength, httpMethod.toString(), maxHTTPLength));
|
||||
throw new InvalidParameterValueException(String.format("User data is too long for http %s request", httpMethod.toString()));
|
||||
}
|
||||
if (userDataLength > VM_USERDATA_MAX_LENGTH.value()) {
|
||||
s_logger.warn(String.format("Base64 encoded user data (size: %d bytes) has exceeded configurable max length of %d bytes", userDataLength, VM_USERDATA_MAX_LENGTH.value()));
|
||||
throw new InvalidParameterValueException("User data has exceeded configurable max length: " + VM_USERDATA_MAX_LENGTH.value());
|
||||
}
|
||||
|
||||
return decodedUserData;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
// under the License.
|
||||
package org.apache.cloudstack.hypervisor.xenserver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -24,6 +23,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.xmlrpc.XmlRpcException;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.xensource.xenapi.Connection;
|
||||
import com.xensource.xenapi.Types;
|
||||
@ -36,17 +36,24 @@ public class ExtraConfigurationUtility {
|
||||
Map<String, Object> recordMap = vmr.toMap();
|
||||
for (String key : extraConfig.keySet()) {
|
||||
String cfg = extraConfig.get(key);
|
||||
Map<String, String> configParams = prepareKeyValuePair(cfg);
|
||||
// cfg is either param=value or map-param:key=value
|
||||
Pair<String, String> configParam = prepareKeyValuePair(cfg);
|
||||
if (configParam == null) {
|
||||
LOG.warn("Invalid extra config passed: " + cfg);
|
||||
continue;
|
||||
}
|
||||
|
||||
// paramKey is either param or param:key for map parameters
|
||||
String paramKey = configParams.keySet().toString().replaceAll("[\\[\\]]", "");
|
||||
String paramValue = configParams.get(paramKey);
|
||||
// paramKey is either param or map-param:key for map parameters
|
||||
String paramKey = configParam.first();
|
||||
String paramValue = configParam.second();
|
||||
|
||||
//Map params
|
||||
LOGGER.debug("Applying [{}] configuration as [{}].", paramKey, paramValue);
|
||||
if (paramKey.contains(":")) {
|
||||
// Map params - paramKey is map-param:key
|
||||
applyConfigWithNestedKeyValue(conn, vm, recordMap, paramKey, paramValue);
|
||||
} else {
|
||||
// Params - paramKey is param
|
||||
applyConfigWithKeyValue(conn, vm, recordMap, paramKey, paramValue);
|
||||
}
|
||||
}
|
||||
@ -65,6 +72,7 @@ public class ExtraConfigurationUtility {
|
||||
* Nested keys contain ":" between the paramKey and need to split into operation param and key
|
||||
* */
|
||||
private static void applyConfigWithNestedKeyValue(Connection conn, VM vm, Map<String, Object> recordMap, String paramKey, String paramValue) {
|
||||
// paramKey is map-param:key
|
||||
int i = paramKey.indexOf(":");
|
||||
String actualParam = paramKey.substring(0, i);
|
||||
String keyName = paramKey.substring(i + 1);
|
||||
@ -75,12 +83,13 @@ public class ExtraConfigurationUtility {
|
||||
}
|
||||
|
||||
try {
|
||||
// map-param param with '_'
|
||||
switch (actualParam) {
|
||||
case "VCPUs_params":
|
||||
vm.setVCPUsParams(conn, putInMap(vm.getVCPUsParams(conn), keyName, paramValue));
|
||||
break;
|
||||
case "platform":
|
||||
vm.setOtherConfig(conn, putInMap(vm.getOtherConfig(conn), keyName, paramValue));
|
||||
vm.addToPlatform(conn, keyName, paramValue);
|
||||
break;
|
||||
case "HVM_boot_params":
|
||||
vm.setHVMBootParams(conn, putInMap(vm.getHVMBootParams(conn), keyName, paramValue));
|
||||
@ -109,6 +118,7 @@ public class ExtraConfigurationUtility {
|
||||
}
|
||||
|
||||
try {
|
||||
// param with '_'
|
||||
switch (paramKey) {
|
||||
case "HVM_boot_policy":
|
||||
vm.setHVMBootPolicy(conn, paramValue);
|
||||
@ -152,7 +162,7 @@ public class ExtraConfigurationUtility {
|
||||
case "VCPUs_at_startup":
|
||||
vm.setVCPUsAtStartup(conn, Long.valueOf(paramValue));
|
||||
break;
|
||||
case "is-a-template":
|
||||
case "is_a_template":
|
||||
vm.setIsATemplate(conn, Boolean.valueOf(paramValue));
|
||||
break;
|
||||
case "memory_static_max":
|
||||
@ -177,12 +187,28 @@ public class ExtraConfigurationUtility {
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> prepareKeyValuePair(String cfg) {
|
||||
Map<String, String> configKeyPair = new HashMap<>();
|
||||
protected static Pair<String, String> prepareKeyValuePair(String cfg) {
|
||||
// cfg is either param=value or map-param:key=value
|
||||
int indexOfEqualSign = cfg.indexOf("=");
|
||||
String key = cfg.substring(0, indexOfEqualSign).replace("-", "_");
|
||||
if (indexOfEqualSign <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String key;
|
||||
// Replace '-' with '_' in param / map-param only
|
||||
if (cfg.contains(":")) {
|
||||
int indexOfColon = cfg.indexOf(":");
|
||||
if (indexOfColon <= 0 || indexOfEqualSign < indexOfColon) {
|
||||
return null;
|
||||
}
|
||||
String mapParam = cfg.substring(0, indexOfColon).replace("-", "_");
|
||||
String paramKey = cfg.substring(indexOfColon + 1, indexOfEqualSign);
|
||||
key = mapParam + ":" + paramKey;
|
||||
} else {
|
||||
key = cfg.substring(0, indexOfEqualSign).replace("-", "_");
|
||||
}
|
||||
|
||||
String value = cfg.substring(indexOfEqualSign + 1);
|
||||
configKeyPair.put(key, value);
|
||||
return configKeyPair;
|
||||
return new Pair<>(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 org.apache.cloudstack.hypervisor.xenserver;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ExtraConfigurationUtilityTest {
|
||||
|
||||
@Test
|
||||
public void prepareKeyValuePairTest() {
|
||||
// Map params
|
||||
verifyKeyValuePairForConfigParam("platform:exp-nested-hvm=true", "platform:exp-nested-hvm", "true");
|
||||
verifyKeyValuePairForConfigParam("other_config:my_key=my_value", "other_config:my_key", "my_value");
|
||||
verifyKeyValuePairForConfigParam("test-config:test-key=test-value", "test_config:test-key", "test-value");
|
||||
|
||||
// Params
|
||||
verifyKeyValuePairForConfigParam("is_a_template=true", "is_a_template", "true");
|
||||
verifyKeyValuePairForConfigParam("is-a-template=true", "is_a_template", "true");
|
||||
verifyKeyValuePairForConfigParam("memory_dynamic_min=536870912", "memory_dynamic_min", "536870912");
|
||||
verifyKeyValuePairForConfigParam("VCPUs_at_startup=2", "VCPUs_at_startup", "2");
|
||||
verifyKeyValuePairForConfigParam("VCPUs-max=4", "VCPUs_max", "4");
|
||||
}
|
||||
|
||||
private void verifyKeyValuePairForConfigParam(String cfg, String expectedKey, String expectedValue) {
|
||||
Pair<String, String> keyValuePair = ExtraConfigurationUtility.prepareKeyValuePair(cfg);
|
||||
Assert.assertEquals(expectedKey, keyValuePair.first());
|
||||
Assert.assertEquals(expectedValue, keyValuePair.second());
|
||||
}
|
||||
}
|
||||
@ -57,7 +57,7 @@ public class VxlanGuestNetworkGuru extends GuestNetworkGuru {
|
||||
// This guru handles only Guest Isolated network that supports Source nat service
|
||||
if (networkType == NetworkType.Advanced && isMyTrafficType(offering.getTrafficType()) &&
|
||||
(offering.getGuestType() == Network.GuestType.Isolated || offering.getGuestType() == Network.GuestType.L2) &&
|
||||
isMyIsolationMethod(physicalNetwork)) {
|
||||
isMyIsolationMethod(physicalNetwork) && !offering.isSystemOnly()) {
|
||||
return true;
|
||||
} else {
|
||||
logger.trace("We only take care of Guest networks of type " + GuestType.Isolated + " or " + GuestType.L2 + " in zone of type " + NetworkType.Advanced);
|
||||
|
||||
@ -25,6 +25,7 @@ import javax.inject.Inject;
|
||||
import com.cloud.agent.api.storage.DeleteEntityDownloadURLCommand;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.storage.Upload;
|
||||
import com.cloud.utils.StringUtils;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
@ -62,20 +63,43 @@ public class CloudStackImageStoreDriverImpl extends NfsImageStoreDriverImpl {
|
||||
return nfsTO;
|
||||
}
|
||||
|
||||
private String createObjectNameForExtractUrl(String installPath, ImageFormat format, DataObject dataObject) {
|
||||
String objectNameInUrl = dataObject.getName();
|
||||
try {
|
||||
objectNameInUrl = cleanObjectName(objectNameInUrl);
|
||||
} catch (Exception e) {
|
||||
objectNameInUrl = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
if (format != null) {
|
||||
objectNameInUrl = objectNameInUrl + "." + format.getFileExtension();
|
||||
} else if (installPath.lastIndexOf(".") != -1) {
|
||||
objectNameInUrl = objectNameInUrl + "." + installPath.substring(installPath.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
return objectNameInUrl;
|
||||
}
|
||||
|
||||
private String cleanObjectName(String objectName) {
|
||||
if (StringUtils.isEmpty(objectName)) {
|
||||
throw new IllegalArgumentException("Object name is empty or null");
|
||||
}
|
||||
return objectName.trim()
|
||||
.replaceAll("[^a-zA-Z0-9]+", "-")
|
||||
.replaceAll("-{2,}", "-")
|
||||
.replaceAll("^-|-$", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createEntityExtractUrl(DataStore store, String installPath, ImageFormat format, DataObject dataObject) {
|
||||
// find an endpoint to send command
|
||||
EndPoint ep = _epSelector.select(store);
|
||||
// Create Symlink at ssvm
|
||||
String path = installPath;
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
if (format != null) {
|
||||
uuid = uuid + "." + format.getFileExtension();
|
||||
} else if (path.lastIndexOf(".") != -1) {
|
||||
uuid = uuid + "." + path.substring(path.lastIndexOf(".") + 1);
|
||||
}
|
||||
String objectNameInUrl = createObjectNameForExtractUrl(path, format, dataObject);
|
||||
String objectPathInUrl = UUID.randomUUID().toString();
|
||||
CreateEntityDownloadURLCommand cmd = new CreateEntityDownloadURLCommand(((ImageStoreEntity)store).getMountPoint(),
|
||||
path, uuid, dataObject == null ? null: dataObject.getTO());
|
||||
path, objectNameInUrl, objectPathInUrl, dataObject == null ? null: dataObject.getTO());
|
||||
Answer ans = null;
|
||||
if (ep == null) {
|
||||
String errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||
@ -90,10 +114,10 @@ public class CloudStackImageStoreDriverImpl extends NfsImageStoreDriverImpl {
|
||||
throw new CloudRuntimeException(errorString);
|
||||
}
|
||||
// Construct actual URL locally now that the symlink exists at SSVM
|
||||
return generateCopyUrl(ep.getPublicAddr(), uuid);
|
||||
return generateCopyUrl(ep.getPublicAddr(), objectNameInUrl, objectPathInUrl);
|
||||
}
|
||||
|
||||
private String generateCopyUrl(String ipAddress, String uuid) {
|
||||
private String generateCopyUrl(String ipAddress, String fileName, String filePath) {
|
||||
|
||||
String hostname = ipAddress;
|
||||
String scheme = "http";
|
||||
@ -116,7 +140,7 @@ public class CloudStackImageStoreDriverImpl extends NfsImageStoreDriverImpl {
|
||||
}
|
||||
scheme = "https";
|
||||
}
|
||||
return scheme + "://" + hostname + "/userdata/" + uuid;
|
||||
return scheme + "://" + hostname + "/userdata/" + filePath + "/" + fileName;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -136,6 +136,7 @@ import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.userdata.UserDataManager;
|
||||
import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
@ -562,7 +563,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
||||
configValuesForValidation.add(StorageManager.STORAGE_POOL_DISK_WAIT.key());
|
||||
configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.key());
|
||||
configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.key());
|
||||
configValuesForValidation.add(VM_USERDATA_MAX_LENGTH_STRING);
|
||||
configValuesForValidation.add(UserDataManager.VM_USERDATA_MAX_LENGTH_STRING);
|
||||
}
|
||||
|
||||
private void weightBasedParametersForValidation() {
|
||||
@ -1291,7 +1292,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
||||
throw new InvalidParameterValueException("Please enter a value less than 257 for the configuration parameter:" + name);
|
||||
}
|
||||
}
|
||||
if (VM_USERDATA_MAX_LENGTH_STRING.equalsIgnoreCase(name)) {
|
||||
if (UserDataManager.VM_USERDATA_MAX_LENGTH_STRING.equalsIgnoreCase(name)) {
|
||||
if (val > 1048576) {
|
||||
throw new InvalidParameterValueException("Please enter a value less than 1048576 for the configuration parameter:" + name);
|
||||
}
|
||||
@ -7860,7 +7861,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] {SystemVMUseLocalStorage, IOPS_MAX_READ_LENGTH, IOPS_MAX_WRITE_LENGTH,
|
||||
BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE, VM_SERVICE_OFFERING_MAX_CPU_CORES,
|
||||
VM_SERVICE_OFFERING_MAX_RAM_SIZE, VM_USERDATA_MAX_LENGTH, MIGRATE_VM_ACROSS_CLUSTERS, ENABLE_ACCOUNT_SETTINGS_FOR_DOMAIN,
|
||||
VM_SERVICE_OFFERING_MAX_RAM_SIZE, MIGRATE_VM_ACROSS_CLUSTERS, ENABLE_ACCOUNT_SETTINGS_FOR_DOMAIN,
|
||||
ENABLE_DOMAIN_SETTINGS_FOR_CHILD_DOMAIN, ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, AllowNonRFC1918CompliantIPs
|
||||
};
|
||||
}
|
||||
|
||||
@ -70,6 +70,7 @@ import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.cloudstack.userdata.UserDataManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
@ -252,6 +253,8 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
||||
@Inject
|
||||
private UserVmManager userVmMgr;
|
||||
@Inject
|
||||
private UserDataManager userDataMgr;
|
||||
@Inject
|
||||
private UserVmDao userVmDao;
|
||||
@Inject
|
||||
private HostDao hostDao;
|
||||
@ -571,7 +574,7 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
||||
userDataDetails = cmd.getUserDataDetails().toString();
|
||||
}
|
||||
userData = userVmMgr.finalizeUserData(userData, userDataId, template);
|
||||
userData = userVmMgr.validateUserData(userData, cmd.getHttpMethod());
|
||||
userData = userDataMgr.validateUserData(userData, cmd.getHttpMethod());
|
||||
if (userData != null) {
|
||||
profileVO.setUserData(userData);
|
||||
}
|
||||
@ -650,7 +653,7 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
||||
}
|
||||
VirtualMachineTemplate template = entityMgr.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId);
|
||||
userData = userVmMgr.finalizeUserData(userData, userDataId, template);
|
||||
userData = userVmMgr.validateUserData(userData, cmd.getHttpMethod());
|
||||
userData = userDataMgr.validateUserData(userData, cmd.getHttpMethod());
|
||||
vmProfile.setUserDataId(userDataId);
|
||||
vmProfile.setUserData(userData);
|
||||
vmProfile.setUserDataDetails(userDataDetails);
|
||||
|
||||
@ -259,7 +259,7 @@ public class UploadMonitorImpl extends ManagerBase implements UploadMonitor {
|
||||
// Create Symlink at ssvm
|
||||
String path = vmTemplateHost.getInstallPath();
|
||||
String uuid = UUID.randomUUID().toString() + "." + template.getFormat().getFileExtension(); // adding "." + vhd/ova... etc.
|
||||
CreateEntityDownloadURLCommand cmd = new CreateEntityDownloadURLCommand(((ImageStoreEntity)store).getMountPoint(), path, uuid, null);
|
||||
CreateEntityDownloadURLCommand cmd = new CreateEntityDownloadURLCommand(((ImageStoreEntity)store).getMountPoint(), path, uuid, null, null);
|
||||
Answer ans = ep.sendMessage(cmd);
|
||||
if (ans == null || !ans.getResult()) {
|
||||
errorString = "Unable to create a link for " + type + " id:" + template.getId() + "," + (ans == null ? "" : ans.getDetails());
|
||||
@ -315,7 +315,7 @@ public class UploadMonitorImpl extends ManagerBase implements UploadMonitor {
|
||||
throw new CloudRuntimeException(errorString);
|
||||
}
|
||||
|
||||
CreateEntityDownloadURLCommand cmd = new CreateEntityDownloadURLCommand(((ImageStoreEntity)secStore).getMountPoint(), path, uuid, null);
|
||||
CreateEntityDownloadURLCommand cmd = new CreateEntityDownloadURLCommand(((ImageStoreEntity)secStore).getMountPoint(), path, uuid, null, null);
|
||||
Answer ans = ep.sendMessage(cmd);
|
||||
if (ans == null || !ans.getResult()) {
|
||||
errorString = "Unable to create a link for " + type + " id:" + entityId + "," + (ans == null ? "" : ans.getDetails());
|
||||
|
||||
@ -95,8 +95,6 @@ public interface UserVmManager extends UserVmService {
|
||||
|
||||
String finalizeUserData(String userData, Long userDataId, VirtualMachineTemplate template);
|
||||
|
||||
String validateUserData(String userData, HTTPMethod httpmethod);
|
||||
|
||||
void validateExtraConfig(long accountId, HypervisorType hypervisorType, String extraConfig);
|
||||
|
||||
boolean isVMUsingLocalStorage(VMInstanceVO vm);
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
// under the License.
|
||||
package com.cloud.vm;
|
||||
|
||||
import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH;
|
||||
import static com.cloud.storage.Volume.IOPS_LIMIT;
|
||||
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
|
||||
import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
|
||||
@ -133,7 +132,6 @@ import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
||||
import org.apache.cloudstack.utils.security.ParserUtils;
|
||||
import org.apache.cloudstack.vm.UnmanagedVMsManager;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
@ -2812,6 +2810,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
||||
userDataDetails = cmd.getUserdataDetails().toString();
|
||||
}
|
||||
userData = finalizeUserData(userData, userDataId, template);
|
||||
userData = userDataManager.validateUserData(userData, cmd.getHttpMethod());
|
||||
|
||||
long accountId = vmInstance.getAccountId();
|
||||
|
||||
@ -4959,56 +4958,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String validateUserData(String userData, HTTPMethod httpmethod) {
|
||||
byte[] decodedUserData = null;
|
||||
if (userData != null) {
|
||||
|
||||
if (userData.contains("%")) {
|
||||
try {
|
||||
userData = URLDecoder.decode(userData, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new InvalidParameterValueException("Url decoding of userdata failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!Base64.isBase64(userData)) {
|
||||
throw new InvalidParameterValueException("User data is not base64 encoded");
|
||||
}
|
||||
// If GET, use 4K. If POST, support up to 1M.
|
||||
if (httpmethod.equals(HTTPMethod.GET)) {
|
||||
if (userData.length() >= MAX_HTTP_GET_LENGTH) {
|
||||
throw new InvalidParameterValueException("User data is too long for an http GET request");
|
||||
}
|
||||
if (userData.length() > VM_USERDATA_MAX_LENGTH.value()) {
|
||||
throw new InvalidParameterValueException("User data has exceeded configurable max length : " + VM_USERDATA_MAX_LENGTH.value());
|
||||
}
|
||||
decodedUserData = Base64.decodeBase64(userData.getBytes());
|
||||
if (decodedUserData.length > MAX_HTTP_GET_LENGTH) {
|
||||
throw new InvalidParameterValueException("User data is too long for GET request");
|
||||
}
|
||||
} else if (httpmethod.equals(HTTPMethod.POST)) {
|
||||
if (userData.length() >= MAX_HTTP_POST_LENGTH) {
|
||||
throw new InvalidParameterValueException("User data is too long for an http POST request");
|
||||
}
|
||||
if (userData.length() > VM_USERDATA_MAX_LENGTH.value()) {
|
||||
throw new InvalidParameterValueException("User data has exceeded configurable max length : " + VM_USERDATA_MAX_LENGTH.value());
|
||||
}
|
||||
decodedUserData = Base64.decodeBase64(userData.getBytes());
|
||||
if (decodedUserData.length > MAX_HTTP_POST_LENGTH) {
|
||||
throw new InvalidParameterValueException("User data is too long for POST request");
|
||||
}
|
||||
}
|
||||
|
||||
if (decodedUserData == null || decodedUserData.length < 1) {
|
||||
throw new InvalidParameterValueException("User data is too short");
|
||||
}
|
||||
// Re-encode so that the '=' paddings are added if necessary since 'isBase64' does not require it, but python does on the VR.
|
||||
return Base64.encodeBase64String(decodedUserData);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", async = true)
|
||||
public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException, ResourceAllocationException {
|
||||
@ -6107,13 +6056,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
||||
}
|
||||
|
||||
String userData = cmd.getUserData();
|
||||
userData = userDataManager.validateUserData(userData, cmd.getHttpMethod());
|
||||
Long userDataId = cmd.getUserdataId();
|
||||
String userDataDetails = null;
|
||||
if (MapUtils.isNotEmpty(cmd.getUserdataDetails())) {
|
||||
userDataDetails = cmd.getUserdataDetails().toString();
|
||||
}
|
||||
userData = finalizeUserData(userData, userDataId, template);
|
||||
userData = userDataManager.validateUserData(userData, cmd.getHttpMethod());
|
||||
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
Long callerId = caller.getId();
|
||||
|
||||
@ -99,4 +99,9 @@ public class DiagnosticsDataObject implements DataObject {
|
||||
public Long getRefCount() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return dataStore.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,11 +71,13 @@ import org.apache.cloudstack.api.command.admin.template.ListVnfTemplatesCmdByAdm
|
||||
import org.apache.cloudstack.api.command.admin.template.RegisterVnfTemplateCmdByAdmin;
|
||||
import org.apache.cloudstack.api.command.admin.template.UpdateVnfTemplateCmdByAdmin;
|
||||
import org.apache.cloudstack.api.command.admin.vm.DeployVnfApplianceCmdByAdmin;
|
||||
import org.apache.cloudstack.api.command.admin.vm.ListVnfAppliancesCmdByAdmin;
|
||||
import org.apache.cloudstack.api.command.user.template.DeleteVnfTemplateCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
|
||||
import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.ListVnfAppliancesCmd;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
@ -130,6 +132,8 @@ public class VnfTemplateManagerImpl extends ManagerBase implements VnfTemplateMa
|
||||
cmdList.add(DeleteVnfTemplateCmd.class);
|
||||
cmdList.add(DeployVnfApplianceCmd.class);
|
||||
cmdList.add(DeployVnfApplianceCmdByAdmin.class);
|
||||
cmdList.add(ListVnfAppliancesCmd.class);
|
||||
cmdList.add(ListVnfAppliancesCmdByAdmin.class);
|
||||
return cmdList;
|
||||
}
|
||||
|
||||
|
||||
@ -122,6 +122,7 @@ import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
|
||||
import org.apache.cloudstack.config.ApiServiceConfiguration;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.userdata.UserDataManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
@ -189,6 +190,9 @@ public class AutoScaleManagerImplTest {
|
||||
@Mock
|
||||
UserVmManager userVmMgr;
|
||||
|
||||
@Mock
|
||||
UserDataManager userDataMgr;
|
||||
|
||||
@Mock
|
||||
EntityManager entityManager;
|
||||
|
||||
@ -406,7 +410,7 @@ public class AutoScaleManagerImplTest {
|
||||
|
||||
userDataDetails.put("0", new HashMap<>() {{ put("key1", "value1"); put("key2", "value2"); }});
|
||||
Mockito.doReturn(userDataFinal).when(userVmMgr).finalizeUserData(any(), any(), any());
|
||||
Mockito.doReturn(userDataFinal).when(userVmMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));
|
||||
Mockito.doReturn(userDataFinal).when(userDataMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));
|
||||
}
|
||||
|
||||
@After
|
||||
@ -760,7 +764,7 @@ public class AutoScaleManagerImplTest {
|
||||
Mockito.verify(autoScaleVmProfileDao).persist(Mockito.any());
|
||||
|
||||
Mockito.verify(userVmMgr).finalizeUserData(any(), any(), any());
|
||||
Mockito.verify(userVmMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));
|
||||
Mockito.verify(userDataMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));
|
||||
}
|
||||
}
|
||||
|
||||
@ -821,7 +825,7 @@ public class AutoScaleManagerImplTest {
|
||||
Mockito.verify(autoScaleVmProfileDao).persist(Mockito.any());
|
||||
|
||||
Mockito.verify(userVmMgr).finalizeUserData(any(), any(), any());
|
||||
Mockito.verify(userVmMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));
|
||||
Mockito.verify(userDataMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -266,6 +266,7 @@ public class UploadManagerImpl extends ManagerBase implements UploadManager {
|
||||
}
|
||||
// Create the directory structure so that its visible under apache server root
|
||||
String extractDir = "/var/www/html/userdata/";
|
||||
extractDir = extractDir + cmd.getFilepathInExtractURL() + File.separator;
|
||||
Script command = new Script("/bin/su", logger);
|
||||
command.add("-s");
|
||||
command.add("/bin/bash");
|
||||
@ -288,11 +289,11 @@ public class UploadManagerImpl extends ManagerBase implements UploadManager {
|
||||
}
|
||||
|
||||
// Create a random file under the directory for security reasons.
|
||||
String uuid = cmd.getExtractLinkUUID();
|
||||
String filename = cmd.getFilenameInExtractURL();
|
||||
// Create a symbolic link from the actual directory to the template location. The entity would be directly visible under /var/www/html/userdata/cmd.getInstallPath();
|
||||
command = new Script("/bin/bash", logger);
|
||||
command.add("-c");
|
||||
command.add("ln -sf /mnt/SecStorage/" + cmd.getParent() + File.separator + cmd.getInstallPath() + " " + extractDir + uuid);
|
||||
command.add("ln -sf /mnt/SecStorage/" + cmd.getParent() + File.separator + cmd.getInstallPath() + " " + extractDir + filename);
|
||||
result = command.execute();
|
||||
if (result != null) {
|
||||
String errorString = "Error in linking err=" + result;
|
||||
|
||||
@ -21,8 +21,10 @@ Tests DRS on a cluster
|
||||
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import Iterable
|
||||
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.cloudstackAPI import (migrateSystemVm, listRouters, listSystemVms)
|
||||
from marvin.lib.base import (Cluster, Configurations, Host, Network, NetworkOffering, ServiceOffering, VirtualMachine,
|
||||
Zone)
|
||||
from marvin.lib.common import (get_domain, get_zone, get_template)
|
||||
@ -98,6 +100,41 @@ class TestClusterDRS(cloudstackTestCase):
|
||||
)
|
||||
cls._cleanup.append(cls.network)
|
||||
|
||||
cls.migrateSvms(cls.cluster)
|
||||
|
||||
@classmethod
|
||||
def migrateSvms(cls, cluster):
|
||||
"""
|
||||
for testing the balanced algorithm we must make sure there is at least as more free memory on host[1] than on
|
||||
host[0]. As a grude measure we migrate any and all system vms to host[0] before the testing commences
|
||||
|
||||
:param cluster: the cluser to check
|
||||
:return: None
|
||||
"""
|
||||
|
||||
systemVmIds = []
|
||||
cmds = listSystemVms.listSystemVmsCmd()
|
||||
responseS = cls.apiclient.listSystemVms(cmds)
|
||||
if isinstance(responseS, Iterable):
|
||||
for svm in responseS:
|
||||
if svm.hostid != cls.hosts[0].id:
|
||||
systemVmIds.append(svm.id)
|
||||
cmdv = listRouters.listRoutersCmd()
|
||||
responseR = cls.apiclient.listRouters(cmdv)
|
||||
if isinstance(responseR, Iterable):
|
||||
for svm in responseR:
|
||||
if svm.hostid != cls.hosts[0].id:
|
||||
systemVmIds.append(svm.id)
|
||||
numToMigrate = len(systemVmIds)
|
||||
cls.logger.debug(f'system vms and routers to migrate -- {numToMigrate}')
|
||||
cmdM = migrateSystemVm.migrateSystemVmCmd()
|
||||
cmdM.hostId=cls.hosts[0].id
|
||||
for id in systemVmIds:
|
||||
cmdM.virtualmachineid=id
|
||||
responseM = cls.apiclient.migrateSystemVm(cmdM)
|
||||
cls.logger.debug(f'migrated {responseM}')
|
||||
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(TestClusterDRS, cls).tearDownClass()
|
||||
@ -111,7 +148,6 @@ class TestClusterDRS(cloudstackTestCase):
|
||||
def tearDown(self):
|
||||
super(TestClusterDRS, self).tearDown()
|
||||
|
||||
@classmethod
|
||||
def get_vm_host_id(cls, vm_id):
|
||||
list_vms = VirtualMachine.list(cls.apiclient, id=vm_id)
|
||||
vm = list_vms[0]
|
||||
@ -188,8 +224,8 @@ class TestClusterDRS(cloudstackTestCase):
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id, zoneid=self.zone.id,
|
||||
networkids=self.network.id, hostid=self.hosts[1].id)
|
||||
vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
|
||||
self.cleanup.append(self.virtual_machine_2)
|
||||
vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
|
||||
|
||||
self.assertNotEqual(vm_1_host_id, vm_2_host_id, msg="Both VMs should be on different hosts")
|
||||
self.wait_for_vm_start(self.virtual_machine_1)
|
||||
@ -216,13 +252,15 @@ class TestClusterDRS(cloudstackTestCase):
|
||||
|
||||
@attr(tags=["advanced"], required_hardware="false")
|
||||
def test_02_balanced_drs_algorithm(self):
|
||||
""" Verify DRS algorithm - balanced"""
|
||||
|
||||
# 1. Deploy vm-1 on host 1
|
||||
# 2. Deploy vm-2 on host 2
|
||||
# 3. Execute DRS to move all VMs on different hosts
|
||||
"""
|
||||
Verify DRS algorithm - balanced
|
||||
|
||||
# 1. Deploy vm-1 on host 1
|
||||
# 2. Deploy vm-2 on host 2
|
||||
# 3. Execute DRS to move all VMs on different hosts
|
||||
"""
|
||||
self.logger.debug("=== Running test_02_balanced_drs_algorithm ===")
|
||||
|
||||
# 1. Deploy vm-1 on host 1
|
||||
self.services["virtual_machine"]["name"] = "virtual-machine-1"
|
||||
self.services["virtual_machine"]["displayname"] = "virtual-machine-1"
|
||||
@ -240,8 +278,8 @@ class TestClusterDRS(cloudstackTestCase):
|
||||
serviceofferingid=self.service_offering.id,
|
||||
templateid=self.template.id, zoneid=self.zone.id,
|
||||
networkids=self.network.id, hostid=self.hosts[0].id)
|
||||
vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
|
||||
self.cleanup.append(self.virtual_machine_2)
|
||||
vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
|
||||
|
||||
self.assertEqual(vm_1_host_id, vm_2_host_id, msg="Both VMs should be on same hosts")
|
||||
self.wait_for_vm_start(self.virtual_machine_1)
|
||||
@ -256,7 +294,8 @@ class TestClusterDRS(cloudstackTestCase):
|
||||
migration["virtualmachineid"]: migration["destinationhostid"] for migration in migrations
|
||||
}
|
||||
|
||||
self.assertEqual(len(vm_to_dest_host_map), 1, msg="DRS plan should have 1 migrations")
|
||||
# this is one if no svm is considered to be migrated, it might be higher
|
||||
self.assertTrue(len(vm_to_dest_host_map) >= 1, msg="DRS plan should have at least 1 migrations")
|
||||
|
||||
executed_plan = self.cluster.executeDrsPlan(self.apiclient, vm_to_dest_host_map)
|
||||
self.wait_for_plan_completion(executed_plan)
|
||||
@ -264,4 +303,6 @@ class TestClusterDRS(cloudstackTestCase):
|
||||
vm_1_host_id = self.get_vm_host_id(self.virtual_machine_1.id)
|
||||
vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
|
||||
|
||||
self.assertNotEqual(vm_1_host_id, vm_2_host_id, msg="Both VMs should be on different hosts")
|
||||
self.assertTrue(
|
||||
vm_1_host_id != self.virtual_machine_1.hostid or vm_2_host_id != self.virtual_machine_2.hostid,
|
||||
msg="At least one VM should have been migrated to a different host")
|
||||
|
||||
@ -90,7 +90,7 @@ export default {
|
||||
}, {
|
||||
name: 'vnf.settings',
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/image/TemplateVnfSettings.vue'))),
|
||||
show: (record) => { return record.templatetype === 'VNF' }
|
||||
show: (record) => { return record.templatetype === 'VNF' && 'updateVnfTemplate' in store.getters.apis }
|
||||
},
|
||||
{
|
||||
name: 'events',
|
||||
|
||||
@ -82,7 +82,7 @@ export default {
|
||||
}, {
|
||||
name: 'vnf.appliances',
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/network/VnfAppliancesTab.vue'))),
|
||||
show: () => { return 'deployVnfAppliance' in store.getters.apis }
|
||||
show: () => { return 'listVnfAppliances' in store.getters.apis }
|
||||
}, {
|
||||
name: 'guest.ip.range',
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/network/GuestIpRanges.vue'))),
|
||||
@ -337,7 +337,7 @@ export default {
|
||||
name: 'vnfapp',
|
||||
title: 'label.vnf.appliances',
|
||||
icon: 'gateway-outlined',
|
||||
permission: ['listVnfTemplates'],
|
||||
permission: ['listVnfAppliances'],
|
||||
resourceType: 'UserVm',
|
||||
params: () => {
|
||||
return { details: 'servoff,tmpl,nics', isvnf: true }
|
||||
|
||||
@ -256,11 +256,17 @@ export default {
|
||||
(record.type !== 'ROOT' && !record.virtualmachineid && !['Allocated', 'Uploaded'].includes(record.state)))
|
||||
},
|
||||
args: (record, store) => {
|
||||
var fields = ['volumeid', 'name', 'displaytext', 'ostypeid', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled']
|
||||
var fields = ['volumeid', 'name', 'displaytext', 'ostypeid', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled']
|
||||
if (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) {
|
||||
fields.push('domainid')
|
||||
fields.push('account')
|
||||
}
|
||||
if (['Admin'].includes(store.userInfo.roletype) || store.features.userpublictemplateenabled) {
|
||||
fields.push('ispublic')
|
||||
}
|
||||
if (['Admin'].includes(store.userInfo.roletype)) {
|
||||
fields.push('isfeatured')
|
||||
}
|
||||
return fields
|
||||
},
|
||||
mapping: {
|
||||
|
||||
@ -130,7 +130,7 @@ export default {
|
||||
params.networkid = this.resource.id
|
||||
}
|
||||
this.fetchLoading = true
|
||||
api('listVirtualMachines', params).then(json => {
|
||||
api('listVnfAppliances', params).then(json => {
|
||||
this.virtualmachines = json.listvirtualmachinesresponse.virtualmachine || []
|
||||
for (const vm of this.virtualmachines) {
|
||||
for (const vmnic of vm.nic) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user