Greg Padgett has uploaded a new change for review. Change subject: WIP core, restapi, webadmin: cloud-init [3/4] - backend ......................................................................
WIP core, restapi, webadmin: cloud-init [3/4] - backend Work in progress: support using cloud-init to perform initial setup of virtual machines run through the oVirt management interface. Further details available at: http://www.ovirt.org/Features/Cloud-Init_Integration This patch adds backend support to generate cloud-init configuration payload from a CloudInitParameters object that can accompany a Run VM request. To do: - hash root password (using crypt(3) method) - payload for user files - further testing (including unit tests) Change-Id: Ida8ffa5b92ff79cdac5e401c0a815ffcf517590d Signed-off-by: Greg Padgett <gpadg...@redhat.com> --- M backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java M backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmOnceCommand.java A backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/CloudInitParameters.java M backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmOnceParams.java M backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmParams.java A backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/InitializationType.java M backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java M backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/CreateVmVDSCommandParameters.java M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmResource.java M backend/manager/modules/vdsbroker/pom.xml M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/CreateVmVDSCommand.java A backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/CloudInitHandler.java A backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/CreateVmFromCloudInitVDSCommand.java M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilder.java M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilderBase.java M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmOldInfoBuilder.java M frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/core/Common.gwt.xml M frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/userportal/VmItemBehavior.java M frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/RunOnceModel.java M frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/SpiceConsoleModel.java M frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/VmListModel.java M pom.xml 22 files changed, 543 insertions(+), 29 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/50/14350/1 diff --git a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java index a7687f5..dbebce8 100644 --- a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java +++ b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java @@ -530,9 +530,8 @@ // the VM can run with display type which is different from its default display type (getParameters().getUseVnc() ? DisplayType.vnc : DisplayType.qxl)); - if (getParameters().getReinitialize()) { - getVm().setUseSysPrep(true); - } + getVm().setInitializationType(getParameters().getInitializationType()); + // if we attach floppy we don't need the sysprep if (!StringUtils.isEmpty(getVm().getFloppyPath())) { getVmStaticDAO().update(getVm().getStaticData()); diff --git a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmOnceCommand.java b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmOnceCommand.java index 2a0a941..db2f07a 100644 --- a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmOnceCommand.java +++ b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmOnceCommand.java @@ -60,12 +60,16 @@ protected CreateVmVDSCommandParameters initVdsCreateVmParams() { getVm().setRunOnce(true); CreateVmVDSCommandParameters createVmParams = super.initVdsCreateVmParams(); - SysPrepParams sysPrepParams = new SysPrepParams(); RunVmOnceParams runOnceParams = getParameters(); + + SysPrepParams sysPrepParams = new SysPrepParams(); sysPrepParams.setSysPrepDomainName(runOnceParams.getSysPrepDomainName()); sysPrepParams.setSysPrepUserName(runOnceParams.getSysPrepUserName()); sysPrepParams.setSysPrepPassword(runOnceParams.getSysPrepPassword()); createVmParams.setSysPrepParams(sysPrepParams); + + createVmParams.setCloudInitParameters(runOnceParams.getCloudInitParameters()); + return createVmParams; } diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/CloudInitParameters.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/CloudInitParameters.java new file mode 100644 index 0000000..f3b759c --- /dev/null +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/CloudInitParameters.java @@ -0,0 +1,82 @@ +package org.ovirt.engine.core.common.action; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface; + +public class CloudInitParameters implements Serializable { + private static final long serialVersionUID = -139795494679460456L; + + private String hostname; + private String authorizedKeys; + + private List<String> dnsServers; + private String dnsSearch; + private Map<String, VdsNetworkInterface> interfaces; + private List<String> startOnBoot; + + private TimeZone timeZone; + private String rootPassword; + + public CloudInitParameters() { + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + public String getHostname() { + return hostname; + } + + public void setAuthorizedKeys(String authorizedKeys) { + this.authorizedKeys = authorizedKeys; + } + public String getAuthorizedKeys() { + return authorizedKeys; + } + + public void setDnsServers(List<String> dnsServers) { + this.dnsServers = dnsServers; + } + public List<String> getDnsServers() { + return dnsServers; + } + + public void setDnsSearch(String dnsSearch) { + this.dnsSearch = dnsSearch; + } + public String getDnsSearch() { + return dnsSearch; + } + + public void setInterfaces(Map <String, VdsNetworkInterface> interfaces) { + this.interfaces = interfaces; + } + public Map<String, VdsNetworkInterface> getInterfaces() { + return interfaces; + } + + public void setStartOnBoot(List<String> startOnBoot) { + this.startOnBoot = startOnBoot; + } + public List<String> getStartOnBoot() { + return startOnBoot; + } + + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + public TimeZone getTimeZone() { + return timeZone; + } + + public void setRootPassword(String password) { + this.rootPassword = password; + } + public String getRootPassword() { + return rootPassword; + } +} diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmOnceParams.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmOnceParams.java index cb56f6e..ac59065 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmOnceParams.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmOnceParams.java @@ -12,6 +12,8 @@ private String sysPrepPassword; + private CloudInitParameters cloudInitParameters; + public RunVmOnceParams() { } @@ -47,4 +49,12 @@ return sysPrepPassword; } + public void setCloudInitParameters(CloudInitParameters cloudInitParameters) { + this.cloudInitParameters = cloudInitParameters; + } + + public CloudInitParameters getCloudInitParameters() { + return cloudInitParameters; + } + } diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmParams.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmParams.java index cdda4f4..2db5bd0 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmParams.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/action/RunVmParams.java @@ -1,6 +1,7 @@ package org.ovirt.engine.core.common.action; import org.ovirt.engine.core.common.businessentities.BootSequence; +import org.ovirt.engine.core.common.businessentities.InitializationType; import org.ovirt.engine.core.common.businessentities.VmPayload; import org.ovirt.engine.core.common.users.VdcUser; import org.ovirt.engine.core.compat.Guid; @@ -21,7 +22,7 @@ private VdcUser privateRequestingUser; private boolean _internal; private Guid _destinationVdsId; - private boolean privateReinitialize; + private InitializationType privateInitializationType; private Boolean privateRunAsStateless; private String initrd_url; private String kernel_url; @@ -162,12 +163,12 @@ privateRequestingUser = value; } - public boolean getReinitialize() { - return privateReinitialize; + public InitializationType getInitializationType() { + return privateInitializationType; } - public void setReinitialize(boolean value) { - privateReinitialize = value; + public void setInitializationType(InitializationType value) { + privateInitializationType = value; } public Boolean getRunAsStateless() { diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/InitializationType.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/InitializationType.java new file mode 100644 index 0000000..a3ce9e2 --- /dev/null +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/InitializationType.java @@ -0,0 +1,15 @@ +package org.ovirt.engine.core.common.businessentities; + +public enum InitializationType { + None, + Sysprep, + CloudInit; + + public int getValue() { + return this.ordinal(); + } + + public static InitializationType forValue(int value) { + return values()[value]; + } +} diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java index 877867c..d8a225f 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/businessentities/VM.java @@ -967,14 +967,14 @@ } } - private boolean useSysPrep; + private InitializationType initializationType; - public boolean useSysPrep() { - return useSysPrep; + public InitializationType getInitializationType() { + return initializationType; } - public void setUseSysPrep(boolean value) { - useSysPrep = value; + public void setInitializationType(InitializationType value) { + initializationType = value; } public boolean isFirstRun() { diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/CreateVmVDSCommandParameters.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/CreateVmVDSCommandParameters.java index 2247b86..9760924 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/CreateVmVDSCommandParameters.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/CreateVmVDSCommandParameters.java @@ -1,5 +1,6 @@ package org.ovirt.engine.core.common.vdscommands; +import org.ovirt.engine.core.common.action.CloudInitParameters; import org.ovirt.engine.core.common.action.SysPrepParams; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.compat.Guid; @@ -13,6 +14,8 @@ private VM _vm; private SysPrepParams sysPrepParams; + + private CloudInitParameters cloudInitParameters; public VM getVm() { return _vm; @@ -33,4 +36,12 @@ public void setSysPrepParams(SysPrepParams sysPrepParams) { this.sysPrepParams = sysPrepParams; } + + public CloudInitParameters getCloudInitParameters() { + return cloudInitParameters; + } + + public void setCloudInitParameters(CloudInitParameters CloudInitParameters) { + this.cloudInitParameters = CloudInitParameters; + } } diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmResource.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmResource.java index 056acca..3538f43 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmResource.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmResource.java @@ -51,6 +51,7 @@ import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VmManagementParametersBase; import org.ovirt.engine.core.common.action.VmOperationParameterBase; +import org.ovirt.engine.core.common.businessentities.InitializationType; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VDSGroup; import org.ovirt.engine.core.common.businessentities.VmStatic; @@ -226,17 +227,21 @@ if (action.isSetPause() && action.isPause()) { params.setRunAndPause(true); } - return doAction(VdcActionType.RunVmOnce, setReinitializeSysPrep(params), action); + return doAction(VdcActionType.RunVmOnce, setInitializationType(params), action); } - private VdcActionParametersBase setReinitializeSysPrep(RunVmOnceParams params) { + private VdcActionParametersBase setInitializationType(RunVmOnceParams params) { //REVISE when BE supports default val. for RunVmOnceParams.privateReinitialize org.ovirt.engine.core.common.businessentities.VM vm = getEntity(org.ovirt.engine.core.common.businessentities.VM.class, VdcQueryType.GetVmByVmId, new GetVmByVmIdParameters(guid), "VM"); - if(vm.getVmOs().isWindows() && vm.isFirstRun()) { - params.setReinitialize(true); + if (vm.isFirstRun() && vm.getVmOs().isWindows()) { + params.setInitializationType(InitializationType.Sysprep); + } else if (vm.isFirstRun() && vm.getVmOs().isLinux()) { + params.setInitializationType(InitializationType.CloudInit); + } else { + params.setInitializationType(InitializationType.None); } return params; } diff --git a/backend/manager/modules/vdsbroker/pom.xml b/backend/manager/modules/vdsbroker/pom.xml index 9679ac5..58faf0c 100644 --- a/backend/manager/modules/vdsbroker/pom.xml +++ b/backend/manager/modules/vdsbroker/pom.xml @@ -75,6 +75,12 @@ <artifactId>jta</artifactId> </dependency> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + <version>${snakeyaml.version}</version> + </dependency> + <!-- <dependency> <groupId>org.jboss.jbossas</groupId> diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/CreateVmVDSCommand.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/CreateVmVDSCommand.java index 26db456..91c17f8 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/CreateVmVDSCommand.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/CreateVmVDSCommand.java @@ -3,6 +3,7 @@ import java.util.List; import org.apache.commons.lang.StringUtils; +import org.ovirt.engine.core.common.businessentities.InitializationType; import org.ovirt.engine.core.common.businessentities.Snapshot; import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotStatus; import org.ovirt.engine.core.common.businessentities.VM; @@ -17,6 +18,7 @@ import org.ovirt.engine.core.utils.transaction.TransactionMethod; import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.ovirt.engine.core.vdsbroker.vdsbroker.CreateVDSCommand; +import org.ovirt.engine.core.vdsbroker.vdsbroker.CreateVmFromCloudInitVDSCommand; import org.ovirt.engine.core.vdsbroker.vdsbroker.CreateVmFromSysPrepVDSCommand; import org.ovirt.engine.core.vdsbroker.vdsbroker.CreateVmFromSysPrepVDSCommandParameters; import org.ovirt.engine.core.vdsbroker.vdsbroker.VDSGenericException; @@ -48,6 +50,15 @@ createVmFromSysPrepParam.setSysPrepParams(getParameters().getSysPrepParams()); command = new CreateVmFromSysPrepVDSCommand<CreateVmFromSysPrepVDSCommandParameters>(createVmFromSysPrepParam); + command.execute(); + if (command.getVDSReturnValue().getSucceeded()) { + vm.setInitialized(true); + saveSetInitializedToDb(vm.getId()); + } else { + HandleCommandResult(command); + } + } else if (isCloudInitUsed(vm)) { + command = new CreateVmFromCloudInitVDSCommand<CreateVmVDSCommandParameters>(getParameters()); command.execute(); if (command.getVDSReturnValue().getSucceeded()) { vm.setInitialized(true); @@ -98,10 +109,17 @@ * @return */ private boolean isSysprepUsed(final VM vm) { - return vm.useSysPrep() && vm.getVmOs().isWindows() + return vm.getInitializationType() == InitializationType.Sysprep + && vm.getVmOs().isWindows() && StringUtils.isEmpty(vm.getFloppyPath()); } + private boolean isCloudInitUsed(final VM vm) { + return vm.getInitializationType() == InitializationType.CloudInit + && vm.getVmOs().isLinux() + && StringUtils.isEmpty(vm.getCdPath()); + } + private void HandleVdsInformation() { DbFacade.getInstance() .getVdsDynamicDao() diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/CloudInitHandler.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/CloudInitHandler.java new file mode 100644 index 0000000..24879ab --- /dev/null +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/CloudInitHandler.java @@ -0,0 +1,262 @@ +package org.ovirt.engine.core.vdsbroker.vdsbroker; + + +import java.io.IOException; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.lang.StringUtils; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.ovirt.engine.core.common.action.CloudInitParameters; +import org.ovirt.engine.core.common.businessentities.network.NetworkBootProtocol; +import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface; +import org.ovirt.engine.core.utils.log.Log; +import org.ovirt.engine.core.utils.log.LogFactory; +import org.springframework.util.CollectionUtils; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +public class CloudInitHandler { + private static Log log = LogFactory.getLog(CloudInitHandler.class); + + private final CloudInitParameters params; + private final Map<String, Object> metaData; + private final Map<String, Object> userData; + private final Map<String, byte[]> files; + private int nextFileIndex; + private String interfaces; + + private enum CloudInitFileMode { + FILE, + NETWORK; + } + + public CloudInitHandler(CloudInitParameters cloudInitParams) { + this.params = cloudInitParams; + metaData = new HashMap<String, Object>(); + userData = new HashMap<String, Object>(); + files = new HashMap<String, byte[]>(); + nextFileIndex = 0; + } + + + public Map<String, byte[]> getFileData() + throws UnsupportedEncodingException, IOException, JsonGenerationException, JsonMappingException { + // Process each supported parameter in turn: validate (TODO), transform, store in user-data/meta-data + + try { + storeHostname(); + storeAuthorizedKeys(); + storeNetwork(); + storeTimeZone(); + storeRootPassword(); + storeUserFiles(); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("Malformed input", ex); + } + + // Add other required/supplemental data + storeExecutionParameters(); + + // Store completed meta-data and user-data + String metaDataStr = mapToJson(metaData); + String userDataStr = mapToYaml(userData); + log.debugFormat("cloud-init meta-data:\n" + metaDataStr); + log.debugFormat("cloud-init user-data:\n" + userDataStr); + files.put("openstack/latest/meta_data.json", metaDataStr.getBytes("UTF-8")); + files.put("openstack/latest/user_data", userDataStr.getBytes("UTF-8")); + + return files; + } + + + private void storeHostname() { + if (!StringUtils.isEmpty(params.getHostname())) { + metaData.put("hostname", params.getHostname()); + metaData.put("name", params.getHostname()); // TODO needed? + } + } + + private void storeAuthorizedKeys() { + if (!StringUtils.isEmpty(params.getAuthorizedKeys())) { + metaData.put("public_keys", normalizeAuthorizedKeys(params.getAuthorizedKeys())); + } + } + + private List<String> normalizeAuthorizedKeys(String authorizedKeys) { + List<String> keys = new ArrayList<String>(); + for (String key : params.getAuthorizedKeys().split("(\\r?\\n|\\r)+")) { + if (!StringUtils.isEmpty(key)) { + keys.add(key); + } + } + return keys; + } + + private void storeNetwork() throws UnsupportedEncodingException { + StringBuilder output = new StringBuilder(); + + if (!CollectionUtils.isEmpty(params.getDnsServers())) { + output.append("dns-nameservers"); + for (String server : params.getDnsServers()) { + output.append(" " + server); + } + output.append("\n"); + } + + if (!StringUtils.isEmpty(params.getDnsSearch())) { + output.append("dns-search " + params.getDnsSearch() + "\n"); + } + + if (!CollectionUtils.isEmpty(params.getInterfaces())) { + Map<String, VdsNetworkInterface> interfaces = params.getInterfaces(); + List<String> names = new ArrayList<String>(interfaces.keySet()); + Collections.sort(names); + + for (String name : names) { + VdsNetworkInterface iface = interfaces.get(name); + output.append("iface " + name + " inet " + + networkBootProtocolToString(iface.getBootProtocol()) + "\n"); + output.append(" address " + iface.getAddress() + "\n"); + output.append(" netmask " + iface.getSubnet() + "\n"); + if (!StringUtils.isEmpty(iface.getGateway())) { + output.append(" gateway " + iface.getGateway() + "\n"); + } + } + } + + if (!CollectionUtils.isEmpty(params.getStartOnBoot())) { + output.append("auto"); + for (String name : params.getStartOnBoot()) { + output.append(" " + name); + } + output.append("\n"); + } + + interfaces = output.toString(); + + if (!interfaces.isEmpty()) { + // Workaround for cloud-init 0.6.3, which requires the "network-interfaces" + // meta-data entry instead of the "network_config" file reference + metaData.put("network-interfaces", interfaces); + + // Cloud-init will translate this as needed for ifcfg-based systems + storeNextFile(CloudInitFileMode.NETWORK, "/etc/network/interfaces", interfaces.getBytes("US-ASCII")); + } + } + + private String networkBootProtocolToString(NetworkBootProtocol proto) { + switch (proto) { + case DHCP: + return "dhcp"; + case STATIC_IP: + return "static"; + case NONE: + default: + return "none"; + } + } + + private void storeTimeZone() { + if (params.getTimeZone() != null) { + userData.put("timezone", params.getTimeZone().getID()); + } + } + + private void storeRootPassword() { + if (!StringUtils.isEmpty(params.getRootPassword())) { + // TODO crypt + metaData.put("password", params.getRootPassword()); + } + } + + private void storeUserFiles() { + // TODO + } + + + private void storeExecutionParameters() { + // Store defaults in meta-data and user-data that apply regardless + // of parameters passed in from the user. + + // New instance id required for cloud-init to process data on startup + metaData.put("uuid", UUID.randomUUID().toString()); + + Map<String, String> meta = new HashMap<String, String>(); + // Local allows us to set up networking + meta.put("dsmode", "local"); + meta.put("essential", "false"); + meta.put("role", "server"); + metaData.put("meta", meta); + + metaData.put("launch_index", "0"); + metaData.put("availability_zone", "nova"); + + // Don't create ec2-user + userData.put("user", "root"); + + // Create new system ssh keys + userData.put("ssh_deletekeys", "True"); + + // Redirect log output from cloud-init execution from terminal + Map<String, String> output = new HashMap<String, String>(); + output.put("all", ">> /var/log/cloud-init-output.log"); + userData.put("output", output); + + // Disable metadata-server-based datasources to prevent long boot times + List<String> runcmd = new ArrayList<String>(); + runcmd.add("sed -i '/^datasource_list: /d' /etc/cloud/cloud.cfg; echo 'datasource_list: [\"NoCloud\", \"ConfigDrive\"]' >> /etc/cloud/cloud.cfg"); + userData.put("runcmd", runcmd); + } + + + @SuppressWarnings("unchecked") + private void storeNextFile(CloudInitFileMode fileMode, String destinationPath, byte[] data) { + String contentPath = String.format("/content/%04d", nextFileIndex++); + Map<String, String> mdEntry = new HashMap<String, String>(); + + mdEntry.put("content_path", contentPath); + mdEntry.put("path", destinationPath); + + if (fileMode == CloudInitFileMode.FILE) { + if (!metaData.containsKey("files")) { + metaData.put("files", new ArrayList<Map<String, String>>()); + } + ((ArrayList<Map<String, String>>) metaData.get("files")).add(mdEntry); + } + else { + metaData.put("network_config", mdEntry); + } + + files.put("openstack" + contentPath, data); + } + + + private String mapToJson(Map<String, Object> input) + throws IOException, JsonGenerationException, JsonMappingException { + ObjectMapper mapper = new ObjectMapper(); + JsonFactory factory = new JsonFactory(); + StringWriter writer = new StringWriter(); + JsonGenerator generator = factory.createJsonGenerator(writer); + generator.useDefaultPrettyPrinter(); + mapper.writeValue(generator, input); + return writer.toString(); + } + + private String mapToYaml(Map<String, Object> input) { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Yaml yaml = new Yaml(options); + return yaml.dump(input); + } +} diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/CreateVmFromCloudInitVDSCommand.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/CreateVmFromCloudInitVDSCommand.java new file mode 100644 index 0000000..b178317 --- /dev/null +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/CreateVmFromCloudInitVDSCommand.java @@ -0,0 +1,24 @@ +package org.ovirt.engine.core.vdsbroker.vdsbroker; + +import java.util.Map; + +import org.ovirt.engine.core.common.vdscommands.CreateVmVDSCommandParameters; + +public class CreateVmFromCloudInitVDSCommand<P extends CreateVmVDSCommandParameters> + extends CreateVDSCommand<P> { + public CreateVmFromCloudInitVDSCommand(P parameters) throws Exception { + super(parameters); + + CloudInitHandler cloudInitHandler = new CloudInitHandler(parameters.getCloudInitParameters()); + Map<String, byte[]> cloudInitContent; + try { + cloudInitContent = cloudInitHandler.getFileData(); + } catch (Exception e) { + throw new Exception("Failed to build cloud-init data:", e); + } + + if (cloudInitContent != null && !cloudInitContent.isEmpty()) { + builder.buildCloudInitVmPayload(cloudInitContent); + } + } +} diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilder.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilder.java index 79e4a19..c5a3b58 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilder.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilder.java @@ -40,6 +40,7 @@ private static final String DEVICES = "devices"; private static final String USB_BUS = "usb"; private final static String FIRST_MASTER_MODEL = "ich9-ehci1"; + private static final String CLOUD_INIT_VOL_ID = "cidata"; private final List<Map<String, Object>> devices = new ArrayList<Map<String, Object>>(); private List<VmDevice> managedDevices = null; @@ -152,6 +153,10 @@ for (VmDevice vmDevice : vmDevices) { // skip unamanged devices (handled separtely) if (!vmDevice.getIsManaged()) { + continue; + } + // ignore additional cd devices if a payload is present + if (!VmPayload.isPayload(vmDevice.getSpecParams()) && vmDevices.size() > 1) { continue; } struct = new HashMap<String, Object>(); @@ -438,6 +443,31 @@ addDevice(struct, vmDevice, vm.getFloppyPath()); } + @Override + protected void buildCloudInitVmPayload(Map<String, byte[]> cloudInitContent) { + VmPayload vmPayload = new VmPayload(); + vmPayload.setType(VmDeviceType.CDROM); + vmPayload.setVolumeId(CLOUD_INIT_VOL_ID); + for (Map.Entry<String, byte[]> entry : cloudInitContent.entrySet()) { + vmPayload.getFiles().put(entry.getKey(), Base64.encodeBase64String(entry.getValue())); + } + + VmDevice vmDevice = + new VmDevice(new VmDeviceId(Guid.NewGuid(), vm.getId()), + VmDeviceType.DISK.getName(), + VmDeviceType.CDROM.getName(), + "", + 0, + vmPayload.getSpecParams(), + true, + true, + true, + ""); + Map<String, Object> struct = new HashMap<String, Object>(); + addCdDetails(vmDevice, struct); + addDevice(struct, vmDevice, vm.getCdPath() == null ? "" : vm.getCdPath()); + } + private static void addBootOrder(VmDevice vmDevice, Map<String, Object> struct) { String s = String.valueOf(vmDevice.getBootOrder()); if (!org.apache.commons.lang.StringUtils.isEmpty(s) && !s.equals("0")) { diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilderBase.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilderBase.java index 6b7e672..7f94b4d 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilderBase.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmInfoBuilderBase.java @@ -271,6 +271,8 @@ protected abstract void buildSysprepVmPayload(String strSysPrepContent); + protected abstract void buildCloudInitVmPayload(Map<String, byte[]> cloudInitContent); + protected abstract void buildVmUsbDevices(); protected abstract void buildVmMemoryBalloon(); diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmOldInfoBuilder.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmOldInfoBuilder.java index ada087c..de77af5 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmOldInfoBuilder.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VmOldInfoBuilder.java @@ -117,6 +117,11 @@ createInfo.put(VdsProperties.sysprepInf, binarySysPrep); } + @Override + protected void buildCloudInitVmPayload(Map<String, byte[]> cloudInitContent) { + // Not supported in old code + } + /** * Find VM device for this disk from the list of VM devices. * diff --git a/frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/core/Common.gwt.xml b/frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/core/Common.gwt.xml index 475ac14..1fea382 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/core/Common.gwt.xml +++ b/frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/core/Common.gwt.xml @@ -35,6 +35,7 @@ <include name="common/businessentities/IImage.java" /> <include name="common/businessentities/ImageStatus.java" /> <include name="common/businessentities/Nameable.java" /> + <include name="common/businessentities/InitializationType.java" /> <!-- Network business entities --> <include name="common/businessentities/network/VdsNetworkInterface.java" /> diff --git a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/userportal/VmItemBehavior.java b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/userportal/VmItemBehavior.java index 5b97c52..f858bcd 100644 --- a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/userportal/VmItemBehavior.java +++ b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/userportal/VmItemBehavior.java @@ -10,6 +10,7 @@ import org.ovirt.engine.core.common.action.StopVmParameters; import org.ovirt.engine.core.common.action.StopVmTypeEnum; import org.ovirt.engine.core.common.action.VdcActionType; +import org.ovirt.engine.core.common.businessentities.InitializationType; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VmPool; import org.ovirt.engine.core.common.businessentities.VmPoolType; @@ -135,7 +136,7 @@ // use sysprep iff the vm is not initialized and vm has Win OS boolean reinitialize = !entity.isInitialized() && AsyncDataProvider.IsWindowsOsType(entity.getVmOs()); RunVmParams tempVar = new RunVmParams(entity.getId()); - tempVar.setReinitialize(reinitialize); + tempVar.setInitializationType(InitializationType.Sysprep); Frontend.RunAction(VdcActionType.RunVm, tempVar); } diff --git a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/RunOnceModel.java b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/RunOnceModel.java index 01d10a5..a7e0ef5 100644 --- a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/RunOnceModel.java +++ b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/RunOnceModel.java @@ -7,6 +7,7 @@ import org.ovirt.engine.core.common.action.RunVmOnceParams; import org.ovirt.engine.core.common.businessentities.Disk; import org.ovirt.engine.core.common.businessentities.DisplayType; +import org.ovirt.engine.core.common.businessentities.InitializationType; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface; import org.ovirt.engine.core.common.queries.GetAllDisksByVmIdParameters; @@ -419,17 +420,29 @@ // pseudo floppy disk image. In order not to change the back-end // interface, the Reinitialize variable was changed to a read-only // property and its value is based on the selected floppy image. - public boolean getReinitialize() + // A similar comparison is done for cloud-init iso images, so the + // variable was changed from a boolean to an Enum. + public InitializationType getInitializationType() { - return ((Boolean) getAttachFloppy().getEntity() && getFloppyImage().getSelectedItem() != null && getFloppyImage().getSelectedItem() - .equals("[sysprep]")); //$NON-NLS-1$ + if ((Boolean) getAttachFloppy().getEntity() + && "[sysprep]".equals(getFloppyImage().getSelectedItem())) { //$NON-NLS-1$ + return InitializationType.Sysprep; + } + else if ((Boolean) getAttachIso().getEntity() + && "[cloud-init]".equals(getIsoImage().getSelectedItem())) { //$NON-NLS-1$ + return InitializationType.CloudInit; + } + else { + return InitializationType.None; + } } public String getFloppyImagePath() { if ((Boolean) getAttachFloppy().getEntity()) { - return getReinitialize() ? "" : (String) getFloppyImage().getSelectedItem(); //$NON-NLS-1$ + return getInitializationType() == InitializationType.Sysprep + ? "" : (String) getFloppyImage().getSelectedItem(); //$NON-NLS-1$ } else { @@ -571,7 +584,7 @@ params.setRunAndPause((Boolean) getRunAndPause().getEntity()); params.setAcpiEnable(true); params.setRunAsStateless((Boolean) getRunAsStateless().getEntity()); - params.setReinitialize(getReinitialize()); + params.setInitializationType(getInitializationType()); params.setCustomProperties((String) getCustomProperties().getEntity()); // kernel params @@ -597,6 +610,9 @@ { params.setSysPrepPassword((String) getSysPrepPassword().getEntity()); } + + CloudInitParameters ciParams = new CloudInitParameters(); + params.setCloudInitParameters(ciParams); EntityModel displayProtocolSelectedItem = (EntityModel) getDisplayProtocol().getSelectedItem(); params.setUseVnc((DisplayType) displayProtocolSelectedItem.getEntity() == DisplayType.vnc); @@ -714,7 +730,13 @@ new INewAsyncCallback() { @Override public void onSuccess(Object model, Object returnValue) { + VM selectedVM = (VM) vm; List<String> images = (List<String>) returnValue; + + if (AsyncDataProvider.IsLinuxOsType(selectedVM.getVmOs())) { + // Add a pseudo cd image used for Linux Cloud-Init + images.add("[cloud-init]"); //$NON-NLS-1$ + } getIsoImage().setItems(images); if (getIsoImage().getIsChangable() @@ -849,7 +871,7 @@ boolean isFloppyAttached = (Boolean) getAttachFloppy().getEntity(); boolean isVmFirstRun = (Boolean) getIsVmFirstRun().getEntity(); - getIsSysprepEnabled().setEntity(getIsWindowsOS() && getReinitialize()); + getIsSysprepEnabled().setEntity(getIsWindowsOS() && getInitializationType() == InitializationType.Sysprep); } public boolean Validate() { diff --git a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/SpiceConsoleModel.java b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/SpiceConsoleModel.java index f8b1713..2f6f2ec 100644 --- a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/SpiceConsoleModel.java +++ b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/SpiceConsoleModel.java @@ -16,6 +16,7 @@ import org.ovirt.engine.core.common.action.VmOperationParameterBase; import org.ovirt.engine.core.common.businessentities.DisplayType; import org.ovirt.engine.core.common.businessentities.ImageFileType; +import org.ovirt.engine.core.common.businessentities.InitializationType; import org.ovirt.engine.core.common.businessentities.RepoFileMetaData; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.UsbPolicy; @@ -205,7 +206,7 @@ !getEntity().isInitialized() && AsyncDataProvider.IsWindowsOsType(getEntity().getVmOs()); RunVmParams tempVar = new RunVmParams(getEntity().getId()); tempVar.setRunAsStateless(getEntity().isStateless()); - tempVar.setReinitialize(reinitialize); + tempVar.setInitializationType(InitializationType.CloudInit); Frontend.RunMultipleAction(VdcActionType.RunVm, new ArrayList<VdcActionParametersBase>(Arrays.asList(new VdcActionParametersBase[] { tempVar }))); diff --git a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/VmListModel.java b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/VmListModel.java index ce8df1b..aee1ce3 100644 --- a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/VmListModel.java +++ b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/VmListModel.java @@ -14,6 +14,7 @@ import org.ovirt.engine.core.common.action.AttachEntityToTagParameters; import org.ovirt.engine.core.common.action.ChangeDiskCommandParameters; import org.ovirt.engine.core.common.action.ChangeVMClusterParameters; +import org.ovirt.engine.core.common.action.CloudInitParameters; import org.ovirt.engine.core.common.action.HibernateVmParameters; import org.ovirt.engine.core.common.action.MigrateVmParameters; import org.ovirt.engine.core.common.action.MigrateVmToServerParameters; @@ -32,6 +33,7 @@ import org.ovirt.engine.core.common.businessentities.Disk.DiskStorageType; import org.ovirt.engine.core.common.businessentities.DiskImage; import org.ovirt.engine.core.common.businessentities.DisplayType; +import org.ovirt.engine.core.common.businessentities.InitializationType; import org.ovirt.engine.core.common.businessentities.MigrationSupport; import org.ovirt.engine.core.common.businessentities.Quota; import org.ovirt.engine.core.common.businessentities.StorageDomain; @@ -1770,9 +1772,16 @@ { VM a = (VM) item; // use sysprep iff the vm is not initialized and vm has Win OS - boolean reinitialize = !a.isInitialized() && AsyncDataProvider.IsWindowsOsType(a.getVmOs()); RunVmParams tempVar = new RunVmParams(a.getId()); - tempVar.setReinitialize(reinitialize); + if (!a.isInitialized() && AsyncDataProvider.IsWindowsOsType(a.getVmOs())) { + tempVar.setInitializationType(InitializationType.Sysprep); + } + else if (!a.isInitialized() && AsyncDataProvider.IsLinuxOsType(a.getVmOs())) { + tempVar.setInitializationType(InitializationType.CloudInit); + } + else { + tempVar.setInitializationType(InitializationType.None); + } list.add(tempVar); } diff --git a/pom.xml b/pom.xml index da9ca9e..46d0372 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,7 @@ <jbosssx-bare.version>2.0.4</jbosssx-bare.version> <log4j.version>1.2.16</log4j.version> <infinispan.version>5.2.5.Final</infinispan.version> + <snakeyaml.version>1.8</snakeyaml.version> </properties> <dependencyManagement> <dependencies> @@ -282,6 +283,11 @@ <artifactId>infinispan-core</artifactId> <version>${infinispan.version}</version> </dependency> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + <version>${snakeyaml.version}</version> + </dependency> </dependencies> </dependencyManagement> <dependencies> -- To view, visit http://gerrit.ovirt.org/14350 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ida8ffa5b92ff79cdac5e401c0a815ffcf517590d Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Greg Padgett <gpadg...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches