Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@

import javax.annotation.Nonnull;

import org.apache.log4j.Logger;

import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.api.ACL;
Expand All @@ -52,6 +50,7 @@
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;

import com.cloud.agent.api.LogLevel;
import com.cloud.event.EventTypes;
Expand Down Expand Up @@ -190,7 +189,7 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG
@Parameter(name = ApiConstants.DISPLAY_VM, type = CommandType.BOOLEAN, since = "4.2", description = "an optional field, whether to the display the vm to the end user or not.", authorized = {RoleType.Admin})
private Boolean displayVm;

@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.3", description = "used to specify the custom parameters.")
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.3", description = "used to specify the custom parameters. 'extraconfig' is not allowed to be passed in details")
private Map details;

@Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "Deployment planner to use for vm allocation. Available to ROOT admin only", since = "4.4", authorized = { RoleType.Admin })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
@Parameter(name = ApiConstants.INSTANCE_NAME, type = CommandType.STRING, description = "instance name of the user vm", since = "4.4", authorized = {RoleType.Admin})
private String instanceName;

@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Details in key/value pairs.")
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Details in key/value pairs. 'extraconfig' is not allowed to be passed in details.")
protected Map<String, String> details;

@ACL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,8 @@
import javax.inject.Inject;
import javax.naming.ConfigurationException;

import com.cloud.agent.api.PrepareForMigrationAnswer;
import com.cloud.agent.api.to.DpdkTO;
import com.cloud.offering.NetworkOffering;
import com.cloud.offerings.dao.NetworkOfferingDetailsDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd;
import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
Expand Down Expand Up @@ -97,6 +94,7 @@
import com.cloud.agent.api.PingRoutingCommand;
import com.cloud.agent.api.PlugNicAnswer;
import com.cloud.agent.api.PlugNicCommand;
import com.cloud.agent.api.PrepareForMigrationAnswer;
import com.cloud.agent.api.PrepareForMigrationCommand;
import com.cloud.agent.api.RebootAnswer;
import com.cloud.agent.api.RebootCommand;
Expand All @@ -116,6 +114,7 @@
import com.cloud.agent.api.UnregisterVMCommand;
import com.cloud.agent.api.routing.NetworkElementCommand;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.agent.api.to.DpdkTO;
import com.cloud.agent.api.to.GPUDeviceTO;
import com.cloud.agent.api.to.NicTO;
import com.cloud.agent.api.to.VirtualMachineTO;
Expand Down Expand Up @@ -166,7 +165,9 @@
import com.cloud.network.router.VirtualRouter;
import com.cloud.offering.DiskOffering;
import com.cloud.offering.DiskOfferingInfo;
import com.cloud.offering.NetworkOffering;
import com.cloud.offering.ServiceOffering;
import com.cloud.offerings.dao.NetworkOfferingDetailsDao;
import com.cloud.org.Cluster;
import com.cloud.resource.ResourceManager;
import com.cloud.resource.ResourceState;
Expand Down Expand Up @@ -1123,6 +1124,9 @@ public void orchestrateStart(final String vmUuid, final Map<VirtualMachineProfil

vmGuru.finalizeDeployment(cmds, vmProfile, dest, ctx);

// Get VM extraConfig from DB and set to VM TO
addExtraConfig(vmTO);

work = _workDao.findById(work.getId());
if (work == null || work.getStep() != Step.Prepare) {
throw new ConcurrentOperationException("Work steps have been changed: " + work);
Expand Down Expand Up @@ -1287,6 +1291,16 @@ public void orchestrateStart(final String vmUuid, final Map<VirtualMachineProfil
}
}

// Add extra config data to the vmTO as a Map
private void addExtraConfig(VirtualMachineTO vmTO) {
Map<String, String> details = vmTO.getDetails();
for (String key : details.keySet()) {
if (key.startsWith(ApiConstants.EXTRA_CONFIG)) {
vmTO.addExtraConfig(key, details.get(key));
}
}
}

// for managed storage on KVM, need to make sure the path field of the volume in question is populated with the IQN
private void handlePath(final DiskTO[] disks, final HypervisorType hypervisorType) {
if (hypervisorType != HypervisorType.KVM) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2236,7 +2236,11 @@ So if getMinSpeed() returns null we fall back to getSpeed().

vm.addComp(devices);

addExtraConfigComponent(extraConfig, vm);
// Add extra configuration to User VM Domain XML before starting
if (vmTO.getType().equals(VirtualMachine.Type.User) && MapUtils.isNotEmpty(extraConfig)) {
s_logger.info("Appending extra configuration data to guest VM domain XML");
addExtraConfigComponent(extraConfig, vm);
}

return vm;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.cloudstack.hypervisor.xenserver.ExtraConfigurationUtility;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.commons.collections.CollectionUtils;
Expand Down Expand Up @@ -1404,7 +1405,7 @@ public VM createVmFromTemplate(final Connection conn, final VirtualMachineTO vmS
}
}
try {
finalizeVmMetaData(vm, conn, vmSpec);
finalizeVmMetaData(vm, vmr, conn, vmSpec);
} catch (final Exception e) {
throw new CloudRuntimeException("Unable to finalize VM MetaData: " + vmSpec);
}
Expand Down Expand Up @@ -1859,7 +1860,7 @@ protected void fillHostInfo(final Connection conn, final StartupRoutingCommand c
}
}

