Greg Padgett has uploaded a new change for review. Change subject: restapi: cloud-init [6/6] - rest api for start vm ......................................................................
restapi: cloud-init [6/6] - rest api for start vm Support using cloud-init to perform initial setup of virtual machines. Further details available at: http://www.ovirt.org/Features/Cloud-Init_Integration This patch adds support for specifying Cloud-Init configuration when starting a VM using the REST API. Change-Id: I6ad0bfeca23cf8d4b2887010081d63c258032611 Bug-Url: https://bugzilla.redhat.com/?????? Signed-off-by: Greg Padgett <gpadg...@redhat.com> --- M backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd M backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata.yaml M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendCapabilitiesResource.java M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmResource.java M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/VmValidator.java M backend/manager/modules/restapi/types/src/main/java/org/ovirt/engine/api/restapi/types/VmMapper.java 6 files changed, 274 insertions(+), 1 deletion(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/37/15537/1 diff --git a/backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd b/backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd index 5d18fa0..20061ba 100644 --- a/backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd +++ b/backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd @@ -583,6 +583,7 @@ <xs:element ref="nfs_versions" minOccurs="0"/> <xs:element ref="pm_proxy_types" minOccurs="0"/> <xs:element ref="cpu_modes" minOccurs="0"/> + <xs:element ref="attachment_types" minOccurs="0"/> <!-- Gluster related --> <xs:element ref="gluster_volume_types" minOccurs="0"/> @@ -894,6 +895,20 @@ <xs:annotation> <xs:appinfo> <jaxb:property name="CpuModes"/> + </xs:appinfo> + </xs:annotation> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:element name="attachment_types" type="AttachmentTypes"/> + + <xs:complexType name="AttachmentTypes"> + <xs:sequence> + <xs:element name="attachment_type" type="xs:string" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:appinfo> + <jaxb:property name="AttachmentTypes"/> </xs:appinfo> </xs:annotation> </xs:element> @@ -2073,6 +2088,112 @@ </xs:sequence> </xs:complexType> + <xs:complexType name="CloudInit"> + <xs:sequence> + <xs:element name="hostname" type="xs:string" minOccurs="0"/> + <xs:element name="network" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="interfaces" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="interface" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:sequence> + <xs:element name="boot_protocol" type="xs:string"/> + <xs:element name="address" type="xs:string" minOccurs="0"/> + <xs:element name="netmask" type="xs:string" minOccurs="0"/> + <xs:element name="gateway" type="xs:string" minOccurs="0"/> + <xs:element name="onboot" type="xs:boolean"/> + </xs:sequence> + <xs:attribute name="name" type="xs:string" use="required"/> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="dns_servers" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="dns_server" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="dns_search_domains" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="dns_search_domain" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> <!-- </network> --> + <xs:element name="authorized_keys" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="authorized_key" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="user" type="xs:string" use="required"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="regenerate_ssh_keys" type="xs:boolean" minOccurs="0"/> + <xs:element name="timezone" type="xs:string" minOccurs="0"/> + <xs:element name="passwords" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="password" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="user" type="xs:string" use="required"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="files" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="file" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:sequence> + <xs:element name="path" type="xs:string"/> + <xs:element name="content"> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="encoding" type="xs:string" use="required"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:element name="initialization" type="Initialization"/> + + <xs:complexType name="Initialization"> + <xs:choice> + <xs:element name="cloud-init" type="CloudInit" minOccurs="0"/> + </xs:choice> + </xs:complexType> + <xs:complexType name="VmPlacementPolicy"> <xs:sequence> <xs:element name="host" type="Host" minOccurs="0" maxOccurs="1"/> @@ -2118,6 +2239,7 @@ <xs:element ref="domain" minOccurs="0" maxOccurs="1"/> <xs:element name="custom_properties" type="CustomProperties" minOccurs="0"/> <xs:element name="payloads" type="Payloads" minOccurs="0"/> + <xs:element name="initialization" type="Initialization" minOccurs="0"/> <xs:element name="statistics" type="Statistics" minOccurs="0" maxOccurs="1"/> <xs:element name="disks" type="Disks" minOccurs="0" maxOccurs="1"/> <xs:element name="nics" type="Nics" minOccurs="0" maxOccurs="1"/> diff --git a/backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata.yaml b/backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata.yaml index 43f6d35..def03ee 100644 --- a/backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata.yaml +++ b/backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata.yaml @@ -207,6 +207,24 @@ action.vm.display.type: 'xs:string', action.vm.stateless: 'xs:boolean', action.vm.os.cmdline: 'xs:string', action.vm.domain.user.username: 'xs:string', action.pause: 'xs:boolean', action.vm.os.boot--COLLECTION: {boot.dev: 'xs:string'}, action.vm.domain.user.password: 'xs:string'} + action.vm.initialization.cloud-init--COLLECTION: {cloud-init.hostname: 'xs:string', + cloud-init.network.interfaces.name: 'xs:string', + cloud-init.network.interfaces.boot_protocol: 'xs:string', + cloud-init.network.interfaces.address: 'xs:string', + cloud-init.network.interfaces.netmask: 'xs:string', + cloud-init.network.interfaces.gateway: 'xs:string', + cloud-init.network.interfaces.onboot: 'xs:boolean', + cloud-init.network.dns_servers.dns_server: 'xs:string', + cloud-init.network.dns_search_domains.dns_search_domain: 'xs:string', + cloud-init.authorized_keys.authorized_key: 'xs:string', + cloud-init.authorized_keys.authorized_key.user: 'xs:string', + cloud-init.regenerate_ssh_keys: 'xs:boolean', + cloud-init.timezone: 'xs:string', + cloud-init.passwords.password: 'xs:string', + cloud-init.passwords.password.user: 'xs:string', + cloud-init.files.file.path: 'xs:string', + cloud-init.files.file.content: 'xs:string', + cloud-init.files.file.content.encoding: 'xs:string'} urlparams: {} headers: Content-Type: {value: application/xml|json, required: true} diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendCapabilitiesResource.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendCapabilitiesResource.java index fb050d1..6816744 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendCapabilitiesResource.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendCapabilitiesResource.java @@ -5,6 +5,8 @@ import java.util.Set; import org.ovirt.engine.api.common.util.LinkHelper; +import org.ovirt.engine.api.model.AttachmentType; +import org.ovirt.engine.api.model.AttachmentTypes; import org.ovirt.engine.api.model.BootDevice; import org.ovirt.engine.api.model.BootDevices; import org.ovirt.engine.api.model.BootProtocol; @@ -211,6 +213,7 @@ addReportedDeviceTypes(version, ReportedDeviceType.values()); addIpVersions(version, IpVersion.values()); addCpuModes(version, CpuMode.values()); + addAttachmentTypes(version, AttachmentType.values()); version.setFeatures(featuresHelper.getFeatures(v)); @@ -226,6 +229,15 @@ return version; } + private void addAttachmentTypes(VersionCaps version, AttachmentType[] values) { + if (VersionUtils.greaterOrEqual(version, VERSION_3_3)) { + version.setAttachmentTypes(new AttachmentTypes()); + for (AttachmentType mode : values) { + version.getAttachmentTypes().getAttachmentTypes().add(mode.value()); + } + } + } + private void addCpuModes(VersionCaps version, CpuMode[] values) { if (VersionUtils.greaterOrEqual(version, VERSION_3_2)) { version.setCpuModes(new CpuModes()); 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 e16c67c..d07606c 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 @@ -244,7 +244,7 @@ "VM"); if (vm.isFirstRun() && vm.getVmOs().isWindows()) { params.setInitializationType(InitializationType.Sysprep); - } else if (vm.isFirstRun() && vm.getVmOs().isLinux()) { + } else if (params.getCloudInitParameters() != null && vm.getVmOs().isLinux()) { params.setInitializationType(InitializationType.CloudInit); } else { params.setInitializationType(InitializationType.None); diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/VmValidator.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/VmValidator.java index 6225034..d5f271b 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/VmValidator.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/VmValidator.java @@ -14,6 +14,7 @@ private DisplayValidator displayValidator = new DisplayValidator(); private PlacementPolicyValidator placementPolicyValidator = new PlacementPolicyValidator(); private PayloadValidator payloadValidator = new PayloadValidator(); + private CloudInitValidator cloudInitValidator = new CloudInitValidator(); @Override public void validateEnums(VM vm) { @@ -37,5 +38,10 @@ payloadValidator.validateEnums(payload); } } + if (vm.isSetInitialization()) { + if (vm.getInitialization().isSetCloudInit()) { + cloudInitValidator.validateEnums(vm.getInitialization().getCloudInit()); + } + } } } diff --git a/backend/manager/modules/restapi/types/src/main/java/org/ovirt/engine/api/restapi/types/VmMapper.java b/backend/manager/modules/restapi/types/src/main/java/org/ovirt/engine/api/restapi/types/VmMapper.java index 67973d9..6ee6df1 100644 --- a/backend/manager/modules/restapi/types/src/main/java/org/ovirt/engine/api/restapi/types/VmMapper.java +++ b/backend/manager/modules/restapi/types/src/main/java/org/ovirt/engine/api/restapi/types/VmMapper.java @@ -3,16 +3,25 @@ import static org.ovirt.engine.core.compat.NGuid.createGuidFromString; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TimeZone; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.api.common.util.StatusUtils; +import org.ovirt.engine.api.model.AttachmentType; import org.ovirt.engine.api.model.Boot; import org.ovirt.engine.api.model.BootDevice; import org.ovirt.engine.api.model.CPU; +import org.ovirt.engine.api.model.CloudInit; +import org.ovirt.engine.api.model.CloudInit.AuthorizedKeys.AuthorizedKey; +import org.ovirt.engine.api.model.CloudInit.Files.File; +import org.ovirt.engine.api.model.CloudInit.Network.Interfaces.Interface; +import org.ovirt.engine.api.model.BootProtocol; +import org.ovirt.engine.api.model.CloudInit.Passwords.Password; import org.ovirt.engine.api.model.Cluster; import org.ovirt.engine.api.model.CpuMode; import org.ovirt.engine.api.model.CpuTopology; @@ -46,7 +55,9 @@ import org.ovirt.engine.api.restapi.utils.CustomPropertiesParser; import org.ovirt.engine.api.restapi.utils.GuidUtils; import org.ovirt.engine.api.restapi.utils.UsbMapperUtils; +import org.ovirt.engine.core.common.action.CloudInitParameters; import org.ovirt.engine.core.common.action.RunVmOnceParams; +import org.ovirt.engine.core.common.action.CloudInitParameters.Attachment; import org.ovirt.engine.core.common.businessentities.BootSequence; import org.ovirt.engine.core.common.businessentities.MigrationSupport; import org.ovirt.engine.core.common.businessentities.OriginType; @@ -57,6 +68,8 @@ import org.ovirt.engine.core.common.businessentities.VmPayload; import org.ovirt.engine.core.common.businessentities.VmStatic; import org.ovirt.engine.core.common.businessentities.VmTemplate; +import org.ovirt.engine.core.common.businessentities.network.NetworkBootProtocol; +import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface; import org.ovirt.engine.core.common.utils.VmDeviceType; import org.ovirt.engine.core.compat.NGuid; import org.ovirt.engine.core.compat.Version; @@ -485,6 +498,11 @@ if (vm.getDomain().getUser().isSetPassword()) { params.setSysPrepPassword(vm.getDomain().getUser().getPassword()); } + } + } + if (vm.isSetInitialization()) { + if (vm.getInitialization().isSetCloudInit()) { + params.setCloudInitParameters(map(vm.getInitialization().getCloudInit(), null)); } } @@ -949,6 +967,103 @@ return entity; } + @Mapping(from = Attachment.AttachmentType.class, to = org.ovirt.engine.api.model.AttachmentType.class) + public static org.ovirt.engine.api.model.AttachmentType map(Attachment.AttachmentType attachmentType, org.ovirt.engine.api.model.AttachmentType template) { + switch (attachmentType) { + case BASE64: return org.ovirt.engine.api.model.AttachmentType.BASE64; + case PLAINTEXT: return org.ovirt.engine.api.model.AttachmentType.PLAINTEXT; + default: return null; + } + } + + @Mapping(from = org.ovirt.engine.api.model.AttachmentType.class, to = Attachment.AttachmentType.class) + public static Attachment.AttachmentType map(org.ovirt.engine.api.model.AttachmentType attachmentType, Attachment.AttachmentType template) { + switch (attachmentType) { + case BASE64: return Attachment.AttachmentType.BASE64; + case PLAINTEXT: return Attachment.AttachmentType.PLAINTEXT; + default: return null; + } + } + + @Mapping(from = CloudInit.class, to = CloudInitParameters.class) + public static CloudInitParameters map(CloudInit model, CloudInitParameters template) { + CloudInitParameters entity = template != null ? template : new CloudInitParameters(); + + entity.setHostname(model.getHostname()); + + if (model.getAuthorizedKeys() != null + && !model.getAuthorizedKeys().getAuthorizedKey().isEmpty()) { + StringBuilder keys = new StringBuilder(); + for (AuthorizedKey key : model.getAuthorizedKeys().getAuthorizedKey()) { + if ("root".equals(key.getUser())) { + if (keys.length() > 0) { + keys.append("\n"); + } + keys.append(key.getValue()); + } + } + entity.setAuthorizedKeys(keys.toString()); + } + + entity.setRegenerateKeys(model.isRegenerateSshKeys()); + + if (model.getNetwork() != null) { + if (model.getNetwork().getInterfaces() != null + && !model.getNetwork().getInterfaces().getInterface().isEmpty()) { + for (Interface iface : model.getNetwork().getInterfaces().getInterface()) { + VdsNetworkInterface vdsNetworkInterface = new VdsNetworkInterface(); + NetworkBootProtocol protocol = HostNicMapper.map(BootProtocol.fromValue(iface.getBootProtocol()), null); + vdsNetworkInterface.setBootProtocol(protocol); + if (protocol == NetworkBootProtocol.DHCP) { + vdsNetworkInterface.setAddress(iface.getAddress()); + vdsNetworkInterface.setSubnet(iface.getNetmask()); + vdsNetworkInterface.setGateway(iface.getGateway()); + } + if (iface.isOnboot()) { + if (entity.getStartOnBoot() == null) { + entity.setStartOnBoot(new ArrayList<String>()); + } + entity.getStartOnBoot().add(iface.getName()); + } + } + + if (model.getNetwork().getDnsServers() != null + && model.getNetwork().getDnsServers().getDnsServer().isEmpty()) { + entity.setDnsServers(model.getNetwork().getDnsServers().getDnsServer()); + } + + if (model.getNetwork().getDnsSearchDomains() != null + && model.getNetwork().getDnsSearchDomains().getDnsSearchDomain().isEmpty()) { + entity.setDnsSearch(model.getNetwork().getDnsSearchDomains().getDnsSearchDomain()); + } + } + } + + if (model.getTimezone() != null) { + entity.setTimeZone(TimeZone.getTimeZone(model.getTimezone())); + } + + if (model.getPasswords() != null && !model.getPasswords().getPassword().isEmpty()) { + for (Password password : model.getPasswords().getPassword()) { + if ("root".equals(password.getUser())) { + entity.setRootPassword(password.getValue()); + } + } + } + + if (model.getFiles() != null && !model.getFiles().getFile().isEmpty()) { + entity.setAttachments(new HashMap<String, Attachment>()); + for (File file : model.getFiles().getFile()) { + Attachment attachment = new Attachment(); + attachment.setAttachmentType(map(AttachmentType.fromValue(file.getContent().getEncoding()), null)); + attachment.setContent(file.getContent().getValue()); + entity.getAttachments().put(file.getPath(), attachment); + } + } + + return entity; + } + static String cpuTuneToString(final CpuTune tune) { final StringBuilder builder = new StringBuilder(); boolean first = true; -- To view, visit http://gerrit.ovirt.org/15537 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I6ad0bfeca23cf8d4b2887010081d63c258032611 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