protected void finalizeVmMetaData(final VM vm, final Connection conn, final VirtualMachineTO vmSpec) throws Exception {
protected void finalizeVmMetaData(final VM vm, final VM.Record vmr, final Connection conn, final VirtualMachineTO vmSpec) throws Exception {

final Map<String, String> details = vmSpec.getDetails();
if (details != null) {
Expand Down Expand Up @@ -1890,6 +1891,13 @@ protected void finalizeVmMetaData(final VM vm, final Connection conn, final Virt
}
}
}

// Add configuration settings VM record for User VM instances before creating VM
Map<String, String> extraConfig = vmSpec.getExtraConfig();
if (vmSpec.getType().equals(VirtualMachine.Type.User) && MapUtils.isNotEmpty(extraConfig)) {
s_logger.info("Appending user extra configuration settings to VM");
ExtraConfigurationUtility.setExtraConfigurationToVm(conn,vmr, vm, extraConfig);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// 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 java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.xmlrpc.XmlRpcException;

import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.exception.CloudRuntimeException;
import com.xensource.xenapi.Connection;
import com.xensource.xenapi.Types;
import com.xensource.xenapi.VM;

public class ExtraConfigurationUtility {
private static final Logger LOG = Logger.getLogger(ExtraConfigurationUtility.class);

public static void setExtraConfigurationToVm(Connection conn, VM.Record vmr, VM vm, Map<String, String> extraConfig) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you factor out (at least the switches) for readability?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Map<String, Object> recordMap = vmr.toMap();
for (String key : extraConfig.keySet()) {
String cfg = extraConfig.get(key);
Map<String, String> configParams = prepareKeyValuePair(cfg);

// paramKey is either param or param:key for map parameters
String paramKey = configParams.keySet().toString().replaceAll("[\\[\\]]", "");
String paramValue = configParams.get(paramKey);

//Map params
if (paramKey.contains(":")) {
applyConfigWithNestedKeyValue(conn, vm, recordMap, paramKey, paramValue);
} else {
applyConfigWithKeyValue(conn, vm, recordMap, paramKey, paramValue);
}
}
}

private static boolean isValidOperation(Map<String, Object> recordMap, String actualParam) {
return recordMap.containsKey(actualParam);
}

/**
* 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) {
int i = paramKey.indexOf(":");
String actualParam = paramKey.substring(0, i);
String keyName = paramKey.substring(i + 1);

if (!isValidOperation(recordMap, actualParam)) {
LOG.error("Unsupported extra configuration has been passed " + actualParam);
throw new InvalidParameterValueException("Unsupported extra configuration option has been passed: " + actualParam);
}

try {
switch (actualParam) {
case "VCPUs_params":
vm.addToVCPUsParams(conn, keyName, paramValue);
break;
case "platform":
vm.addToOtherConfig(conn, keyName, paramValue);
break;
case "HVM_boot_params":
vm.addToHVMBootParams(conn, keyName, paramValue);
break;
case "other_config":
vm.addToOtherConfig(conn, keyName, paramValue);
break;
case "xenstore_data":
vm.addToXenstoreData(conn, keyName, paramValue);
break;
default:
String msg = String.format("Passed configuration %s is not supported", paramKey);
LOG.warn(msg);
}
} catch (XmlRpcException | Types.XenAPIException e) {
LOG.error("Exception caught while setting VM configuration. exception: " + e.getMessage());
throw new CloudRuntimeException("Exception caught while setting VM configuration", e);
}
}

private static void applyConfigWithKeyValue(Connection conn, VM vm, Map<String, Object> recordMap, String paramKey, String paramValue) {
if (!isValidOperation(recordMap, paramKey)) {
LOG.error("Unsupported extra configuration has been passed: " + paramKey);
throw new InvalidParameterValueException("Unsupported extra configuration parameter key has been passed: " + paramKey);
}

try {
switch (paramKey) {
case "HVM_boot_policy":
vm.setHVMBootPolicy(conn, paramValue);
break;
case "HVM_shadow_multiplier":
vm.setHVMShadowMultiplier(conn, Double.valueOf(paramValue));
break;
case "PV_kernel":
vm.setPVKernel(conn, paramValue);
break;
case "PV_ramdisk":
vm.setPVRamdisk(conn, paramValue);
break;
case "PV_args":
vm.setPVArgs(conn, paramValue);
break;
case "PV_legacy_args":
vm.setPVLegacyArgs(conn, paramValue);
break;
case "PV_bootloader":
vm.setPVBootloader(conn, paramValue);
break;
case "PV_bootloader_args":
vm.setPVBootloaderArgs(conn, paramValue);
break;
case "ha_restart_priority":
vm.setHaRestartPriority(conn, paramValue);
break;
case "start_delay":
vm.setStartDelay(conn, Long.valueOf(paramValue));
break;
case "shutdown_delay":
vm.setShutdownDelay(conn, Long.valueOf(paramValue));
break;
case "order":
vm.setOrder(conn, Long.valueOf(paramValue));
break;
case "VCPUs_max":
vm.setVCPUsMax(conn, Long.valueOf(paramValue));
break;
case "VCPUs_at_startup":
vm.setVCPUsAtStartup(conn, Long.valueOf(paramValue));
break;
case "is-a-template":
vm.setIsATemplate(conn, Boolean.valueOf(paramValue));
break;
case "memory_static_max":
vm.setMemoryStaticMax(conn, Long.valueOf(paramValue));
break;
case "memory_static_min":
vm.setMemoryStaticMin(conn, Long.valueOf(paramValue));
break;
case "memory_dynamic_max":
vm.setMemoryDynamicMax(conn, Long.valueOf(paramValue));
break;
case "memory_dynamic_min":
vm.setMemoryDynamicMin(conn, Long.valueOf(paramValue));
break;
default:
String anotherMessage = String.format("Passed configuration %s is not supported", paramKey);
LOG.error(anotherMessage);
}
} catch (XmlRpcException | Types.XenAPIException e) {
LOG.error("Exception caught while setting VM configuration, exception: " + e.getMessage());
throw new CloudRuntimeException("Exception caught while setting VM configuration: ", e);
}
}

private static Map<String, String> prepareKeyValuePair(String cfg) {
Map<String, String> configKeyPair = new HashMap<>();
int indexOfEqualSign = cfg.indexOf("=");
String key = cfg.substring(0, indexOfEqualSign).replace("-", "_");
String value = cfg.substring(indexOfEqualSign + 1);
configKeyPair.put(key, value);
return configKeyPair;
}
}
Loading